DevNumb commited on
Commit
5b33abd
Β·
verified Β·
1 Parent(s): be6de94

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -49
app.py CHANGED
@@ -8,6 +8,7 @@ import numpy as np
8
  import joblib
9
  import pandas as pd
10
  import os
 
11
  from fastapi import FastAPI, HTTPException
12
  from pydantic import BaseModel, Field
13
  from typing import List, Optional, Dict, Any
@@ -26,9 +27,10 @@ app = FastAPI(
26
  # LOAD MODEL AND PREPROCESSORS
27
  # ============================================
28
 
29
- MODEL_PATH = "production_model.pkl"
30
- SCALER_PATH = "scaler.pkl"
31
- FEATURES_PATH = "features.pkl"
 
32
 
33
  model = None
34
  scaler = None
@@ -38,20 +40,89 @@ def load_model():
38
  """Load the trained Random Forest model and preprocessors"""
39
  global model, scaler, feature_names
40
 
41
- try:
42
- if os.path.exists(MODEL_PATH) and os.path.exists(SCALER_PATH):
43
- model = joblib.load(MODEL_PATH)
44
- scaler = joblib.load(SCALER_PATH)
45
- feature_names = joblib.load(FEATURES_PATH)
46
- print(f"βœ… Loaded Random Forest model with {model.n_estimators} trees")
47
- print(f"βœ… Features: {feature_names}")
48
- return True
49
- else:
50
- print("⚠️ Model files not found. Please train the model first.")
51
- return False
52
- except Exception as e:
53
- print(f"❌ Error loading model: {e}")
 
 
 
 
54
  return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  # ============================================
57
  # REQUEST/RESPONSE MODELS
@@ -192,22 +263,28 @@ class OptimizeResponse(BaseModel):
192
  def get_mcp_data() -> MCPResponse:
193
  """Generate MCP (Model Card + Performance + Capabilities) JSON output"""
194
 
195
- # Feature importance (typically loaded from trained model)
196
- # These are example values - replace with actual from your trained model
197
- feature_importance = {
198
- "total_building_load_rt": 0.324,
199
- "avg_outside_temp_f": 0.156,
200
- "avg_cooling_water_temp_c": 0.112,
201
- "avg_humidity_pct": 0.089,
202
- "hour": 0.078,
203
- "avg_chilled_water_rate_lps": 0.067,
204
- "month": 0.054,
205
- "avg_dew_point_f": 0.043,
206
- "day_of_year": 0.032,
207
- "avg_wind_speed_mph": 0.021,
208
- "avg_pressure_in": 0.015,
209
- "day_of_week": 0.009
210
- }
 
 
 
 
 
 
211
 
212
  # Input features description
213
  input_features = [
@@ -304,10 +381,10 @@ def get_mcp_data() -> MCPResponse:
304
  version="2.0.0",
305
  description="Ensemble model that builds multiple decision trees to predict chiller plant energy efficiency (kW/TR) based on operational and environmental conditions. The model outputs the mean prediction of all trees for robust, non-linear regression.",
306
  architecture={
307
- "n_estimators": model.n_estimators if model else 100,
308
- "max_depth": model.max_depth if model else 12,
309
- "min_samples_split": model.min_samples_split if model else 2,
310
- "min_samples_leaf": model.min_samples_leaf if model else 1,
311
  "bootstrap": True,
312
  "oob_score": False,
313
  "random_state": 42
@@ -342,7 +419,7 @@ def get_mcp_data() -> MCPResponse:
342
  "mape": 4.2,
343
  "cv_rmse": 0.045
344
  },
345
- feature_importance=feature_importance,
346
  validation_method="Time-series cross validation",
347
  test_size=0.20,
348
  training_date=datetime.now().strftime("%Y-%m-%d")
@@ -406,14 +483,17 @@ def prepare_features(input_data: ChillerInput) -> np.ndarray:
406
 
407
  def predict_kw_per_tr(input_data: ChillerInput) -> float:
408
  """Predict Combined_Kw_per_TR using the Random Forest model"""
409
- if model is None or scaler is None:
410
  raise ValueError("Model not loaded properly")
411
 
412
  # Prepare features
413
  features = prepare_features(input_data)
414
 
415
- # Scale features (if scaler exists)
416
- features_scaled = scaler.transform(features)
 
 
 
417
 
418
  # Predict
419
  prediction = model.predict(features_scaled)[0]
@@ -431,7 +511,7 @@ def optimize_chw_setpoint(input_data: ChillerInput) -> float:
431
  best_sp = current_sp
432
 
433
  for sp in test_setpoints:
434
- # Create test input with modified setpoint (note: setpoint affects chilled water rate)
435
  test_input = ChillerInput(
436
  total_building_load_rt=input_data.total_building_load_rt,
437
  avg_chilled_water_rate_lps=input_data.avg_chilled_water_rate_lps,
@@ -470,13 +550,16 @@ def calculate_savings(current_kw: float, optimal_kw: float, load_rt: float) -> t
470
 
471
  def estimate_confidence_interval(input_data: ChillerInput) -> Dict[str, float]:
472
  """Estimate prediction confidence interval using ensemble variance"""
473
- if model is None:
474
  return {"lower": None, "upper": None, "std": None}
475
 
476
  try:
477
  # Get predictions from all trees
478
  features = prepare_features(input_data)
479
- features_scaled = scaler.transform(features)
 
 
 
480
 
481
  # Get individual tree predictions
482
  tree_predictions = np.array([tree.predict(features_scaled)[0]
@@ -528,10 +611,10 @@ async def health():
528
  return {
529
  "status": "healthy" if model is not None else "degraded",
530
  "model_loaded": model is not None,
531
- "model_type": "RandomForestRegressor" if model is not None else None,
532
- "n_estimators": model.n_estimators if model is not None else None,
533
  "scaler_loaded": scaler is not None,
534
- "feature_count": 12
535
  }
536
 
537
  @app.get("/mcp", response_model=MCPResponse)
@@ -553,7 +636,7 @@ async def predict_endpoint(input_data: ChillerInput):
553
  """Predict Combined_Kw_per_TR for given conditions"""
554
  try:
555
  if model is None:
556
- raise HTTPException(status_code=503, detail="Model not loaded")
557
 
558
  # Make prediction
559
  kw_per_tr = predict_kw_per_tr(input_data)
@@ -578,7 +661,7 @@ async def optimize_endpoint(input_data: ChillerInput):
578
  """Get optimization recommendations"""
579
  try:
580
  if model is None:
581
- raise HTTPException(status_code=503, detail="Model not loaded")
582
 
583
  # Predict current efficiency
584
  current_kw = predict_kw_per_tr(input_data)
@@ -641,7 +724,7 @@ async def optimize_endpoint(input_data: ChillerInput):
641
  operator_action="Check if all chillers are running optimally"
642
  ))
643
 
644
- # Free cooling recommendation (based on wet bulb approximation)
645
  if input_data.avg_outside_temp_f < 50 and input_data.avg_humidity_pct < 60:
646
  recommendations.append(OptimizationRecommendation(
647
  action="Free Cooling",
 
8
  import joblib
9
  import pandas as pd
10
  import os
11
+ import sys
12
  from fastapi import FastAPI, HTTPException
13
  from pydantic import BaseModel, Field
14
  from typing import List, Optional, Dict, Any
 
27
  # LOAD MODEL AND PREPROCESSORS
28
  # ============================================
29
 
30
+ # Try different possible filenames
31
+ MODEL_PATHS = ["production_model.pkl", "model.pkl", "random_forest_model.pkl"]
32
+ SCALER_PATHS = ["scaler.pkl", "standard_scaler.pkl"]
33
+ FEATURES_PATHS = ["features.pkl", "feature.pkl", "feature_names.pkl"] # Fixed: includes 'feature.pkl'
34
 
35
  model = None
36
  scaler = None
 
40
  """Load the trained Random Forest model and preprocessors"""
41
  global model, scaler, feature_names
42
 
43
+ # Try to load model
44
+ model_loaded = False
45
+ for model_path in MODEL_PATHS:
46
+ try:
47
+ if os.path.exists(model_path):
48
+ model = joblib.load(model_path)
49
+ print(f"βœ… Loaded model from {model_path}")
50
+ print(f" Type: {type(model).__name__}")
51
+ if hasattr(model, 'n_estimators'):
52
+ print(f" Trees: {model.n_estimators}")
53
+ model_loaded = True
54
+ break
55
+ except Exception as e:
56
+ print(f"⚠️ Failed to load {model_path}: {e}")
57
+
58
+ if not model_loaded:
59
+ print("❌ No model file found. Please check model files.")
60
  return False
61
+
62
+ # Try to load scaler
63
+ scaler_loaded = False
64
+ for scaler_path in SCALER_PATHS:
65
+ try:
66
+ if os.path.exists(scaler_path):
67
+ scaler = joblib.load(scaler_path)
68
+ print(f"βœ… Loaded scaler from {scaler_path}")
69
+ scaler_loaded = True
70
+ break
71
+ except Exception as e:
72
+ print(f"⚠️ Failed to load {scaler_path}: {e}")
73
+
74
+ # Try to load feature names
75
+ features_loaded = False
76
+ for features_path in FEATURES_PATHS:
77
+ try:
78
+ if os.path.exists(features_path):
79
+ feature_names = joblib.load(features_path)
80
+ print(f"βœ… Loaded feature names from {features_path}")
81
+ print(f" Features: {feature_names}")
82
+ features_loaded = True
83
+ break
84
+ except Exception as e:
85
+ print(f"⚠️ Failed to load {features_path}: {e}")
86
+
87
+ # If no feature names file, check if model has feature_names attribute
88
+ if not features_loaded and hasattr(model, 'feature_names_in_'):
89
+ feature_names = list(model.feature_names_in_)
90
+ print(f"βœ… Using feature names from model: {feature_names}")
91
+ features_loaded = True
92
+
93
+ # If still no features, use default 12-feature list
94
+ if not features_loaded:
95
+ feature_names = [
96
+ 'total_building_load_rt',
97
+ 'avg_chilled_water_rate_lps',
98
+ 'avg_cooling_water_temp_c',
99
+ 'avg_outside_temp_f',
100
+ 'avg_dew_point_f',
101
+ 'avg_humidity_pct',
102
+ 'avg_wind_speed_mph',
103
+ 'avg_pressure_in',
104
+ 'hour',
105
+ 'day_of_week',
106
+ 'month',
107
+ 'day_of_year'
108
+ ]
109
+ print(f"βœ… Using default feature names")
110
+
111
+ return model_loaded
112
+
113
+ # Load model on startup
114
+ load_success = load_model()
115
+
116
+ # Print debug info about loaded files
117
+ print("\nπŸ“ Files in directory:")
118
+ for file in os.listdir('.'):
119
+ if file.endswith('.pkl') or file.endswith('.joblib'):
120
+ size = os.path.getsize(file) / 1024 # KB
121
+ print(f" - {file} ({size:.1f} KB)")
122
+
123
+ print(f"\nπŸ“Š Model Load Status: {'SUCCESS' if model else 'FAILED'}")
124
+ print(f"πŸ“Š Scaler Load Status: {'SUCCESS' if scaler else 'FAILED'}")
125
+ print(f"πŸ“Š Features Load Status: {'SUCCESS' if feature_names else 'FAILED'}")
126
 
127
  # ============================================
128
  # REQUEST/RESPONSE MODELS
 
263
  def get_mcp_data() -> MCPResponse:
264
  """Generate MCP (Model Card + Performance + Capabilities) JSON output"""
265
 
266
+ # Try to extract actual feature importance from model if available
267
+ feature_importance_dict = {}
268
+ if model and hasattr(model, 'feature_importances_') and feature_names:
269
+ importances = model.feature_importances_
270
+ for name, imp in zip(feature_names, importances):
271
+ feature_importance_dict[name] = float(imp)
272
+ else:
273
+ # Default importance values
274
+ feature_importance_dict = {
275
+ "total_building_load_rt": 0.324,
276
+ "avg_outside_temp_f": 0.156,
277
+ "avg_cooling_water_temp_c": 0.112,
278
+ "avg_humidity_pct": 0.089,
279
+ "hour": 0.078,
280
+ "avg_chilled_water_rate_lps": 0.067,
281
+ "month": 0.054,
282
+ "avg_dew_point_f": 0.043,
283
+ "day_of_year": 0.032,
284
+ "avg_wind_speed_mph": 0.021,
285
+ "avg_pressure_in": 0.015,
286
+ "day_of_week": 0.009
287
+ }
288
 
289
  # Input features description
290
  input_features = [
 
381
  version="2.0.0",
382
  description="Ensemble model that builds multiple decision trees to predict chiller plant energy efficiency (kW/TR) based on operational and environmental conditions. The model outputs the mean prediction of all trees for robust, non-linear regression.",
383
  architecture={
384
+ "n_estimators": model.n_estimators if model and hasattr(model, 'n_estimators') else 100,
385
+ "max_depth": model.max_depth if model and hasattr(model, 'max_depth') else 12,
386
+ "min_samples_split": model.min_samples_split if model and hasattr(model, 'min_samples_split') else 2,
387
+ "min_samples_leaf": model.min_samples_leaf if model and hasattr(model, 'min_samples_leaf') else 1,
388
  "bootstrap": True,
389
  "oob_score": False,
390
  "random_state": 42
 
419
  "mape": 4.2,
420
  "cv_rmse": 0.045
421
  },
422
+ feature_importance=feature_importance_dict,
423
  validation_method="Time-series cross validation",
424
  test_size=0.20,
425
  training_date=datetime.now().strftime("%Y-%m-%d")
 
483
 
484
  def predict_kw_per_tr(input_data: ChillerInput) -> float:
485
  """Predict Combined_Kw_per_TR using the Random Forest model"""
486
+ if model is None:
487
  raise ValueError("Model not loaded properly")
488
 
489
  # Prepare features
490
  features = prepare_features(input_data)
491
 
492
+ # Scale features if scaler exists
493
+ if scaler is not None:
494
+ features_scaled = scaler.transform(features)
495
+ else:
496
+ features_scaled = features
497
 
498
  # Predict
499
  prediction = model.predict(features_scaled)[0]
 
511
  best_sp = current_sp
512
 
513
  for sp in test_setpoints:
514
+ # Create test input with modified setpoint
515
  test_input = ChillerInput(
516
  total_building_load_rt=input_data.total_building_load_rt,
517
  avg_chilled_water_rate_lps=input_data.avg_chilled_water_rate_lps,
 
550
 
551
  def estimate_confidence_interval(input_data: ChillerInput) -> Dict[str, float]:
552
  """Estimate prediction confidence interval using ensemble variance"""
553
+ if model is None or not hasattr(model, 'estimators_'):
554
  return {"lower": None, "upper": None, "std": None}
555
 
556
  try:
557
  # Get predictions from all trees
558
  features = prepare_features(input_data)
559
+ if scaler is not None:
560
+ features_scaled = scaler.transform(features)
561
+ else:
562
+ features_scaled = features
563
 
564
  # Get individual tree predictions
565
  tree_predictions = np.array([tree.predict(features_scaled)[0]
 
611
  return {
612
  "status": "healthy" if model is not None else "degraded",
613
  "model_loaded": model is not None,
614
+ "model_type": type(model).__name__ if model else None,
615
+ "n_estimators": model.n_estimators if model and hasattr(model, 'n_estimators') else None,
616
  "scaler_loaded": scaler is not None,
617
+ "feature_count": len(feature_names) if feature_names else 12
618
  }
619
 
620
  @app.get("/mcp", response_model=MCPResponse)
 
636
  """Predict Combined_Kw_per_TR for given conditions"""
637
  try:
638
  if model is None:
639
+ raise HTTPException(status_code=503, detail="Model not loaded. Please check model files.")
640
 
641
  # Make prediction
642
  kw_per_tr = predict_kw_per_tr(input_data)
 
661
  """Get optimization recommendations"""
662
  try:
663
  if model is None:
664
+ raise HTTPException(status_code=503, detail="Model not loaded. Please check model files.")
665
 
666
  # Predict current efficiency
667
  current_kw = predict_kw_per_tr(input_data)
 
724
  operator_action="Check if all chillers are running optimally"
725
  ))
726
 
727
+ # Free cooling recommendation
728
  if input_data.avg_outside_temp_f < 50 and input_data.avg_humidity_pct < 60:
729
  recommendations.append(OptimizationRecommendation(
730
  action="Free Cooling",