feat(B05 노선 추천 엔진): 합치기 판정 = 바뀐 측점 횡단 차이 · 후보 줄 세움 = 노선 횡단 토공 · 균형 종단 위 5 개 (419-1)

- 420 cross_earthwork · cross_earthwork_delta 이음 — 합치기 멈춤 = 계획고 지반고 노선 횡단 +10 %
- 토공 · 공사비 줄 세움 = 상한 누른 계획선 횡단(종단 켬) · 지반고 횡단(종단 끔)
- 위 후보 수 3 → 5(검증_sub4 균형 종단 토공 최소 = 줄 세움 5 위)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
This commit is contained in:
2026-10-18 12:56:34 +09:00
co-authored by Claude Opus 5.5
parent 44f77df1c8
commit 4ae4fa49ea
5 changed files with 159 additions and 56 deletions
@@ -40,9 +40,9 @@ def runs() -> dict:
caught: dict = {}
real = engine.build_pool
def spy(graph, follow, max_grade, min_radius_m, measure, snap):
def spy(graph, follow, max_grade, min_radius_m, measure, snap, **kw):
caught["measure"] = measure
caught["pool"] = real(graph, follow, max_grade, min_radius_m, measure, snap)
caught["pool"] = real(graph, follow, max_grade, min_radius_m, measure, snap, **kw)
return caught["pool"]
engine._POOLS.clear()
@@ -17,6 +17,7 @@ from B05_Profile.B05_Profile_Engine_RouteInitial import ends_grade, generate_ini
from B05_Profile.B05_Profile_Engine_RouteInitial_Merge import (
TOTAL_EARTH_TOL,
Guard,
RouteEarth,
merge_same_side,
)
from B05_Profile.B05_Profile_Engine_RouteInitial_Pool import SHORTLIST, choose, hard_limits
@@ -113,7 +114,7 @@ def _merge(earth_base: float, delta: float):
outline = build_planned_polyline(
SAME, min_radius_m=RADIUS, simplify=False, curve_flags=[True] * len(SAME)
)
base = {**FLAT, "cut_m3": earth_base, "fill_m3": 0.0}
base = {**FLAT, "cut_m3": 0.0, "fill_m3": 0.0} # 띠 가늠은 안 씀
calls = []
def cross(before, after, window):
@@ -121,7 +122,8 @@ def _merge(earth_base: float, delta: float):
return delta
guard = Guard(SearchGrid(SAME, _flat_terrain(402.0, 262.0), [], stream_offset_m=100.0), 0.14)
merged = merge_same_side((outline, base), guard, RADIUS, lambda line: base, cross)[0]
earth = RouteEarth(lambda line: earth_base, cross)
merged = merge_same_side((outline, base), guard, RADIUS, lambda line: base, earth)[0]
return merged, calls
@@ -148,22 +150,31 @@ def _route(name: str, earth: float, station: int = 0):
def test_choose_profiles_only_shortlist() -> None:
"""종단 반영 켬 토공 = 상한 누른 계획선 토공 위 3 개만 균형 종단 · 그 사이에서 고름."""
routes = [_route(f"r{i}", 100.0 + 10 * i) for i in range(8)] + [_route("off", 1.0, 1)]
"""종단 반영 켬 토공 = 노선 횡단 토공(줄 세움) 위 `SHORTLIST` 개만 균형 종단 · 그 사이에서 고름 ·
줄 세움 = `earth`(켬 = 상한 누른 계획선 · 끔 = 지반고) · 없으면 띠 가늠."""
routes = [_route(f"r{i}", 100.0 + 10 * i) for i in range(12)] + [_route("off", 1.0, 1)]
last = f"r{SHORTLIST - 1}"
profiled = []
def profile(route):
profiled.append(route[1]["name"])
# 균형 종단 토공은 순서가 바뀜 — 셋째가 가장 작음
earth = {"r0": 900.0, "r1": 800.0, "r2": 300.0}.get(route[1]["name"], 50.0)
# 균형 종단 토공은 순서가 바뀜 — 줄 세운 마지막이 가장 작음 · 밖은 더 작아도 안 봄
earth = 300.0 if route[1]["name"] == last else 900.0
route[1]["profile"] = {"cut_m3": earth, "fill_m3": 0, "borrow_m3": 0, "spoil_m3": 0}
chosen = choose("earthwork", routes, True, profile)
assert profiled == ["r0", "r1", "r2"] and len(profiled) == SHORTLIST
assert chosen[1]["name"] == "r2"
assert profiled == [f"r{i}" for i in range(SHORTLIST)]
assert chosen[1]["name"] == last
# 종단 끔 = 줄 세우기 값 그대로 · 종단 안 붙임
profiled.clear()
assert choose("earthwork", routes, False, profile)[1]["name"] == "r0" and not profiled
# 횡단 줄 세움이 띠 가늠과 다르면 그것을 따름(끔 = 지반고 횡단 하나로 고름)
cross = {f"r{i}": 1000.0 - i for i in range(12)}
def earth(route, capped):
return cross.get(route[1]["name"], 0.0) + (0.0 if capped else 5.0)
assert choose("earthwork", routes, False, None, earth)[1]["name"] == "r11"
def test_ends_grade() -> None: