Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
146 lines
6.3 KiB
Python
146 lines
6.3 KiB
Python
"""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
|