- 옮김: 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
156 lines
5.8 KiB
Python
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%) — 설치 여부는 설계자 판단"
|
|
)
|