#!/usr/bin/env python3
"""
Saskatchewan HVAC Verified Route Calculator
Uses OSRM-verified distances to calculate optimal routes
Includes Basic and Guaranteed Protection Service (GPS) pricing
"""

import json

# Verified distances from OSRM
SITE_VISIT_HOURS = 2.5
HOURLY_RATE = 80  # Tech cost per hour
MILEAGE_RATE = 1.25  # Pass-through (not marked up)
HOTEL_COST = 150  # Per night
CONSUMABLES_PER_SITE = 50  # Per trip
OVERTIME_MULTIPLIER = 1.5
GROSS_MARGIN = 0.50  # 50% gross margin

# GPS (Guaranteed Protection Service) add-ons
GPS_EMERGENCY_PARTS = 250  # Per site
GPS_EMERGENCY_FACTOR = 0.3  # Factor for expected emergency trips
GPS_SERVICE_HOURS = 2.5  # Hours per emergency service call
AVG_SPEED_KMH = 110  # Saskatchewan highway speed for emergency response

def cost_to_retail(cost, pass_through=False):
    """Convert cost to retail price with 50% gross margin"""
    if pass_through:
        return cost  # Mileage is pass-through
    return cost / (1 - GROSS_MARGIN)  # Divide by 0.5 = multiply by 2

# All verified distances (km, hours)
distances = {
    # Regina routes
    ("Regina Shop", "Lumsden"): (32.3, 0.49),
    ("Lumsden", "Regina ReStore"): (33.3, 0.49),
    ("Regina ReStore", "Regina Butterfield"): (3.6, 0.09),
    ("Regina Butterfield", "Regina Shop"): (6.5, 0.16),

    ("Regina Shop", "Bulyea"): (71.1, 1.07),
    ("Bulyea", "Strasbourg"): (12.8, 0.21),
    ("Strasbourg", "Regina Shop"): (84.1, 1.28),

    ("Regina Shop", "Nokomis"): (132.5, 2.02),
    ("Regina Shop", "Jansen"): (180.6, 2.76),
    ("Jansen", "Nokomis"): (65.0, 0.95),
    ("Nokomis", "Regina Shop"): (132.5, 2.02),

    ("Regina Shop", "Moose Jaw"): (71.9, 0.94),
    ("Moose Jaw", "Swift Current"): (176.0, 2.10),
    ("Swift Current", "Regina Shop"): (243.2, 2.87),
    ("Swift Current", "Moose Jaw"): (176.0, 2.10),
    ("Moose Jaw", "Regina Shop"): (71.9, 0.94),

    ("Regina Shop", "Yorkton"): (188.4, 2.50),
    ("Yorkton", "Regina Shop"): (188.4, 2.50),

    # Saskatoon routes
    ("Saskatoon Shop", "Jansen"): (154.9, 2.09),
    ("Saskatoon Shop", "Nokomis"): (165.1, 2.12),
    ("Nokomis", "Saskatoon Shop"): (164.5, 2.09),
    ("Jansen", "Saskatoon Shop"): (154.9, 2.09),

    ("Saskatoon Shop", "Saskatoon LINN"): (2.5, 0.07),
    ("Saskatoon LINN", "Saskatoon Old TT"): (7.4, 0.17),
    ("Saskatoon Old TT", "Casa Rio"): (17.3, 0.35),
    ("Casa Rio", "Saskatoon Shop"): (15.1, 0.32),

    ("Saskatoon Shop", "Clavet"): (36.1, 0.52),
    ("Clavet", "Cardinal Estates"): (12.3, 0.19),
    ("Cardinal Estates", "Shields"): (16.9, 0.27),
    ("Shields", "Saskatoon Shop"): (46.8, 0.63),

    ("Saskatoon Shop", "Allan"): (62.4, 0.82),
    ("Allan", "Young"): (24.0, 0.38),
    ("Young", "Saskatoon Shop"): (93.3, 1.18),

    ("Saskatoon Shop", "Prud'Homme"): (60.8, 0.88),
    ("Prud'Homme", "Humboldt"): (70.6, 0.91),
    ("Humboldt", "Tarnopol"): (74.0, 1.03),
    ("Tarnopol", "Saskatoon Shop"): (119.2, 1.58),

    ("Saskatoon Shop", "Neuanlage"): (40.7, 0.53),
    ("Neuanlage", "Waldheim"): (32.1, 0.52),
    ("Waldheim", "Rosthern"): (25.9, 0.34),
    ("Rosthern", "Saskatoon Shop"): (67.1, 0.83),

    ("Saskatoon Shop", "Prince Albert"): (140.9, 1.72),
    ("Prince Albert", "Saskatoon Shop"): (140.9, 1.72),
}

# Shop to site distances
shop_to_site = {
    "Lumsden": {"Regina": 32.3, "Saskatoon": 228.0},
    "Regina ReStore": {"Regina": 3.0, "Saskatoon": 260.3},
    "Regina Butterfield": {"Regina": 6.5, "Saskatoon": 259.7},
    "Bulyea": {"Regina": 71.1, "Saskatoon": 226.2},
    "Strasbourg": {"Regina": 84.0, "Saskatoon": 214.8},
    "Nokomis": {"Regina": 132.5, "Saskatoon": 165.1},
    "Jansen": {"Regina": 180.6, "Saskatoon": 154.9},
    "Moose Jaw": {"Regina": 71.9, "Saskatoon": 226.7},
    "Swift Current": {"Regina": 244.8, "Saskatoon": 271.0},
    "Yorkton": {"Regina": 188.4, "Saskatoon": 330.5},
    "Saskatoon LINN": {"Regina": 261.2, "Saskatoon": 2.5},
    "Saskatoon Old TT": {"Regina": 256.5, "Saskatoon": 7.4},
    "Casa Rio": {"Regina": 245.7, "Saskatoon": 15.4},
    "Neuanlage": {"Regina": 298.8, "Saskatoon": 40.7},
    "Waldheim": {"Regina": 314.7, "Saskatoon": 56.6},
    "Rosthern": {"Regina": 324.9, "Saskatoon": 66.8},
    "Clavet": {"Regina": 241.1, "Saskatoon": 36.1},
    "Cardinal Estates": {"Regina": 231.7, "Saskatoon": 32.5},
    "Shields": {"Regina": 223.8, "Saskatoon": 46.5},
    "Allan": {"Regina": 218.8, "Saskatoon": 62.4},
    "Young": {"Regina": 199.8, "Saskatoon": 93.8},
    "Prud'Homme": {"Regina": 272.8, "Saskatoon": 60.8},
    "Humboldt": {"Regina": 218.4, "Saskatoon": 112.8},
    "Tarnopol": {"Regina": 291.9, "Saskatoon": 119.5},
    "Prince Albert": {"Regina": 362.2, "Saskatoon": 140.9},
}

def calc_route_cost(total_hours, total_km, sites_list, hotel_nights=0):
    """Calculate detailed cost breakdown for a route"""
    num_sites = len(sites_list)

    # Labour calculation
    regular_hours = min(total_hours, 8)
    overtime_hours = max(0, total_hours - 8)
    labour_cost = (regular_hours * HOURLY_RATE) + (overtime_hours * HOURLY_RATE * OVERTIME_MULTIPLIER)

    # Mileage (pass-through)
    mileage_cost = total_km * MILEAGE_RATE

    # Consumables
    consumables_cost = num_sites * CONSUMABLES_PER_SITE

    # Hotel
    hotel_cost = hotel_nights * HOTEL_COST

    # Total cost
    total_cost = labour_cost + mileage_cost + consumables_cost + hotel_cost

    # GPS add-ons - calculated per-site based on distance from closest shop
    gps_parts_cost = num_sites * GPS_EMERGENCY_PARTS

    # Calculate emergency travel/labour for each site from its closest shop
    gps_travel_cost = 0
    gps_labour_cost = 0
    for site_name in sites_list:
        if site_name in shop_to_site:
            # Get distance from closest shop
            distances = shop_to_site[site_name]
            closest_distance = min(distances["Regina"], distances["Saskatoon"])

            # Round trip for emergency call
            emergency_km = closest_distance * 2
            emergency_drive_hours = emergency_km / AVG_SPEED_KMH
            emergency_total_hours = emergency_drive_hours + GPS_SERVICE_HOURS

            # Apply 0.3 factor (expected emergency trips)
            gps_travel_cost += emergency_km * GPS_EMERGENCY_FACTOR * MILEAGE_RATE
            gps_labour_cost += emergency_total_hours * GPS_EMERGENCY_FACTOR * HOURLY_RATE

    gps_total_addon = gps_parts_cost + gps_travel_cost + gps_labour_cost

    return {
        "labour_cost": round(labour_cost, 2),
        "mileage_cost": round(mileage_cost, 2),
        "consumables_cost": round(consumables_cost, 2),
        "hotel_cost": round(hotel_cost, 2),
        "total_cost": round(total_cost, 2),
        "gps_parts_cost": round(gps_parts_cost, 2),
        "gps_travel_cost": round(gps_travel_cost, 2),
        "gps_labour_cost": round(gps_labour_cost, 2),
        "gps_total_addon": round(gps_total_addon, 2),
        "gps_total_cost": round(total_cost + gps_total_addon, 2),
        # Retail prices
        "labour_retail": round(cost_to_retail(labour_cost), 2),
        "mileage_retail": round(mileage_cost, 2),  # Pass-through
        "consumables_retail": round(cost_to_retail(consumables_cost), 2),
        "hotel_retail": round(cost_to_retail(hotel_cost), 2),
        "total_retail": round(cost_to_retail(labour_cost) + mileage_cost + cost_to_retail(consumables_cost) + cost_to_retail(hotel_cost), 2),
        "gps_parts_retail": round(cost_to_retail(gps_parts_cost), 2),
        "gps_travel_retail": round(gps_travel_cost, 2),  # Pass-through (mileage)
        "gps_labour_retail": round(cost_to_retail(gps_labour_cost), 2),
        "gps_total_addon_retail": round(cost_to_retail(gps_parts_cost) + gps_travel_cost + cost_to_retail(gps_labour_cost), 2),
    }

# Build routes with detailed cost breakdowns
routes = []

# Helper to build route
def add_route(name, branch, sites, total_km, drive_hours, schedule, hotel_nights=0, note=None):
    num_sites = len(sites)
    total_hours = drive_hours + (num_sites * SITE_VISIT_HOURS)
    overtime = max(0, total_hours - 8)

    costs = calc_route_cost(total_hours, total_km, sites, hotel_nights)

    # Calculate per-site allocation
    km_per_site = total_km / num_sites if num_sites > 0 else 0
    hours_per_site = total_hours / num_sites if num_sites > 0 else 0

    route_data = {
        "name": name,
        "branch": branch,
        "sites": sites,
        "km": round(total_km, 1),
        "drive_hours": round(drive_hours, 2),
        "total_hours": round(total_hours, 1),
        "overtime": round(overtime, 1),
        "schedule": schedule,
        # Cost breakdown
        "costs": costs,
        # Per-site metrics
        "km_per_site": round(km_per_site, 1),
        "hours_per_site": round(hours_per_site, 2),
    }
    if note:
        route_data["note"] = note

    routes.append(route_data)

# Regina Local: Shop -> Lumsden -> ReStore -> Butterfield -> Shop
r1_km = 32.3 + 33.3 + 3.6 + 6.5
r1_drive = 0.49 + 0.49 + 0.09 + 0.16
add_route("Regina Local", "Regina",
    ["Lumsden", "Regina ReStore", "Regina Butterfield"],
    r1_km, r1_drive,
    [{"day": 1, "sites": ["Lumsden", "Regina ReStore", "Regina Butterfield"], "km": round(r1_km, 1), "hours": round(r1_drive + 3 * SITE_VISIT_HOURS, 1), "overnight": None}])

# Regina North: Shop -> Bulyea -> Strasbourg -> Shop
r2_km = 71.1 + 12.8 + 84.1
r2_drive = 1.07 + 0.21 + 1.28
add_route("Regina North", "Regina",
    ["Bulyea", "Strasbourg"],
    r2_km, r2_drive,
    [{"day": 1, "sites": ["Bulyea", "Strasbourg"], "km": round(r2_km, 1), "hours": round(r2_drive + 2 * SITE_VISIT_HOURS, 1), "overnight": None}])

# Yorkton: Shop -> Yorkton -> Shop
r3_km = 188.4 * 2
r3_drive = 2.50 * 2
add_route("Yorkton", "Regina",
    ["Yorkton"],
    r3_km, r3_drive,
    [{"day": 1, "sites": ["Yorkton"], "km": round(r3_km, 1), "hours": round(r3_drive + 1 * SITE_VISIT_HOURS, 1), "overnight": None}])

# Regina West: 2-day trip
r4_d1_km = 71.9 + 176.0
r4_d1_drive = 0.94 + 2.10
r4_d2_km = 243.2
r4_d2_drive = 2.87
r4_km = r4_d1_km + r4_d2_km
r4_drive = r4_d1_drive + r4_d2_drive
add_route("Regina West", "Regina",
    ["Moose Jaw", "Swift Current"],
    r4_km, r4_drive,
    [
        {"day": 1, "sites": ["Moose Jaw", "Swift Current"], "km": round(r4_d1_km, 1), "hours": round(r4_d1_drive + 2 * SITE_VISIT_HOURS, 1), "overnight": "Swift Current"},
        {"day": 2, "sites": [], "km": round(r4_d2_km, 1), "hours": round(r4_d2_drive, 1), "overnight": None}
    ],
    hotel_nights=1)

# Jansen + Nokomis from Saskatoon
r5_km = 154.9 + 65.0 + 164.5
r5_drive = 2.09 + 0.95 + 2.09
add_route("North Corridor", "Saskatoon",
    ["Jansen", "Nokomis"],
    r5_km, r5_drive,
    [{"day": 1, "sites": ["Jansen", "Nokomis"], "km": round(r5_km, 1), "hours": round(r5_drive + 2 * SITE_VISIT_HOURS, 1), "overnight": None}],
    note="Verified: Saskatoon saves $69 vs Regina for this loop")

# Saskatoon Local
r6_km = 2.5 + 7.4 + 17.3 + 15.1
r6_drive = 0.07 + 0.17 + 0.35 + 0.32
add_route("Saskatoon Local", "Saskatoon",
    ["Saskatoon LINN", "Saskatoon Old TT", "Casa Rio"],
    r6_km, r6_drive,
    [{"day": 1, "sites": ["Saskatoon LINN", "Saskatoon Old TT", "Casa Rio"], "km": round(r6_km, 1), "hours": round(r6_drive + 3 * SITE_VISIT_HOURS, 1), "overnight": None}])

# Saskatoon South
r7_km = 36.1 + 12.3 + 16.9 + 46.8
r7_drive = 0.52 + 0.19 + 0.27 + 0.63
add_route("Saskatoon South", "Saskatoon",
    ["Clavet", "Cardinal Estates", "Shields"],
    r7_km, r7_drive,
    [{"day": 1, "sites": ["Clavet", "Cardinal Estates", "Shields"], "km": round(r7_km, 1), "hours": round(r7_drive + 3 * SITE_VISIT_HOURS, 1), "overnight": None}])

# Saskatoon East
r8_km = 62.4 + 24.0 + 93.3
r8_drive = 0.82 + 0.38 + 1.18
add_route("Saskatoon East", "Saskatoon",
    ["Allan", "Young"],
    r8_km, r8_drive,
    [{"day": 1, "sites": ["Allan", "Young"], "km": round(r8_km, 1), "hours": round(r8_drive + 2 * SITE_VISIT_HOURS, 1), "overnight": None}])

# Saskatoon NE (Humboldt corridor)
r9_km = 60.8 + 70.6 + 74.0 + 119.2
r9_drive = 0.88 + 0.91 + 1.03 + 1.58
add_route("Saskatoon NE (Humboldt)", "Saskatoon",
    ["Prud'Homme", "Humboldt", "Tarnopol"],
    r9_km, r9_drive,
    [{"day": 1, "sites": ["Prud'Homme", "Humboldt", "Tarnopol"], "km": round(r9_km, 1), "hours": round(r9_drive + 3 * SITE_VISIT_HOURS, 1), "overnight": None}])

# Saskatoon North
r10_km = 40.7 + 32.1 + 25.9 + 67.1
r10_drive = 0.53 + 0.52 + 0.34 + 0.83
add_route("Saskatoon North", "Saskatoon",
    ["Neuanlage", "Waldheim", "Rosthern"],
    r10_km, r10_drive,
    [{"day": 1, "sites": ["Neuanlage", "Waldheim", "Rosthern"], "km": round(r10_km, 1), "hours": round(r10_drive + 3 * SITE_VISIT_HOURS, 1), "overnight": None}])

# Prince Albert
r11_km = 140.9 * 2
r11_drive = 1.72 * 2
add_route("Prince Albert", "Saskatoon",
    ["Prince Albert"],
    r11_km, r11_drive,
    [{"day": 1, "sites": ["Prince Albert"], "km": round(r11_km, 1), "hours": round(r11_drive + 1 * SITE_VISIT_HOURS, 1), "overnight": None}])

# Calculate totals
total_sites = sum(len(r["sites"]) for r in routes)
total_km = sum(r["km"] for r in routes)
total_hours = sum(r["total_hours"] for r in routes)
total_overtime = sum(r["overtime"] for r in routes)
total_days = sum(len(r["schedule"]) for r in routes)
total_hotels = sum(1 for r in routes for s in r["schedule"] if s.get("overnight"))

# Aggregate costs
total_labour_cost = sum(r["costs"]["labour_cost"] for r in routes)
total_mileage_cost = sum(r["costs"]["mileage_cost"] for r in routes)
total_consumables_cost = sum(r["costs"]["consumables_cost"] for r in routes)
total_hotel_cost = sum(r["costs"]["hotel_cost"] for r in routes)
total_cost = sum(r["costs"]["total_cost"] for r in routes)

total_labour_retail = sum(r["costs"]["labour_retail"] for r in routes)
total_mileage_retail = sum(r["costs"]["mileage_retail"] for r in routes)
total_consumables_retail = sum(r["costs"]["consumables_retail"] for r in routes)
total_hotel_retail = sum(r["costs"]["hotel_retail"] for r in routes)
total_retail = sum(r["costs"]["total_retail"] for r in routes)

# GPS totals
total_gps_parts_cost = sum(r["costs"]["gps_parts_cost"] for r in routes)
total_gps_travel_cost = sum(r["costs"]["gps_travel_cost"] for r in routes)
total_gps_labour_cost = sum(r["costs"]["gps_labour_cost"] for r in routes)
total_gps_addon_cost = sum(r["costs"]["gps_total_addon"] for r in routes)
total_gps_cost = sum(r["costs"]["gps_total_cost"] for r in routes)

total_gps_parts_retail = sum(r["costs"]["gps_parts_retail"] for r in routes)
total_gps_travel_retail = sum(r["costs"]["gps_travel_retail"] for r in routes)
total_gps_labour_retail = sum(r["costs"]["gps_labour_retail"] for r in routes)
total_gps_addon_retail = sum(r["costs"]["gps_total_addon_retail"] for r in routes)
total_gps_retail = total_retail + total_gps_addon_retail

print("=" * 90)
print("VERIFIED ROUTE SUMMARY")
print("=" * 90)
print()
print(f"{'Route':<30} {'Sites':>5} {'km':>8} {'Hours':>7} {'OT':>6} {'Cost':>10} {'Retail':>10}")
print("-" * 80)

for r in routes:
    ot_str = f"{r['overtime']:.1f}h" if r['overtime'] > 0 else "-"
    print(f"{r['name']:<30} {len(r['sites']):>5} {r['km']:>7.0f} {r['total_hours']:>6.1f}h {ot_str:>6} ${r['costs']['total_cost']:>9.2f} ${r['costs']['total_retail']:>9.2f}")

print("-" * 80)
print(f"{'TOTAL':<30} {total_sites:>5} {total_km:>7.0f} {total_hours:>6.1f}h {total_overtime:.1f}h ${total_cost:>9.2f} ${total_retail:>9.2f}")
print()

print("=" * 90)
print("COST BREAKDOWN (Per Cycle)")
print("=" * 90)
print()
print(f"{'Category':<25} {'Cost':>15} {'Retail':>15} {'Margin':>15}")
print("-" * 70)
print(f"{'Labour':<25} ${total_labour_cost:>14.2f} ${total_labour_retail:>14.2f} ${total_labour_retail - total_labour_cost:>14.2f}")
print(f"{'Mileage (pass-through)':<25} ${total_mileage_cost:>14.2f} ${total_mileage_retail:>14.2f} ${0:>14.2f}")
print(f"{'Consumables':<25} ${total_consumables_cost:>14.2f} ${total_consumables_retail:>14.2f} ${total_consumables_retail - total_consumables_cost:>14.2f}")
print(f"{'Hotels':<25} ${total_hotel_cost:>14.2f} ${total_hotel_retail:>14.2f} ${total_hotel_retail - total_hotel_cost:>14.2f}")
print("-" * 70)
print(f"{'BASIC SERVICE TOTAL':<25} ${total_cost:>14.2f} ${total_retail:>14.2f} ${total_retail - total_cost - total_mileage_cost:>14.2f}")
print()

print("=" * 90)
print("GUARANTEED PROTECTION SERVICE (GPS) ADD-ONS")
print("=" * 90)
print()
print("GPS emergency costs calculated per-site from closest shop:")
print("  - Round-trip mileage to site x 0.3 factor")
print("  - Drive time + 2.5hr service x 0.3 factor")
print()
print(f"{'Category':<25} {'Cost':>15} {'Retail':>15} {'Margin':>15}")
print("-" * 70)
print(f"{'Emergency Parts':<25} ${total_gps_parts_cost:>14.2f} ${total_gps_parts_retail:>14.2f} ${total_gps_parts_retail - total_gps_parts_cost:>14.2f}")
print(f"{'Emergency Travel (0.3x)':<25} ${total_gps_travel_cost:>14.2f} ${total_gps_travel_retail:>14.2f} ${0:>14.2f}")
print(f"{'Emergency Labour (0.3x)':<25} ${total_gps_labour_cost:>14.2f} ${total_gps_labour_retail:>14.2f} ${total_gps_labour_retail - total_gps_labour_cost:>14.2f}")
print("-" * 70)
print(f"{'GPS ADD-ON TOTAL':<25} ${total_gps_addon_cost:>14.2f} ${total_gps_addon_retail:>14.2f} ${total_gps_addon_retail - total_gps_addon_cost - total_gps_travel_cost:>14.2f}")
print()
print(f"{'GPS TOTAL (Basic + GPS)':<25} ${total_gps_cost:>14.2f} ${total_gps_retail:>14.2f}")
print()

print("=" * 90)
print("SERVICE TIER PRICING SUMMARY")
print("=" * 90)
print()
print(f"{'Service Level':<30} {'Per Cycle':>15} {'Annual (x2)':>15}")
print("-" * 60)
print(f"{'Basic Service':<30} ${total_retail:>14,.2f} ${total_retail * 2:>14,.2f}")
print(f"{'Guaranteed Protection (GPS)':<30} ${total_gps_retail:>14,.2f} ${total_gps_retail * 2:>14,.2f}")
print()
print(f"GPS Premium: ${total_gps_addon_retail:,.2f}/cycle (${total_gps_addon_retail * 2:,.2f}/year)")
print()

# Per-site breakdown
print("=" * 90)
print("PER-SITE PRICING")
print("=" * 90)
print()
basic_per_site = total_retail / total_sites
gps_per_site = total_gps_retail / total_sites
print(f"Average cost per site (Basic):  ${basic_per_site:,.2f}/visit  (${basic_per_site * 2:,.2f}/year)")
print(f"Average cost per site (GPS):    ${gps_per_site:,.2f}/visit  (${gps_per_site * 2:,.2f}/year)")
print()

# Per-site GPS emergency breakdown
print("=" * 90)
print("GPS EMERGENCY COST BY SITE (from closest shop)")
print(f"Speed: {AVG_SPEED_KMH} km/h | Service: {GPS_SERVICE_HOURS}h | Factor: {GPS_EMERGENCY_FACTOR}")
print("=" * 90)
print()
print(f"{'Site':<25} {'Closest':>8} {'1-Way km':>10} {'RT km':>10} {'Drive h':>8} {'Total h':>8} {'Travel$':>10} {'Labour$':>10}")
print("-" * 100)
for site_name in sorted(shop_to_site.keys()):
    dist = shop_to_site[site_name]
    regina_d = dist["Regina"]
    saskatoon_d = dist["Saskatoon"]
    closest = "Regina" if regina_d <= saskatoon_d else "S'toon"
    closest_km = min(regina_d, saskatoon_d)
    rt_km = closest_km * 2
    drive_h = rt_km / AVG_SPEED_KMH
    total_h = drive_h + GPS_SERVICE_HOURS
    travel_cost = rt_km * GPS_EMERGENCY_FACTOR * MILEAGE_RATE
    labour_cost = total_h * GPS_EMERGENCY_FACTOR * HOURLY_RATE
    print(f"{site_name:<25} {closest:>8} {closest_km:>10.1f} {rt_km:>10.1f} {drive_h:>8.2f} {total_h:>8.2f} ${travel_cost:>9.2f} ${labour_cost:>9.2f}")
print()

print(f"Total route days: {total_days}")
print(f"Overnight stays: {total_hotels}")
print()

# Output JSON for dashboard
output = {
    "routes": routes,
    "totals": {
        "sites": total_sites,
        "km": round(total_km, 1),
        "hours": round(total_hours, 1),
        "overtime": round(total_overtime, 1),
        "days": total_days,
        "hotels": total_hotels,
    },
    "costs": {
        "labour_cost": round(total_labour_cost, 2),
        "mileage_cost": round(total_mileage_cost, 2),
        "consumables_cost": round(total_consumables_cost, 2),
        "hotel_cost": round(total_hotel_cost, 2),
        "total_cost": round(total_cost, 2),
        "labour_retail": round(total_labour_retail, 2),
        "mileage_retail": round(total_mileage_retail, 2),
        "consumables_retail": round(total_consumables_retail, 2),
        "hotel_retail": round(total_hotel_retail, 2),
        "total_retail": round(total_retail, 2),
        "gps_parts_cost": round(total_gps_parts_cost, 2),
        "gps_travel_cost": round(total_gps_travel_cost, 2),
        "gps_labour_cost": round(total_gps_labour_cost, 2),
        "gps_addon_cost": round(total_gps_addon_cost, 2),
        "gps_total_cost": round(total_gps_cost, 2),
        "gps_parts_retail": round(total_gps_parts_retail, 2),
        "gps_travel_retail": round(total_gps_travel_retail, 2),
        "gps_labour_retail": round(total_gps_labour_retail, 2),
        "gps_addon_retail": round(total_gps_addon_retail, 2),
        "gps_total_retail": round(total_gps_retail, 2),
        "annual_basic_retail": round(total_retail * 2, 2),
        "annual_gps_retail": round(total_gps_retail * 2, 2),
    },
    "per_site": {
        "basic_retail": round(basic_per_site, 2),
        "gps_retail": round(gps_per_site, 2),
        "basic_annual": round(basic_per_site * 2, 2),
        "gps_annual": round(gps_per_site * 2, 2),
    },
    "rates": {
        "hourly_cost": HOURLY_RATE,
        "hourly_retail": cost_to_retail(HOURLY_RATE),
        "mileage_rate": MILEAGE_RATE,
        "consumables_cost": CONSUMABLES_PER_SITE,
        "consumables_retail": cost_to_retail(CONSUMABLES_PER_SITE),
        "hotel_cost": HOTEL_COST,
        "hotel_retail": cost_to_retail(HOTEL_COST),
        "gps_parts_cost": GPS_EMERGENCY_PARTS,
        "gps_parts_retail": cost_to_retail(GPS_EMERGENCY_PARTS),
        "gps_emergency_factor": GPS_EMERGENCY_FACTOR,
        "gross_margin": GROSS_MARGIN,
    },
    "shop_distances": shop_to_site
}

with open("verified_routes.json", "w") as f:
    json.dump(output, f, indent=2)

print("Route data saved to verified_routes.json")
