feat(B03): 계획노선 shapefile 의 CSV 정본화 + LAS 없는 도엽 서피스 좌표계 교정
- 자동설계 체인이 초기값 스냅샷에 `planned_route.csv` 를 함께 남김. 값은 체인이 이미 만든 정점(사업지 좌표계·트림·조밀화 후)이라 재계산 없음. - `load_design_route()` 는 surface_params 가 주어진 호출에 한해 그 CSV 정본을 읽음. 트림 전 원본이 필요한 호출(도엽 범위)은 종전 경로 유지. 지표면·노선이 바뀌면 `discard_initial_snapshot()` 이 폴더째 지워 경로가 저절로 닫힘. - `run_sheet_surface_analysis()` 가 노선을 원본 좌표계로 읽어 도엽 서피스를 딴 자리에 만들던 문제 교정 — `load_design_route()` 로 창구 통일, 도엽 확보 PRJ 탐색도 지형 PRJ 폴더로 교정. shapefile 노선(5179) + 지형 PRJ(5176) 조합에서 트림이 노선을 통째로 지우던 원인. 검증: tmp/tests 158 passed(신규 4건 포함), 공용 브라우저 실사용 경로로 용화 shapefile 한 벌 업로드 → 자동 체인 완주 → CSV 정본 643점, 원본 경로와 최대 좌표차 0.00007m. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ CLAUDE.md 5장(조작·데이터 흐름 정책)의 **초기값** 층이다. 자
|
||||
`pipe_points.json` 편집분이 그대로 남아 초기값과 다른 결과가 나온다(2026-08-29).
|
||||
"""
|
||||
|
||||
import csv
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
@@ -19,6 +20,10 @@ import aiomysql
|
||||
# 스냅샷 폴더는 워크플로우 단계가 아니므로 PROJECT_STORAGE_LAYOUT_V2에 넣지 않는다.
|
||||
SNAPSHOT_DIRNAME = "initial_snapshot"
|
||||
_DB_DUMP_NAME = "db.json"
|
||||
# 설계가 쓰는 계획노선 정본 — shapefile로 온 노선도 여기서는 CSV 한 벌이다.
|
||||
# 사업지(.prj) 좌표계로 옮기고 지표면 밖을 잘라 조밀화까지 끝낸 값이라, 설계 계통은
|
||||
# 이 파일만 읽으면 매번 같은 노선을 본다(2026-09-03 사용자 확정).
|
||||
DESIGN_ROUTE_CSV_NAME = "planned_route.csv"
|
||||
# 초기 설계 체인이 도는 동안만 존재하는 마커(진입 차단 판정용).
|
||||
DESIGNING_LOCK_NAME = "initial_design.lock"
|
||||
# 초기 설계 체인이 실패로 끝났음을 남기는 마커.
|
||||
@@ -40,6 +45,11 @@ def snapshot_dir(project_root: Path) -> Path:
|
||||
return Path(project_root) / SNAPSHOT_DIRNAME
|
||||
|
||||
|
||||
def design_route_csv_path(project_root: Path) -> Path:
|
||||
"""설계용 계획노선 CSV 정본의 자리."""
|
||||
return snapshot_dir(Path(project_root)) / DESIGN_ROUTE_CSV_NAME
|
||||
|
||||
|
||||
def designing_lock_path(project_root: Path) -> Path:
|
||||
"""초기 설계 체인이 도는 동안만 존재하는 마커.
|
||||
|
||||
@@ -158,14 +168,24 @@ def _is_json_native(value: Any) -> bool:
|
||||
|
||||
|
||||
async def save_initial_snapshot(
|
||||
connection: aiomysql.Connection, project_root: Path, route_id: int
|
||||
connection: aiomysql.Connection,
|
||||
project_root: Path,
|
||||
route_id: int,
|
||||
design_route_points: list[dict[str, float]] | None = None,
|
||||
) -> None:
|
||||
"""자동설계 체인 성공 직후 한 번 부른다. 이미 있으면 덮어쓰지 않는다."""
|
||||
"""자동설계 체인 성공 직후 한 번 부른다. 이미 있으면 덮어쓰지 않는다.
|
||||
|
||||
`design_route_points`는 체인이 이미 만들어 둔 계획노선 정점(사업지 좌표계·트림·조밀화
|
||||
후)이다. 받으면 CSV 정본으로 함께 남긴다 — 노선이 shapefile로 왔더라도 설계 계통이
|
||||
읽는 것은 이 CSV 한 벌이다.
|
||||
"""
|
||||
root = Path(project_root)
|
||||
target = snapshot_dir(root)
|
||||
if has_initial_snapshot(root):
|
||||
return
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
if design_route_points:
|
||||
_write_design_route_csv(target / DESIGN_ROUTE_CSV_NAME, design_route_points)
|
||||
|
||||
for tree in _FILE_TREES:
|
||||
_copy_tree(root / tree, target / tree.replace("/", "__"))
|
||||
@@ -181,6 +201,17 @@ async def save_initial_snapshot(
|
||||
(target / _DB_DUMP_NAME).write_text(json.dumps(dump, ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
|
||||
def _write_design_route_csv(path: Path, points: list[dict[str, float]]) -> None:
|
||||
"""계획노선 정점을 CSV로 적는다. 열 이름은 `read_planned_route_csv()`가 아는 것으로."""
|
||||
with path.open("w", encoding="utf-8", newline="") as file:
|
||||
writer = csv.writer(file)
|
||||
writer.writerow(("sequence", "x", "y"))
|
||||
writer.writerows(
|
||||
(index, round(point["x"], 4), round(point["y"], 4))
|
||||
for index, point in enumerate(points)
|
||||
)
|
||||
|
||||
|
||||
def wipe_edited_masters(project_root: Path) -> list[str]:
|
||||
"""사용자 편집 정본을 걷어낸다 — 재계산으로 **진짜 초기값**을 만들기 위한 사전 정리.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user