fix(769-1): 추천 4 갈래 = 계산 로직 결과 그대로 — 같은 쪽 곡선 합치기 · 측점 맞춤 뺌

- 요구 611: 추천노선 = 평면계획선을 긋는 가이드 선 · 합치기 · 스냅은 예정노선 수정 뒤 평면계획선에만
- _trace = 경로 탐색 → 곡선 넣기(curve_every_kink) → 큰 R(widen) 까지 · merge_same_side · settle 뺌
- 예상노선 추적(merged_follow) · 체인 · /route/plan · replan · follow 는 합치기 · 스냅 그대로
- Pool.snap · Pool.cross · build_pool snap · cross 인자 · 엔진 fit · route_earth · merge_delta 걷음(쓰는 곳 없음)
- ALGO_VERSION 9 → 10
- 낡은 시험 고침: test_419_1 · test_351_2 추천 갈래 측점 위 기대 뺌(추적만 측점 위 · 갈래는 안 맞춤 확인 추가) ·
  test_route_initial 영선 · test_351_2 비중 기울기 상한 기대 뺌(원지반 기울기 참고만 · 비중은 벌점만) ·
  test_224_4 build_pool 엿봄 인자

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-03 16:30:29 +09:00
co-authored by Claude Opus 5.5
parent 2937ba6208
commit f7bd8dbf06
7 changed files with 35 additions and 75 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, **kw):
def spy(graph, follow, max_grade, min_radius_m, measure, **kw):
caught["measure"] = measure
caught["pool"] = real(graph, follow, max_grade, min_radius_m, measure, snap, **kw)
caught["pool"] = real(graph, follow, max_grade, min_radius_m, measure, **kw)
return caught["pool"]
engine._POOLS.clear()
@@ -122,8 +122,8 @@ def hill_runs() -> dict:
@pytest.mark.parametrize("mode", ["follow", "gentle", "earthwork", "weighted"])
def test_recommend_result_on_stations(hill_runs: dict, mode: str) -> None:
"""추적(평면계획선 길)만 측점 맞춤 · 추천 갈래는 계산 로직 결과 그대로(769-1 · 요구 611)."""
result = hill_runs[mode]
assert result["off_station_curves"] == 0
outline = build_planned_polyline(
[tuple(n[k] for k in ("x", "y")) for n in result["nodes"]],
min_radius_m=12.0,
@@ -133,16 +133,18 @@ def test_recommend_result_on_stations(hill_runs: dict, mode: str) -> None:
)
# 화면이 펴서 다시 짠 선 = 서버 선
assert np.allclose(outline.vertices, result["planned"], atol=1e-3)
assert station_hits(outline) == len(outline.curves) > 0
if mode != "follow": # 갈래는 기울기 상한 안(언덕은 비켜 갈 수 있음)
if mode == "follow":
assert result["off_station_curves"] == 0
assert station_hits(outline) == len(outline.curves) > 0
# 갈래는 기울기 상한 안(언덕은 비켜 갈 수 있음) · 비중은 제약을 벌점으로만(넘으면 목록)
if mode in ("gentle", "earthwork"):
assert result["metrics"]["over_limit"]["total"] == 0
assert result["metrics"]["max_grade_pct"] <= 14.0 + 1e-6
# 측점 맞춤 필수 — 맞추기 전 선은 안 냄(419)
assert "unsnapped" not in result
def test_합성_지형_modes_before_after(hill_runs: dict) -> None:
"""합성 언덕(14 % · R12) — 갈래별 최대 기울기와 측점 밖 곡선을 함께 지킨다."""
"""합성 언덕(14 % · R12) — 추적만 측점 위 · 추천 갈래는 측점 맞춤 없음(769-1)."""
on_total, curves_total = 0, 0
for mode, result in hill_runs.items():
after = result["metrics"]
@@ -155,7 +157,8 @@ def test_합성_지형_modes_before_after(hill_runs: dict) -> None:
f" · 곡선 {curves}(측점 밖 {result['off_station_curves']})"
)
print(f"측점 위 곡선 {on_total}/{curves_total} = {100 * on_total / curves_total:.0f}%")
assert curves_total and on_total == curves_total
assert curves_total and hill_runs["follow"]["off_station_curves"] == 0
assert on_total < curves_total # 추천 갈래는 맞추지 않음
def test_합성_예상노선_eight_outlines() -> None:
@@ -203,13 +203,12 @@ def hill_runs() -> dict:
@pytest.mark.parametrize("mode", ["gradeline", "earthwork", "gentle", "weighted"])
def test_hill_branches_rules(hill_runs: dict, mode: str) -> None:
"""언덕 — 꺾임만 0 · 측점 밖 0 · 못 지킨 곳 수 = R · 계류 · 측점 · 맞추기 전 선 없음."""
"""언덕 — 꺾임만 0 · 못 지킨 곳 수 = R · 계류 · 맞추기 전 선 없음(측점 맞춤 없음 · 769-1)."""
result = hill_runs[mode]
interior = result["nodes"][1:-1]
assert interior and all(node["radius_m"] is not None for node in interior) # 삼각 0
assert result["off_station_curves"] == 0
over = result["metrics"]["over_limit"]
assert over["total"] == over["radius"] + over["stream"] + over.get("station", 0)
assert over["total"] == over["radius"] + over["stream"]
assert "unsnapped" not in result
assert result["ends_over_limit"] is False and result["ends_grade_pct"] < 14.0
+4 -1
View File
@@ -104,7 +104,10 @@ def test_every_mode_keeps_grade_limit_on_hill(hill: Terrain, mode: str) -> None:
_kept(result)
metrics = result["metrics"]
assert metrics["over_limit"]["total"] == 0
assert metrics["max_grade_pct"] <= GRADE * 100.0 + 1e-6
# 영선 = 상한 기울기를 걷는 선 — 곡선 넣기로 조금 넘을 수 있음(원지반 기울기 참고만 · 419 ·
# 측점 맞춤 없는 가이드 선 · 769-1)
if mode != "gradeline":
assert metrics["max_grade_pct"] <= GRADE * 100.0 + 1e-6
# 기점 · 종점은 옮기지 않고 복도(100 m) 밖으로 안 나감
assert result["planned"][0] == [50.0, 200.0]
assert result["planned"][-1] == [750.0, 200.0]