From 89e04280d0a34e0d937f2b0281502886fe687cde Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 19 Oct 2026 08:54:48 +0900 Subject: [PATCH] =?UTF-8?q?fix(472-2=20=EB=8D=A7=20=ED=9A=A1=EB=8B=A8=20?= =?UTF-8?q?=EC=B0=A8=EB=A1=80):=20=ED=9A=A1=EB=8B=A8=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=3D=20=EC=B8=A1=EC=A0=90(=EB=88=84=EA=B0=80=EA=B1=B0?= =?UTF-8?q?=EB=A6=AC)=20=EC=B0=A8=EB=A1=80=EB=A1=9C=20=EC=9D=BD=EC=9D=8C?= =?UTF-8?q?=20=E2=80=94=20=EC=9D=B4=EB=A6=84=20=EC=B0=A8=EB=A1=80=EB=A9=B4?= =?UTF-8?q?=20cross=5F01080.16m=20=EC=9D=B4=20cross=5F01080m=20=EC=95=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cross_files_in_order 한 벌(Engine_Sections) — B06 상세 GET(_read_section_detail) · B07 측점 파일(_cross_files) 둘 다 이것으로 - B07 측점 도면 장 나눔의 이름 차례 sorted 걷음 - 시험 test_472_2_cross_order(옛 코드 빨강 셋 → 초록) Co-Authored-By: Claude Opus 5.5 --- B05_Profile/B05_Profile_Engine_Sections.py | 12 ++++ B06_Section/B06_Section_Router.py | 8 +-- .../B07_DesignDetail_Router_Support.py | 2 +- .../B07_DesignDetail_Router_Support_Io.py | 10 +-- resources/tester/test_472_2_cross_order.py | 64 +++++++++++++++++++ 5 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 resources/tester/test_472_2_cross_order.py 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