| """ |
| 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 |
| |
| 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 |
| |
| |
| usage_ratio = route_distance_km / battery_range_km |
| |
| |
| if usage_ratio <= 0.7: |
| return 0.0 |
| |
| |
| |
| 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: |
| |
| return (True, 0.0) |
| |
| if route_distance_km is None: |
| |
| return (True, 0.0) |
| |
| |
| feasible = is_route_feasible_for_ev( |
| battery_range_km, route_distance_km, safety_margin_pct |
| ) |
| |
| if not feasible: |
| return (False, float('inf')) |
| |
| |
| overhead = calculate_ev_charging_overhead( |
| route_distance_km, |
| battery_range_km, |
| charging_time_minutes or 30, |
| penalty_weight, |
| ) |
| |
| return (True, overhead) |
|
|