- Router_Design enforce 셋 · 넓힘 뒤 설계 — 파이썬은 입력 조립만 · 측점 묶음을 Node 한 번(B06_Section_Engine_Design_Node · cross.design) · Node 못 부르면 파이썬 짝 - 소단 구간 셈을 화면 파일에서 common_util_berm_spans.ts 로 · 서버도 같은 TS(cross.berm_spans) · Persist 가 다시 내보냄 - 값: 고정 자료 기준값 0 차(면적 · 계획고 · 누가토량 −17,570.7) · 설계선 점 13 곳 1e-4 m(반올림 동점 · 브레인 승인) — 기준값 시험에 판정 더함 - 시험: compute_cross_design 갈아 끼우던 시험 다섯을 묶음 자리로(까닭: [저장] 다시 셈이 묶음으로 부름) · 소단 글 시험 자리 옮김 - 등록부 · 위키 계산 목록 · B06 지침 줄 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
169 lines
6.9 KiB
Python
169 lines
6.9 KiB
Python
"""PLAN 472-1 (요구 273) — B05 · B06 기준값 시험 · 구조 다시 세움 단계마다의 문.
|
||
|
||
고정 자료 `fixtures/golden_472/`(검증_랩탑메인 노선 370 · 읽기만 뜸) 하나로 길 넷의 값을 떠
|
||
`golden_472_values.json` 과 견줌(값 꼴 · 길 = `helper_472_1_paths.py` 머리).
|
||
|
||
못박는 것
|
||
- 길마다 자기 값과 0 차 — 측점(수 · 열쇠 · 누가거리 · x · y) · 계획고 ·
|
||
면적(절 · 성 · 토사 · 암 · 측구) · 층따기 밑수 · 단면 모양 · 누가토량(측점별 · 끝값) ·
|
||
관 길이 · 종단 경고 · 기울기 위반 수.
|
||
- 두 길 사이 = 「알려진 차」(`KNOWN`)만 · 그 밖 차는 실패 · 안 나타난 알려진 차도 실패
|
||
(목록에서 지움 — 472-4 가 짝을 하나 옮길 때마다 한 줄씩 지워 마지막에 빈 목록).
|
||
- 화면 참값 — 정본 누가토량 −17,570.70 ㎥ · 측점 57(465 기준값).
|
||
이 고정 자료가 안 거치는 짝인데 값이 갈리는 자리 — X1 단면 모양 기본값(설계 없는 새 측점 없음) ·
|
||
X3 면적 두 갈래(구조물 = 관 351 하나 · 두 길 다 Node TS 로 셈) · X4 · X6(노선 안 바뀜).
|
||
값 다시 뜨기 = `helper_472_1_dump.py`(승인 때만 · 시험은 다시 안 뜸).
|
||
|
||
./venv/Scripts/python.exe -m pytest resources/tester/test_472_1_golden.py -q
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import copy
|
||
import json
|
||
from dataclasses import dataclass
|
||
|
||
import helper_472_1_paths as paths
|
||
import pytest
|
||
|
||
pytestmark = pytest.mark.skipif(
|
||
not (paths.ROOT / "config" / "node_modules" / "vite").is_dir(),
|
||
reason="프론트엔드 의존성 미설치",
|
||
)
|
||
|
||
GOLDEN = json.loads(paths.VALUES.read_text(encoding="utf-8"))
|
||
AREAS = ("cut_m2", "fill_m2", "cut_soil_m2", "cut_rock_m2", "ditch_m2")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class Known:
|
||
"""알려진 차 한 줄 — 칸 → 허용 폭(None = 같지 않아도 됨 · 글 칸) · 까닭."""
|
||
|
||
id: str
|
||
fields: dict[str, float | None]
|
||
why: str
|
||
|
||
|
||
KNOWN: dict[tuple[str, str], list[Known]] = {
|
||
# [저장] 재계산 = 정본 그대로(사용자 원칙 — 입력 안 바뀌면 값 안 바뀜) · 경고만 지금 코드
|
||
("server_save", "stored"): [
|
||
Known(
|
||
"W0",
|
||
{"warnings": None},
|
||
"정본 종단 경고 = 10-17 저장 때 코드(법정 R · L 미달 46 줄 전) · "
|
||
"[저장] 재계산이 지금 코드로 다시 씀(472 덧 · 기하 그대로)",
|
||
),
|
||
],
|
||
("server_route", "stored"): [
|
||
Known(
|
||
"R7",
|
||
{"fill_m2": 0.00011, "cumulative_m3": 0.011},
|
||
"다시 뜬 표본 마지막 자리 — grid:37 성토 1e-4 ㎡ · 누가토량 0.01 ㎥ 넷(끝값 같음)",
|
||
),
|
||
Known(
|
||
"W0",
|
||
{"warnings": None},
|
||
"정본 종단 경고 = 10-17 저장 때 코드(법정 R · L 미달 46 줄 전) · "
|
||
"노선 저장 체인은 지금 코드",
|
||
),
|
||
],
|
||
("browser", "server_route"): [
|
||
Known(
|
||
"X2",
|
||
{
|
||
"x": 0.002,
|
||
"y": 0.002,
|
||
**dict.fromkeys(AREAS, 0.1),
|
||
"bench_m": 1.0,
|
||
"cumulative_m3": 2.0,
|
||
"final_cumulative_m3": 1.0,
|
||
},
|
||
"입력 선 — 브라우저 /route/plan planned(0.1 mm) · 서버 노선 geojson(1 mm) · "
|
||
"측점 자리 ≤ 1 mm → 표본 · 면적 ≤ 0.07 ㎡ · 층따기 밑수 ≤ 0.52 m · "
|
||
"누가토량 끝값 0.69 ㎥(−17,570.01 ↔ −17,570.70)",
|
||
),
|
||
],
|
||
}
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def got(tmp_path_factory) -> dict:
|
||
out = {}
|
||
for name in paths.PATHS:
|
||
out[name] = paths.run_path(name, tmp_path_factory.mktemp(name))
|
||
return out
|
||
|
||
|
||
def diffs(a: dict, b: dict) -> list[tuple[str, str, object, object]]:
|
||
"""(칸, 측점 열쇠, a, b) — 측점은 같은 차례(수가 다르면 측점 수 한 줄)."""
|
||
out = []
|
||
for field in ("station_count", "final_cumulative_m3", "pipe_lengths", "warnings", "violations"):
|
||
if a[field] != b[field]:
|
||
out.append((field, "-", a[field], b[field]))
|
||
if a["station_count"] == b["station_count"]:
|
||
for x, y in zip(a["stations"], b["stations"], strict=True):
|
||
out += [(k, x["key"], x[k], y[k]) for k in x if x[k] != y[k]]
|
||
return out
|
||
|
||
|
||
def allowed(diff: tuple, known: Known) -> bool:
|
||
field, _key, a, b = diff
|
||
if field not in known.fields:
|
||
return False
|
||
width = known.fields[field]
|
||
if width is None:
|
||
return True
|
||
return isinstance(a, (int, float)) and isinstance(b, (int, float)) and abs(a - b) <= width
|
||
|
||
|
||
@pytest.mark.parametrize("name", paths.PATHS)
|
||
def test_길마다_기준값_그대로(got, name) -> None:
|
||
found = diffs(got[name], GOLDEN[name])
|
||
assert not found, f"{name} 기준값과 다름 {len(found)}곳 — 앞 10: {found[:10]}"
|
||
|
||
|
||
@pytest.mark.parametrize("pair", list(KNOWN), ids=lambda pair: f"{pair[0]}-{pair[1]}")
|
||
def test_두_길_사이_알려진_차만(got, pair) -> None:
|
||
known = KNOWN[pair]
|
||
found = diffs(got[pair[0]], got[pair[1]])
|
||
stray = [d for d in found if not any(allowed(d, k) for k in known)]
|
||
assert not stray, f"알려진 차 밖 {len(stray)}곳 — 앞 10: {stray[:10]}"
|
||
gone = [k.id for k in known if not any(allowed(d, k) for d in found)]
|
||
assert not gone, f"안 나타난 알려진 차 {gone} — KNOWN 에서 지움"
|
||
|
||
|
||
def test_저장_설계선_점_정본과_1e4_안(tmp_path) -> None:
|
||
"""[저장] 다시 셈 설계선(472-4 ② = Node TS) — 정본과 점 수 · offset 같음 · 고도 1e-4 m 안.
|
||
|
||
알려진 차 = 반올림 동점(파이썬 round 짝수 쪽 · TS 위쪽) 13 점(브레인 10-19 승인).
|
||
"""
|
||
detail = copy.deepcopy(paths.read("detail.json"))
|
||
before = {s["chainage_m"]: s["design"]["design_line"] for s in detail["cross_sections"]}
|
||
sections, _ = paths._save_tail(detail, paths.stage_project(tmp_path / "project"))
|
||
moved = 0
|
||
for section in sections:
|
||
old, new = before[section["chainage_m"]], section["design"]["design_line"]
|
||
assert [p["offset_m"] for p in new] == [p["offset_m"] for p in old], section["chainage_m"]
|
||
for a, b in zip(old, new, strict=True):
|
||
gap = abs(a["elevation_m"] - b["elevation_m"])
|
||
assert gap <= 1e-4 + 1e-9, (section["chainage_m"], a, b)
|
||
moved += gap > 0
|
||
assert moved == 13, f"반올림 동점 점 수 {moved} — 알려진 차(13)와 다름"
|
||
|
||
|
||
def test_화면_참값() -> None:
|
||
stored = GOLDEN["stored"]
|
||
assert stored["final_cumulative_m3"] == -17570.7
|
||
assert stored["station_count"] == 57
|
||
assert stored["pipe_lengths"] == {"pipe:p35100": 7.9}
|
||
|
||
|
||
def test_고정_자료_크기() -> None:
|
||
size = sum(p.stat().st_size for p in paths.FIXTURE.rglob("*") if p.is_file())
|
||
assert size < 5e6
|
||
|
||
|
||
@pytest.mark.skip(reason="API 왕복(목록 4.2 ③ 선택) — 472-1 에서 건너뜀")
|
||
def test_API_왕복() -> None:
|
||
raise AssertionError
|