fix(B05 노선 추천 엔진): 원지반 기울기 참고만 · 삼각 없음 · 측점 필수 · 합치기 멈춤 노선 토공 +10 % · 토공 고르기 위 3 개만 균형 종단 (419-1)

- 못 지킨 곳 수 = R · 계류 · 측점(원지반 기울기는 목록 · grade 수에 참고) · 후보 거름 · 큰 R 판 · 합치기 · 측점 맞춤의 기울기 막기 걷음 · 탐색 링크 상한 남김
- 꺾임만 → 곡선(155° 이상도) 또는 교점 지움 · 측점 맞춤 곡선 끔 · 늦춤 단계 걷음
- 맞추기 전 선(unsnapped) 걷음 · 시종점 기울기 ends_grade_pct · ends_over_limit
- 검증_sub4 첫 갈래 122.6 s → 23.8 s · 곡선 33 → 16~18 · 세 갈래 다른 선

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:45:38 +09:00
co-authored by Claude Opus 5.5
parent 71da77f105
commit ad855b35d7
10 changed files with 472 additions and 165 deletions
+16 -8
View File
@@ -2,8 +2,9 @@
./venv/Scripts/python.exe -m pytest resources/tester/test_route_initial.py -q
공통 제약(종단기울기 상한 · 최소 곡선반지름 · 계류 이격)은 넘지 못함 — 지형상 불가능할 때만
`metrics.violations` 에 측점 · 값 · 사유로 뜨고 `over_limit.total` 은 그 수와 같다.
공통 제약(최소 곡선반지름 · 계류 이격)은 넘지 못함 — 지형상 불가능할 때만 `metrics.violations` 에
측점 · 값 · 사유로 뜨고 `over_limit.total` 은 그 수와 같다. 원지반 기울기(탐색 링크 상한은 막음)는
참고만 — 목록엔 뜨되 `total` 에 안 듦(419).
예상노선 추적(follow)은 전처리 초기값과 같은 선 — 제약 탐색 없이 넘은 곳만 목록(PLAN 23-1).
갈래는 제 목표 비교값이 가장 좋은 후보 — 실프로젝트(랩탑_보조 · 읽기만)에서 갈래가 갈림.
"""
@@ -32,8 +33,8 @@ from B05_Profile.B05_Profile_Engine_RouteInitial_Metrics import (
grade_line,
station_label,
)
from B05_Profile.B05_Profile_Engine_RouteInitial_Pool import GOALS
from B05_Profile.B05_Profile_Engine_RouteInitial_Profile import profile_earth
from B05_Profile.B05_Profile_Engine_RouteInitial_Pool import GOALS, SHORTLIST
from B05_Profile.B05_Profile_Engine_RouteInitial_Profile import plain_metrics, profile_earth
from common_util.common_util_route_polyline import build_planned_polyline
SEARCHED = [mode for mode in MODES if mode != "follow"]
@@ -68,9 +69,10 @@ def _run(terrain: Terrain, mode: str, streams=(), weights=None) -> dict:
def _kept(result: dict) -> None:
"""제약을 지켰거나, 못 지킨 곳이 목록에 있고 수가 같음."""
"""제약을 지켰거나, 못 지킨 곳이 목록에 있고 수가 같음(원지반 기울기는 참고 · 수에 안 듦)."""
metrics = result["metrics"]
assert metrics["over_limit"]["total"] == len(metrics["violations"])
hard = [v for v in metrics["violations"] if v["kind"] != "grade"]
assert metrics["over_limit"]["total"] == len(hard)
if not metrics["violations"]:
assert metrics["max_grade_pct"] <= GRADE * 100.0 + 1e-6
assert result["relaxed"] is False
@@ -109,7 +111,7 @@ def test_follow_is_initial_polyline_and_lists_violations(hill: Terrain) -> None:
assert result["relaxed"] is False and result["terrain_bound_pct"] is None
grades = [v for v in result["metrics"]["violations"] if v["kind"] == "grade"]
assert grades and all("예상노선 추적" in v["reason"] for v in grades)
assert result["metrics"]["over_limit"]["total"] == len(result["metrics"]["violations"])
_kept(result)
def _earth(result: dict) -> float:
@@ -123,13 +125,19 @@ def _earth(result: dict) -> float:
def _check_branch_goals(runs: dict) -> None:
"""갈래마다 제 목표 비교값(`GOALS` — 절성토 · 최대 기울기 · 목표 기울기 편차 · 위험 길이 ·
계류 횡단 · 공사비)이 갈래(같은 후보 묶음) 가운데 1 등 · 새 비교값이 서로 맞음(PLAN 23-5).
고르기는 측점에 맞춘 선 · 측점 밖 곡선 없는 후보 먼저(383-1)."""
고르기는 측점에 맞춘 선 · 측점 밖 곡선 없는 후보 먼저(383-1). 토공 · 공사비는 상한 누른 계획선
토공(종단 전 값) 위 `SHORTLIST` 안에서 균형 종단으로 고름(419) — 종단 전 값으로 위 3 안."""
branches = [runs[mode]["metrics"] for mode in BRANCHES]
def rank(goal, m: dict) -> tuple:
return (m["over_limit"].get("station", 0) > 0, *goal(m))
for mode, goal in GOALS.items():
if mode in ("earthwork", "cost"):
plain = sorted(rank(goal, plain_metrics(m)) for m in branches)
own = rank(goal, plain_metrics(runs[mode]["metrics"]))
assert own <= plain[min(SHORTLIST, len(plain)) - 1], mode
continue
assert all(rank(goal, runs[mode]["metrics"]) <= rank(goal, m) for m in branches), mode
for result in runs.values():
m = result["metrics"]