- 서버 재계산(_recompute) 맨 앞 refresh_alignment — 저장 base_pvi · 편집 · 정책 · 기준으로 경고만 지금 코드 값 · 기하(계획고 · 곡선 · 표본 · 위반)는 저장값 그대로 · 같으면 안 씀 · grade_summary 함께 - 파이썬 경고 글을 TS buildAlignment 꼴로(접두 「chainage 」 뺌 · 기울기 초과 · 불균형 총괄 줄 뺌 — violations · balance 칸이 정본) - 거울 시험 test_472_save_warnings(옛 코드 실패 확인) · 기준값 경고 칸만 다시 뜸(server_save 5→50 · server_route 51→50 · 브레인 승인) · W0 정리 · W1 지움 - 위키 계산 목록 P8 · [저장] 차례 cross 줄 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
131 lines
5.8 KiB
Python
131 lines
5.8 KiB
Python
"""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"])
|