# -*- coding: utf-8 -*- """다단 추가 기슭막이도 수량이 섬 — ④ (2026-09-14 브레인 판정 「수량이 통째로 빠지는 것」). 앞서 B06 횡단도는 다단 벽(`design.extra_wall_counts` · `revet_adjust.extra*` · `extra_spans`)을 그리고 면적에도 넣었는데, B08 전개는 관 유입·유출 기준벽만 셈 → 다단 벽 돌쌓기·터파기가 한 줄도 안 섬. 길 — 실제로 **선** 단 수·높이는 지형이 정하므로(요청보다 적게 설 수 있음) 서버 Node 재계산이 관 연장처럼 「선 다단 벽 목록」(`design.extra_walls`)을 소유 측점에 남기고, B08 이 그 목록으로 줄을 세움. 값이 없는 다단(높이를 안 적어 기본 1.5m 로 지반에 맞춰 그린 단)은 **미확정·금액 밖** 규칙 그대로. """ from __future__ import annotations 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 B08_Quantity.B08_Quantity_Engine_Pipe import ( # noqa: E402 extra_walls_from_designs, facility_structures, ) from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table # noqa: E402 LAYOUTS = (ROOT / "B06_Section" / "B06_Section_Structure_Layouts.ts").read_text(encoding="utf-8") NODE = (ROOT / "B06_Section" / "B06_Section_Server_Calc_Node.ts").read_text(encoding="utf-8") PREBUILD = (ROOT / "B06_Section" / "B06_Section_Server_Calc_Prebuild.py").read_text( encoding="utf-8" ) WALLS = [ { "key": "extra0", "side": "outlet", "form": "돌쌓기(메)", "height_m": 1.8, "height_set": True, "form_set": True, "before_m": 4.0, "after_m": 4.0, }, { "key": "extra1", "side": "outlet", "form": "돌쌓기(메)", "height_m": 1.3, "height_set": False, "form_set": False, "before_m": 5.0, "after_m": 5.0, }, ] POINT = { "chainage_m": 85.05, "options": { "pipe_diameter_mm": 1000, "outlet_revet_form": "돌쌓기(메)", "outlet_revet_height_m": 2.0, "outlet_revet_length_m": 10, "inlet_revet_form": "돌쌓기(찰)", "inlet_revet_height_m": 2.0, "inlet_revet_length_m": 10, "revet_foundation": "기초유", }, } def _tiers(rows: list[dict]) -> list[dict]: return [row for row in rows if "다단" in str(row.get("attachment_label") or "")] def test_설계에서_다단_목록을_측점별로_읽는다() -> None: designs = [ {"chainage_m": 85.0, "design": {"extra_walls": WALLS}}, {"chainage_m": 140.0, "design": {"cut_area_m2": 1.0}}, ] assert extra_walls_from_designs(designs) == {85.0: WALLS} def test_선_다단_벽마다_기슭막이_줄이_선다() -> None: rows = facility_structures([POINT], {85.0: WALLS}) tiers = _tiers(rows) assert [row["attachment_label"] for row in tiers] == [ "유출측 다단 기슭막이 1단", "유출측 다단 기슭막이 2단", ] first, second = tiers assert first["type_id"] == "revetment" assert first["options"]["height_m"] == 1.8 and first["options"]["form"] == "돌쌓기(메)" assert (first["start_m"], first["end_m"]) == (85.05 - 4.0, 85.05 + 4.0) assert first["options"]["foundation"] == "기초유" # 기준벽과 같은 기초 assert first["unconfirmed"] == "" # 높이를 안 적은 단 — 줄은 서되 미확정 assert "다단 높이를 안 적음" in second["unconfirmed"] def test_다단_줄이_원단위와_금액_합에_든다() -> None: table = build_table(facility_structures([POINT], {85.0: WALLS}), {"revetment": "기슭막이"}, {}) tiers = [s for s in table["structures"] if "다단" in s["name"]] assert len(tiers) == 2 assert tiers[0]["components"] and not tiers[0]["unconfirmed"] assert tiers[1]["unconfirmed"] def test_윗단이_미확정이면_아랫단도_미확정() -> None: """면적은 첫 미확정 단부터 아래를 없는 것으로 봄(B06) — B08 도 같은 선에서 끊어야 성토·벽이 함께 움직임.""" walls = [ dict(WALLS[1], key="extra0"), dict(WALLS[0], key="extra1"), ] # 1단 미확정 · 2단 높이 적음 tiers = _tiers(facility_structures([POINT], {85.0: walls})) assert "다단 높이를 안 적음" in tiers[0]["unconfirmed"] assert "윗단(1단)이 미확정" in tiers[1]["unconfirmed"] def test_미확정_단은_성토를_안_깎는다() -> None: """2026-09-14 브레인 판정 — 미확정 벽이 성토만 깎으면 「벽도 성토도 없는 빈 자리」가 생김.""" geom = (ROOT / "B06_Section" / "B06_Section_UI_Cross_Culvert_Geom.ts").read_text( encoding="utf-8" ) assert "extendTrimSlope(drawnFillPoints(extrasForArea)" in geom assert "extendTrimSlope(drawnFillPoints(basinExtrasForArea)" in geom assert "applied.h == null" in geom def test_벽_몸이_성토에도_든_겹침을_사유로_드러냄() -> None: """2026-09-14 브레인 — 폐회로는 구조물 면적을 안 뺌(2026-09-06 확정)이라 벽 몸이 성토에도 셈. 고치지 않고 사유로: 258.12 실측 1단 벽 몸 1.335㎡ 가 성토 폐회로 안.""" walls = [dict(WALLS[0], fill_overlap_m2=1.336, before_m=5.0, after_m=5.0)] note = " ".join(_tiers(facility_structures([POINT], {85.0: walls}))[0]["notes"]) assert "성토 면적에도" in note and "1.34㎡" in note and "13.4㎥" in note bare = " ".join(_tiers(facility_structures([POINT], {85.0: WALLS[:1]}))[0]["notes"]) assert "성토 면적에도" not in bare # 값이 없으면(옛 저장본) 지어내지 않음 def test_서버가_벽_몸_겹침을_잰다() -> None: assert "wallBodyInFillM2(" in LAYOUTS and "fill_overlap_m2" in LAYOUTS def test_관_기준벽도_겹침_사유가_뜬다() -> None: """2026-09-14 브레인 ㉡ — 같은 겹침이 관 기준벽에도 있음. 다단에만 뜨면 「다단만 문제」로 읽힘. 258.12 실측 유출 벽 몸 1.417㎡ 가 성토 폐회로 안.""" designs = [ {"chainage_m": 85.0, "design": {"extra_walls": [], "wall_fill_overlap": {"outlet": 1.417}}} ] rows = facility_structures([POINT], extra_walls_from_designs(designs)) by_label = { row["attachment_label"]: " ".join(row["notes"]) for row in rows if row.get("notes") is not None } outlet = next(text for label, text in by_label.items() if "유출" in label) inlet = next(text for label, text in by_label.items() if "유입" in label) assert "성토 면적에도" in outlet and "1.42㎡" in outlet and "14.2㎥" in outlet assert "성토 면적에도" not in inlet # 잰 값이 없는 벽엔 지어내지 않음 assert not _tiers(rows) # 기준벽 값이 다단 줄로 새지 않음 assert "wall_fill_overlap" in LAYOUTS and '"wall_fill_overlap"' in PREBUILD def test_목록이_없으면_종전과_같다() -> None: assert not _tiers(facility_structures([POINT])) assert not _tiers(facility_structures([POINT], {})) def test_서버_재계산이_선_다단_목록을_남긴다() -> None: assert "export function extraWallRows" in LAYOUTS assert "extraWallRows(sections)" in NODE and "extra_walls" in NODE assert '"extra_walls"' in PREBUILD