#!/usr/bin/env python3
"""
Saskatchewan HVAC Route Optimizer
Proposes optimized routes to stay within 12-hour days while minimizing overtime
"""

from dataclasses import dataclass

# Constants
MILES_TO_KM = 1.60934
AVG_SPEED_KMH = 80
SITE_VISIT_HOURS = 2.5
REGULAR_HOURS = 8
MAX_HOURS = 12
OVERTIME_MULTIPLIER = 1.5
ROUTES_PER_YEAR = 2

# Site data with distances from Book 5 (km from closest shop)
# Format: name, branch, km_from_shop, original_route
sites = [
    # Regina sites
    ("Lumsden", "Regina", 32, "Regina - Local"),  # 20 miles = ~32km from Regina
    ("Regina ReStore", "Regina", 8, "Regina - Local"),
    ("Regina Butterfield", "Regina", 3, "Regina - Local"),
    ("Bulyea", "Regina", 71, "Regina - N"),  # 44 miles from Regina
    ("Strasbourg", "Regina", 84, "Regina - N"),
    ("Nokomis", "Regina", 134, "Regina - N"),
    ("Jansen", "Regina", 198, "Regina - N"),  # furthest north
    ("Moose Jaw", "Regina", 77, "Regina - W"),
    ("Swift Current", "Regina", 256, "Regina - W"),
    ("Yorkton", "Regina", 185, "Regina - YQV"),

    # Saskatoon sites
    ("Saskatoon LINN", "Saskatoon", 2, "S'toon - Local"),
    ("Saskatoon Old TT", "Saskatoon", 8, "S'toon - Local"),
    ("Casa Rio", "Saskatoon", 18, "S'toon - Local"),
    ("Humboldt", "Saskatoon", 116, "S'toon - NE"),
    ("Tarnopol", "Saskatoon", 120, "S'toon - NE"),  # 45 miles from Humboldt
    ("Prud'Homme", "Saskatoon", 63, "S'toon - NE"),
    ("Neuanlage", "Saskatoon", 37, "S'toon - PA"),
    ("Rosthern", "Saskatoon", 55, "S'toon - PA"),
    ("Prince Albert", "Saskatoon", 137, "S'toon - PA"),
    ("Waldheim", "Saskatoon", 65, "S'toon - PA"),
    ("Clavet", "Saskatoon", 40, "S'toon - SE"),
    ("Allan", "Saskatoon", 47, "S'toon - SE"),  # 17 miles from Clavet
    ("Young", "Saskatoon", 99, "S'toon - SE"),
    ("Shields", "Saskatoon", 66, "S'toon - SE"),
    ("Cardinal Estates", "Saskatoon", 44, "S'toon - SE"),
]

def calc_route_time(total_km, num_sites):
    """Calculate total route time"""
    driving = total_km / AVG_SPEED_KMH
    site_work = num_sites * SITE_VISIT_HOURS
    return driving + site_work

def calc_overtime(total_hours):
    """Calculate overtime hours"""
    return max(0, total_hours - REGULAR_HOURS)

print("=" * 80)
print("SASKATCHEWAN HVAC OPTIMIZED ROUTE PROPOSAL")
print("=" * 80)
print()
print("CURRENT PROBLEMS:")
print("  - 3 routes exceed 12-hour maximum (can't be done in one day)")
print("  - High overtime (26.8 hours per cycle) increases costs significantly")
print("  - Some routes are underutilized (Yorkton is only 7.1 hours)")
print()

# OPTIMIZED ROUTE PROPOSALS
print("=" * 80)
print("PROPOSED OPTIMIZED ROUTES")
print("=" * 80)
print()

optimized_routes = [
    # Regina Branch - Restructured
    {
        "name": "Regina Day 1: Local + Lumsden",
        "branch": "Regina",
        "sites": ["Lumsden", "Regina ReStore", "Regina Butterfield"],
        "km": 69,  # Same as original
        "notes": "No change - this route is optimal"
    },
    {
        "name": "Regina Day 2: North Loop (Short)",
        "branch": "Regina",
        "sites": ["Bulyea", "Strasbourg"],
        "km": 168,  # Regina -> Bulyea (71) + Bulyea->Strasbourg (13) + Return (84)
        "notes": "Split from original Regina-N. Quick loop to 2 closest northern sites"
    },
    {
        "name": "Regina Day 3: North Loop (Long)",
        "branch": "Regina",
        "sites": ["Nokomis", "Jansen"],
        "km": 332,  # Regina -> Nokomis (134) + Nokomis->Jansen (64) + Return (134)
        "notes": "Split from original Regina-N. The 2 furthest northern sites"
    },
    {
        "name": "Regina Day 4: West to Moose Jaw",
        "branch": "Regina",
        "sites": ["Moose Jaw"],
        "km": 154,  # Round trip to Moose Jaw
        "notes": "Split from Regina-W. Short day - could combine with something"
    },
    {
        "name": "Regina Day 5: West to Swift Current",
        "branch": "Regina",
        "sites": ["Swift Current"],
        "km": 512,  # Round trip to Swift Current
        "notes": "Split from Regina-W. Long drive but single site"
    },
    {
        "name": "Regina Day 6: Yorkton",
        "branch": "Regina",
        "sites": ["Yorkton"],
        "km": 370,  # Same as original
        "notes": "No change - standalone route makes sense given distance"
    },

    # Saskatoon Branch - Restructured
    {
        "name": "Saskatoon Day 1: Local",
        "branch": "Saskatoon",
        "sites": ["Saskatoon LINN", "Saskatoon Old TT", "Casa Rio"],
        "km": 50,  # Same as original
        "notes": "No change - this route is optimal"
    },
    {
        "name": "Saskatoon Day 2: NE Route",
        "branch": "Saskatoon",
        "sites": ["Humboldt", "Tarnopol", "Prud'Homme"],
        "km": 327,  # Same as original
        "notes": "Keep as-is - 11.6h is within limits, some OT acceptable"
    },
    {
        "name": "Saskatoon Day 3: PA Route (South)",
        "branch": "Saskatoon",
        "sites": ["Neuanlage", "Rosthern", "Waldheim"],
        "km": 170,  # Saskatoon -> Neuanlage (37) -> Rosthern (18) -> Waldheim (65) -> Return (50)
        "notes": "Split PA route - take the 3 closer sites"
    },
    {
        "name": "Saskatoon Day 4: Prince Albert Solo",
        "branch": "Saskatoon",
        "sites": ["Prince Albert"],
        "km": 274,  # Round trip to PA
        "notes": "Split PA route - PA on its own due to distance"
    },
    {
        "name": "Saskatoon Day 5: SE Loop (Close)",
        "branch": "Saskatoon",
        "sites": ["Clavet", "Allan", "Cardinal Estates"],
        "km": 130,  # Estimated loop: Stoon -> Clavet -> Allan -> Cardinal -> Return
        "notes": "Split SE route - eastern/closer sites"
    },
    {
        "name": "Saskatoon Day 6: SE Loop (Far)",
        "branch": "Saskatoon",
        "sites": ["Young", "Shields"],
        "km": 265,  # Estimated: Stoon -> Young (99) -> Shields (33) -> Return (133)
        "notes": "Split SE route - further sites"
    },
]

total_opt_hours = 0
total_opt_regular = 0
total_opt_overtime = 0
total_opt_km = 0

for route in optimized_routes:
    km = route["km"]
    sites = len(route["sites"])
    total_hours = calc_route_time(km, sites)
    ot = calc_overtime(total_hours)
    reg = min(total_hours, REGULAR_HOURS)

    total_opt_hours += total_hours
    total_opt_regular += reg
    total_opt_overtime += ot
    total_opt_km += km

    status = "✓" if total_hours <= MAX_HOURS else "⚠ OVER"
    ot_str = f" (OT: {ot:.1f}h)" if ot > 0 else ""

    print(f"{route['name']}")
    print(f"  Sites ({sites}): {', '.join(route['sites'])}")
    print(f"  Distance: {km} km | Driving: {km/AVG_SPEED_KMH:.1f}h | Site work: {sites*SITE_VISIT_HOURS}h")
    print(f"  TOTAL: {total_hours:.1f} hours {status}{ot_str}")
    print(f"  Note: {route['notes']}")
    print()

print("=" * 80)
print("COMPARISON: ORIGINAL vs OPTIMIZED")
print("=" * 80)
print()

# Original stats
orig_hours = 89.9
orig_regular = 63.1
orig_overtime = 26.8
orig_km = 2190
orig_days = 8

print(f"{'Metric':<25} {'Original':>15} {'Optimized':>15} {'Change':>15}")
print("-" * 70)
print(f"{'Total Days':<25} {orig_days:>15} {len(optimized_routes):>15} {len(optimized_routes)-orig_days:>+15}")
print(f"{'Total Distance (km)':<25} {orig_km:>15.0f} {total_opt_km:>15.0f} {total_opt_km-orig_km:>+15.0f}")
print(f"{'Total Hours':<25} {orig_hours:>15.1f} {total_opt_hours:>15.1f} {total_opt_hours-orig_hours:>+15.1f}")
print(f"{'Regular Hours':<25} {orig_regular:>15.1f} {total_opt_regular:>15.1f} {total_opt_regular-orig_regular:>+15.1f}")
print(f"{'Overtime Hours':<25} {orig_overtime:>15.1f} {total_opt_overtime:>15.1f} {total_opt_overtime-orig_overtime:>+15.1f}")
print()

# Cost comparison
HOURLY_RATE = 75
VEHICLE_COST_PER_KM = 0.65

orig_labour = (orig_regular * HOURLY_RATE) + (orig_overtime * HOURLY_RATE * OVERTIME_MULTIPLIER)
orig_vehicle = orig_km * VEHICLE_COST_PER_KM
orig_total = orig_labour + orig_vehicle

opt_labour = (total_opt_regular * HOURLY_RATE) + (total_opt_overtime * HOURLY_RATE * OVERTIME_MULTIPLIER)
opt_vehicle = total_opt_km * VEHICLE_COST_PER_KM
opt_total = opt_labour + opt_vehicle

print("=" * 80)
print("COST COMPARISON (per route cycle)")
print("=" * 80)
print()
print(f"{'Cost Type':<25} {'Original':>15} {'Optimized':>15} {'Savings':>15}")
print("-" * 70)
print(f"{'Labour (reg + OT)':<25} ${orig_labour:>14.2f} ${opt_labour:>14.2f} ${orig_labour-opt_labour:>14.2f}")
print(f"{'Vehicle':<25} ${orig_vehicle:>14.2f} ${opt_vehicle:>14.2f} ${orig_vehicle-opt_vehicle:>14.2f}")
print(f"{'TOTAL':<25} ${orig_total:>14.2f} ${opt_total:>14.2f} ${orig_total-opt_total:>14.2f}")
print()
print(f"Annual savings (x2 cycles): ${(orig_total-opt_total)*2:.2f}")
print()

# Overtime cost breakdown
print("=" * 80)
print("OVERTIME IMPACT ANALYSIS")
print("=" * 80)
print()
print("Original Plan:")
print(f"  Overtime hours per cycle: {orig_overtime:.1f}")
print(f"  Overtime cost per cycle: ${orig_overtime * HOURLY_RATE * OVERTIME_MULTIPLIER:.2f}")
print(f"  Overtime cost per year: ${orig_overtime * HOURLY_RATE * OVERTIME_MULTIPLIER * 2:.2f}")
print()
print("Optimized Plan:")
print(f"  Overtime hours per cycle: {total_opt_overtime:.1f}")
print(f"  Overtime cost per cycle: ${total_opt_overtime * HOURLY_RATE * OVERTIME_MULTIPLIER:.2f}")
print(f"  Overtime cost per year: ${total_opt_overtime * HOURLY_RATE * OVERTIME_MULTIPLIER * 2:.2f}")
print()

ot_savings_pct = ((orig_overtime - total_opt_overtime) / orig_overtime) * 100
print(f"Overtime reduction: {orig_overtime - total_opt_overtime:.1f} hours ({ot_savings_pct:.0f}%)")
print()

print("=" * 80)
print("KEY TAKEAWAYS")
print("=" * 80)
print()
print("1. PROBLEM: 3 routes in original plan exceed 12-hour max (illegal/unsafe)")
print("   SOLUTION: Split into 12 manageable day-routes")
print()
print("2. TRADE-OFF: More days = more vehicle km, but LESS overtime")
print("   - Extra 4 days adds ~531 km driving")
print("   - But saves ~17 hours of overtime")
print()
print("3. NET EFFECT: Optimized plan may cost slightly more in vehicle costs")
print("   but provides:")
print("   - Legal/safe workdays (all under 12 hours)")
print("   - Reduced overtime burden on workers")
print("   - More predictable scheduling")
print()
print("4. CONSIDERATION: Some short days could be combined if OT is acceptable:")
print("   - Regina Day 4 (Moose Jaw) = 4.4h - very short")
print("   - Saskatoon Day 4 (PA) = 5.9h - could add a nearby site")
print()
