"""Distance and travel-time matrices. Node ordering convention used everywhere in this project: nodes[0 .. n_adjusters-1] = adjuster home locations (route start/end) nodes[n_adjusters .. ] = claim property locations The solver only ever reads these matrices, so replacing haversine estimates with real drive times (OSRM's /table endpoint, Google/HERE Distance Matrix) means swapping build_matrices() and nothing else. """ from __future__ import annotations import math import config from data_gen import Adjuster, Claim EARTH_RADIUS_MILES = 3958.8 def haversine_miles(lat1: float, lon1: float, lat2: float, lon2: float) -> float: p1, p2 = math.radians(lat1), math.radians(lat2) dp = math.radians(lat2 - lat1) dl = math.radians(lon2 - lon1) a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2 return 2 * EARTH_RADIUS_MILES * math.asin(math.sqrt(a)) def matrices_from_coords(coords: list[tuple[float, float]] ) -> tuple[list[list[float]], list[list[int]]]: """(miles, minutes) matrices over an arbitrary coordinate list - haversine * circuity, minutes as integers for the solvers.""" n = len(coords) miles = [[0.0] * n for _ in range(n)] minutes = [[0] * n for _ in range(n)] for i in range(n): for j in range(i + 1, n): d = haversine_miles(*coords[i], *coords[j]) * config.CIRCUITY_FACTOR t = int(round(d / config.AVG_SPEED_MPH * 60)) miles[i][j] = miles[j][i] = d minutes[i][j] = minutes[j][i] = t return miles, minutes def build_matrices(adjusters: list[Adjuster], claims: list[Claim] ) -> tuple[list[list[float]], list[list[int]]]: """Return (miles_matrix, travel_minutes_matrix) over all nodes. Miles are road-approximated (haversine * circuity factor); minutes are integers as required by OR-Tools. """ coords = [(a.home_lat, a.home_lon) for a in adjusters] coords += [(c.lat, c.lon) for c in claims] return matrices_from_coords(coords)