Files
Aislo/resources/tester/test_272_berm_source.py
T
eomsangdonandClaude Opus 5.5 dd70ce9b90 fix(467 덧 셋): 제자리로 온 관 = 저장 정본 표본 · 일반 측점 자리 관 지움 = 갈아끼움 · 시설 줄 종류 표 전 측점 안 칠함
- swapCrossSections 같은 자리 원천 끝에 저장 정본(Section_Canon CanonRow 에 자리 · 표본 · Section_Store 가 savedDesigns 넘김) — 옮겼다 제자리로 온 관 · 버린 노선 초안 뒤 측점 = 정본 표본 · 기하
- sameStationList — 시설 측점(irregular) 자리가 일반 측점으로 돌아가면 다른 목록(관 지운 카드가 남던 흠)
- design_facility_lane paint — 종류 표 전 빈 측점을 종단도에 안 씀
- 시험 test_272_swap_keeps_canon · test_272_berm_source 에 셋(옛 코드 실패 확인)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 07:54:29 +09:00

206 lines
7.5 KiB
Python

# -*- coding: utf-8 -*-
"""467 (요구 272 · 분석 4장 (B2)) — 소단 원천 한 벌.
못박는 것
- 소단 사본(세션 `berm`) 쓰는 자리 = 페이지 시설 줄 하나 — 구조물 받기 전 안 씀 ·
같으면 안 씀 · 바뀌면 한 번 · 지우면 빈 목록(B05 만 열어도 씀 · 횡단 보기는 안 씀).
- 다시 셈 = 사본 없으면 저장분 `design.berm`(파이썬 `load_berm_spans` 규칙) —
B05 만 연 값 = B06 연 뒤 값 = [저장] 서버 값(`enforce_design_lines`) · 빈 사본 = 소단 없음.
- 거울 — TS `bermSpansFromStructures` ↔ 파이썬 `load_berm_spans` ·
TS `storedBermSpec` ↔ 파이썬 `berm_from`.
Node 헬퍼 `helper_272_berm_source.mjs` 가 vite ssr 로 묶은 진짜 모듈을 부름.
"""
import json
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B06_Section.B06_Section_Engine_Structures_Wall import load_berm_spans # noqa: E402
from B06_Section.B06_Section_Router_Design import berm_from, enforce_design_lines # noqa: E402
from config.config_system import ( # noqa: E402
STANDARD_CROSS_SECTION,
STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M,
)
def _berm(start, end, **options):
return {
"type_id": "berm",
"placement": "interval",
"start_m": start,
"end_m": end,
"options": options,
}
SAVED = [_berm(0.0, 100.0, width_m=1.0, interval_m=3.0, slope_deg=0.0, depth_m=1.0)]
STRUCTURE_CASES = [
SAVED,
[
_berm(50.0, 80.0, width_m=0), # 폭 0 = 없음
_berm(10.0, 30.0, depth_m=""), # 빈 깊이 = 기본 1.0
_berm(30.0, 40.0, depth_m=-2, width_m="0.7"), # 음수 깊이 = 0 · 글 수
_berm(60.0, 70.0, width_m="abc", interval_m=2.5, slope_deg=4), # 못 읽는 폭 = 기본
],
[{"type_id": "berm", "placement": "point", "chainage_m": 25.0, "options": {}}],
[],
]
STORED_CASES = [
{"width_m": 0.5},
{"width_m": 0.5, "depth_m": -1},
{},
{"width_m": "0.7", "interval_m": 2},
None,
{"width_m": None},
{"width_m": ""},
]
GROUND = [
{"offset_m": o / 2, "elevation_m": round(96.0 + 0.02 * o / 2, 4), "valid": True}
for o in range(-40, 41)
]
STORED_BERM = {"width_m": 1.0, "interval_m": 3.0, "slope_deg": 0.0, "depth_m": 1.0}
LONG = {
"design_profiles": [
{
"samples": [
{"chainage_m": 0, "elevation_m": 100},
{"chainage_m": 200, "elevation_m": 100},
]
}
]
}
def _project(root: Path, structures: list[dict]) -> Path:
route = root / "B05_Profile" / "route"
route.mkdir(parents=True)
(route / "structures.json").write_text(
json.dumps({"revision": 1, "structures": structures}, ensure_ascii=False), encoding="utf-8"
)
return root
@pytest.fixture(scope="module")
def out(tmp_path_factory):
work = tmp_path_factory.mktemp("b272berm")
build = subprocess.run( # noqa: S603 — 고정 실행 파일
[
"node",
str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"),
"build",
"--configLoader",
"runner",
"--ssr",
"../resources/tester/helper_272_berm_source_entry.ts",
"--outDir",
str(work),
"--logLevel",
"error",
],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
timeout=300,
)
assert build.returncode == 0, build.stderr
bundle = work / "bundle.mjs"
shutil.copy(work / "helper_272_berm_source_entry.js", bundle)
job = work / "job.json"
job.write_text(
json.dumps(
{
"standard": STANDARD_CROSS_SECTION,
"rockDefault": STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M,
"structureCases": STRUCTURE_CASES,
"storedCases": STORED_CASES,
"ground": GROUND,
"storedBerm": STORED_BERM,
},
ensure_ascii=False,
),
encoding="utf-8",
)
proc = subprocess.run( # noqa: S603
["node", str(HERE / "helper_272_berm_source.mjs"), str(bundle), str(job)],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
timeout=120,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def test_거울_구조물_소단_구간(out, tmp_path):
for index, (structures, ts) in enumerate(zip(STRUCTURE_CASES, out["spans"], strict=True)):
py = load_berm_spans(_project(tmp_path / f"c{index}", structures))
assert py is not None, index
want = [{"start_m": s, "end_m": e, **spec} for s, e, spec in py]
assert len(ts) == len(want), (index, ts, want)
for got, exp in zip(ts, want, strict=True):
assert got == pytest.approx(exp), (index, got, exp)
def test_거울_저장분_소단_제원(out):
for spec, ts in zip(STORED_CASES, out["stored"], strict=True):
py = berm_from(spec, {})
if py is None:
assert ts is None, spec
continue
want = {k: getattr(py, k) for k in ("width_m", "interval_m", "slope_deg", "depth_m")}
assert ts == pytest.approx(want), spec
def test_시설_줄이_사본을_씀(out):
lane = out["lane"]
assert lane["beforeKnown"] == {"copy": None, "writes": 0} # 구조물 받기 전 = 안 씀
assert lane["loaded"]["writes"] == 1
assert lane["loaded"]["copy"] == out["spans"][0] # 저장 구조물 소단 구간
assert lane["same"]["writes"] == 1 # 같은 구간 · 관만 알림 = 안 씀
assert lane["moved"]["writes"] == 2
assert lane["moved"]["copy"][0]["start_m"] == 5.0 and lane["moved"]["copy"][0]["end_m"] == 105.0
assert lane["removed"] == {
"copy": [],
"writes": 3,
} # 소단 지움 = 빈 사본(저장분 소단으로 안 물러섬)
def test_다시_셈_B05만_B06_저장_같은_값(out, tmp_path):
r = out["refresh"]
no_copy, same, empty = r["noCopy"], r["copySame"], r["copyEmpty"]
assert no_copy["fill"] == same["fill"] and no_copy["cut"] == same["cut"] # B05 만 = B06 연 뒤
assert empty["fill"] != no_copy["fill"] # 빈 사본 = 소단 없음(저장분으로 안 물러섬)
# [저장] = 서버가 구조물 정본으로 다시 셈
root = _project(tmp_path / "save", SAVED)
design = {"ground_type": "soil", "section_mode": "both_fill", "ditch_type": "standard"}
sections = [{"chainage_m": 40.0, "samples": GROUND, "design": design}]
assert enforce_design_lines(LONG, sections, root) == 1
assert abs(sections[0]["design"]["fill_area_m2"] - same["fill"]) < 1e-3
bare = [{"chainage_m": 40.0, "samples": GROUND, "design": dict(design)}]
enforce_design_lines(LONG, bare, _project(tmp_path / "bare", []))
assert abs(bare[0]["design"]["fill_area_m2"] - empty["fill"]) < 1e-3
def test_쓰는_자리_하나():
sync = (ROOT / "B06_Section" / "B06_Section_UI_Page_Design_Sync.ts").read_text(encoding="utf-8")
assert "writeBermSpans" not in sync and "useStructureIds(projectId, structures)" in sync
lane = (ROOT / "A00_Common" / "design" / "design_facility_lane.ts").read_text(encoding="utf-8")
assert "writeBermSpans(projectId, routeId, next)" in lane
def test_시설_줄_종류_표_전_측점_안_칠함(out):
assert out["paint"]["before"] == 0 # 종류 표 전 = 빈 측점을 종단도에 안 씀
assert out["paint"]["after"] == 1 # 받은 뒤 한 번