# -*- coding: utf-8 -*- """65장 세월교 · 박스암거 · 물넘이 포장 — 명세 · 기본값 · 자동 배치 월류 높이. 못박는 것 - 세월교 · 박스 유입 · 유출 = 구조 + 흙막이 칸(형태 · 길이 · 높이 · 전 · 후)만 · 높이 · 측벽 · 바닥판 칸 없음 - 흙막이 칸 기본값(찰 · 10 · 1.5 · 5 · 5) · 추가 흙막이 · 도수로/돌붙임 상자 · 키 `{side}_revet_*` - 「없음」 = 없는 상자(목록 램프 · 잇기 줄 뺌) - 세월교 칸 차례 관경 → 수량(련) → 월류 폭 → 높이 · 정의 기본값 관경 1000 · 폭 10 · 련 2 - 물넘이 포장 경사각 기본 3° · 월류 폭 · 높이 = 세월교와 같은 키 - 자동 배치 새 세월교 빈 월류 높이 = 필요 수심(서버 `ford_height_m` ↔ TS `fordHeightM` 거울) - B06 측벽 읽기 줄은 세월교 · 박스 유입 · 유출 칸에 안 옮김 화면(ORCA)은 따로 봄. """ from __future__ import annotations import json import os import subprocess from pathlib import Path import pytest from B04_PreProcess import B04_PreProcess_Router_Basins as basins from B05_Profile.B05_Profile_Structures_Schema import structure_type_map from common_util.common_util_drainage_pipes import PipePoint from M02_MasterTemplete.M02_Table_Fill_Spec import container_rows HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = Path(HERE).resolve().parents[1] GUARD = [ ["structure", None, None], ["form", ["흙막이"], "돌쌓기(찰)"], ["length", ["흙막이"], "10"], ["height", ["흙막이"], "1.5"], ["before", ["흙막이"], "5"], ["after", ["흙막이"], "5"], ] @pytest.fixture(scope="module") def out() -> dict: proc = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", os.path.join(HERE, "helper_65_ford_box.cjs")], capture_output=True, text=True, encoding="utf-8", timeout=60, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def test_유입_유출은_구조와_흙막이_칸만(out): for spec in ("ford", "box"): for side in ("inlet", "outlet"): assert out[spec][side] == GUARD, (spec, side) assert [i[0] for i in out[spec]["soil_add"]] == [ "form", "length", "height", "before", "after", ] assert out[spec]["chute_add"] == [["structure", None, None]] keys = out[f"{spec}Keys"] assert keys["prefixes"] == ["soil_add_", "chute_add_"] # 측벽 높이 · 좌우 · 상하 · 바닥판 · 날개벽 치수 칸 없음 assert not [ k for k in keys["keys"] if "wall" in k or "slab" in k or k.startswith("wing_in_") ] def test_없음이면_없는_상자(out): for spec in ("fordNone", "boxNone"): assert out[spec]["inlet"] == [True, False] and out[spec]["outlet"] == [True, False] assert out[spec]["soil_add"] == [False, False] def test_흙막이_저장_키와_다시_열기(out): o = out["fordOut"] assert o["inlet_type"] == "흙막이" and o["wing_in"] == "없음" assert (o["inlet_revet_form"], o["inlet_revet_length_m"], o["inlet_revet_height_m"]) == ( "돌망태", 8, 1.2, ) assert o["outlet_type"] == "없음" and not any(k.startswith("outlet_revet_") for k in o) assert o["soil_add_1_form"] == "바자" and o["chute_add_1_structure"] == "돌붙임" back = out["fordBack"] assert back["inlet"][0]["length"] == "8" and back["soil_add"][0]["form"] == "바자" assert out["boxOut"]["wing_out"] == "흙막이" and out["boxOut"]["outlet_revet_length_m"] == 4 def test_세월교_칸_차례와_정의_기본값(out): assert [i[0] for i in out["ford"]["ford"]] == ["diameter", "count", "width", "height"] ford = {o.key: o.default for o in structure_type_map()["ford_bridge"].options} assert (str(ford["pipe_diameter_mm"]), ford["ford_width_m"], ford["pipe_count"]) == ( "1000", 10, 2, ) def test_물넘이_경사각_기본_3_월류_폭_높이_같은_키(out): assert [i[0] for i in out["pave"]["slope"]] == ["angle", "width", "height"] assert out["paveOut"] == {"ford_slope_deg": 3, "ford_width_m": 5, "ford_height_m": 0.12} assert "ford_width_m" not in out["paveRetired"] and "ford_height_m" not in out["paveRetired"] pave = {o.key: o.default for o in structure_type_map()["ford_pavement"].options} assert pave["ford_slope_deg"] == 3 def test_서버_월류_높이_거울(out): for q, b, h in out["table"]: assert basins.ford_height_m(q, b) == h, (q, b) assert basins.ford_height_m(None, 10) is None and basins.ford_height_m(2.54, 0) is None def test_자동_배치_새_세월교는_기본값과_월류_높이(): fresh = PipePoint(chainage_m=120.0, source="stream", facility="ford_bridge") basins._apply_new_defaults([fresh], {120.0: 8.0}) o = fresh.options assert (str(o["pipe_diameter_mm"]), o["ford_width_m"], o["pipe_count"]) == ("1000", 10, 2) assert o["ford_height_m"] == basins.ford_height_m(8.0, 10) == 0.16 kept = PipePoint( chainage_m=120.0, source="stream", facility="ford_bridge", options={"ford_height_m": 0.5} ) basins._apply_new_defaults([kept], {120.0: 8.0}) assert kept.options["ford_height_m"] == 0.5 # 있는 값은 그대로 blind = PipePoint(chainage_m=140.0, source="stream", facility="ford_bridge") basins._apply_new_defaults([blind], {}) assert "ford_height_m" not in blind.options # 유량 없으면 빈칸 def test_집계표_세월교_흙막이_상자_건(): item = { "type_id": "ford_bridge", "options": {"soil_add_1_form": "바자", "soil_add_2_form": "돌망태"}, "복사본": "pipe@50.0", "흙막이단": 0, # 배관 단 수는 세월교에 안 걸림 } rows = container_rows(item, {"gsoiladd2": "SS000001"}) assert [(r["그룹"], r["summary_item"]) for r in rows] == [ ("흙막이 1", None), ("흙막이 2", "SS000001"), ] def test_B06_측벽_읽기_줄은_세월교_박스_칸에_안_옮김(): dock = (ROOT / "B06_Section/B06_Section_UI_Adjust_Dock.ts").read_text(encoding="utf-8") assert "slot === sideSlots?.wingInlet || slot === sideSlots?.wingOutlet) return null" in dock lists = (ROOT / "B05_Profile/B05_Profile_UI_Structures_List.ts").read_text(encoding="utf-8") assert "return isNone(c, state[c.id][0])" in lists