182 lines
5.8 KiB
Python
182 lines
5.8 KiB
Python
"""B05 경로 설계 결과의 aiomysql Raw SQL 접근.
|
|
|
|
routes(경로), route_points(렌더링 샘플), route_statistics(통계) 테이블에
|
|
메타데이터와 상대 경로를 기록한다. 전체 폴리라인은 route_data_path의
|
|
GeoJSON 파일에 저장하고 DB에는 경로만 기록한다.
|
|
"""
|
|
|
|
import json
|
|
from pathlib import PurePosixPath
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
import aiomysql
|
|
|
|
_STAGE_ROOT = "B05_wf2_Route"
|
|
|
|
|
|
def _validate_stage_path(relative_path: str) -> str:
|
|
normalized = PurePosixPath(relative_path.replace("\\", "/"))
|
|
if normalized.is_absolute() or ".." in normalized.parts:
|
|
raise ValueError("DB에는 프로젝트 루트 기준 상대 경로만 저장할 수 있습니다.")
|
|
if not normalized.parts or normalized.parts[0] != _STAGE_ROOT:
|
|
raise ValueError(f"B05 산출물 경로는 {_STAGE_ROOT} 아래여야 합니다.")
|
|
return normalized.as_posix()
|
|
|
|
|
|
async def create_route(
|
|
connection: aiomysql.Connection,
|
|
*,
|
|
project_id: UUID,
|
|
surface_model_id: int | None,
|
|
total_length_m: float | None,
|
|
start_chainage_m: float | None,
|
|
end_chainage_m: float | None,
|
|
grade_percent: list[float] | None,
|
|
constraints: dict[str, Any] | None,
|
|
algorithm_params: dict[str, Any] | None,
|
|
route_data_path: str,
|
|
status: str = "DRAFT",
|
|
) -> int:
|
|
"""경로 메타데이터를 저장하고 생성된 ID를 반환한다."""
|
|
route_rel = _validate_stage_path(route_data_path)
|
|
async with connection.cursor() as cursor:
|
|
await cursor.execute(
|
|
"""
|
|
INSERT INTO routes (
|
|
project_id, surface_model_id, status,
|
|
start_chainage_m, end_chainage_m, total_length_m,
|
|
grade_percent, constraints, algorithm_params,
|
|
route_data_path, computed_at
|
|
)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, CURRENT_TIMESTAMP)
|
|
""",
|
|
(
|
|
str(project_id),
|
|
surface_model_id,
|
|
status,
|
|
start_chainage_m,
|
|
end_chainage_m,
|
|
total_length_m,
|
|
json.dumps(grade_percent) if grade_percent is not None else None,
|
|
json.dumps(constraints, ensure_ascii=False) if constraints is not None else None,
|
|
json.dumps(algorithm_params, ensure_ascii=False)
|
|
if algorithm_params is not None
|
|
else None,
|
|
route_rel,
|
|
),
|
|
)
|
|
route_id = cursor.lastrowid
|
|
if not route_id:
|
|
raise RuntimeError("routes 레코드 생성 결과에 ID가 없습니다.")
|
|
return int(route_id)
|
|
|
|
|
|
async def insert_route_points(
|
|
connection: aiomysql.Connection,
|
|
route_id: int,
|
|
points: list[dict[str, Any]],
|
|
) -> int:
|
|
"""경로 렌더링 샘플 포인트를 일괄 저장하고 저장 건수를 반환한다.
|
|
|
|
각 point dict: {chainage_m, elevation_m, slope_percent, sequence_num}
|
|
"""
|
|
if not points:
|
|
return 0
|
|
rows = [
|
|
(
|
|
route_id,
|
|
point.get("chainage_m"),
|
|
point.get("elevation_m"),
|
|
point.get("slope_percent"),
|
|
point.get("sequence_num"),
|
|
)
|
|
for point in points
|
|
]
|
|
async with connection.cursor() as cursor:
|
|
await cursor.executemany(
|
|
"""
|
|
INSERT INTO route_points (
|
|
route_id, chainage_m, elevation_m, slope_percent, sequence_num
|
|
)
|
|
VALUES (%s, %s, %s, %s, %s)
|
|
""",
|
|
rows,
|
|
)
|
|
return len(rows)
|
|
|
|
|
|
async def create_route_statistics(
|
|
connection: aiomysql.Connection,
|
|
*,
|
|
route_id: int,
|
|
min_slope: float | None,
|
|
max_slope: float | None,
|
|
mean_slope: float | None,
|
|
cut_volume_m3: float | None = None,
|
|
fill_volume_m3: float | None = None,
|
|
tree_cutting_volume: float | None = None,
|
|
cost_score: float | None = None,
|
|
) -> int:
|
|
"""경로 통계를 저장하고 생성된 ID를 반환한다."""
|
|
async with connection.cursor() as cursor:
|
|
await cursor.execute(
|
|
"""
|
|
INSERT INTO route_statistics (
|
|
route_id, min_slope, max_slope, mean_slope,
|
|
cut_volume_m3, fill_volume_m3, tree_cutting_volume, cost_score
|
|
)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
|
""",
|
|
(
|
|
route_id,
|
|
min_slope,
|
|
max_slope,
|
|
mean_slope,
|
|
cut_volume_m3,
|
|
fill_volume_m3,
|
|
tree_cutting_volume,
|
|
cost_score,
|
|
),
|
|
)
|
|
stat_id = cursor.lastrowid
|
|
if not stat_id:
|
|
raise RuntimeError("route_statistics 레코드 생성 결과에 ID가 없습니다.")
|
|
return int(stat_id)
|
|
|
|
|
|
async def get_latest_route(
|
|
connection: aiomysql.Connection, project_id: UUID
|
|
) -> dict[str, Any] | None:
|
|
"""프로젝트의 최신 경로를 조회한다 (없으면 None)."""
|
|
async with connection.cursor() as cursor:
|
|
await cursor.execute(
|
|
"""
|
|
SELECT id, status, total_length_m, route_data_path, computed_at
|
|
FROM routes
|
|
WHERE project_id = %s
|
|
ORDER BY computed_at DESC, id DESC
|
|
LIMIT 1
|
|
""",
|
|
(str(project_id),),
|
|
)
|
|
row = await cursor.fetchone()
|
|
if not row:
|
|
return None
|
|
return {
|
|
"id": int(row[0]),
|
|
"status": row[1],
|
|
"total_length_m": row[2],
|
|
"route_data_path": row[3],
|
|
"computed_at": row[4].isoformat() if row[4] else None,
|
|
}
|
|
|
|
|
|
async def confirm_route(connection: aiomysql.Connection, route_id: int) -> None:
|
|
"""경로 상태를 CONFIRMED로 변경한다."""
|
|
async with connection.cursor() as cursor:
|
|
await cursor.execute(
|
|
"UPDATE routes SET status = 'CONFIRMED' WHERE id = %s",
|
|
(route_id,),
|
|
)
|