- 옮김: 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
220 lines
9.1 KiB
Python
220 lines
9.1 KiB
Python
"""211-1 관 보호공 **유입** 흙막이 빈 칸 기본 높이 = 관경(0.1 올림) + 0.7 — 유출 · 집수정은 + 0.5 그대로.
|
|
|
|
못박는 것
|
|
- 파이썬 `pipe_wall_min_height_m(d, "inlet")` · 새 관 기본값(`_apply_new_defaults`) · 관 세트 스펙 · B08 집계 빈 높이.
|
|
- TS 짝 `buildCulvertSets` ↔ 파이썬 `_culvert_set`(유입 · 유출 스펙 같은 값) · 횡단도 `revetWallSpec`.
|
|
- 저장된 높이는 그대로 · 유출 · 집수정 무변 · 흙막이 아닌 시설(숨은 관)은 본체 높이 그대로.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from B04_PreProcess import B04_PreProcess_Router_Basins as basins
|
|
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
|
|
from B06_Section import B06_Section_Engine_Culvert as culvert
|
|
from B08_Quantity import B08_Quantity_Router_Summary as summary
|
|
from common_util.common_util_drainage_pipes import PipePoint
|
|
from M02_MasterTemplete import M02_Item_Snapshot as snapshots
|
|
from M02_MasterTemplete.StructOptions import M02_SO_Definition as definition
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
|
|
|
|
|
|
def _revet(side: str, height: float | None = None) -> dict:
|
|
found = {
|
|
f"{side}_type": "기슭막이",
|
|
f"{side}_revet_form": "돌쌓기(메)",
|
|
f"{side}_revet_before_m": 5,
|
|
f"{side}_revet_after_m": 5,
|
|
}
|
|
if height is not None:
|
|
found[f"{side}_revet_height_m"] = height
|
|
return found
|
|
|
|
|
|
def test_파이썬_기본_높이_유입만_0_7():
|
|
h = culvert.pipe_wall_min_height_m
|
|
assert h(0.8, "inlet") == 1.5 and h(0.8, "outlet") == 1.3
|
|
assert h(1.2, "inlet") == 1.9 and h(1.2) == 1.7 # 쪽 안 줌 = 옛 값(집수정)
|
|
assert h(1.0, "inlet") == 1.7 and h(1.0, "outlet") == 1.5
|
|
assert h(0.45, "inlet") == 1.2 # 0.5 로 올림 + 0.7
|
|
|
|
|
|
def test_새_관_기본값_유입_흙막이만_0_7():
|
|
fresh = PipePoint(chainage_m=10.0, source="stream", options={"pipe_diameter_mm": 1200})
|
|
basins._apply_new_defaults([fresh])
|
|
assert fresh.options["inlet_revet_height_m"] == pytest.approx(1.9)
|
|
assert fresh.options["outlet_revet_height_m"] == pytest.approx(1.7)
|
|
assert fresh.options["inlet_basin_height_m"] == pytest.approx(1.7)
|
|
# 이미 적은 높이(추천 · 손 값)는 그대로 · 나머지 칸만 채움
|
|
kept = PipePoint(
|
|
chainage_m=20.0,
|
|
source="stream",
|
|
options={"pipe_diameter_mm": 1200, "inlet_revet_height_m": 1.2},
|
|
)
|
|
basins._apply_new_defaults([kept])
|
|
assert kept.options["inlet_revet_height_m"] == 1.2
|
|
assert kept.options["outlet_revet_height_m"] == pytest.approx(1.7)
|
|
|
|
|
|
def test_관_세트_빈_높이_유입_0_7_저장값_그대로():
|
|
blank = culvert._culvert_set({"pipe_diameter_mm": 1200, **_revet("inlet"), **_revet("outlet")})
|
|
assert blank["inlet"]["revet_height_m"] == pytest.approx(1.9)
|
|
assert blank["outlet"]["revet_height_m"] == pytest.approx(1.7)
|
|
stored = culvert._culvert_set(
|
|
{"pipe_diameter_mm": 1200, **_revet("inlet", 1.2), **_revet("outlet", 2.4)}
|
|
)
|
|
assert stored["inlet"]["revet_height_m"] == 1.2 and stored["outlet"]["revet_height_m"] == 2.4
|
|
# 옛 기본 1.5 · 관경 > 1.0 = 저장 값 해석(139-1) — 관경 + 0.5 그대로
|
|
legacy = culvert._culvert_set({"pipe_diameter_mm": 1200, **_revet("inlet", 1.5)})
|
|
assert legacy["inlet"]["revet_height_m"] == pytest.approx(1.7)
|
|
|
|
|
|
_RUNNER = """
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { buildCulvertSets } from "./common_util_culvert_sets.js";
|
|
const input = JSON.parse(readFileSync(process.argv[2], "utf8"));
|
|
const sets = buildCulvertSets(input.points, input.registry);
|
|
writeFileSync(process.argv[3], JSON.stringify([...sets.entries()]));
|
|
"""
|
|
|
|
_WALL_RUNNER = """
|
|
import { writeFileSync } from "node:fs";
|
|
import { revetWallSpec } from "./B06_Section_UI_Cross_Culvert_Const.js";
|
|
const pick = (spec, hidden, auto) => revetWallSpec(spec, {}, hidden, 1.2, auto).pureHeight;
|
|
const base = { structure: "기슭막이" };
|
|
writeFileSync(process.argv[2], JSON.stringify({
|
|
inlet: pick({ ...base, role: "inlet" }, false),
|
|
outlet: pick({ ...base, role: "outlet" }, false),
|
|
stored: pick({ ...base, role: "inlet", revet_height_m: 1.2 }, false),
|
|
hidden: pick({ ...base, role: "inlet" }, true, 2.0),
|
|
}));
|
|
"""
|
|
|
|
|
|
def _tsc(source: Path, out: Path) -> None:
|
|
subprocess.run( # noqa: S603 — 고정 실행 파일 · 타입 오류가 있어도 JS 는 나옴
|
|
[
|
|
"node",
|
|
str(TSC),
|
|
str(source),
|
|
"--outDir",
|
|
str(out),
|
|
"--module",
|
|
"esnext",
|
|
"--target",
|
|
"es2022",
|
|
"--moduleResolution",
|
|
"bundler",
|
|
"--skipLibCheck",
|
|
"--ignoreConfig",
|
|
],
|
|
cwd=str(ROOT),
|
|
capture_output=True,
|
|
)
|
|
|
|
|
|
@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치")
|
|
def test_TS_세트_거울(tmp_path: Path) -> None:
|
|
points = [
|
|
{
|
|
"chainage_m": 20.0,
|
|
"facility": "pipe",
|
|
"options": {"pipe_diameter_mm": 1200, **_revet("inlet"), **_revet("outlet")},
|
|
},
|
|
{
|
|
"chainage_m": 40.0,
|
|
"facility": "pipe",
|
|
"options": {"pipe_diameter_mm": "800", **_revet("inlet", 1.2), **_revet("outlet")},
|
|
},
|
|
{
|
|
"chainage_m": 60.0,
|
|
"facility": "pipe",
|
|
"options": {"pipe_diameter_mm": 1200, **_revet("inlet", 1.5)}, # 옛 1.5
|
|
},
|
|
]
|
|
registry = {
|
|
type_id: {o.key: o.default for o in entry.options}
|
|
for type_id, entry in structure_type_map().items()
|
|
if type_id in ("pipe", "ford_bridge", "ford_pavement", "box_culvert")
|
|
}
|
|
out = tmp_path / "js"
|
|
_tsc(ROOT / "common_util" / "common_util_culvert_sets.ts", out)
|
|
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
|
payload, result = tmp_path / "in.json", tmp_path / "out.json"
|
|
payload.write_text(json.dumps({"points": points, "registry": registry}), encoding="utf-8")
|
|
run = subprocess.run( # noqa: S603
|
|
["node", str(out / "runner.mjs"), str(payload), str(result)],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
assert run.returncode == 0, run.stderr[-2000:]
|
|
got = {at: spec for at, spec in json.loads(result.read_text(encoding="utf-8"))}
|
|
for point in points:
|
|
want = culvert._culvert_set(point["options"])
|
|
spec = got[point["chainage_m"]]
|
|
for side in ("inlet", "outlet"):
|
|
assert spec[side].get("revet_height_m") == want[side].get("revet_height_m"), point
|
|
assert got[20.0]["inlet"]["revet_height_m"] == pytest.approx(1.9)
|
|
assert got[20.0]["outlet"]["revet_height_m"] == pytest.approx(1.7)
|
|
assert got[40.0]["inlet"]["revet_height_m"] == 1.2 # 저장 값 그대로
|
|
assert got[40.0]["outlet"]["revet_height_m"] == pytest.approx(1.3)
|
|
assert got[60.0]["inlet"]["revet_height_m"] == pytest.approx(1.7) # 옛 1.5 → 관경 + 0.5
|
|
|
|
|
|
@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치")
|
|
def test_횡단도_벽_기본_높이(tmp_path: Path) -> None:
|
|
out = tmp_path / "js"
|
|
_tsc(ROOT / "B06_Section" / "logic" / "B06_Section_UI_Cross_Culvert_Const.ts", out)
|
|
built = next(
|
|
out.rglob("B06_Section_UI_Cross_Culvert_Const.js")
|
|
) # 가져온 파일 따라 경로가 깊어짐
|
|
runner = built.parent / "runner.mjs"
|
|
runner.write_text(_WALL_RUNNER, encoding="utf-8")
|
|
result = tmp_path / "out.json"
|
|
run = subprocess.run( # noqa: S603
|
|
["node", str(runner), str(result)], capture_output=True, text=True
|
|
)
|
|
assert run.returncode == 0, run.stderr[-2000:]
|
|
got = json.loads(result.read_text(encoding="utf-8"))
|
|
assert got["inlet"] == pytest.approx(1.9) # Φ1200 + 0.7
|
|
assert got["outlet"] == pytest.approx(1.7) # 무변
|
|
assert got["stored"] == 1.2 # 칸 값 그대로
|
|
assert got["hidden"] == 2.0 # 숨은 관(세월교 · 박스) = 본체 높이 그대로
|
|
|
|
|
|
def test_B08_집계_빈_높이_유입만_0_7(tmp_path: Path) -> None:
|
|
combo = {"layer": "system", "owner": None, "key": "SS000011", "name": "횡단배수 배관"}
|
|
root = tmp_path / "proj"
|
|
edits = root / "B04_PreProcess/drainage/edits"
|
|
edits.mkdir(parents=True)
|
|
point = {
|
|
"chainage_m": 1433.0,
|
|
"source": "user",
|
|
"options": {"pipe_diameter_mm": 800, **_revet("inlet"), **_revet("outlet")},
|
|
"summary_item": combo,
|
|
"group_items": {"gnbf9m9": combo, "g9lq9uk": combo},
|
|
}
|
|
(edits / "pipe_points.json").write_text(
|
|
json.dumps({"points": [point]}, ensure_ascii=False), encoding="utf-8"
|
|
)
|
|
designs = [
|
|
{
|
|
"chainage_m": 1433.0,
|
|
"design": {"pipe_length_m": 18, "inlet_basin": None, "extra_walls": []},
|
|
}
|
|
]
|
|
names = definition.read()["종류"]["pipe"]["이름"]
|
|
linked = {owner: {} for owner in snapshots.assignments(root)}
|
|
out = summary.pipe_summary(root, designs, names, 6.0, linked=linked)
|
|
ids = [c["id"] for c in out["columns"] if c["id"].startswith("pipe:revet")]
|
|
assert ids == ["pipe:revet|돌쌓기(메)|1.3", "pipe:revet|돌쌓기(메)|1.5"], (
|
|
ids
|
|
) # 유출 1.3 · 유입 1.5
|