feat(B05): 계획노선 두 벌 + 노선 갈아 끼우기·초기화 서버 경로

노선을 예상노선(원본)·계획노선(수정본) 두 벌로 나눔 (PLAN 0-7 사용자 확정).

- 예상노선 정본 `B05_Profile/route/expected_route.csv` 신설 — 자동 체인이 한 번
  씀. 초기값 스냅샷 안에도 같은 CSV 가 있으나 그 폴더는 재확정 체인이 지우므로
  스냅샷 밖에 한 벌 둠.
- 계획노선 수정본 `B05_Profile/route/planned_route.csv` — 설계 계통(`load_design_route`)이
  수정본 → 예상노선 → 스냅샷 순으로 읽음. 노선 초기화는 수정본을 지우는 것.
- `GET /route/plan` 두 노선 정점 반환(모달이 점선·실선으로 그림).
- `POST /route/replan` 고친 노선을 수정본에 쓰고(조밀화) 재확정 체인 재사용 —
  배수유역 다시 → 관 새로 → 계획선·종횡단 재생성 → 옛 측점 설계 누가거리 이월.
- `POST /route/replan/reset` 수정본 삭제 후 같은 재계산.
- 횡단배수 지점은 노선이 바뀌면 저장분을 버리고 새로 계산(사용자 확정).
  구조물은 프로젝트 정본이라 옛 측점값 그대로 남음.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-06 18:00:34 +09:00
co-authored by Claude Opus 5
parent a22886c4c9
commit 8e6a1e27eb
4 changed files with 355 additions and 2 deletions
+42 -2
View File
@@ -212,6 +212,40 @@ def find_planned_route_file(input_dir: Path) -> Path | None:
return None
def expected_route_csv_path(project_root: Path) -> Path:
"""예상노선(원본) CSV 자리 — 자동 체인이 낸 노선을 **그대로** 보관한다.
초기값 스냅샷(`initial_snapshot/`) 안에도 같은 CSV가 있지만 그 폴더는 재확정 체인이
통째로 지운다(`discard_initial_snapshot`). 노선 초기화는 언제나 예상노선으로 돌아갈 수
있어야 하므로 스냅샷 **밖**에 한 벌 둔다(2026-09-06).
"""
return Path(project_root) / "B05_Profile" / "route" / "expected_route.csv"
def write_route_csv(path: Path, points: list[dict[str, float]]) -> None:
"""노선 정점을 CSV로 적는다. 열 이름은 `read_planned_route_csv()`가 아는 것."""
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8", newline="") as file:
writer = csv.writer(file)
writer.writerow(("sequence", "x", "y"))
writer.writerows(
(index, round(float(point["x"]), 4), round(float(point["y"]), 4))
for index, point in enumerate(points)
)
def planned_route_working_path(project_root: Path) -> Path:
"""계획노선(수정본) CSV 자리 — 사용자가 노선을 고치면 여기에 쓴다.
노선은 두 벌이다(2026-09-06 사용자 확정): **예상노선**(원본,
`initial_snapshot/planned_route.csv`)은 자동 체인이 한 번 쓰고 안 바뀌며,
**계획노선**(수정본)은 예상노선과 같은 값으로 시작해 사용자가 고쳐 쓴다.
설계 계통은 수정본이 있으면 그것을 읽는다 — 노선 초기화는 이 파일을 지우는 것이며,
그것이 곧 「원본을 수정본으로 복사」와 같다.
"""
return Path(project_root) / "B05_Profile" / "route" / "planned_route.csv"
def load_design_route(
project_root: Path,
surface_params: dict[str, Any] | None = None,
@@ -244,9 +278,15 @@ def load_design_route(
# 지표면·노선이 바뀌면 `discard_initial_snapshot()`이 폴더째 지우므로 이 경로가 저절로
# 닫히고 원본 재판독으로 되돌아간다. 트림 **전** 원본이 필요한 호출(도엽 범위 —
# surface_params 없음)은 여기를 타지 않는다.
# 읽는 순서 — 계획노선(수정본) → 예상노선(원본) → 초기값 스냅샷(옛 자리).
if surface_params:
master = design_route_csv_path(project_root)
if master.is_file():
for master in (
planned_route_working_path(project_root),
expected_route_csv_path(project_root),
design_route_csv_path(project_root),
):
if not master.is_file():
continue
stored = read_planned_route_csv(master)
if stored is not None and len(stored.vertices) >= 2:
return replace_vertices(