- 데이터 = bases[](id · to = 시점 또는 다른 기준점 · linked) · 경로 via_base 대신 to - 옛 꼴(base · via_base) = 읽을 때 · 받을 때 새 꼴로 옮김(migrate 짝 Py · TS · 저장 = 새 꼴) - 행 합 = 거래처 구간 + 기준점 사슬 구간들(방문 집합으로 돌기 끊음 · 안 이은 기준점에서 멈춤) - 도로 구분 = 시점에 닿는 기준점 구간 1차 · 그 밖 기준점 구간 · 거래처 → 기준점 2차 - 화면 = 기준점 여럿 등록 · 삭제(이은 상자 안 이음) · 연결 놓기 = 기준점 id · 길찾기 · 표지 사슬 - 시험 = test_254_1 새로 · 244 · 250 기대값 새 꼴로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
142 lines
6.6 KiB
Python
142 lines
6.6 KiB
Python
"""B08 거리이정표 표 합(PLAN 244-1) — 화면 없음 · 저장 안 함.
|
|
|
|
⚠ 짝 = `B08_Quantity_Milestone_Calc.ts`(같은 식) — 한쪽을 고치면 다른 쪽도 ·
|
|
거울 시험 `resources/tester/test_244_1_b08_milestone.py`.
|
|
조작 중은 TS 짝이 바로 셈 · [저장] · 읽기 때 서버가 이 파일로 다시 셈.
|
|
|
|
표 한 줄 = 경로(업체) 구간 + 기준점 사슬 구간들(254 · 거래처 `to` → 기준점 `to` → … → 시점 ·
|
|
`base_chain` · 방문 집합으로 돌기 끊음 · 안 이은 기준점에서 멈춤) + (목적지 = 중점이면
|
|
시점 → 중점 = 중점 측점 km) · 도로 구분 셋(2차포장 · 1차포장 · 비포장)마다 합 ·
|
|
포장 = 2차 + 1차 · 비포장 · 합계 = 포장 + 비포장(견본 「거리이정표」 시트 수식) ·
|
|
끝수 = 소수 셋째 자리(반올림).
|
|
구간 km = 사용자 값 · 없으면 [길찾기] 자동 값(244-7).
|
|
도로 구분(152 · 254) = 포장 종류는 선 자리로 정함(`road_of`) — 시점에 닿는 기준점 구간 = 1차포장 ·
|
|
그 밖 기준점 구간 · 거래처 → 기준점 = 2차포장 · 거래처 → 시점 · 시점 → 중점 = 포장 · 사용자는
|
|
구간마다 비포장만 고름 · 포장 = 2차 + 1차 + 포장 · pick = 늘 거짓(옛 「도로 구분 고르기」 자리).
|
|
옛 꼴(단일 `base` · 경로 `via_base`) = 읽을 때 `migrate_milestone` 로 새 꼴(`bases[]` · `to`).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from typing import Any
|
|
|
|
ROADS = ("2차포장", "1차포장", "포장", "비포장")
|
|
KEYS = {"2차포장": "s2", "1차포장": "s1", "포장": "pv", "비포장": "un"}
|
|
# 구간 자리 → 포장 종류(152 · 254) — via = → 기준점 · base = 기준점 → 시점 ·
|
|
# direct = 거래처 → 시점 · mid = 시점 → 중점
|
|
PAVED = {"via": "2차포장", "base": "1차포장", "direct": "포장", "mid": "포장"}
|
|
SITE = "site" # 이음 대상 = 시점(그 밖 = 기준점 id)
|
|
|
|
|
|
def road_of(road: str | None, kind: str) -> str:
|
|
"""그 구간 도로 구분 — 비포장만 사용자 값 · 그 밖(빈 값 · 옛 값) = 자리의 포장 종류."""
|
|
return "비포장" if road == "비포장" else PAVED[kind]
|
|
|
|
|
|
def r3(value: float) -> float:
|
|
"""소수 셋째 자리 반올림(0.5 올림 · TS `Math.round` 와 같음)."""
|
|
return math.floor(value * 1000 + 0.5) / 1000
|
|
|
|
|
|
def leg_km(leg: dict[str, Any]) -> float | None:
|
|
"""구간 km — 사용자 값 우선 · 없으면 자동 값."""
|
|
km = leg.get("km")
|
|
return leg.get("auto_km") if km is None else km
|
|
|
|
|
|
def add_legs(sums: dict[str, float], legs: list[dict[str, Any]] | None, kind: str) -> None:
|
|
"""도로 구분마다 더함 — 구간 자리로 포장 종류(152 · 안 고른 구간 없음)."""
|
|
for leg in legs or []:
|
|
sums[KEYS[road_of(leg.get("road"), kind)]] += float(leg_km(leg) or 0)
|
|
|
|
|
|
def mid_km(data: dict[str, Any], mid_default_m: float | None) -> float:
|
|
"""시점 → 중점 km — 사용자 측점(없으면 기본 = 노선 ÷ 2) ÷ 1000."""
|
|
station = (data.get("site") or {}).get("mid_station_m")
|
|
if station is None:
|
|
station = mid_default_m
|
|
return float(station or 0) / 1000
|
|
|
|
|
|
def base_chain(data: dict[str, Any], to: str) -> list[dict[str, Any]]:
|
|
"""기준점 사슬(254) — to 부터 이은 기준점을 따라 시점까지 · 방문 집합(돌기 끊음) ·
|
|
안 이은 · 없는 기준점에서 멈춤."""
|
|
by_id = {b.get("id"): b for b in data.get("bases") or []}
|
|
out: list[dict[str, Any]] = []
|
|
seen: set[str] = set()
|
|
while to != SITE and to not in seen:
|
|
seen.add(to)
|
|
base = by_id.get(to)
|
|
if base is None or base.get("linked", True) is False:
|
|
break
|
|
out.append(base)
|
|
to = base.get("to") or SITE
|
|
return out
|
|
|
|
|
|
def migrate_milestone(data: dict[str, Any]) -> dict[str, Any]:
|
|
"""옛 꼴 → 새 꼴(254) — 옛 `base`(켜짐) = bases[0] id "b1" ·
|
|
경로 `via_base` = to "b1" | "site" · 새 꼴은 그대로(사본)."""
|
|
rest = {k: v for k, v in data.items() if k != "base"}
|
|
old = data.get("base") or {}
|
|
fresh = isinstance(rest.get("bases"), list)
|
|
on = not fresh and bool(old.get("enabled"))
|
|
if fresh:
|
|
bases = rest["bases"]
|
|
elif on:
|
|
keep = ("name", "address", "dir", "lon", "lat", "pos", "manual", "legs")
|
|
bases = [{**{k: old[k] for k in keep if k in old}, "id": "b1", "to": SITE, "linked": True}]
|
|
else:
|
|
bases = []
|
|
routes = []
|
|
for route in rest.get("routes") or []:
|
|
if not isinstance(route.get("to"), str):
|
|
via = on and route.get("via_base", True) is not False
|
|
route = {k: v for k, v in route.items() if k != "via_base"}
|
|
route["to"] = "b1" if via else SITE
|
|
routes.append(route)
|
|
return {**rest, "bases": bases, "routes": routes}
|
|
|
|
|
|
def _kind(owner: dict[str, Any], to_site: str, to_base: str) -> str:
|
|
return to_site if (owner.get("to") or SITE) == SITE else to_base
|
|
|
|
|
|
def compute_milestone(data: dict[str, Any], mid_default_m: float | None) -> list[dict[str, Any]]:
|
|
"""표 줄 — 경로 차례 · 경로 안 줄 차례."""
|
|
data = migrate_milestone(data)
|
|
site = data.get("site") or {}
|
|
mid = {"road": site.get("mid_road") or "비포장", "km": mid_km(data, mid_default_m)}
|
|
out: list[dict[str, Any]] = []
|
|
for route in data.get("routes") or []:
|
|
for index, row in enumerate(route.get("rows") or []):
|
|
sums = {"s2": 0.0, "s1": 0.0, "pv": 0.0, "un": 0.0}
|
|
# 안 이은 거래처 = 구간 km 없음(155) · 이음 = 거래처 구간 + 기준점 사슬(254)
|
|
if route.get("linked", True) is not False:
|
|
add_legs(sums, route.get("legs"), _kind(route, "direct", "via"))
|
|
for base in base_chain(data, route.get("to") or SITE):
|
|
add_legs(sums, base.get("legs"), _kind(base, "base", "via"))
|
|
if row.get("dest") == "mid":
|
|
add_legs(sums, [mid], "mid")
|
|
paved = sums["s2"] + sums["s1"] + sums["pv"]
|
|
out.append(
|
|
{
|
|
"route": route.get("id"),
|
|
"index": index,
|
|
"item": row.get("item") or "",
|
|
"company": route.get("name") or "",
|
|
"dest": row.get("dest") or "start",
|
|
"s2": r3(sums["s2"]),
|
|
"s1": r3(sums["s1"]),
|
|
"pv": r3(sums["pv"]),
|
|
"un": r3(sums["un"]),
|
|
"paved": r3(paved),
|
|
"unpaved": r3(sums["un"]),
|
|
"total": r3(paved + sums["un"]),
|
|
"note": row.get("note") or "",
|
|
"pick": False,
|
|
}
|
|
)
|
|
return out
|