| import numpy as np |
| import pandas as pd |
| from typing import List, Dict |
|
|
| class FleetReadinessAgent: |
| """AI agent for fleet electrification readiness & procurement intelligence.""" |
|
|
| def __init__(self): |
| |
| self.ev_catalogue = [ |
| {"model": "Tata Ultra EV 7", "oem": "Tata Motors", "category": "Freight", "range_km": 200, "battery_kwh": 168, "price_inr_lakh": 45, "payload_tons": 7}, |
| {"model": "Ashok Leyland AVTR EV", "oem": "Ashok Leyland", "category": "Freight", "range_km": 300, "battery_kwh": 250, "price_inr_lakh": 75, "payload_tons": 16}, |
| {"model": "Mahindra Treo Zor", "oem": "Mahindra Electric", "category": "Last Mile", "range_km": 130, "battery_kwh": 48, "price_inr_lakh": 12, "payload_tons": 0.5}, |
| {"model": "Piaggio Ape E-City", "oem": "Piaggio", "category": "Last Mile", "range_km": 90, "battery_kwh": 26, "price_inr_lakh": 6, "payload_tons": 0.3}, |
| {"model": "JBM ECO-LIFE", "oem": "JBM Auto", "category": "Bus", "range_km": 250, "battery_kwh": 200, "price_inr_lakh": 120, "payload_tons": 12}, |
| {"model": " Volvo FM Electric", "oem": "Volvo Group", "category": "Construction", "range_km": 320, "battery_kwh": 450, "price_inr_lakh": 250, "payload_tons": 25}, |
| ] |
|
|
| def score_asset(self, asset: Dict) -> Dict: |
| """Compute Electrification Readiness Index (ERI) 0-100.""" |
| route_fit = self._route_fit(asset['route_distance_km']) |
| payload_fit = self._payload_fit(asset['payload_tons'], asset['category']) |
| duty_fit = self._duty_fit(asset['duty_cycle_hrs'], asset['dwell_time_hrs']) |
| terrain_fit = self._terrain_fit(asset['terrain_score']) |
| age_penalty = max(0, 1 - asset['age_years'] / 15) |
|
|
| |
| score = ( |
| route_fit * 0.30 + |
| payload_fit * 0.20 + |
| duty_fit * 0.25 + |
| terrain_fit * 0.15 + |
| age_penalty * 0.10 |
| ) * 100 |
|
|
| confidence = min(0.95, 0.6 + (len([route_fit, payload_fit, duty_fit, terrain_fit]) * 0.08)) |
|
|
| recommended = self._match_ev(asset) |
| tco_savings = self._estimate_tco_savings(asset, recommended) |
| co2_reduction = self._estimate_co2_reduction(asset) |
|
|
| reasoning = [ |
| f"Route distance {asset['route_distance_km']} km fits EV range with {route_fit:.0%} confidence.", |
| f"Payload/category compatibility: {payload_fit:.0%}.", |
| f"Duty cycle {asset['duty_cycle_hrs']}h vs dwell {asset['dwell_time_hrs']}h gives {duty_fit:.0%} charging feasibility.", |
| f"Terrain difficulty score {asset['terrain_score']} maps to {terrain_fit:.0%} energy efficiency." |
| ] |
|
|
| return { |
| "asset_id": asset['asset_id'], |
| "readiness_score": round(score, 1), |
| "confidence": round(confidence, 2), |
| "recommended_ev": recommended['model'], |
| "oem": recommended['oem'], |
| "battery_capacity_kwh": recommended['battery_kwh'], |
| "range_km": recommended['range_km'], |
| "tco_savings_inr_lakh": round(tco_savings, 2), |
| "payback_months": round(recommended['price_inr_lakh'] / (tco_savings / 12 + 0.01), 1), |
| "co2_reduction_tons_yr": round(co2_reduction, 2), |
| "reasoning": reasoning |
| } |
|
|
| def _route_fit(self, distance_km: float) -> float: |
| |
| if distance_km <= 100: |
| return 1.0 |
| if distance_km <= 200: |
| return 0.85 |
| if distance_km <= 300: |
| return 0.60 |
| return 0.35 |
|
|
| def _payload_fit(self, payload_tons: float, category: str) -> float: |
| if category.lower() in ["last mile", "intra-plant"]: |
| return 1.0 if payload_tons <= 1 else 0.8 |
| if payload_tons <= 7: |
| return 0.9 |
| if payload_tons <= 16: |
| return 0.75 |
| if payload_tons <= 25: |
| return 0.55 |
| return 0.35 |
|
|
| def _duty_fit(self, duty_hrs: float, dwell_hrs: float) -> float: |
| if duty_hrs <= 8 and dwell_hrs >= 8: |
| return 1.0 |
| if duty_hrs <= 12 and dwell_hrs >= 4: |
| return 0.8 |
| if duty_hrs <= 16 and dwell_hrs >= 2: |
| return 0.55 |
| return 0.3 |
|
|
| def _terrain_fit(self, terrain_score: float) -> float: |
| |
| return max(0.2, 1 - (terrain_score - 1) * 0.2) |
|
|
| def _match_ev(self, asset: Dict) -> Dict: |
| candidates = [ev for ev in self.ev_catalogue if ev['payload_tons'] >= asset['payload_tons']] |
| if not candidates: |
| candidates = self.ev_catalogue |
| |
| best = min(candidates, key=lambda x: x['price_inr_lakh'] / (x['range_km'] + 1)) |
| return best |
|
|
| def _estimate_tco_savings(self, asset: Dict, ev: Dict) -> float: |
| diesel_cost_per_km = asset['diesel_l_per_100km'] * 90 / 100 |
| electricity_cost_per_km = ev['battery_kwh'] * 9 / ev['range_km'] |
| annual_km = asset['route_distance_km'] * 250 |
| annual_fuel_savings = (diesel_cost_per_km - electricity_cost_per_km) * annual_km / 100000 |
| maintenance_savings = 0.5 |
| return annual_fuel_savings + maintenance_savings |
|
|
| def _estimate_co2_reduction(self, asset: Dict) -> float: |
| annual_km = asset['route_distance_km'] * 250 |
| diesel_co2_kg_per_km = asset['diesel_l_per_100km'] * 2.68 / 100 |
| ev_co2_kg_per_km = 0.05 |
| return (diesel_co2_kg_per_km - ev_co2_kg_per_km) * annual_km / 1000 |
|
|
| def score_fleet(self, assets: List[Dict]) -> Dict: |
| results = [self.score_asset(a) for a in assets] |
| ready = [r for r in results if r['readiness_score'] >= 70] |
| return { |
| "total_assets": len(results), |
| "ready_assets": len(ready), |
| "avg_readiness": round(np.mean([r['readiness_score'] for r in results]), 1), |
| "total_potential_savings_inr_lakh": round(sum(r['tco_savings_inr_lakh'] for r in ready), 2), |
| "total_co2_reduction_tons_yr": round(sum(r['co2_reduction_tons_yr'] for r in ready), 2), |
| "asset_scores": results |
| } |
|
|