"""178-1 사면안정 벽(흙막이 · 옹벽) 가드 한 벌 — 카드 · 서버 면적 · B07 · 3D. 흠: 카드는 「연동 관 벽이 이 측점에 실제로 설 때만」 독립 벽을 건너뛰는데, 서버 면적 (`computeStoredLayouts` → Node) · B07 은 링크만 있어도 건너뛰고 3D 는 관 측점만 봤다. ⇒ 관 링크는 있으나 관 벽이 안 서는 측점에서 카드엔 흙막이가 서고 서버 면적 · B07 엔 없음. 고침: `ownWallsDrawAt` 한 벌을 세 곳이 함께 씀. 서버 면적은 같은 TS 를 Node 로 돌리므로 번들을 새로 빌드해 그 결과로 판정한다. """ import copy import json import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] VITE = ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js" FIXTURE = ROOT / "resources" / "tester" / "fixtures" / "b06_73_4_body_guard_sections.json" SOIL_GUARD = { "structure_id": "w1", "type_id": "soil_guard", "name": "흙막이", "start_m": 150, "end_m": 370, "anchor_m": 260, "form": "돌쌓기(찰)", "height_m": 1.5, } @pytest.fixture(scope="module") def bundle(tmp_path_factory) -> Path: if not VITE.is_file(): pytest.skip("프론트엔드 의존성 미설치") out = tmp_path_factory.mktemp("server_calc") subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", str(VITE), "build", "--configLoader", "runner", "--ssr", "../B06_Section/B06_Section_Server_Calc_Node.ts", "--outDir", str(out), "--logLevel", "error", ], cwd=str(ROOT), check=True, capture_output=True, ) return out / "B06_Section_Server_Calc_Node.js" def _areas(bundle: Path, sections: list, tmp_path: Path) -> dict[float, tuple]: src, dst = tmp_path / "in.json", tmp_path / "out.json" src.write_text(json.dumps({"detail": {"cross_sections": sections}}), encoding="utf-8") subprocess.run(["node", str(bundle), str(src), str(dst)], check=True, cwd=str(ROOT)) # noqa: S603 rows = json.loads(dst.read_text(encoding="utf-8"))["areas"] return {row["chainage_m"]: (row["cut_area_m2"], row["fill_area_m2"]) for row in rows} def _sections() -> list: return copy.deepcopy(json.loads(FIXTURE.read_text(encoding="utf-8"))["cross_sections"]) def test_링크만_있고_관_벽이_안_서면_흙막이_면적_행이_나온다(bundle, tmp_path): """173.087 관의 다단 구간값(20 m)이 160 · 180 을 덮어 링크는 서지만 관 벽(전 · 후 5 m)은 안 섬.""" sections = [s for s in _sections() if s["chainage_m"] < 300] sections[1]["design"]["extra_spans"] = {"extra0": {"before_m": 20, "after_m": 20}} for section in sections: if not section.get("culvert"): section["revetment"] = SOIL_GUARD areas = _areas(bundle, sections, tmp_path) assert 160 in areas and 180 in areas, f"흙막이가 선 측점 면적 행 없음: {sorted(areas)}" # 표준 성토(160 = 1.45 · 180 = 5.32 ㎡)보다 벽이 사면을 끊어 줄어듦 assert areas[160][1] < 1.45 and areas[180][1] < 5.32 def test_관_벽이_서는_측점은_독립_벽을_안_그린다(bundle, tmp_path): """340 · 360 은 352.14 관 벽이 섬 — 흙막이를 얹어도 면적이 관 벽 그대로(겹쳐 그리지 않음).""" plain = _areas(bundle, _sections(), tmp_path) walled = _sections() for section in walled: if not section.get("culvert"): section["revetment"] = SOIL_GUARD areas = _areas(bundle, walled, tmp_path) assert areas[340] == plain[340] and areas[360] == plain[360] def test_카드_서버_3D_가_가드_한_벌을_쓴다(): files = { "카드": ROOT / "B06_Section" / "B06_Section_UI_Cross_View.ts", "서버 면적 · B07": ROOT / "B06_Section" / "B06_Section_Structure_Layouts.ts", "3D": ROOT / "B05_Profile" / "B05_Profile_UI_Corridor_Structures.ts", } for where, path in files.items(): source = path.read_text(encoding="utf-8") assert "ownWallsDrawAt(" in source, f"{where} 가 가드 한 벌을 안 씀" assert "culvertWallsStandAt(" not in source, f"{where} 에 따로 가드가 남음"