"""PLAN 472 덧 (요구 272) — [저장] · [확정] 재계산이 종단 경고를 저장 입력으로 다시 씀. 증상 — 검증_랩탑메인 정본 종단 경고 5 줄(10-17 옛 코드) · 지금 코드로 셈하면 50 줄 · [저장] · [확정] 재계산(`B06_Section_Server_Calc_Prebuild._recompute`)이 경고를 다시 안 셈. 고침 — `_recompute` 맨 앞 `_refresh_alignment_file` → `refresh_alignment`(저장 base_pvi · 편집 · 정책 · 기준 → `rebuild_alignment_profile` 의 경고만) · 기하는 저장값 그대로 · 같으면 안 씀. 경고 글 = TS `buildAlignment` 와 한 벌(거울). 고정 자료 = `fixtures/golden_472/detail.json`. ./venv/Scripts/python.exe -m pytest resources/tester/test_472_save_warnings.py -q """ from __future__ import annotations import copy import inspect import json import subprocess import sys from pathlib import Path HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] sys.path.insert(0, str(ROOT)) from B05_Profile.B05_Profile_Engine_Grade import ground_profile # noqa: E402 from B05_Profile.B05_Profile_Engine_Grade_Alignment import chainage_key # noqa: E402 from B05_Profile.B05_Profile_Engine_Grade_Profile import rebuild_alignment_profile # noqa: E402 from B06_Section import B06_Section_Server_Calc_Prebuild as prebuild # noqa: E402 FIXTURE = HERE / "fixtures" / "golden_472" DETAIL = json.loads((FIXTURE / "detail.json").read_text(encoding="utf-8")) def _longitudinal() -> dict: return copy.deepcopy(DETAIL["longitudinal"]) def test_정본_경고는_옛_코드_5줄() -> None: assert len(DETAIL["longitudinal"]["profile_alignment"]["warnings"]) == 5 def test_다시_셈_경고만_새로_기하는_그대로() -> None: longitudinal = _longitudinal() before = copy.deepcopy(longitudinal) summary = prebuild.refresh_alignment(longitudinal) warnings = longitudinal["profile_alignment"]["warnings"] assert len(warnings) == 50 assert sum("법정 최소 R" in w for w in warnings) == 46 # 432 법정 R · L 미달 assert not [w for w in warnings if w.startswith("chainage ")] assert summary is not None and summary["warnings"] == warnings assert summary["id"] == before["design_profiles"][0]["id"] assert longitudinal["design_profiles"][0]["summary"]["warnings"] == warnings # 경고 밖 = 저장값 그대로(계획고 · 곡선 · 표본 · 위반 · 정책 — 6 자리 반올림 끝자리도) longitudinal["profile_alignment"]["warnings"] = before["profile_alignment"]["warnings"] longitudinal["design_profiles"][0]["summary"]["warnings"] = before["design_profiles"][0][ "summary" ]["warnings"] assert longitudinal == before def test_선형_없는_옛_종단은_그대로() -> None: longitudinal = _longitudinal() longitudinal["profile_alignment"].pop("base_pvi") keep = copy.deepcopy(longitudinal) assert prebuild.refresh_alignment(longitudinal) is None assert longitudinal == keep def test_파일_다시_씀_같으면_안_씀(tmp_path) -> None: path = tmp_path / "B06_Section" / "longitudinal" / "longitudinal.json" path.parent.mkdir(parents=True) path.write_text(json.dumps(_longitudinal()), encoding="utf-8") relative = "B06_Section/longitudinal/longitudinal.json" first = prebuild._refresh_alignment_file(tmp_path, relative) stored = json.loads(path.read_text(encoding="utf-8")) assert first is not None and len(stored["profile_alignment"]["warnings"]) == 50 written = path.stat().st_mtime_ns assert prebuild._refresh_alignment_file(tmp_path, relative) == first assert path.stat().st_mtime_ns == written # 같은 값 — 다시 안 씀 assert prebuild._refresh_alignment_file(tmp_path, "없음/longitudinal.json") is None def test_재계산_맨_앞에서_선형_다시_셈() -> None: source = inspect.getsource(prebuild._recompute) assert "_refresh_alignment_file" in source and "update_longitudinal_grade_summary" in source assert source.index("_refresh_alignment_file") < source.index("get_section_detail(") def test_TS_거울_같은_경고_목록(tmp_path) -> None: longitudinal = _longitudinal() alignment = longitudinal["profile_alignment"] # 옛 정책엔 법정 R · L 이 없음 — 두 쪽 다 기준으로 채운 정책(433-13 · TS `readAlignment`) policy = rebuild_alignment_profile(longitudinal, alignment["edits"])[0]["policy"] assert ( policy["min_vertical_radius_m"] == 100.0 and "min_vertical_radius_m" not in alignment["policy"] ) chainage, ground = ground_profile(longitudinal) curve = alignment["curves"][1] station = longitudinal["stations"][5] cases = [ alignment["edits"], {"station_offsets": {}, "curve_radii": {chainage_key(curve["chainage_m"]): 500.0}}, {"station_offsets": {chainage_key(station["chainage_m"]): 3.0}, "curve_radii": {}}, ] base = { "policy": policy, "onlyExplicitCurves": bool(alignment.get("only_explicit_curves")), "basePvi": alignment["base_pvi"], "chainage": chainage.tolist(), "ground": ground.tolist(), "stations": [ {"station_id": s.get("station_id"), "chainage_m": s["chainage_m"]} for s in longitudinal["stations"] ], } source = tmp_path / "in.json" source.write_text(json.dumps({"base": base, "cases": cases}), encoding="utf-8") proc = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(HERE / "helper_472_save_warnings.cjs"), str(source)], capture_output=True, text=True, encoding="utf-8", timeout=60, ) assert proc.returncode == 0, proc.stderr for edits, got in zip(cases, json.loads(proc.stdout), strict=True): py, _profile = rebuild_alignment_profile(copy.deepcopy(longitudinal), edits) assert got["warnings"] == py["warnings"] assert got["violations"] == len(py["violations"])