-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpredict.py
More file actions
235 lines (189 loc) · 9.1 KB
/
predict.py
File metadata and controls
235 lines (189 loc) · 9.1 KB
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
"""
BMS Battery Condition Predictor
Loads trained model artifacts and provides prediction API for dashboard and hardware integration.
Usage:
from predict import BatteryPredictor
predictor = BatteryPredictor()
condition, confidence = predictor.predict(
voltage=3.7, current=1.2, temperature=28, soc=75, cycle_count=150
)
print(f"Condition: {condition}, Confidence: {confidence}")
"""
import os
import numpy as np
import pandas as pd
import joblib
class BatteryPredictor:
"""
Battery condition classifier using trained ML model.
Loads model artifacts (model, scaler, label encoder) and provides
methods for single-sample and batch predictions.
"""
def __init__(self, model_dir="."):
"""
Initialize the predictor by loading saved model artifacts.
Args:
model_dir: Directory containing model .pkl files
"""
self.model_dir = model_dir
self.model = None
self.scaler = None
self.label_encoder = None
self.app_model = None
self.app_scaler = None
self.app_label_encoder = None
self._load_artifacts()
def _load_artifacts(self):
"""Load model, scaler, and label encoder from disk."""
# --- Condition model (required) ---
model_path = os.path.join(self.model_dir, "bms_model.pkl")
scaler_path = os.path.join(self.model_dir, "scaler.pkl")
encoder_path = os.path.join(self.model_dir, "label_encoder.pkl")
for path, name in [
(model_path, "Model"),
(scaler_path, "Scaler"),
(encoder_path, "Label Encoder"),
]:
if not os.path.exists(path):
raise FileNotFoundError(
f"{name} not found at {path}. "
f"Run 'python train_model.py' first to train the model."
)
self.model = joblib.load(model_path)
self.scaler = joblib.load(scaler_path)
self.label_encoder = joblib.load(encoder_path)
# --- Application model (optional) ---
app_model_path = os.path.join(self.model_dir, "app_model.pkl")
app_scaler_path = os.path.join(self.model_dir, "app_scaler.pkl")
app_encoder_path = os.path.join(self.model_dir, "app_label_encoder.pkl")
if all(os.path.exists(p) for p in [app_model_path, app_scaler_path, app_encoder_path]):
self.app_model = joblib.load(app_model_path)
self.app_scaler = joblib.load(app_scaler_path)
self.app_label_encoder = joblib.load(app_encoder_path)
def predict(self, voltage, current, temperature, soc=50.0, cycle_count=100, voltage_delta=0.0):
"""
Predict battery condition from sensor values.
Args:
voltage: Battery voltage (V)
current: Battery current (A)
temperature: Battery temperature (°C)
soc: State of charge (%)
cycle_count: Number of charge cycles
voltage_delta: Rate of voltage change
Returns:
tuple: (condition_label: str, confidence_dict: dict)
- condition_label: "Normal", "Warning", or "Unsafe"
- confidence_dict: Probability for each class
"""
features = np.array([[voltage, current, temperature, soc, cycle_count, voltage_delta]])
features_scaled = self.scaler.transform(features)
prediction = self.model.predict(features_scaled)[0]
probabilities = self.model.predict_proba(features_scaled)[0]
condition_label = self.label_encoder.inverse_transform([prediction])[0]
confidence_dict = {
label: round(float(prob) * 100, 1)
for label, prob in zip(self.label_encoder.classes_, probabilities)
}
return condition_label, confidence_dict
def predict_application(self, voltage, current, temperature, soc=50.0, cycle_count=100, voltage_delta=0.0):
"""
Predict suitable application category for a battery.
Returns:
tuple: (application_label: str, confidence_dict: dict)
- application_label: "High-Performance", "Moderate-Load", "Low-Power", or "Unsuitable"
- confidence_dict: Probability for each class
"""
if self.app_model is None:
return None, {}
features = np.array([[voltage, current, temperature, soc, cycle_count, voltage_delta]])
features_scaled = self.app_scaler.transform(features)
prediction = self.app_model.predict(features_scaled)[0]
probabilities = self.app_model.predict_proba(features_scaled)[0]
app_label = self.app_label_encoder.inverse_transform([prediction])[0]
confidence_dict = {
label: round(float(prob) * 100, 1)
for label, prob in zip(self.app_label_encoder.classes_, probabilities)
}
return app_label, confidence_dict
def predict_batch(self, df):
"""
Predict conditions for a batch of readings.
Args:
df: DataFrame with columns matching FEATURE_COLUMNS
Returns:
DataFrame with added 'predicted_condition' and confidence columns
"""
from data_pipeline import FEATURE_COLUMNS
available_features = [col for col in FEATURE_COLUMNS if col in df.columns]
X = df[available_features].values.astype(np.float64)
# Pad with zeros if some features are missing
if X.shape[1] < 6:
padding = np.zeros((X.shape[0], 6 - X.shape[1]))
X = np.hstack([X, padding])
X_scaled = self.scaler.transform(X)
predictions = self.model.predict(X_scaled)
probabilities = self.model.predict_proba(X_scaled)
result = df.copy()
result["predicted_condition"] = self.label_encoder.inverse_transform(predictions)
for i, label in enumerate(self.label_encoder.classes_):
result[f"confidence_{label}"] = (probabilities[:, i] * 100).round(1)
return result
if __name__ == "__main__":
print("🔋 BMS Battery Predictor — Self Test\n")
try:
predictor = BatteryPredictor()
print("✅ Model artifacts loaded successfully\n")
except FileNotFoundError as e:
print(f"❌ {e}")
print(" Run: python train_model.py")
exit(1)
# Test cases
test_cases = [
{"voltage": 3.7, "current": 1.0, "temperature": 25, "soc": 75, "cycle_count": 100, "expected": "Normal"},
{"voltage": 3.1, "current": 3.0, "temperature": 45, "soc": 20, "cycle_count": 300, "expected": "Warning"},
{"voltage": 2.6, "current": 4.5, "temperature": 65, "soc": 5, "cycle_count": 500, "expected": "Unsafe"},
{"voltage": 4.35, "current": 0.5, "temperature": 30, "soc": 95, "cycle_count": 50, "expected": "Warning"},
{"voltage": 4.6, "current": 4.2, "temperature": 60, "soc": 3, "cycle_count": 800, "expected": "Unsafe"},
]
print(f"{'Test':>5s} | {'Voltage':>8s} {'Current':>8s} {'Temp':>6s} {'SoC':>5s} | {'Expected':>10s} → {'Predicted':>10s} | {'Confidence':>12s} | {'Pass':>4s}")
print("-" * 95)
all_passed = True
for i, tc in enumerate(test_cases, 1):
condition, conf = predictor.predict(
voltage=tc["voltage"],
current=tc["current"],
temperature=tc["temperature"],
soc=tc["soc"],
cycle_count=tc["cycle_count"],
)
passed = condition == tc["expected"]
if not passed:
all_passed = False
top_conf = max(conf.values())
status = "✅" if passed else "⚠️"
print(
f" {i:>3d} | {tc['voltage']:>7.2f}V {tc['current']:>7.2f}A {tc['temperature']:>5.1f}°C {tc['soc']:>4.1f}% "
f"| {tc['expected']:>10s} → {condition:>10s} | {top_conf:>10.1f}% | {status}"
)
print(f"\n{'✅ All condition tests passed!' if all_passed else '⚠️ Some predictions did not match expected labels (may be acceptable depending on model confidence)'}")
# --- Application suitability tests ---
if predictor.app_model is not None:
print("\n" + "=" * 95)
print("🎯 APPLICATION SUITABILITY PREDICTIONS")
print("=" * 95)
app_tests = [
{"voltage": 3.8, "current": 0.8, "temperature": 25, "soc": 90, "cycle_count": 80},
{"voltage": 3.5, "current": 2.0, "temperature": 35, "soc": 55, "cycle_count": 350},
{"voltage": 3.2, "current": 3.0, "temperature": 45, "soc": 20, "cycle_count": 600},
{"voltage": 2.7, "current": 4.5, "temperature": 60, "soc": 5, "cycle_count": 900},
]
for i, tc in enumerate(app_tests, 1):
app_label, app_conf = predictor.predict_application(
voltage=tc["voltage"], current=tc["current"],
temperature=tc["temperature"], soc=tc["soc"],
cycle_count=tc["cycle_count"],
)
top_conf = max(app_conf.values())
print(f" {i:>3d} | {tc['voltage']:.2f}V {tc['current']:.1f}A {tc['temperature']}°C SoC:{tc['soc']}% Cycles:{tc['cycle_count']} → {app_label} ({top_conf:.1f}%)")
else:
print("\n⚠️ Application model not found. Run: python train_model.py --target application")