fix(764-1): 추천 측점 맞춤 덜 나쁜 자리에 원지반 기울기 늘어남 · 옮긴 거리 따짐

- 요구 606-2 · 분석 761-6 — 설계 지형에서 측점 맞춤이 후보 7/8 을 비탈로 옮겨 추천 셋이 같은 선
- 덜 나쁜 자리 순위 = 계류 나란히 → 기울기 늘어남 → 옮긴 거리 · 꼴(합친 꼴)을 가로질러 견줌
- 크게 망침(기울기 넘친 만큼 +5 %p 초과 · 원래 선에서 10 m 초과)은 그것 없이 다 놓는 길이 없을 때만 · 그때 가장 덜 망치는 자리(곡선 가운데 = 측점 유지)
- 놓을 자리 풀이 기억(층 · 다시 셈 공통) · ALGO_VERSION 9(옛 저장본 안 꺼냄)
- 지킴 시험 test_764_1_snap_grade.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-03 15:48:26 +09:00
co-authored by Claude Opus 5.5
parent 8811fbf85a
commit d9e8dc4177
4 changed files with 183 additions and 37 deletions
+93
View File
@@ -0,0 +1,93 @@
"""764-1 추천 측점 맞춤 — 덜 나쁜 자리 고를 때 원지반 기울기 늘어남 · 옮긴 거리를 따짐(요구 606-2).
./venv/Scripts/python.exe -m pytest resources/tester/test_764_1_snap_grade.py -q
· 가까운 측점 자리가 비탈(기울기 늘어남)이면 다음 측점 자리로 — 옛 순위는 가까운 측점 · 토공을
앞세워 비탈 자리를 골랐음.
· 모든 자리가 크게 망치면 그중 가장 덜 망치는 자리(곡선 가운데 = 측점은 지킴).
· 판(`ALGO_VERSION`)이 다른 저장본은 꺼내지 않음.
"""
import json
import math
import pytest
from B05_Profile.B05_Profile_Engine_RouteInitial_Station import (
STATION_M,
curve_mc_chainages,
off_station_count,
snap_curves_to_stations,
)
from B05_Profile.B05_Profile_Engine_RouteInitial_Store import (
ALGO_VERSION,
read_stored_any,
store_dir,
)
from common_util.common_util_route_polyline import build_planned_polyline
RADIUS = 12.0
# MC ≈ 109.5 m — 가까운 측점 100 · 다음 120
POINTS = [(0.0, 0.0), (110.0, 0.0), (230.0, 90.0)]
class _Slope:
"""직접 잼 흉내 — 늘 못 지남(덜 나쁜 자리만 남음) · 곡선 가운데가 x < 112 면 비탈 쪽.
토공은 비탈 쪽이 더 작게(옛 순위가 비탈을 고르도록 유혹)."""
def __init__(self, steep: float, flat: float) -> None:
self.steep, self.flat = steep, flat
def rise(self, local, reference) -> float:
arc = local[1:-1]
return self.steep if sum(x for x, _ in arc) / len(arc) < 112.0 else self.flat
def judge(self, local, reference):
return False, (False, 0.0, -1000.0 * self.rise(local, reference))
def ok(self, local, reference) -> bool:
return False
def _snap(guard) -> float:
outline = build_planned_polyline(
POINTS,
min_radius_m=RADIUS,
simplify=False,
curve_flags=[True] * 3,
radii=[None, 40.0, None],
)
(mc,) = curve_mc_chainages(outline)
assert abs(mc - 100.0) < abs(mc - 120.0) # 가까운 측점 = 비탈 쪽
snapped = snap_curves_to_stations(outline, RADIUS, guard=guard)
assert snapped is not None and off_station_count(snapped) == 0
(placed,) = curve_mc_chainages(snapped)
return round(placed / STATION_M) * STATION_M
def test_less_bad_spot_avoids_slope() -> None:
"""가까운 측점 자리가 기울기를 크게 늘리면 다음 측점 자리."""
assert _snap(_Slope(steep=0.30, flat=0.0)) == 120.0
def test_all_ruined_takes_least_ruin() -> None:
"""모든 자리가 크게 망치면 그중 가장 덜 망치는 자리 · MC 는 측점에."""
assert _snap(_Slope(steep=0.20, flat=0.40)) == 100.0
assert _snap(_Slope(steep=0.40, flat=0.20)) == 120.0
@pytest.mark.parametrize("version, found", [(ALGO_VERSION - 1, False), (ALGO_VERSION, True)])
def test_other_version_not_read(tmp_path, version: int, found: bool) -> None:
folder = store_dir(tmp_path)
folder.mkdir(parents=True)
data = {"version": version, "source": "s", "pool": "p", "request": {}, "result": {}}
(folder / "k.json").write_text(json.dumps(data), encoding="utf-8")
got = read_stored_any(tmp_path, "p", lambda source: "k")
assert (got is not None) is found
def test_ruin_threshold_constants() -> None:
from B05_Profile.B05_Profile_Engine_RouteInitial_Station import RUIN_GAP_M, RUIN_RISE
assert RUIN_RISE == pytest.approx(0.05) and math.isclose(RUIN_GAP_M, STATION_M / 2)