interhack26 / loader.py
danvancea's picture
Upload 34 files
4e22ad8 verified
Raw
History Blame Contribute Delete
4.09 kB
"""Parse a request dict + product/van catalogues into solver inputs.
The shape of the request dict matches `sample_request.json`. The catalogues
(`products.json`, `vans.json`) are read from disk lazily — keeps startup
cheap and avoids globals.
"""
from __future__ import annotations
import json
from dataclasses import dataclass
from pathlib import Path
_BASE = Path(__file__).parent
DEFAULT_PRODUCTS = _BASE / "products.json"
DEFAULT_VANS = _BASE / "vans.json"
SERVICE_BASE_S = 180.0 # parking + paperwork buffer per stop
SERVICE_PER_CELL_S = 10.0 # crate-handling time per unit cell
@dataclass
class Depot:
id: str
lat: float
lng: float
open_s: int
close_s: int
@dataclass
class Driver:
id: str
shift_start_s: int
shift_end_s: int
@dataclass
class Fleet:
num_vans: int
capacity_kg: float
capacity_cells: int
@dataclass
class Stop:
id: str
lat: float
lng: float
t_open_s: int
t_close_s: int
delivery_cells: int
delivery_kg: float
pickup_cells: int
pickup_kg: float
service_time_s: float
def _hms(s: str) -> int:
h, m = s.split(":")
return int(h) * 3600 + int(m) * 60
def _van_capacity_cells(spec: dict) -> int:
cube = spec["cube_size_m"]
return (
int(round(spec["length_m"] / cube))
* int(round(spec["width_m"] / cube))
* int(round(spec["height_m"] / cube))
)
def _van_spec(vans_path: Path, van_type: str) -> dict:
data = json.loads(vans_path.read_text(encoding="utf-8"))
for v in data["van_types"]:
if v["type"] == van_type:
return v
raise KeyError(f"unknown van_type: {van_type}")
def _line_cells(p: dict, qty: int) -> int:
return p["length_cells"] * p["width_cells"] * p["height_cells"] * qty
def load(
req: dict,
products_path: Path = DEFAULT_PRODUCTS,
vans_path: Path = DEFAULT_VANS,
) -> tuple[Depot, Fleet, list[Driver], list[Stop]]:
products = {
p["id"]: p
for p in json.loads(products_path.read_text(encoding="utf-8"))["products"]
}
spec = _van_spec(vans_path, req["fleet"]["van_type"])
depot = Depot(
id=req["depot"]["id"],
lat=req["depot"]["coords"]["lat"],
lng=req["depot"]["coords"]["lng"],
open_s=_hms(req["depot"]["open"]),
close_s=_hms(req["depot"]["close"]),
)
fleet = Fleet(
num_vans=req["fleet"]["num_vans"],
capacity_kg=float(spec["max_payload_kg"]),
capacity_cells=_van_capacity_cells(spec),
)
drivers = [
Driver(d["id"], _hms(d["shift_start"]), _hms(d["shift_end"]))
for d in req["drivers"]
]
stops: list[Stop] = []
for s in req["stops"]:
d_cells = sum(_line_cells(products[ln["product_id"]], ln["qty"]) for ln in s["deliveries"])
d_kg = sum(products[ln["product_id"]]["weight_kg"] * ln["qty"] for ln in s["deliveries"])
p_cells = sum(_line_cells(products[ln["product_id"]], ln["qty"]) for ln in s.get("pickups", []))
p_kg = sum(products[ln["product_id"]]["weight_kg"] * ln["qty"] for ln in s.get("pickups", []))
stops.append(Stop(
id=s["id"],
lat=s["coords"]["lat"], lng=s["coords"]["lng"],
t_open_s=_hms(s["time_window"]["open"]),
t_close_s=_hms(s["time_window"]["close"]),
delivery_cells=d_cells, delivery_kg=d_kg,
pickup_cells=p_cells, pickup_kg=p_kg,
service_time_s=SERVICE_BASE_S + SERVICE_PER_CELL_S * (d_cells + p_cells),
))
return depot, fleet, drivers, stops
def build_travel_matrix(depot: Depot, stops: list[Stop]):
"""Travel-time + distance matrix scoped to depot + the request's stops."""
from travel_time import build_matrix
from graph_manager import get_or_build_graph
points = [("DEPOT", depot.lat, depot.lng)] + [(s.id, s.lat, s.lng) for s in stops]
return build_matrix(get_or_build_graph(), points)