feat(B05): 추천 갈래 다시 — 영선 · 안전 우선 · 계류 횡단 최소 · 공사비 어림 최소(PLAN 23-5a)
- 갈래 short · balance 뺌(요청 422) · gradeline · safe · crossings · cost 더함 — 각 목표로 제약 탐색한 후보를 묶음에 넣고 제 비교값으로 고름 - 탐색 목표 셋 더함: 목표 기울기 편차(영선 · 등경사선) · 위험(옆경사 35° 이상 · 계류 버퍼 안 통과 길이) · 계류 횡단 링크 · 공사비는 상대 단가 그대로 - 비교값 더함: side_slope_avg_pct · side_slope_max_pct · hazard_m · stream_crossings · stream_offset_avg_m · grade_dev_pct · cost_index - 공사비 상대 단가(절성토 1 ㎥ = 1 · 연장 1 m = 2 · 계류 횡단 1 곳 = 400)는 임시 상대값 · 브레인 임의 판정 - 요청 criteria.target_grade_pct(영선 목표 기울기 · 없으면 상한) · 응답 criteria.target_grade_pct - 시험 test_route_initial 31 · 왕복 시험은 화면이 아는 갈래만(새 넷은 23-5b 뒤) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""초기 계획노선 갈래(PLAN 20 · 23장).
|
||||
|
||||
예상노선 추종 · 토공 최소 · 기울기 순한 · 비중 · 짧은 노선 · 흙 균형.
|
||||
예상노선 추종 · 영선 노선 · 토공 최소 · 기울기 순한 · 안전 우선 · 계류 횡단 최소 ·
|
||||
공사비 어림 최소 · 비중.
|
||||
|
||||
계산 자리 = 서버 단독(③). 결과는 화면 캐시용 — 이 모듈은 파일을 쓰지 않는다. 적용은 화면이
|
||||
받은 노드로 기존 `POST /route/replan`([확인])을 부른다.
|
||||
@@ -10,8 +11,12 @@
|
||||
제약 탐색은 하지 않고, 넘은 곳은 `metrics.violations` 에 뜬다.
|
||||
· earthwork — 후보 중 절 · 성토 합(`cut_m3 + fill_m3`) 최소.
|
||||
· gentle — 후보 중 최대 종단기울기 최소(같으면 평균 최소).
|
||||
· short — 후보 중 연장 최소(PLAN 23-4c).
|
||||
· balance — 후보 중 흙 남음 |절토 − 성토| 최소 · 같으면 절성토 합 최소(PLAN 23-4c).
|
||||
· gradeline — 영선 노선: 목표 기울기(`target_grade` · 없으면 상한)로 등고선을 건너가는
|
||||
등경사선(PEGGER 식) · 후보 중 목표 기울기 편차(`grade_dev_pct`) 최소(PLAN 23-5).
|
||||
· safe — 안전 우선: 후보 중 위험 통과 길이(옆경사 35° 이상 · 계류 버퍼 안) 최소.
|
||||
· crossings — 계류 횡단 최소: 후보 중 계류 횡단 수 최소 · 같으면 계류에서 먼 쪽.
|
||||
· cost — 공사비 어림 최소: 후보 중 상대 공사비(토공 + 계류 횡단 구조물 + 연장 · 임시 상대
|
||||
단가 `..._Metrics.COST_*`) 최소.
|
||||
· weighted — 탐색 비용의 가중합 한 번(후보 고르기 아님 · PLAN 23-4a · `..._Pool.weighted`).
|
||||
끝값은 그 갈래와 같은 선 — 예상 100 = 추적 · 토공 100 = 토공 최소 · 기울기 100 =
|
||||
기울기 순한. 제약은 (1 − 예상 몫)만큼 벌점으로만 따지고 넘은 곳은 목록에 뜸.
|
||||
@@ -83,15 +88,17 @@ STREAM_CLASSES = ("소하천", "지방하천", "국가하천")
|
||||
PENDING: tuple[str, ...] = ()
|
||||
|
||||
# ── 탐색 · 측정 값 ───────────────────────────────────────────────────────────────
|
||||
MODES = ("follow", "earthwork", "gentle", "weighted", "short", "balance")
|
||||
MODES = ("follow", "gradeline", "earthwork", "gentle", "safe", "crossings", "cost", "weighted")
|
||||
DEFAULT_WEIGHTS = {"expected": 50.0, "earthwork": 25.0, "grade": 25.0}
|
||||
MODE_WEIGHTS = {
|
||||
"follow": {"expected": 100.0, "earthwork": 0.0, "grade": 0.0},
|
||||
"earthwork": {"expected": 0.0, "earthwork": 100.0, "grade": 0.0},
|
||||
"gentle": {"expected": 0.0, "earthwork": 0.0, "grade": 100.0},
|
||||
# 비중 몫이 없는 갈래 — 연장 · 흙 균형은 비교값으로만 고름
|
||||
"short": {"expected": 0.0, "earthwork": 0.0, "grade": 0.0},
|
||||
"balance": {"expected": 0.0, "earthwork": 0.0, "grade": 0.0},
|
||||
# 비중 몫이 없는 갈래(PLAN 23-5) — 제 목표로 찾은 후보에서 비교값으로 고름
|
||||
**{
|
||||
mode: {"expected": 0.0, "earthwork": 0.0, "grade": 0.0}
|
||||
for mode in ("gradeline", "safe", "crossings", "cost")
|
||||
},
|
||||
}
|
||||
# 후보 묶음은 갈래와 무관 — 같은 입력이면 다시 짜지 않음(최근 몇 벌만 · 프로세스 메모리)
|
||||
POOL_CACHE_SIZE = 4
|
||||
@@ -208,6 +215,7 @@ def generate_initial_route(
|
||||
min_radius_m: float,
|
||||
stream_offset_m: float | None = None,
|
||||
grade_smooth_m: float | None = None,
|
||||
target_grade: float | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""갈래 하나로 초기 계획노선을 만들고 비교값을 붙여 돌려준다. `max_grade` 는 비율(0.14).
|
||||
|
||||
@@ -222,6 +230,8 @@ def generate_initial_route(
|
||||
used = resolve_weights(mode, weights)
|
||||
stream_offset = STREAM_OFFSET_M if stream_offset_m is None else float(stream_offset_m)
|
||||
grade_smooth = GRADE_SMOOTH_M if grade_smooth_m is None else float(grade_smooth_m)
|
||||
# 영선 노선 목표 기울기(비율) — 없으면 상한 · 상한 넘게 넣어도 상한(제약 안에서만)
|
||||
goal_grade = max_grade if target_grade is None else min(float(target_grade), max_grade)
|
||||
|
||||
def measure(
|
||||
outline: Any, *, bound: float | None, follow: bool = False, weighted: bool = False
|
||||
@@ -240,6 +250,7 @@ def generate_initial_route(
|
||||
grade_smooth_m=grade_smooth,
|
||||
follow=follow,
|
||||
weighted=weighted,
|
||||
target_grade=goal_grade,
|
||||
)
|
||||
|
||||
def follow_line() -> tuple[Any, dict[str, Any]]:
|
||||
@@ -252,7 +263,7 @@ def generate_initial_route(
|
||||
pool: Pool | None = None
|
||||
target = _weighted_end(used) if mode == "weighted" else mode
|
||||
key = _pool_key(
|
||||
expected, terrain, streams, max_grade, min_radius_m, stream_offset, grade_smooth
|
||||
expected, terrain, streams, max_grade, min_radius_m, stream_offset, grade_smooth, goal_grade
|
||||
)
|
||||
if target == "follow": # 비중 예상 100 = 추적 그 선(PLAN 23-3e) · 묶음은 짜지 않음(빠른 길)
|
||||
outline, metrics = follow_line()
|
||||
@@ -265,6 +276,7 @@ def generate_initial_route(
|
||||
streams,
|
||||
stream_offset_m=stream_offset,
|
||||
grade_smooth_m=grade_smooth,
|
||||
target_grade=goal_grade,
|
||||
)
|
||||
graph = RouteGraph(grid, expected, limit=max_grade, min_radius_m=min_radius_m)
|
||||
while len(_POOLS) >= POOL_CACHE_SIZE:
|
||||
@@ -297,6 +309,7 @@ def generate_initial_route(
|
||||
"road_width_m": ROAD_WIDTH_M,
|
||||
"corridor_m": CORRIDOR_M,
|
||||
"grade_smooth_m": round(grade_smooth, 2),
|
||||
"target_grade_pct": round(goal_grade * 100.0, 2),
|
||||
# 기준값 확정(PLAN 20장, 2026-09-29) — 더는 후보가 아니다. 항상 빈 목록.
|
||||
"pending": list(PENDING),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user