diff --git a/B03_FileInput/B03_FileInput_Service_Chain.py b/B03_FileInput/B03_FileInput_Service_Chain.py index e51abe468..c5fac8502 100644 --- a/B03_FileInput/B03_FileInput_Service_Chain.py +++ b/B03_FileInput/B03_FileInput_Service_Chain.py @@ -390,7 +390,6 @@ async def run_preprocess_chain( mark_designing, read_design_failure, ) - from common_util.common_util_route_geometry import expected_route_csv_path from common_util.common_util_storage import resolve_stored_project_path from common_util.common_util_surface_confirmation import ( design_terrain, @@ -416,9 +415,9 @@ async def run_preprocess_chain( missing = "sheet" if design == "las" else "las" sets.note_unbuilt(root, missing, f"{missing} 지표면 모델 없음 — 한 벌만") else: - # 예상노선(공유)은 설계 지형 벌이 씀 — 다른 지형으로 자른 것이 남지 않게 - expected = expected_route_csv_path(root) - had_expected = expected.is_file() + # 예상노선(공유 · 추천이 씀)은 설계 지형으로 자른 것 하나 — 먼저 적어 두면 두 벌이 같은 + # 평면 노선으로 지어짐(지형만 다름 · 다른 지형 벌 [초기화]도 같은 노선) + await _seed_expected_route(project_id, root, params) try: async with pool.acquire() as connection: model_id = await sets.switch_terrain(connection, project_id, other) @@ -438,8 +437,6 @@ async def run_preprocess_chain( await sets.clear_active_set(connection, root, str(project_id)) sets.note_unbuilt(root, other, f"짓기 오류: {exc}") finally: - if not had_expected: - expected.unlink(missing_ok=True) async with pool.acquire() as connection: await sets.switch_terrain(connection, project_id, design) marks.append((f"{other} 벌", time.perf_counter())) @@ -456,6 +453,26 @@ async def run_preprocess_chain( schedule_precompute(project_id) # 추천은 지형 · 예정노선만 씀 — 실패 길에도(763-3) +async def _seed_expected_route(project_id: UUID, root: Path, surface: dict[str, Any]) -> None: + """예상노선 정본을 설계 지형으로 잘라 적음(없을 때만 · 자동 체인 2.5 와 같은 값).""" + from common_util.common_util_route_geometry import expected_route_csv_path, write_route_csv + from config.config_db import get_db_pool + + path = expected_route_csv_path(root) + if path.is_file(): + return + async with get_db_pool().acquire() as connection, connection.cursor() as cursor: + await cursor.execute( + "SELECT route_start_m, route_end_m FROM projects WHERE id = %s", (str(project_id),) + ) + row = await cursor.fetchone() + points = await asyncio.to_thread( + _planned_route_points_in_project_crs, root, surface, (row[0], row[1]) if row else None + ) + if points: + write_route_csv(path, points) + + async def _ensure_initial_polyline( project_id: UUID, project_root: Path, points: list[dict[str, float]] ) -> None: diff --git a/resources/tester/test_765_design_sets.py b/resources/tester/test_765_design_sets.py index fa91eda29..2948283c2 100644 --- a/resources/tester/test_765_design_sets.py +++ b/resources/tester/test_765_design_sets.py @@ -428,9 +428,11 @@ def _chain_world(monkeypatch, tmp_path: Path, *, other, built_other=True): async def auto(pid, model_id=None, inner=False): log.append(("auto", model_id, inner)) - expected.parent.mkdir(parents=True, exist_ok=True) - expected.write_text(str(model_id), encoding="utf-8") # 체인이 처음 쓰는 예상노선 - return {"route_id": 1} if (built_other or len(log) > 2) else None + if not expected.is_file(): # 체인 2.5 — 없을 때만 그 지형으로 자른 예상노선 + expected.parent.mkdir(parents=True, exist_ok=True) + expected.write_text(str(model_id), encoding="utf-8") + first = sum(1 for item in log if item[0] == "auto") == 1 + return {"route_id": 1} if (built_other or not first) else None async def switch(_c, _pid, terrain): log.append(("switch", terrain)) @@ -459,7 +461,14 @@ def _chain_world(monkeypatch, tmp_path: Path, *, other, built_other=True): monkeypatch.setattr(sets, "park_active_set", park) monkeypatch.setattr(sets, "clear_active_set", clear) monkeypatch.setattr(sets, "set_fingerprint", fingerprint) + + async def seed(_pid, _root, surface): + log.append(("seed", surface["source_filter"])) + expected.parent.mkdir(parents=True, exist_ok=True) + expected.write_text("design", encoding="utf-8") + monkeypatch.setattr(chain, "run_auto_design_chain", auto) + monkeypatch.setattr(chain, "_seed_expected_route", seed) monkeypatch.setattr(router, "schedule_precompute", lambda pid: log.append("precompute")) return chain, log, expected @@ -474,6 +483,7 @@ def test_전처리_체인_다른_지형_먼저_떠_두고_설계_지형(monkeypa summary = asyncio.run(chain.run_preprocess_chain(PID, 5)) # type: ignore[arg-type] assert summary == {"route_id": 1} assert log == [ + ("seed", "classification"), # 예상노선 = 설계 지형으로 먼저 ("switch", "sheet"), ("auto", 6, True), ("park", "sheet"), @@ -481,7 +491,7 @@ def test_전처리_체인_다른_지형_먼저_떠_두고_설계_지형(monkeypa ("auto", 5, True), "precompute", ] - assert expected.read_text(encoding="utf-8") == "5" # 예상노선 = 설계 지형 벌이 씀 + assert expected.read_text(encoding="utf-8") == "design" # 두 벌 같은 평면 노선 assert not (tmp_path / SETS_DIR / "las").exists() assert not (tmp_path / "initial_design.lock").exists() @@ -499,6 +509,7 @@ def test_전처리_체인_다른_벌_실패해도_설계_지형(monkeypatch, tmp summary = asyncio.run(chain.run_preprocess_chain(PID, 5)) # type: ignore[arg-type] assert summary == {"route_id": 1} assert log == [ + ("seed", "classification"), ("switch", "sheet"), ("auto", 6, True), "clear",