Files
Aislo/resources/tester/test_b06_fill_slope_warn.py
T
eomsangdonandClaude Opus 5.5 281c71b55b refactor(logic): 348-1 순수 계산 TS 49 파일 <페이지>/logic/ 로 옮김 — 이름 앞머리 그대로 · 동작 고침 없음
- 옮김: B04 2 · B05 23 · B06 11 · A00_Common/design 13 (9,299 줄) · 이미 logic/ 에 있던 16 파일 그대로
- 안 옮김: 묶음 입구(package.json build:*) · 파이썬 · 순수지만 계산 아닌 8 (보기 결선 · 콜백 · API 타입 · 갈아끼움 알림판)
- import 266 줄(142 파일) · 시험 · 주석 · 짝 표시 문자열 경로 고침 · B05 · B06 CLAUDE.md 짝 줄 logic/
- 시험 도우미 새 경로: 가짜 require 열쇠 · B05()/B06() 부름 · 형제 import 자리 · tsc 묶어 돌리는 시험 · 정규식 · rglob
- design_view_cross 줄 한도 시험 685 → 688 (긴 import 경로 줄바꿈 3줄)
- 확인: typecheck 0 · 서버 묶음 넷(corridor · server-calc · b07-extent · b07-note) 다시 지어 코드 같음(//#region 경로 주석만 다름) · 관련 시험 270 파일 — 남은 실패 19 는 옮기기 전과 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
2026-10-16 13:44:50 +09:00

156 lines
5.8 KiB
Python

"""성토사면 길이 5m 초과 경고 — 벽 선 쪽만 빼고(좌·우 갈라) 경고만 (2026-09-14 브레인 (나)).
① 5m 를 **넘는** 쪽만 — 5.00 은 아님 · 원지반을 못 만난 하한값(≥)도 5m 이하면 아님
② 벽이 선 쪽은 뺀다 — 배관 유입(상단측)·유출(반대측) 기슭막이, 집수정은 벽 아님
③ 한쪽에만 벽 → 반대쪽은 그대로 경고 · 독립 기슭막이 설치 측(좌/우/양쪽)
④ 세월교·BOX암거는 양쪽 측벽
⑤ 문구는 브레인 승인 그대로
TS 를 실제로 돌린다(파이썬 짝이 없는 화면 판정).
"""
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"
SOURCE = PROJECT_ROOT / "B06_Section" / "logic" / "B06_Section_UI_Cross_FillSlope_Warn.ts"
_RUNNER = """
import { readFileSync, writeFileSync } from "node:fs";
import { FILL_SLOPE_WARN_TEXT, fillSlopeWarnings } from "./B06_Section_UI_Cross_FillSlope_Warn.js";
const [inputPath, outputPath] = process.argv.slice(2);
const cases = JSON.parse(readFileSync(inputPath, "utf8"));
const warnings = fillSlopeWarnings(cases.map((c) => c.section), (section) =>
cases.find((c) => c.section.station_id === section.station_id).lengths,
);
writeFileSync(outputPath, JSON.stringify({
text: FILL_SLOPE_WARN_TEXT,
warnings: warnings.map((w) => [w.section.station_id, w.sides.map((s) => s.side)]),
}));
"""
LONG = {"lengthM": 7.0, "open": False}
BOTH = {"left": LONG, "right": LONG}
def _culvert(inlet: str = "기슭막이", outlet: str = "기슭막이", **extra: object) -> dict:
return {"inlet": {"structure": inlet}, "outlet": {"structure": outlet}, **extra}
CASES = [
{"section": {"station_id": "plain"}, "lengths": BOTH},
{
"section": {"station_id": "edge"},
"lengths": {
"left": {"lengthM": 5.0, "open": False},
"right": {"lengthM": 4.2, "open": True},
},
},
# 상단측 좌 → 유입(좌) 기슭막이 · 유출(우) 기슭막이 — 양쪽 다 벽.
{
"section": {"station_id": "pipe", "uphill_side": "left", "culvert": _culvert()},
"lengths": BOTH,
},
# 유입이 집수정 → 좌(유입측)는 벽 없음 → 좌만 경고.
{
"section": {"station_id": "basin", "uphill_side": "left", "culvert": _culvert("집수정")},
"lengths": BOTH,
},
# 상단측 우 → 유출은 좌 · 유출이 집수정이면 좌만 경고.
{
"section": {
"station_id": "right_up",
"uphill_side": "right",
"culvert": _culvert(outlet="집수정"),
},
"lengths": BOTH,
},
{
"section": {"station_id": "own_left", "culvert": _culvert(hidden_pipe=True, side="좌")},
"lengths": BOTH,
},
{
"section": {
"station_id": "revet_auto",
"revetment": {"side": None},
"design": {"section_mode": "left_cut"},
},
"lengths": {"left": None, "right": LONG},
},
{"section": {"station_id": "ford", "ford": {}}, "lengths": BOTH},
]
def _run(tmp_path: Path) -> dict:
out = tmp_path / "js"
subprocess.run( # noqa: S603 — 고정 실행 파일
[
"node",
str(TSC),
str(SOURCE),
# 실행에 드는 import 는 이것 하나 — 나머지는 타입 import 라 지워진다.
str(SOURCE.with_name("B06_Section_UI_Cross_Culvert_Const.ts")),
"--outDir",
str(out),
"--module",
"esnext",
"--target",
"es2022",
"--moduleResolution",
"bundler",
"--ignoreConfig",
# 타입 줄기가 별칭(@util 등)으로 번져 단독 컴파일로는 못 푼다 — 검사는 typecheck 몫.
"--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_벽_선_쪽만_빼고_5m_넘는_쪽을_경고한다(tmp_path: Path) -> None:
got = dict(_run(tmp_path)["warnings"])
assert got == {
"plain": ["left", "right"],
"basin": ["left"],
"right_up": ["left"],
"own_left": ["right"],
}
# 5.00 · 하한값 4.2(≥) · 양쪽 벽 · 자동 설치 측 벽 · 세월교는 경고 없음.
assert not {"edge", "pipe", "revet_auto", "ford"} & set(got)
@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치")
def test_문구는_승인된_그대로(tmp_path: Path) -> None:
assert _run(tmp_path)["text"] == (
"성토사면 길이 5m 초과 — 법령상 옹벽·석축 설치 대상 "
"(산림자원법 시행규칙 별표2 Ⅰ.2.차.(3).(나) · 임도설치 규정 별표7 2.차.(3).(나)) "
"※ 실무 표본에서도 흔함(영월 63% · 봉화 49%) — 설치 여부는 설계자 판단"
)