"""터파기 단면 — TS 한 벌(`common_util_excavation.ts`)이 법정 표 · 실무 정본 값을 냄 (2026-09-09). 화면(B06 횡단도)이 그리고 서버(B08 수량 · Node `excavation.trench`)가 세는 값. 파이썬 짝(`common_util_excavation.py`)은 운영 부름 0 이라 걷음(511-B B-6) — 기대값 = 걷기 전 파이썬 값 그대로. ⚠ 근거의 급이 다른 두 갈래를 함께 잠근다 — 관은 **법정 표**(KCS 44 40 10 그림 3.2-1), 벽은 **실무 정본 식**(구조도 기슭막이 xls). """ from __future__ import annotations import json import re import subprocess from pathlib import Path import pytest PROJECT_ROOT = Path(__file__).resolve().parents[2] # 걷기 전 파이썬 짝 값(511-B B-6) PIPE_TABLE = { 300: 700, 350: 800, 400: 900, 450: 1000, 500: 1100, 600: 1200, 700: 1400, 800: 1600, 900: 1700, 1000: 1800, 1100: 2000, 1200: 2100, 1300: 2200, 1400: 2400, 1500: 2500, } # fmt: skip WALL_FOUNDATION_DEPTH_M = 0.5 WALL_FOUNDATION_WIDTH_M = 0.9 WALL_TRENCH_CLEARANCE_M = 0.2 TSC = PROJECT_ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc" _RUNNER = """ import { writeFileSync } from "node:fs"; import { PIPE_TRENCH_WIDTH_MM, WALL_FOUNDATION_DEPTH_M, WALL_FOUNDATION_WIDTH_M, WALL_TRENCH_CLEARANCE_M, pipeTrenchWidthM, wallTrenchAreaM2, wallTrenchWidthM, } from "./common_util_excavation.js"; const out = { table: PIPE_TRENCH_WIDTH_MM, pipe: [300, 800, 1000, 1200, 1500, 250, 1600].map((d) => pipeTrenchWidthM(d)), wallWithFoundation: [1.0, 2.0, 3.0].map((h) => wallTrenchAreaM2(h, 0.7, true)), wallBlindingOnly: [1.0, 2.0, 3.0].map((h) => wallTrenchAreaM2(h, 0.7, false)), wallWidth: [0.7, 1.05, 0].map((t) => wallTrenchWidthM(t)), nulls: [pipeTrenchWidthM(null), wallTrenchAreaM2(null, 0.7, true), wallTrenchAreaM2(1, null, true)], practice: [1.0, 2.0, 3.0].map((h) => wallTrenchAreaM2(h, 0.83, true)), consts: [WALL_FOUNDATION_DEPTH_M, WALL_FOUNDATION_WIDTH_M, WALL_TRENCH_CLEARANCE_M], }; writeFileSync(process.argv[2], JSON.stringify(out)); """ def _ts_values(tmp_path: Path) -> dict: out = tmp_path / "js" subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", str(TSC), str(PROJECT_ROOT / "common_util" / "common_util_excavation.ts"), "--outDir", str(out), "--module", "esnext", "--target", "es2022", "--moduleResolution", "bundler", "--ignoreConfig", ], cwd=str(PROJECT_ROOT), check=True, capture_output=True, ) for emitted in out.glob("*.js"): text = emitted.read_text(encoding="utf-8") emitted.write_text( re.sub(r'(from "\./[^"]+?)(")', lambda m: m.group(1) + ".js" + m.group(2), text), encoding="utf-8", ) (out / "runner.mjs").write_text(_RUNNER, encoding="utf-8") result = tmp_path / "out.json" subprocess.run( # noqa: S603 ["node", str(out / "runner.mjs"), str(result)], cwd=str(PROJECT_ROOT), check=True, capture_output=True, ) return json.loads(result.read_text(encoding="utf-8")) @pytest.fixture(scope="module") def ts(tmp_path_factory) -> dict: if not TSC.is_file(): pytest.skip("프론트엔드 의존성 미설치") return _ts_values(tmp_path_factory.mktemp("excavation")) def test_걷기_전_파이썬_값과_같다(ts: dict) -> None: assert {int(k): v for k, v in ts["table"].items()} == PIPE_TABLE assert ts["pipe"] == [0.7, 1.6, 1.8, 2.1, 2.5, None, None] assert ts["wallWithFoundation"] == pytest.approx([1.35, 2.25, 3.15], abs=1e-12) assert ts["wallBlindingOnly"] == pytest.approx([0.97, 1.87, 2.77], abs=1e-12) assert ts["wallWidth"][:2] == pytest.approx([0.9, 1.25], abs=1e-12) assert ts["wallWidth"][2] is None assert ts["nulls"] == [None, None, None] assert ts["consts"] == [ WALL_FOUNDATION_DEPTH_M, WALL_FOUNDATION_WIDTH_M, WALL_TRENCH_CLEARANCE_M, ] def test_관은_표를_그대로_쓴다(ts: dict) -> None: """⚠ 관경 + 2b 로 계산하지 않는다 — 표가 관 두께·작업여유를 담고 있다(800 → 1.6).""" width_800 = ts["pipe"][1] assert width_800 == 1.6 and width_800 != 0.8 + 2 * 0.3 assert all(width is not None for width in ts["pipe"][1:5]) # 800 · 1000 · 1200 · 1500 def test_표에_없는_관경은_지어내지_않는다(ts: dict) -> None: assert ts["pipe"][5:] == [None, None] # 250 · 1600 def test_실무_정본_수치와_맞는다(ts: dict) -> None: """xls 7탭: H=1.0 → 1.03+0.45 = 1.48 · H=2.0 → 2.51 · H=3.0 → 3.54 (평균두께 0.83).""" for area, expected in zip(ts["practice"], (1.48, 2.51, 3.54), strict=True): assert area == pytest.approx(expected, abs=0.01) def test_기초_유무가_기초분을_가른다(ts: dict) -> None: with_base, without = ts["wallWithFoundation"][1], ts["wallBlindingOnly"][1] # H 2.0 · 0.7 assert with_base - without == pytest.approx( WALL_FOUNDATION_DEPTH_M * WALL_FOUNDATION_WIDTH_M - 0.1 * 0.7 ) def test_여유폭은_평균두께에_더한다(ts: dict) -> None: assert ts["wallWidth"][0] == pytest.approx(0.7 + WALL_TRENCH_CLEARANCE_M)