"""299-1 물넘이 연동 범위 안 측점의 자기 관 — 같은 측점에 함께 얹힘 · 그림 · 수량 한 번. 까닭 — `attach_culvert_sets` 가 측점마다 첫 맞는 시설 하나만 얹고 멈춰, 검증 프로젝트 95 m 배관 (관경 800 · 도수로)이 90 m 물넘이포장(폭 10 → 85~95 m 연동)에 가려 횡단도 · 관 연장에서 빠졌다. 못박는 것 - 키가 다른 시설(배관 culvert · 세월교 ford · 박스 box · 물넘이 ford_pavement)은 같은 측점에 함께. - 같은 키는 소유 측점 것이 옆 측점 연동보다 먼저 · 세월교 · 박스 흙막이(guard)는 자기 관과 부딪히면 관 먼저(흙막이 없는 자리는 그대로 흙막이). - 수량 = 소유 측점 한 번 — 소비처(B06 `Structure_Layouts` · B08)가 `chainage_m` 의 가장 가까운 측점만 세므로 시설마다 주인 한 곳 · 관 연장은 주인 측점의 `culvert`(흙막이 `body` 아님)만. - 파이썬 ↔ TS 짝(`common_util_culvert_sets.ts`) 같은 값. """ from __future__ import annotations import json import sys from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] for path in (ROOT, Path(__file__).resolve().parent): if str(path) not in sys.path: sys.path.insert(0, str(path)) import test_b06_culvert_sets_mirror as mirror # noqa: E402 from B06_Section.B06_Section_Engine_Culvert import attach_culvert_sets, pipe_points_file # noqa: E402 # 검증 프로젝트 꼴 — 20 · 90 물넘이(폭 10) · 95 배관(도수로) · 100 배관 + 100.3 박스(유출 흙막이) · 60 세월교(흙막이) POINTS = [ {"chainage_m": 20.0, "facility": "ford_pavement", "options": {"ford_width_m": 10.0}}, {"chainage_m": 90.0, "facility": "ford_pavement", "options": {"ford_width_m": 10.0}}, { "chainage_m": 95.0, "facility": "pipe", "options": {"pipe_diameter_mm": 800, "chute_add_1_structure": "도수로"}, }, { "chainage_m": 60.0, "facility": "ford_bridge", "options": { "ford_width_m": 6.0, "inlet_type": "흙막이", "inlet_revet_form": "콘크리트", "inlet_revet_height_m": 1.5, }, }, {"chainage_m": 100.0, "facility": "pipe", "options": {"pipe_diameter_mm": 600}}, { "chainage_m": 100.3, "facility": "box_culvert", "options": {"outlet_revet_form": "돌쌓기(찰)", "outlet_revet_height_m": 3.5}, }, ] CHAINAGES = [0.0, 20.0, 40.0, 60.0, 80.0, 85.0, 90.0, 95.0, 100.0, 105.0] # 같은 키 — 옆 측점 연동(88.5 · 폭 10 · 주인 88)이 90 m 에 더 가깝지만 소유(92.4 · 주인 90)가 먼저 SAME_KEY_POINTS = [ {"chainage_m": 88.5, "facility": "ford_pavement", "options": {"ford_width_m": 10.0}}, {"chainage_m": 92.4, "facility": "ford_pavement", "options": {"ford_width_m": 0.1}}, ] SAME_KEY_CHAINAGES = [85.0, 88.0, 90.0, 95.0] def _attach(tmp_path: Path, points: list[dict], chainages: list[float]) -> list[dict]: target = pipe_points_file(tmp_path) target.parent.mkdir(parents=True, exist_ok=True) target.write_text(json.dumps({"points": points}, ensure_ascii=False), encoding="utf-8") sections = [{"chainage_m": value} for value in chainages] attach_culvert_sets(tmp_path, sections) return sections def _at(sections: list[dict], chainage: float) -> dict: return next(s for s in sections if s["chainage_m"] == chainage) @pytest.fixture() def sections(tmp_path: Path) -> list[dict]: return _attach(tmp_path, POINTS, CHAINAGES) def test_연동_물넘이_범위_안_자기_관_함께(sections): at95 = _at(sections, 95.0) assert at95["culvert"]["chainage_m"] == 95.0 assert at95["culvert"]["type"] == "pipe" assert at95["culvert"]["chute"]["structure"] == "도수로" assert at95["ford_pavement"]["chainage_m"] == 90.0 assert set(_at(sections, 85.0)) == {"chainage_m", "ford_pavement"} def test_흙막이는_자기_관에_밀림_없으면_그대로(sections): at100 = _at(sections, 100.0) assert at100["box"]["chainage_m"] == 100.3 assert at100["culvert"]["chainage_m"] == 100.0 assert "body" not in at100["culvert"] # 관 — 흙막이 세트 아님 at60 = _at(sections, 60.0) assert at60["ford"]["chainage_m"] == 60.0 assert at60["culvert"]["chainage_m"] == 60.0 # 세월교 흙막이 def test_같은_키는_소유_측점_먼저(tmp_path): sections = _attach(tmp_path, SAME_KEY_POINTS, SAME_KEY_CHAINAGES) assert _at(sections, 90.0)["ford_pavement"]["chainage_m"] == 92.4 assert _at(sections, 85.0)["ford_pavement"]["chainage_m"] == 88.5 def test_수량은_주인_측점_한_번(sections): """소비처 규칙 — 시설 `chainage_m` 의 가장 가까운 측점만 셈.""" owner = { p["chainage_m"]: min(CHAINAGES, key=lambda c: abs(c - p["chainage_m"])) for p in POINTS } counted: dict[tuple[str, float], int] = {} for section in sections: for key in ("culvert", "ford", "box", "ford_pavement"): value = section.get(key) if value and owner[value["chainage_m"]] == section["chainage_m"]: name = "guard" if key == "culvert" and "body" in value else key counted[(name, value["chainage_m"])] = ( counted.get((name, value["chainage_m"]), 0) + 1 ) assert counted == { ("ford_pavement", 20.0): 1, ("ford_pavement", 90.0): 1, ("culvert", 95.0): 1, # 관 연장 · 도수로 — 95 m 한 번 ("ford", 60.0): 1, ("guard", 60.0): 1, ("culvert", 100.0): 1, ("box", 100.3): 1, } # 박스 흙막이는 자기 관에 밀려 어디에도 안 실림 — 관 연장 · 수량을 두 번 세지 않음 assert not any(s.get("culvert", {}).get("chainage_m") == 100.3 for s in sections) @pytest.mark.skipif(not mirror.TSC.is_file(), reason="프론트엔드 의존성 미설치") @pytest.mark.parametrize( ("points", "chainages"), [(POINTS, CHAINAGES), (SAME_KEY_POINTS, SAME_KEY_CHAINAGES)] ) def test_파이썬_TS_짝_같은_값(tmp_path, monkeypatch, points, chainages): expected = _attach(tmp_path / "py", points, chainages) monkeypatch.setattr(mirror, "POINTS", points) monkeypatch.setattr(mirror, "CHAINAGES", chainages) assert mirror._ts_sections(tmp_path) == expected