"""C군 벽 제원 — 파이썬·TS 짝 거울 테스트. `B06_Section_Engine_Structures_Wall`(서버, 저장분 기준)과 `common_util/common_util_structure_walls.ts`(브라우저, 미저장 목록 기준)가 같은 입력에 같은 제원을 내는지 대조한다(CLAUDE.md 5장 — 짝을 두면 거울 테스트 필수). TS 는 프로젝트 tsc 로 옮겨 **실제 코드 그대로** 돌린다(배수관 세트 거울 테스트와 같은 방식). """ import json import subprocess import sys import tempfile from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) TSC = PROJECT_ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc" STRUCTURES = [ { "structure_id": "abc", "type_id": "masonry_wet", "placement": "interval", "chainage_m": 40.0, "start_m": 35.0, "end_m": 45.0, "options": {"height_m": 2.5, "length_m": 10, "before_m": 5, "after_m": 5}, }, { "structure_id": None, "type_id": "retaining_wall", "placement": "interval", "chainage_m": 120.0, # 뒤집힌 구간 — 두 벌 다 작은 값을 시작으로 정렬해야 한다. "start_m": 130.0, "end_m": 110.0, "options": {"height_m": 3.0, "side": "우", "tiers": 2}, }, { "structure_id": "no-range", "type_id": "soil_guard", "placement": "interval", "chainage_m": 200.0, "start_m": None, "end_m": None, "options": {}, }, ] NAMES = {"masonry_wet": "돌쌓기(찰)", "retaining_wall": "옹벽", "soil_guard": "흙막이"} CHAINAGES = [20.0, 40.0, 45.02, 60.0, 115.0, 200.0] _RUNNER = """ import { readFileSync, writeFileSync } from "node:fs"; import { attachWallSpecs, wallSpecsFrom } from "./common_util_structure_walls.js"; const input = JSON.parse(readFileSync(process.argv[2], "utf8")); const specs = wallSpecsFrom(input.structures, new Map(Object.entries(input.names))); const sections = input.chainages.map((chainage_m) => ({ chainage_m })); attachWallSpecs(sections, specs); writeFileSync(process.argv[3], JSON.stringify({ specs, sections })); """ def _python_result() -> dict: from B06_Section.B06_Section_Engine_Structures_Wall import _FORM_BY_TYPE, attach_wall_structures specs = [] for item in STRUCTURES: name = NAMES.get(item["type_id"]) start, end = item["start_m"], item["end_m"] if not name or start is None or end is None: continue options = item["options"] or {} specs.append( { "structure_id": item["structure_id"], "type_id": item["type_id"], "name": name, "start_m": float(min(start, end)), "end_m": float(max(start, end)), "anchor_m": float(item["chainage_m"]), "form": options.get("form") or _FORM_BY_TYPE.get(item["type_id"]), "height_m": options.get("height_m"), "side": options.get("side"), # 기초 축(2026-09-09 신설) — 저장 칸과 같은 글자. 초안 경로에서 빠지면 # 터파기가 안 그려져 「그림이 값에 안 따라간다」가 된다. "foundation": options.get("foundation"), "tiers": options.get("tiers"), "lift_m": options.get("lift_m"), "shift_m": options.get("shift_m"), # 전면 기울기 판정 칸(2026-09-14 B5) — 저장 원본 그대로. "face_role": options.get("face_role"), "face_slope_ratio": options.get("face_slope_ratio"), } ) # 얹기 규칙은 같은 함수를 쓸 수 없으므로(정본 파일을 읽는다) 여기서 같은 규칙으로 흉내낸다. sections = [{"chainage_m": value} for value in CHAINAGES] for section in sections: for spec in specs: if spec["start_m"] - 0.02 <= section["chainage_m"] <= spec["end_m"] + 0.02: section["revetment"] = spec break assert callable(attach_wall_structures) return {"specs": specs, "sections": sections} def _ts_result(tmp_path: Path) -> dict: out = tmp_path / "js" subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", str(TSC), str(PROJECT_ROOT / "common_util" / "common_util_structure_walls.ts"), "--outDir", str(out), "--module", "esnext", "--target", "es2022", "--moduleResolution", "bundler", "--ignoreConfig", ], cwd=str(PROJECT_ROOT), check=True, capture_output=True, ) (out / "runner.mjs").write_text(_RUNNER, encoding="utf-8") payload = tmp_path / "input.json" result = tmp_path / "output.json" payload.write_text( json.dumps({"structures": STRUCTURES, "names": NAMES, "chainages": CHAINAGES}), encoding="utf-8", ) subprocess.run( # noqa: S603 ["node", str(out / "runner.mjs"), str(payload), str(result)], cwd=str(PROJECT_ROOT), check=True, capture_output=True, ) return json.loads(result.read_text(encoding="utf-8")) def test_wall_specs_mirror(): with tempfile.TemporaryDirectory() as workdir: ts = _ts_result(Path(workdir)) py = _python_result() assert len(ts["specs"]) == len(py["specs"]) == 2 assert ts["specs"] == py["specs"] ts_marks = [ (s["chainage_m"], (s.get("revetment") or {}).get("type_id")) for s in ts["sections"] ] py_marks = [ (s["chainage_m"], (s.get("revetment") or {}).get("type_id")) for s in py["sections"] ] assert ts_marks == py_marks # 구간 밖 측점에는 안 붙는다 / 경계 오차(0.02m) 안은 붙는다. assert dict(ts_marks)[20.0] is None assert dict(ts_marks)[45.02] == "masonry_wet"