| import numpy as np |
| import pandas as pd |
| from typing import List, Dict |
| from datetime import datetime, timedelta |
| from sklearn.linear_model import LinearRegression |
| from sklearn.preprocessing import PolynomialFeatures |
|
|
| class APMAgent: |
| """AI agent for EV battery asset performance management.""" |
|
|
| def __init__(self): |
| self.soh_threshold_low = 75.0 |
| self.temp_threshold = 45.0 |
| self.max_cycles = 2500 |
|
|
| def compute_soh(self, df: pd.DataFrame) -> float: |
| """Estimate current SOH from BMS history using empirical degradation model.""" |
| if df.empty: |
| return 100.0 |
| latest = df.iloc[-1] |
| cycles = latest['cycle_count'] |
| avg_temp = df['temperature'].mean() |
| |
| temp_factor = 1 + max(0, avg_temp - 25) * 0.008 |
| soh = 100 * np.exp(-cycles / (self.max_cycles / temp_factor)) |
| |
| fast_charge_events = (df['current'] > 200).sum() |
| soh -= fast_charge_events * 0.08 |
| return max(60.0, round(soh, 2)) |
|
|
| def forecast_rul(self, df: pd.DataFrame, vehicle_id: str) -> Dict: |
| """Predict remaining useful life in cycles and days.""" |
| if len(df) < 5: |
| return self._fallback(df, vehicle_id) |
|
|
| soh_now = self.compute_soh(df) |
|
|
| |
| X = df[['cycle_count']].values |
| y = [self.compute_soh(df.iloc[:i+1]) for i in range(len(df))] |
|
|
| poly = PolynomialFeatures(degree=2) |
| X_poly = poly.fit_transform(X) |
| model = LinearRegression() |
| model.fit(X_poly, y) |
|
|
| future_cycles = np.arange(int(df['cycle_count'].max()), int(df['cycle_count'].max()) + 1000, 10) |
| future_X = poly.transform(future_cycles.reshape(-1, 1)) |
| predicted_soh = model.predict(future_X) |
|
|
| |
| below_eol = np.where(predicted_soh < 70.0)[0] |
| if len(below_eol) > 0: |
| eol_cycle = int(future_cycles[below_eol[0]]) |
| else: |
| eol_cycle = int(df['cycle_count'].max()) + 500 |
|
|
| rul_cycles = max(0, eol_cycle - int(df['cycle_count'].max())) |
| |
| rul_days = int(rul_cycles / 1.5) |
|
|
| risk_level = self._risk_level(soh_now, rul_days) |
| recommendations = self._recommendations(df, soh_now, rul_days) |
|
|
| return { |
| "vehicle_id": vehicle_id, |
| "current_soh": soh_now, |
| "predicted_rul_cycles": rul_cycles, |
| "predicted_rul_days": rul_days, |
| "risk_level": risk_level, |
| "recommendations": recommendations, |
| "soh_trajectory": predicted_soh[:50].tolist() |
| } |
|
|
| def _fallback(self, df: pd.DataFrame, vehicle_id: str) -> Dict: |
| soh = self.compute_soh(df) if not df.empty else 95.0 |
| return { |
| "vehicle_id": vehicle_id, |
| "current_soh": soh, |
| "predicted_rul_cycles": 1200, |
| "predicted_rul_days": 800, |
| "risk_level": "LOW", |
| "recommendations": ["Collect more telemetry for higher-confidence RUL prediction."], |
| "soh_trajectory": [] |
| } |
|
|
| def _risk_level(self, soh: float, rul_days: int) -> str: |
| if soh < self.soh_threshold_low or rul_days < 90: |
| return "CRITICAL" |
| if soh < 85 or rul_days < 180: |
| return "HIGH" |
| if soh < 92 or rul_days < 365: |
| return "MEDIUM" |
| return "LOW" |
|
|
| def _recommendations(self, df: pd.DataFrame, soh: float, rul_days: int) -> List[str]: |
| recs = [] |
| if soh < self.soh_threshold_low: |
| recs.append("Schedule battery pack inspection or replacement within 30 days.") |
| if rul_days < 180: |
| recs.append("High priority: plan secondary powertrain or procurement.") |
| avg_temp = df['temperature'].mean() |
| if avg_temp > self.temp_threshold: |
| recs.append("Thermal stress detected — review cooling system and charging schedule.") |
| fast_charges = (df['current'] > 200).sum() |
| if fast_charges > len(df) * 0.15: |
| recs.append("Reduce frequent fast charging to slow degradation.") |
| if not recs: |
| recs.append("Asset healthy. Continue scheduled maintenance.") |
| return recs |
|
|
| def detect_anomalies(self, df: pd.DataFrame) -> List[Dict]: |
| """Detect thermal and electrical anomalies.""" |
| alerts = [] |
| for _, row in df.iterrows(): |
| if row['temperature'] > 50: |
| alerts.append({ |
| "timestamp": row['timestamp'].isoformat(), |
| "vehicle_id": row['vehicle_id'], |
| "type": "THERMAL_CRITICAL", |
| "value": row['temperature'], |
| "message": f"Battery temperature {row['temperature']}°C exceeds critical threshold." |
| }) |
| elif row['temperature'] > self.temp_threshold: |
| alerts.append({ |
| "timestamp": row['timestamp'].isoformat(), |
| "vehicle_id": row['vehicle_id'], |
| "type": "THERMAL_WARNING", |
| "value": row['temperature'], |
| "message": f"Battery temperature {row['temperature']}°C above recommended range." |
| }) |
| if row['voltage'] < 2.8 * 96: |
| alerts.append({ |
| "timestamp": row['timestamp'].isoformat(), |
| "vehicle_id": row['vehicle_id'], |
| "type": "LOW_VOLTAGE", |
| "value": row['voltage'], |
| "message": "Pack voltage below safe operating limit." |
| }) |
| return alerts[-20:] |
|
|