File size: 3,654 Bytes
fcf8749
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""
EV Utilities for Phase 7.
Provides EV feasibility checks and charging overhead calculations.
"""

from typing import Optional


def is_route_feasible_for_ev(
    battery_range_km: float,
    route_distance_km: float,
    safety_margin_pct: float = 10.0,
) -> bool:
    """
    Check if a route is feasible for an EV driver.
    
    Args:
        battery_range_km: Driver's EV battery range in km
        route_distance_km: Total route distance in km
        safety_margin_pct: Safety margin percentage (default 10%)
    
    Returns:
        True if the route is within EV range with safety margin
    """
    if battery_range_km <= 0:
        return False
    if route_distance_km is None or route_distance_km <= 0:
        return True  # No distance info, assume feasible
    
    effective_range = battery_range_km * (1.0 - safety_margin_pct / 100.0)
    return route_distance_km <= effective_range


def calculate_ev_charging_overhead(
    route_distance_km: float,
    battery_range_km: float,
    charging_time_minutes: int,
    penalty_weight: float = 0.3,
) -> float:
    """
    Calculate effort overhead for EV drivers due to range/charging constraints.
    
    Penalizes routes that use a high percentage of battery,
    reflecting the mental/logistical burden of range management.
    
    Args:
        route_distance_km: Total route distance
        battery_range_km: EV battery range
        charging_time_minutes: Typical charging time
        penalty_weight: Weight for penalty (default 0.3)
    
    Returns:
        Additional effort score for EV charging overhead
    """
    if battery_range_km <= 0 or route_distance_km <= 0:
        return 0.0
    
    # Calculate usage ratio (0.0 to 1.0+)
    usage_ratio = route_distance_km / battery_range_km
    
    # Only penalize when usage exceeds 70% of range
    if usage_ratio <= 0.7:
        return 0.0
    
    # Overhead increases as usage approaches or exceeds 100%
    # More burden = more mental/planning effort
    charging_overhead = (usage_ratio - 0.7) * charging_time_minutes * penalty_weight
    
    return max(0.0, charging_overhead)


def get_ev_effort_adjustment(
    driver_is_ev: bool,
    battery_range_km: Optional[float],
    charging_time_minutes: Optional[int],
    route_distance_km: Optional[float],
    safety_margin_pct: float = 10.0,
    penalty_weight: float = 0.3,
) -> tuple[bool, float]:
    """
    Get EV feasibility and effort adjustment for a driver-route pair.
    
    Args:
        driver_is_ev: Whether driver uses EV
        battery_range_km: EV battery range (None for ICE)
        charging_time_minutes: EV charging time (None for ICE)
        route_distance_km: Route distance
        safety_margin_pct: Safety margin for range check
        penalty_weight: Weight for charging overhead
    
    Returns:
        Tuple of (is_feasible, effort_adjustment)
    """
    if not driver_is_ev:
        return (True, 0.0)
    
    if battery_range_km is None or battery_range_km <= 0:
        # EV without range info - assume feasible, no adjustment
        return (True, 0.0)
    
    if route_distance_km is None:
        # No distance info - assume feasible, no adjustment
        return (True, 0.0)
    
    # Check feasibility
    feasible = is_route_feasible_for_ev(
        battery_range_km, route_distance_km, safety_margin_pct
    )
    
    if not feasible:
        return (False, float('inf'))
    
    # Calculate charging overhead
    overhead = calculate_ev_charging_overhead(
        route_distance_km,
        battery_range_km,
        charging_time_minutes or 30,
        penalty_weight,
    )
    
    return (True, overhead)