diff --git a/B05_Profile/B05_Profile_Engine_Sections.py b/B05_Profile/B05_Profile_Engine_Sections.py index 2a6509a62..c4c5f6871 100644 --- a/B05_Profile/B05_Profile_Engine_Sections.py +++ b/B05_Profile/B05_Profile_Engine_Sections.py @@ -163,6 +163,18 @@ def valid_cross_names(cross_dir: Path, stations: list[Any]) -> set[str]: } +def cross_files_in_order(cross_dir: Path, stations: list[Any]) -> list[Path]: + """잔재를 걸러 읽을 cross_*.json — **측점 차례**(이름 차례면 `01080.16` 이 `01080` 앞).""" + valid = valid_cross_names(cross_dir, stations) + + def chainage(path: Path) -> tuple[float, str]: + match = re.fullmatch(rf"cross_{CROSS_KEY_PATTERN}m", path.stem) + return (float(match.group(1)) if match else float("inf"), path.name) + + files = [path for path in cross_dir.glob("cross_*.json") if not valid or path.name in valid] + return sorted(files, key=chainage) + + def prune_stale_cross_files(cross_dir: Path, stations: list[Any]) -> set[str]: """stations에 없는 잔재 cross_*.json을 삭제하고 유효 파일명 집합을 반환한다. diff --git a/B06_Section/B06_Section_Router.py b/B06_Section/B06_Section_Router.py index 7dccc86fe..03dd566c3 100644 --- a/B06_Section/B06_Section_Router.py +++ b/B06_Section/B06_Section_Router.py @@ -16,9 +16,9 @@ from fastapi.responses import JSONResponse, Response from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path from B05_Profile.B05_Profile_Engine_Grade_Profile import rebuild_alignment_profile from B05_Profile.B05_Profile_Engine_Sections import ( + cross_files_in_order, relabel_pipe_stations, run_section_generation, - valid_cross_names, ) from B05_Profile.B05_Profile_Engine_Sections_Core import SectionGenerationOptions from B06_Section.B06_Section_Engine_Attach import attach_saved_shapes @@ -248,12 +248,10 @@ def _read_section_detail(project_root: Path, longitudinal_file_path: str) -> dic longitudinal = json.loads(longitudinal_path.read_text(encoding="utf-8")) stations = longitudinal.get("stations") if isinstance(longitudinal, dict) else None - # 조회는 지우지 않음 — 잔재는 걸러 읽기만(지우기는 측점을 쓰는 쪽 몫) - valid_names = valid_cross_names(cross_dir, stations if isinstance(stations, list) else []) + # 조회는 지우지 않음 — 잔재는 걸러 읽기만(지우기는 측점을 쓰는 쪽 몫) · 측점 차례(472-2) cross_sections = [ json.loads(path.read_text(encoding="utf-8")) - for path in sorted(cross_dir.glob("cross_*.json")) - if not valid_names or path.name in valid_names + for path in cross_files_in_order(cross_dir, stations if isinstance(stations, list) else []) ] if not isinstance(longitudinal, dict) or not all( isinstance(section, dict) for section in cross_sections diff --git a/B07_DesignDetail/B07_DesignDetail_Router_Support.py b/B07_DesignDetail/B07_DesignDetail_Router_Support.py index d961ffe21..676d21211 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router_Support.py +++ b/B07_DesignDetail/B07_DesignDetail_Router_Support.py @@ -286,7 +286,7 @@ def _cross_sheet_plan(project_root: Path, longitudinal_path: Path) -> list[dict[ """ longitudinal = _read_json(longitudinal_path) chainages: list[int | float] = [] - for path in sorted(_cross_files(longitudinal_path, longitudinal)): + for path in _cross_files(longitudinal_path, longitudinal): match = _CROSS_ID.fullmatch(path.stem) if not match: continue diff --git a/B07_DesignDetail/B07_DesignDetail_Router_Support_Io.py b/B07_DesignDetail/B07_DesignDetail_Router_Support_Io.py index bbf43700b..c7b847d3f 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router_Support_Io.py +++ b/B07_DesignDetail/B07_DesignDetail_Router_Support_Io.py @@ -9,7 +9,7 @@ import re from pathlib import Path from typing import Any -from B05_Profile.B05_Profile_Engine_Sections import cross_station, valid_cross_names +from B05_Profile.B05_Profile_Engine_Sections import cross_files_in_order, cross_station logger = logging.getLogger(__name__) @@ -39,12 +39,8 @@ def _cross_files(longitudinal_path: Path, longitudinal: dict[str, Any]) -> list[ if not cross_dir.is_dir(): raise FileNotFoundError("B06 횡단면 파일을 찾을 수 없습니다.") stations = longitudinal.get("stations") - # 읽기는 지우지 않음 — 잔재는 걸러 읽기만(지우기는 측점을 쓰는 쪽 몫 · R26) - valid_names = valid_cross_names(cross_dir, stations if isinstance(stations, list) else []) - files = sorted(cross_dir.glob("cross_*.json")) - if valid_names: - files = [path for path in files if path.name in valid_names] - return files + # 읽기는 지우지 않음 — 잔재는 걸러 읽기만(지우기는 측점을 쓰는 쪽 몫 · R26) · 측점 차례(472-2) + return cross_files_in_order(cross_dir, stations if isinstance(stations, list) else []) def _station_map(longitudinal: dict[str, Any]) -> dict[int | float, dict[str, Any]]: diff --git a/resources/tester/test_472_2_cross_order.py b/resources/tester/test_472_2_cross_order.py new file mode 100644 index 000000000..40d9be02f --- /dev/null +++ b/resources/tester/test_472_2_cross_order.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +"""472-2 덧 — 횡단 파일을 측점(누가거리) 차례로 읽는지. + +이름 차례면 `cross_01080.16m` 이 `cross_01080m` 앞(「.」 < 「m」) → GET 상세의 측점 차례가 +어긋나 화면 차례 짝(detailCross)이 갈리고, B07 측점 도면 장 나눔도 차례가 뒤집힘. +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from B06_Section.B06_Section_Router import _read_section_detail # noqa: E402 +from B07_DesignDetail.B07_DesignDetail_Router_Support_Io import _cross_files # noqa: E402 + +CHAINAGES = [1060.0, 1080.0, 1080.16, 1100.0, 999.5] +NAMES = { + 1060.0: "cross_01060m.json", + 1080.0: "cross_01080m.json", + 1080.16: "cross_01080.16m.json", + 1100.0: "cross_01100m.json", + 999.5: "cross_00999.50m.json", +} + + +def _stage(tmp_path: Path) -> Path: + stage = tmp_path / "B05_Profile" + (stage / "cross_sections").mkdir(parents=True) + (stage / "longitudinal").mkdir() + longitudinal = stage / "longitudinal" / "longitudinal.json" + longitudinal.write_text( + json.dumps({"stations": [{"chainage_m": m} for m in CHAINAGES]}), encoding="utf-8" + ) + for chainage, name in NAMES.items(): + (stage / "cross_sections" / name).write_text( + json.dumps({"chainage_m": chainage, "samples": []}), encoding="utf-8" + ) + return longitudinal + + +def test_상세_GET_은_측점_차례(tmp_path: Path) -> None: + longitudinal = _stage(tmp_path) + detail = _read_section_detail(tmp_path, str(longitudinal.relative_to(tmp_path))) + got = [section["chainage_m"] for section in detail["cross_sections"]] + assert got == sorted(CHAINAGES) + + +def test_B07_측점_파일도_측점_차례(tmp_path: Path) -> None: + longitudinal = _stage(tmp_path) + files = _cross_files(longitudinal, json.loads(longitudinal.read_text(encoding="utf-8"))) + assert [path.name for path in files] == [NAMES[m] for m in sorted(CHAINAGES)] + + +def test_B07_장_나눔도_측점_차례() -> None: + """장 나눔 쪽이 이름으로 다시 줄 세우지 않음(`sorted(_cross_files(…))` = 이름 차례).""" + source = (ROOT / "B07_DesignDetail" / "B07_DesignDetail_Router_Support.py").read_text( + encoding="utf-8" + ) + assert "sorted(_cross_files(" not in source