"""PLAN 472-1 (요구 273) — B05 · B06 기준값 시험 · 구조 다시 세움 단계마다의 문. 고정 자료 `fixtures/golden_472/`(검증_랩탑메인 노선 370 · 읽기만 뜸) 하나로 길 넷의 값을 떠 `golden_472_values.json` 과 견줌(값 꼴 · 길 = `helper_472_1_paths.py` 머리). 못박는 것 - 길마다 자기 값과 0 차 — 측점(수 · 열쇠 · 누가거리 · x · y) · 계획고 · 면적(절 · 성 · 토사 · 암 · 측구) · 층따기 밑수 · 단면 모양 · 누가토량(측점별 · 끝값) · 관 길이 · 종단 경고 · 기울기 위반 수. - 두 길 사이 = 「알려진 차」(`KNOWN`)만 · 그 밖 차는 실패 · 안 나타난 알려진 차도 실패 (목록에서 지움). 짝 모으기(511-B) 끝 — 남은 줄은 계산 짝이 아닌 까닭(표본 출처 · 입력 선 자릿수 · 저장 때 코드) · 줄마다 `why`. - 화면 참값 — 정본 누가토량 −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}, "지반 표본 출처(G1) — 정본 = 10-17 파이썬 표본 · 노선 저장 = Node 격자 표본 · " "grid:37 성토 1e-4 ㎡ · 누가토량 0.01 ㎥ 넷(끝값 같음 · 기준값 다시 뜸 안 함 B-2)", ), 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