DevNumb commited on
Commit
14513fd
·
verified ·
1 Parent(s): 2d45bd9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -89
app.py CHANGED
@@ -1,6 +1,6 @@
1
  # ============================================
2
  # YORK CHILLER OPTIMIZER API
3
- # Using existing model files with NumPy 2.0
4
  # ============================================
5
 
6
  import os
@@ -8,31 +8,22 @@ import sys
8
  import warnings
9
  warnings.filterwarnings('ignore')
10
 
11
- # CRITICAL: Create numpy._core module before any imports
12
- # This is needed for models saved with NumPy 2.0
13
- import importlib
14
- import types
15
-
16
- # Create the missing _core module
17
- if 'numpy._core' not in sys.modules:
18
- mock_core = types.ModuleType('numpy._core')
19
- sys.modules['numpy._core'] = mock_core
20
- print("✅ Created numpy._core module for compatibility")
21
-
22
- # Now import numpy (this will use our mock)
23
  import numpy as np
24
  print(f"NumPy version: {np.__version__}")
25
 
26
- # Now import everything else
27
  import joblib
28
  from datetime import datetime
29
  from typing import List, Optional, Dict, Any
30
  from fastapi import FastAPI, HTTPException
31
  from pydantic import BaseModel, Field
 
 
32
 
33
  app = FastAPI(
34
  title="York Chiller Energy Optimizer",
35
- description="Random Forest Model for Chiller Energy Efficiency",
36
  version="2.0.0"
37
  )
38
 
@@ -52,26 +43,22 @@ def load_existing_model():
52
  print("\n📂 Checking for model files...")
53
 
54
  # Check what files exist
55
- if os.path.exists("production_model.pkl"):
56
- print(f" ✅ Found: production_model.pkl")
57
- else:
58
- print(f" ❌ Missing: production_model.pkl")
59
- return False
60
-
61
- if os.path.exists("scaler.pkl"):
62
- print(f" Found: scaler.pkl")
63
- else:
64
- print(f" ⚠️ Missing: scaler.pkl")
65
 
66
- if os.path.exists("features.pkl"):
67
- print(f" Found: features.pkl")
68
- else:
69
- print(f" ⚠️ Missing: features.pkl")
70
 
71
- # Load the model with special handling
72
  try:
73
- # Try normal load first
74
- import joblib
75
  model = joblib.load("production_model.pkl")
76
  print(f"\n✅ Model loaded successfully")
77
  print(f" Type: {type(model).__name__}")
@@ -92,18 +79,9 @@ def load_existing_model():
92
 
93
  except Exception as e:
94
  print(f"❌ Error loading model: {e}")
95
-
96
- # Try alternative loading method
97
- try:
98
- print(" Trying alternative loading method...")
99
- import pickle
100
- with open("production_model.pkl", 'rb') as f:
101
- model = pickle.load(f)
102
- print(f"✅ Model loaded with pickle")
103
- model_feature_count = 12
104
- except Exception as e2:
105
- print(f"❌ Alternative loading failed: {e2}")
106
- return False
107
 
108
  # Load scaler
109
  try:
@@ -119,10 +97,12 @@ def load_existing_model():
119
  if os.path.exists("features.pkl"):
120
  feature_names = joblib.load("features.pkl")
121
  print(f"✅ Features loaded from features.pkl")
122
- print(f" Features: {feature_names}")
123
  if isinstance(feature_names, list):
124
- model_feature_count = len(feature_names)
125
- print(f" Updated feature count to: {model_feature_count}")
 
 
 
126
  except Exception as e:
127
  print(f"⚠️ Could not load feature names: {e}")
128
  # Create default 12 feature names
@@ -149,13 +129,14 @@ load_success = load_existing_model()
149
 
150
  if model:
151
  print(f"\n📊 Model Configuration:")
152
- print(f" Status: ONLINE")
153
  print(f" Features expected: {model_feature_count}")
154
- print(f" Scaler: {'Loaded' if scaler else 'Not loaded'}")
 
155
  else:
156
  print(f"\n📊 Model Configuration:")
157
- print(f" Status: OFFLINE - Model failed to load")
158
- print(f" Check NumPy version compatibility")
159
 
160
  # ============================================
161
  # REQUEST MODELS
@@ -177,8 +158,8 @@ class ChillerInput(BaseModel):
177
  day_of_year: int = Field(..., ge=1, le=366, description="Day of the year (1-365)")
178
 
179
  # Optional optimization parameters
180
- current_chw_setpoint_c: Optional[float] = Field(8.0, ge=5, le=10)
181
- current_limit_pct: Optional[float] = Field(100, ge=50, le=100)
182
 
183
  class PredictionResponse(BaseModel):
184
  status: str
@@ -204,42 +185,58 @@ class OptimizeResponse(BaseModel):
204
  summary: Dict[str, str]
205
 
206
  # ============================================
207
- # PREDICTION FUNCTION
208
  # ============================================
209
 
210
- def prepare_features(input_data: ChillerInput) -> np.ndarray:
211
- """Prepare features in the correct order for the model"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
- # Create feature array based on feature_names from features.pkl
214
  if feature_names and isinstance(feature_names, list):
215
- # Build features in the order specified by features.pkl
216
- feature_dict = {
217
- 'total_building_load_rt': input_data.total_building_load_rt,
218
- 'avg_chilled_water_rate_lps': input_data.avg_chilled_water_rate_lps,
219
- 'avg_cooling_water_temp_c': input_data.avg_cooling_water_temp_c,
220
- 'avg_outside_temp_f': input_data.avg_outside_temp_f,
221
- 'avg_dew_point_f': input_data.avg_dew_point_f,
222
- 'avg_humidity_pct': input_data.avg_humidity_pct,
223
- 'avg_wind_speed_mph': input_data.avg_wind_speed_mph,
224
- 'avg_pressure_in': input_data.avg_pressure_in,
225
- 'hour': input_data.hour,
226
- 'day_of_week': input_data.day_of_week,
227
- 'month': input_data.month,
228
- 'day_of_year': input_data.day_of_year
229
- }
230
-
231
- # Build array in the order of feature_names
232
  features_list = []
233
  for f_name in feature_names:
234
  if f_name in feature_dict:
235
  features_list.append(feature_dict[f_name])
236
  else:
237
- # If feature name not found, use 0
238
- print(f" ⚠️ Feature '{f_name}' not found, using 0")
239
- features_list.append(0.0)
 
 
 
 
 
 
 
 
 
240
 
241
  features = np.array([features_list])
242
- print(f" Using {len(features_list)} features from features.pkl")
243
 
244
  else:
245
  # Default: use all 12 features in standard order
@@ -257,7 +254,7 @@ def prepare_features(input_data: ChillerInput) -> np.ndarray:
257
  input_data.month,
258
  input_data.day_of_year
259
  ]])
260
- print(f" Using default 12 features")
261
 
262
  # Apply scaler if available
263
  if scaler is not None:
@@ -270,12 +267,14 @@ def prepare_features(input_data: ChillerInput) -> np.ndarray:
270
  return features
271
 
272
  def predict_kw_per_tr(input_data: ChillerInput) -> float:
273
- """Predict Combined_Kw_per_TR"""
274
  if model is None:
275
  raise ValueError("Model not loaded")
276
 
277
- features = prepare_features(input_data)
278
  prediction = model.predict(features)[0]
 
 
279
  prediction = np.clip(prediction, 0.4, 1.2)
280
 
281
  return float(prediction)
@@ -296,7 +295,8 @@ async def root():
296
  "loaded": model is not None,
297
  "features_expected": model_feature_count,
298
  "features_from_pkl": len(feature_names) if feature_names else 0,
299
- "scaler_loaded": scaler is not None
 
300
  },
301
  "endpoints": {
302
  "/": "This information",
@@ -305,7 +305,8 @@ async def root():
305
  "/optimize": "POST - Get optimization recommendations"
306
  },
307
  "interpretation": {
308
- "kw_per_tr": "Combined energy efficiency - LOWER is better",
 
309
  "excellent": "< 0.55 kW/TR",
310
  "good": "0.55-0.65 kW/TR",
311
  "fair": "0.65-0.75 kW/TR",
@@ -327,10 +328,10 @@ async def health():
327
 
328
  @app.post("/predict", response_model=PredictionResponse)
329
  async def predict_endpoint(input_data: ChillerInput):
330
- """Predict Combined_Kw_per_TR"""
331
  try:
332
  if model is None:
333
- raise HTTPException(status_code=503, detail="Model not loaded")
334
 
335
  kw_per_tr = predict_kw_per_tr(input_data)
336
 
@@ -350,12 +351,12 @@ async def optimize_endpoint(input_data: ChillerInput):
350
  """Get optimization recommendations"""
351
  try:
352
  if model is None:
353
- raise HTTPException(status_code=503, detail="Model not loaded")
354
 
355
  # Current efficiency
356
  current_kw = predict_kw_per_tr(input_data)
357
 
358
- # Test different CHW setpoints
359
  current_sp = input_data.current_chw_setpoint_c or 8.0
360
  test_setpoints = [6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0]
361
 
@@ -387,7 +388,27 @@ async def optimize_endpoint(input_data: ChillerInput):
387
  recommended_value=f"{best_sp:.1f}°C",
388
  expected_savings=f"{savings_pct:.1f}%",
389
  priority="HIGH" if savings_pct > 5 else "MEDIUM",
390
- operator_action=f"Adjust CHW setpoint to {best_sp:.1f}°C"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  ))
392
 
393
  # Efficiency rating
@@ -409,7 +430,8 @@ async def optimize_endpoint(input_data: ChillerInput):
409
  "optimal_efficiency": f"{best_kw:.3f} kW/TR",
410
  "potential_savings": f"{savings_pct:.1f}%",
411
  "efficiency_rating": rating,
412
- "recommendation": message
 
413
  }
414
 
415
  return OptimizeResponse(
 
1
  # ============================================
2
  # YORK CHILLER OPTIMIZER API
3
+ # Using existing model files with NumPy 1.23.5
4
  # ============================================
5
 
6
  import os
 
8
  import warnings
9
  warnings.filterwarnings('ignore')
10
 
11
+ # Import normally with NumPy 1.23.5
 
 
 
 
 
 
 
 
 
 
 
12
  import numpy as np
13
  print(f"NumPy version: {np.__version__}")
14
 
15
+ # Import rest of the libraries
16
  import joblib
17
  from datetime import datetime
18
  from typing import List, Optional, Dict, Any
19
  from fastapi import FastAPI, HTTPException
20
  from pydantic import BaseModel, Field
21
+ from sklearn.ensemble import RandomForestRegressor
22
+ from sklearn.preprocessing import StandardScaler
23
 
24
  app = FastAPI(
25
  title="York Chiller Energy Optimizer",
26
+ description="Random Forest Model for Chiller Energy Efficiency - Based on 12 Operational Features",
27
  version="2.0.0"
28
  )
29
 
 
43
  print("\n📂 Checking for model files...")
44
 
45
  # Check what files exist
46
+ files_found = []
47
+ for file in ['production_model.pkl', 'scaler.pkl', 'features.pkl']:
48
+ if os.path.exists(file):
49
+ size = os.path.getsize(file) / 1024
50
+ files_found.append(f"{file} ({size:.1f} KB)")
51
+ print(f" ✅ Found: {file}")
52
+ else:
53
+ print(f" Missing: {file}")
 
 
54
 
55
+ if not os.path.exists("production_model.pkl"):
56
+ print(" production_model.pkl not found! Cannot continue.")
57
+ return False
 
58
 
59
+ # Load the model
60
  try:
61
+ # Try loading with joblib
 
62
  model = joblib.load("production_model.pkl")
63
  print(f"\n✅ Model loaded successfully")
64
  print(f" Type: {type(model).__name__}")
 
79
 
80
  except Exception as e:
81
  print(f"❌ Error loading model: {e}")
82
+ print(f" This might be a NumPy version mismatch.")
83
+ print(f" Current NumPy: {np.__version__}")
84
+ return False
 
 
 
 
 
 
 
 
 
85
 
86
  # Load scaler
87
  try:
 
97
  if os.path.exists("features.pkl"):
98
  feature_names = joblib.load("features.pkl")
99
  print(f"✅ Features loaded from features.pkl")
 
100
  if isinstance(feature_names, list):
101
+ print(f" Feature count: {len(feature_names)}")
102
+ print(f" First 5 features: {feature_names[:5]}")
103
+ # Update feature count if needed
104
+ if model_feature_count == 0:
105
+ model_feature_count = len(feature_names)
106
  except Exception as e:
107
  print(f"⚠️ Could not load feature names: {e}")
108
  # Create default 12 feature names
 
129
 
130
  if model:
131
  print(f"\n📊 Model Configuration:")
132
+ print(f" Status: ONLINE")
133
  print(f" Features expected: {model_feature_count}")
134
+ print(f" Scaler: {'Loaded' if scaler else 'Not loaded'}")
135
+ print(f" NumPy version: {np.__version__}")
136
  else:
137
  print(f"\n📊 Model Configuration:")
138
+ print(f" Status: OFFLINE")
139
+ print(f" NumPy version: {np.__version__}")
140
 
141
  # ============================================
142
  # REQUEST MODELS
 
158
  day_of_year: int = Field(..., ge=1, le=366, description="Day of the year (1-365)")
159
 
160
  # Optional optimization parameters
161
+ current_chw_setpoint_c: Optional[float] = Field(8.0, ge=5, le=10, description="Current CHW setpoint (°C)")
162
+ current_limit_pct: Optional[float] = Field(100, ge=50, le=100, description="Current limit percentage")
163
 
164
  class PredictionResponse(BaseModel):
165
  status: str
 
185
  summary: Dict[str, str]
186
 
187
  # ============================================
188
+ # FEATURE PREPARATION FUNCTION
189
  # ============================================
190
 
191
+ def prepare_features_for_model(input_data: ChillerInput) -> np.ndarray:
192
+ """
193
+ Prepare features in the order expected by the model
194
+ Uses feature_names from features.pkl if available
195
+ """
196
+
197
+ # Create dictionary of all possible features
198
+ feature_dict = {
199
+ 'total_building_load_rt': input_data.total_building_load_rt,
200
+ 'avg_chilled_water_rate_lps': input_data.avg_chilled_water_rate_lps,
201
+ 'avg_cooling_water_temp_c': input_data.avg_cooling_water_temp_c,
202
+ 'avg_outside_temp_f': input_data.avg_outside_temp_f,
203
+ 'avg_dew_point_f': input_data.avg_dew_point_f,
204
+ 'avg_humidity_pct': input_data.avg_humidity_pct,
205
+ 'avg_wind_speed_mph': input_data.avg_wind_speed_mph,
206
+ 'avg_pressure_in': input_data.avg_pressure_in,
207
+ 'hour': input_data.hour,
208
+ 'day_of_week': input_data.day_of_week,
209
+ 'month': input_data.month,
210
+ 'day_of_year': input_data.day_of_year,
211
+ # Alternative names that might be in the model
212
+ 'load': input_data.total_building_load_rt,
213
+ 'chilled_water_flow': input_data.avg_chilled_water_rate_lps,
214
+ 'outside_temp': input_data.avg_outside_temp_f,
215
+ 'humidity': input_data.avg_humidity_pct,
216
+ }
217
 
218
+ # If we have feature names from features.pkl, use them
219
  if feature_names and isinstance(feature_names, list):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  features_list = []
221
  for f_name in feature_names:
222
  if f_name in feature_dict:
223
  features_list.append(feature_dict[f_name])
224
  else:
225
+ # Try to find alternative
226
+ found = False
227
+ for key in feature_dict:
228
+ if f_name.lower() in key.lower() or key.lower() in f_name.lower():
229
+ features_list.append(feature_dict[key])
230
+ print(f" Mapped '{f_name}' → '{key}'")
231
+ found = True
232
+ break
233
+ if not found:
234
+ # Default value if feature not found
235
+ print(f" ⚠️ Feature '{f_name}' not found, using 0")
236
+ features_list.append(0.0)
237
 
238
  features = np.array([features_list])
239
+ print(f" Prepared {len(features_list)} features from features.pkl")
240
 
241
  else:
242
  # Default: use all 12 features in standard order
 
254
  input_data.month,
255
  input_data.day_of_year
256
  ]])
257
+ print(f" Prepared default 12 features")
258
 
259
  # Apply scaler if available
260
  if scaler is not None:
 
267
  return features
268
 
269
  def predict_kw_per_tr(input_data: ChillerInput) -> float:
270
+ """Predict Combined_Kw_per_TR using the loaded model"""
271
  if model is None:
272
  raise ValueError("Model not loaded")
273
 
274
+ features = prepare_features_for_model(input_data)
275
  prediction = model.predict(features)[0]
276
+
277
+ # Clip to realistic range
278
  prediction = np.clip(prediction, 0.4, 1.2)
279
 
280
  return float(prediction)
 
295
  "loaded": model is not None,
296
  "features_expected": model_feature_count,
297
  "features_from_pkl": len(feature_names) if feature_names else 0,
298
+ "scaler_loaded": scaler is not None,
299
+ "numpy_version": np.__version__
300
  },
301
  "endpoints": {
302
  "/": "This information",
 
305
  "/optimize": "POST - Get optimization recommendations"
306
  },
307
  "interpretation": {
308
+ "kw_per_tr": "Combined energy efficiency indicator - LOWER is better",
309
+ "typical_range": "0.45 - 1.0 kW/TR",
310
  "excellent": "< 0.55 kW/TR",
311
  "good": "0.55-0.65 kW/TR",
312
  "fair": "0.65-0.75 kW/TR",
 
328
 
329
  @app.post("/predict", response_model=PredictionResponse)
330
  async def predict_endpoint(input_data: ChillerInput):
331
+ """Predict Combined_Kw_per_TR for given conditions"""
332
  try:
333
  if model is None:
334
+ raise HTTPException(status_code=503, detail="Model not loaded - check logs")
335
 
336
  kw_per_tr = predict_kw_per_tr(input_data)
337
 
 
351
  """Get optimization recommendations"""
352
  try:
353
  if model is None:
354
+ raise HTTPException(status_code=503, detail="Model not loaded - check logs")
355
 
356
  # Current efficiency
357
  current_kw = predict_kw_per_tr(input_data)
358
 
359
+ # Test different CHW setpoints to find optimum
360
  current_sp = input_data.current_chw_setpoint_c or 8.0
361
  test_setpoints = [6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0]
362
 
 
388
  recommended_value=f"{best_sp:.1f}°C",
389
  expected_savings=f"{savings_pct:.1f}%",
390
  priority="HIGH" if savings_pct > 5 else "MEDIUM",
391
+ operator_action=f"Adjust CHW setpoint from {current_sp:.1f}°C to {best_sp:.1f}°C on chiller controller"
392
+ ))
393
+
394
+ # Load-based recommendations
395
+ if input_data.total_building_load_rt < 600:
396
+ recommendations.append(OptimizationRecommendation(
397
+ action="Chiller Sequencing",
398
+ current_value=f"{input_data.total_building_load_rt:.0f} RT",
399
+ recommended_value="Single chiller operation",
400
+ expected_savings="15-25% reduction in parasitic losses",
401
+ priority="HIGH",
402
+ operator_action="Consider operating only one chiller"
403
+ ))
404
+ elif input_data.total_building_load_rt > 1800:
405
+ recommendations.append(OptimizationRecommendation(
406
+ action="Chiller Staging",
407
+ current_value=f"{input_data.total_building_load_rt:.0f} RT",
408
+ recommended_value="Verify all chillers online",
409
+ expected_savings="Prevents overload conditions",
410
+ priority="HIGH",
411
+ operator_action="Check if all chillers are operating properly"
412
  ))
413
 
414
  # Efficiency rating
 
430
  "optimal_efficiency": f"{best_kw:.3f} kW/TR",
431
  "potential_savings": f"{savings_pct:.1f}%",
432
  "efficiency_rating": rating,
433
+ "recommendation": message,
434
+ "load": f"{input_data.total_building_load_rt:.0f} RT"
435
  }
436
 
437
  return OptimizeResponse(