Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
106 lines
4.2 KiB
Python
106 lines
4.2 KiB
Python
"""178-1 사면안정 벽(흙막이 · 옹벽) 가드 한 벌 — 카드 · 서버 면적 · B07 · 3D.
|
|
|
|
흠: 카드는 「연동 관 벽이 이 측점에 실제로 설 때만」 독립 벽을 건너뛰는데, 서버 면적
|
|
(`computeStoredLayouts` → Node) · B07 은 링크만 있어도 건너뛰고 3D 는 관 측점만 봤다.
|
|
⇒ 관 링크는 있으나 관 벽이 안 서는 측점에서 카드엔 흙막이가 서고 서버 면적 · B07 엔 없음.
|
|
|
|
고침: `ownWallsDrawAt` 한 벌을 세 곳이 함께 씀. 서버 면적은 같은 TS 를 Node 로 돌리므로
|
|
번들을 새로 빌드해 그 결과로 판정한다.
|
|
"""
|
|
|
|
import copy
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
VITE = ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"
|
|
FIXTURE = ROOT / "resources" / "tester" / "fixtures" / "b06_73_4_body_guard_sections.json"
|
|
|
|
SOIL_GUARD = {
|
|
"structure_id": "w1",
|
|
"type_id": "soil_guard",
|
|
"name": "흙막이",
|
|
"start_m": 150,
|
|
"end_m": 370,
|
|
"anchor_m": 260,
|
|
"form": "돌쌓기(찰)",
|
|
"height_m": 1.5,
|
|
}
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def bundle(tmp_path_factory) -> Path:
|
|
if not VITE.is_file():
|
|
pytest.skip("프론트엔드 의존성 미설치")
|
|
out = tmp_path_factory.mktemp("server_calc")
|
|
subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
[
|
|
"node",
|
|
str(VITE),
|
|
"build",
|
|
"--configLoader",
|
|
"runner",
|
|
"--ssr",
|
|
"../B06_Section/B06_Section_Server_Calc_Node.ts",
|
|
"--outDir",
|
|
str(out),
|
|
"--logLevel",
|
|
"error",
|
|
],
|
|
cwd=str(ROOT),
|
|
check=True,
|
|
capture_output=True,
|
|
)
|
|
return out / "B06_Section_Server_Calc_Node.js"
|
|
|
|
|
|
def _areas(bundle: Path, sections: list, tmp_path: Path) -> dict[float, tuple]:
|
|
src, dst = tmp_path / "in.json", tmp_path / "out.json"
|
|
src.write_text(json.dumps({"detail": {"cross_sections": sections}}), encoding="utf-8")
|
|
subprocess.run(["node", str(bundle), str(src), str(dst)], check=True, cwd=str(ROOT)) # noqa: S603
|
|
rows = json.loads(dst.read_text(encoding="utf-8"))["areas"]
|
|
return {row["chainage_m"]: (row["cut_area_m2"], row["fill_area_m2"]) for row in rows}
|
|
|
|
|
|
def _sections() -> list:
|
|
return copy.deepcopy(json.loads(FIXTURE.read_text(encoding="utf-8"))["cross_sections"])
|
|
|
|
|
|
def test_링크만_있고_관_벽이_안_서면_흙막이_면적_행이_나온다(bundle, tmp_path):
|
|
"""173.087 관의 다단 구간값(20 m)이 160 · 180 을 덮어 링크는 서지만 관 벽(전 · 후 5 m)은 안 섬."""
|
|
sections = [s for s in _sections() if s["chainage_m"] < 300]
|
|
sections[1]["design"]["extra_spans"] = {"extra0": {"before_m": 20, "after_m": 20}}
|
|
for section in sections:
|
|
if not section.get("culvert"):
|
|
section["revetment"] = SOIL_GUARD
|
|
areas = _areas(bundle, sections, tmp_path)
|
|
assert 160 in areas and 180 in areas, f"흙막이가 선 측점 면적 행 없음: {sorted(areas)}"
|
|
# 표준 성토(160 = 1.45 · 180 = 5.32 ㎡)보다 벽이 사면을 끊어 줄어듦
|
|
assert areas[160][1] < 1.45 and areas[180][1] < 5.32
|
|
|
|
|
|
def test_관_벽이_서는_측점은_독립_벽을_안_그린다(bundle, tmp_path):
|
|
"""340 · 360 은 352.14 관 벽이 섬 — 흙막이를 얹어도 면적이 관 벽 그대로(겹쳐 그리지 않음)."""
|
|
plain = _areas(bundle, _sections(), tmp_path)
|
|
walled = _sections()
|
|
for section in walled:
|
|
if not section.get("culvert"):
|
|
section["revetment"] = SOIL_GUARD
|
|
areas = _areas(bundle, walled, tmp_path)
|
|
assert areas[340] == plain[340] and areas[360] == plain[360]
|
|
|
|
|
|
def test_카드_서버_3D_가_가드_한_벌을_쓴다():
|
|
files = {
|
|
"카드": ROOT / "B06_Section" / "B06_Section_UI_Cross_View.ts",
|
|
"서버 면적 · B07": ROOT / "B06_Section" / "B06_Section_Structure_Layouts.ts",
|
|
"3D": ROOT / "B05_Profile" / "B05_Profile_UI_Corridor_Structures.ts",
|
|
}
|
|
for where, path in files.items():
|
|
source = path.read_text(encoding="utf-8")
|
|
assert "ownWallsDrawAt(" in source, f"{where} 가 가드 한 벌을 안 씀"
|
|
assert "culvertWallsStandAt(" not in source, f"{where} 에 따로 가드가 남음"
|