"""사면길이 3m 초과 — 소단 **검토 대상 정보 한 줄**(경고 아님 · 2026-09-15 브레인 ③). ① 법령 별표2 Ⅰ.2.차.(5) 「붕괴 또는 밀려 내려갈 **우려가 있는 지역**에는 사면길이 2~3m 마다」 — 「우려 있음」을 우리가 판정 못 함 ⇒ 경고가 아니라 정보 ② 3m 를 **넘는** 쪽만(3.00 은 아님) · 절토·성토 둘 다 · 원지반을 못 만난 하한값(≥)도 넘으면 들어감 ③ 벽이 선 쪽은 뺌(성토사면 5m 경고와 같은 규칙 · `wallSides`) ④ 문구는 브레인 문구 그대로 TS 를 실제로 돌린다(파이썬 짝이 없는 화면 판정 — `test_b06_fill_slope_warn` 과 같은 방식). """ from __future__ import annotations import json import re import subprocess from pathlib import Path import pytest PROJECT_ROOT = Path(__file__).resolve().parents[2] TSC = PROJECT_ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc" FOLDER = PROJECT_ROOT / "B06_Section" SOURCES = [ FOLDER / "B06_Section_UI_Cross_Berm_Info.ts", FOLDER / "B06_Section_UI_Cross_FillSlope_Warn.ts", FOLDER / "B06_Section_UI_Cross_Culvert_Const.ts", ] _RUNNER = """ import { readFileSync, writeFileSync } from "node:fs"; import { BERM_INFO_TEXT, bermReviewStations } from "./B06_Section_UI_Cross_Berm_Info.js"; const [inputPath, outputPath] = process.argv.slice(2); const cases = JSON.parse(readFileSync(inputPath, "utf8")); const rows = bermReviewStations(cases.map((c) => c.section), (section) => cases.find((c) => c.section.station_id === section.station_id).lengths, ); writeFileSync(outputPath, JSON.stringify({ text: BERM_INFO_TEXT, rows: rows.map((w) => [w.section.station_id, w.sides.map((s) => s.side)]), })); """ LONG = {"lengthM": 4.0, "open": False} CASES = [ {"section": {"station_id": "both"}, "lengths": {"left": LONG, "right": LONG}}, { "section": {"station_id": "edge"}, "lengths": { "left": {"lengthM": 3.0, "open": False}, "right": {"lengthM": 2.5, "open": True}, }, }, { "section": {"station_id": "open_long"}, "lengths": {"left": None, "right": {"lengthM": 3.4, "open": True}}, }, {"section": {"station_id": "ford", "ford": {}}, "lengths": {"left": LONG, "right": LONG}}, ] def _run(tmp_path: Path) -> dict: out = tmp_path / "js" subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", str(TSC), *map(str, SOURCES), "--outDir", str(out), "--module", "esnext", "--target", "es2022", "--moduleResolution", "bundler", "--ignoreConfig", "--noCheck", "--noResolve", ], 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") payload, result = tmp_path / "input.json", tmp_path / "output.json" payload.write_text(json.dumps(CASES, ensure_ascii=False), 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")) @pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치") def test_3m_넘는_사면만_벽_선_쪽은_뺀다(tmp_path: Path) -> None: got = _run(tmp_path) assert dict(got["rows"]) == {"both": ["left", "right"], "open_long": ["right"]} assert got["text"] == "사면길이 3m 초과 — 소단 검토 대상(별표2 차.(5) · 붕괴 우려 지역 조건)"