DevNumb commited on
Commit
8a4b04d
·
verified ·
1 Parent(s): ea59c0a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +276 -51
app.py CHANGED
@@ -1,6 +1,7 @@
1
  # ============================================
2
  # YORK CHILLER OPTIMIZER API
3
  # Compatible with NumPy 2.0.2 and pandas 2.2.3
 
4
  # ============================================
5
 
6
  import os
@@ -23,8 +24,8 @@ import joblib
23
  import pickle
24
 
25
  app = FastAPI(
26
- title="York Chiller Energy Optimizer",
27
- description="Random Forest Model for Chiller Energy Efficiency - 12 Features",
28
  version="2.0.0"
29
  )
30
 
@@ -142,9 +143,9 @@ else:
142
  # ============================================
143
 
144
  class ChillerInput(BaseModel):
145
- """12 input features exactly matching your model"""
146
- total_building_load: float = Field(..., ge=200, le=2500, description="Total building cooling load (RT)")
147
- avg_chilled_water_rate: float = Field(..., ge=50, le=500, description="Average chilled water flow rate (L/sec)")
148
  avg_cooling_water_temp: float = Field(..., ge=15, le=35, description="Average cooling water temperature (°C)")
149
  avg_outside_temp: float = Field(..., ge=32, le=120, description="Average outside air temperature (°F)")
150
  avg_dew_point: float = Field(..., ge=20, le=80, description="Average dew point temperature (°F)")
@@ -156,13 +157,22 @@ class ChillerInput(BaseModel):
156
  month: int = Field(..., ge=1, le=12, description="Month of the year (1-12)")
157
  day_of_year: int = Field(..., ge=1, le=366, description="Day of the year (1-365)")
158
 
159
- # Optional optimization parameters
160
  current_chw_setpoint_c: Optional[float] = Field(8.0, ge=5, le=10, description="Current CHW setpoint (°C)")
 
 
161
 
162
  class PredictionResponse(BaseModel):
163
  status: str
164
  kw_per_tr: float
 
 
 
 
 
165
  efficiency_rating: str
 
 
166
  features_used: int
167
  model_type: str
168
  timestamp: str
@@ -178,8 +188,14 @@ class OptimizationRecommendation(BaseModel):
178
  class OptimizeResponse(BaseModel):
179
  timestamp: str
180
  current_kw_per_tr: float
 
181
  optimal_kw_per_tr: float
 
182
  efficiency_improvement_pct: float
 
 
 
 
183
  recommendations: List[OptimizationRecommendation]
184
  summary: Dict[str, str]
185
 
@@ -218,11 +234,49 @@ def predict_kw_per_tr(input_data: ChillerInput) -> float:
218
  # Predict
219
  prediction = model.predict(features)[0]
220
 
221
- # Clip to realistic range
222
- prediction = np.clip(prediction, 0.4, 1.2)
 
 
223
 
224
  return float(prediction)
225
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  def get_efficiency_rating(kw_per_tr: float) -> str:
227
  """Get efficiency rating based on kW/TR value"""
228
  if kw_per_tr < 0.55:
@@ -266,13 +320,14 @@ def optimize_chw_setpoint(input_data: ChillerInput) -> tuple:
266
 
267
  @app.get("/")
268
  async def root():
269
- """Root endpoint with API information"""
270
  feature_count = len(feature_names) if feature_names and isinstance(feature_names, list) else len(EXPECTED_FEATURES)
271
 
272
  return {
273
- "service": "York Chiller Energy Optimizer",
274
  "model_type": "Random Forest Regressor",
275
  "version": "2.0.0",
 
276
  "status": "online" if model is not None else "model_not_loaded",
277
  "model_info": {
278
  "loaded": model is not None,
@@ -284,15 +339,36 @@ async def root():
284
  "endpoints": {
285
  "/": "This information",
286
  "/health": "Health check",
287
- "/predict": "POST - Predict efficiency (kW/TR)",
288
- "/optimize": "POST - Get optimization recommendations"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  },
290
  "interpretation": {
291
- "kw_per_tr": "Combined energy efficiency - LOWER is better",
292
- "excellent": "< 0.55 kW/TR",
293
- "good": "0.55-0.65 kW/TR",
294
- "fair": "0.65-0.75 kW/TR",
295
- "poor": "> 0.75 kW/TR"
296
  }
297
  }
298
 
@@ -313,20 +389,61 @@ async def health():
313
 
314
  @app.post("/predict", response_model=PredictionResponse)
315
  async def predict_endpoint(input_data: ChillerInput):
316
- """Predict Combined_Kw_per_TR for given conditions"""
317
  try:
318
  if model is None:
319
  raise HTTPException(status_code=503, detail="Model not loaded - check logs")
320
 
 
321
  kw_per_tr = predict_kw_per_tr(input_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  rating = get_efficiency_rating(kw_per_tr)
323
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  feature_count = len(feature_names) if feature_names and isinstance(feature_names, list) else len(EXPECTED_FEATURES)
325
 
326
  return PredictionResponse(
327
  status="success",
328
  kw_per_tr=round(kw_per_tr, 4),
 
 
 
 
 
329
  efficiency_rating=rating,
 
 
330
  features_used=feature_count,
331
  model_type="RandomForestRegressor",
332
  timestamp=datetime.now().isoformat()
@@ -337,83 +454,130 @@ async def predict_endpoint(input_data: ChillerInput):
337
 
338
  @app.post("/optimize", response_model=OptimizeResponse)
339
  async def optimize_endpoint(input_data: ChillerInput):
340
- """Get optimization recommendations"""
341
  try:
342
  if model is None:
343
  raise HTTPException(status_code=503, detail="Model not loaded - check logs")
344
 
345
- # Current efficiency
346
- current_kw = predict_kw_per_tr(input_data)
 
 
 
 
347
 
348
  # Find optimal setpoint
349
- optimal_sp, optimal_kw = optimize_chw_setpoint(input_data)
 
350
 
351
  # Calculate savings
352
- savings_pct = ((current_kw - optimal_kw) / current_kw) * 100 if current_kw > 0 else 0
353
  savings_pct = max(0, savings_pct)
354
 
355
- # Build recommendations
 
 
 
 
 
 
 
 
 
 
 
 
356
  recommendations = []
357
 
 
358
  current_sp = input_data.current_chw_setpoint_c or 8.0
359
  if optimal_sp != current_sp and savings_pct > 1:
360
  recommendations.append(OptimizationRecommendation(
361
  action="CHW Setpoint Optimization",
362
  current_value=f"{current_sp:.1f}°C",
363
  recommended_value=f"{optimal_sp:.1f}°C",
364
- expected_savings=f"{savings_pct:.1f}%",
365
  priority="HIGH" if savings_pct > 5 else "MEDIUM",
366
- operator_action=f"Adjust CHW setpoint to {optimal_sp:.1f}°C"
367
  ))
368
 
369
- # Load-based recommendations
370
- if input_data.total_building_load < 600:
 
 
371
  recommendations.append(OptimizationRecommendation(
372
  action="Chiller Sequencing",
373
- current_value=f"{input_data.total_building_load:.0f} RT",
374
- recommended_value="Single chiller operation",
375
- expected_savings="15-25% reduction",
376
  priority="HIGH",
377
- operator_action="Consider operating only one chiller"
378
  ))
379
  elif input_data.total_building_load > 1800:
380
  recommendations.append(OptimizationRecommendation(
381
- action="Chiller Staging",
382
- current_value=f"{input_data.total_building_load:.0f} RT",
383
- recommended_value="Verify all chillers",
384
- expected_savings="Prevents overload",
385
- priority="HIGH",
386
- operator_action="Check if all chillers are operating"
 
 
 
 
 
 
 
 
 
 
 
387
  ))
388
 
389
  # Free cooling recommendation
390
  if input_data.avg_outside_temp < 50 and input_data.avg_humidity < 60:
 
 
391
  recommendations.append(OptimizationRecommendation(
392
- action="Free Cooling",
393
- current_value="Not enabled",
394
- recommended_value="Consider enabling",
395
- expected_savings="20-40%",
396
  priority="HIGH",
397
- operator_action="Enable economizer/free cooling"
398
  ))
399
 
400
  # Efficiency rating
401
- rating = get_efficiency_rating(current_kw)
 
 
402
 
403
  summary = {
404
- "current_efficiency": f"{current_kw:.3f} kW/TR",
405
- "optimal_efficiency": f"{optimal_kw:.3f} kW/TR",
406
- "potential_savings": f"{savings_pct:.1f}%",
 
 
 
 
 
407
  "efficiency_rating": rating,
408
- "load": f"{input_data.total_building_load:.0f} RT",
409
- "recommended_setpoint": f"{optimal_sp:.1f}°C"
 
410
  }
411
 
412
  return OptimizeResponse(
413
  timestamp=datetime.now().isoformat(),
414
- current_kw_per_tr=round(current_kw, 4),
415
- optimal_kw_per_tr=round(optimal_kw, 4),
 
 
416
  efficiency_improvement_pct=round(savings_pct, 2),
 
 
 
 
417
  recommendations=recommendations,
418
  summary=summary
419
  )
@@ -421,6 +585,67 @@ async def optimize_endpoint(input_data: ChillerInput):
421
  except Exception as e:
422
  raise HTTPException(status_code=500, detail=str(e))
423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  if __name__ == "__main__":
425
  import uvicorn
426
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  # ============================================
2
  # YORK CHILLER OPTIMIZER API
3
  # Compatible with NumPy 2.0.2 and pandas 2.2.3
4
+ # For 4-Chiller Plants (1000+ TR)
5
  # ============================================
6
 
7
  import os
 
24
  import pickle
25
 
26
  app = FastAPI(
27
+ title="York Chiller Energy Optimizer - 4 Chiller Plant Edition",
28
+ description="Random Forest Model for Chiller Energy Efficiency - 12 Features (1000+ TR Plants)",
29
  version="2.0.0"
30
  )
31
 
 
143
  # ============================================
144
 
145
  class ChillerInput(BaseModel):
146
+ """12 input features - optimized for 1000+ TR plants"""
147
+ total_building_load: float = Field(..., ge=400, le=2500, description="Total building cooling load (RT) - For 4 chiller plant: 400-2500 TR")
148
+ avg_chilled_water_rate: float = Field(..., ge=200, le=2000, description="Average chilled water flow rate (L/sec)")
149
  avg_cooling_water_temp: float = Field(..., ge=15, le=35, description="Average cooling water temperature (°C)")
150
  avg_outside_temp: float = Field(..., ge=32, le=120, description="Average outside air temperature (°F)")
151
  avg_dew_point: float = Field(..., ge=20, le=80, description="Average dew point temperature (°F)")
 
157
  month: int = Field(..., ge=1, le=12, description="Month of the year (1-12)")
158
  day_of_year: int = Field(..., ge=1, le=366, description="Day of the year (1-365)")
159
 
160
+ # Optional parameters for 4-chiller plant
161
  current_chw_setpoint_c: Optional[float] = Field(8.0, ge=5, le=10, description="Current CHW setpoint (°C)")
162
+ num_chillers_operating: Optional[int] = Field(4, ge=1, le=4, description="Number of chillers currently operating")
163
+ electricity_rate_usd_per_kwh: Optional[float] = Field(0.12, ge=0.05, le=0.50, description="Electricity rate ($/kWh)")
164
 
165
  class PredictionResponse(BaseModel):
166
  status: str
167
  kw_per_tr: float
168
+ total_power_kw: float
169
+ power_per_chiller_kw: float
170
+ cooling_load_tr: float
171
+ load_per_chiller_tr: float
172
+ cop: float
173
  efficiency_rating: str
174
+ plant_summary: Dict[str, Any]
175
+ annual_cost_estimate: Dict[str, Any]
176
  features_used: int
177
  model_type: str
178
  timestamp: str
 
188
  class OptimizeResponse(BaseModel):
189
  timestamp: str
190
  current_kw_per_tr: float
191
+ current_total_power_kw: float
192
  optimal_kw_per_tr: float
193
+ optimal_total_power_kw: float
194
  efficiency_improvement_pct: float
195
+ power_savings_kw: float
196
+ power_savings_pct: float
197
+ annual_savings_usd: float
198
+ co2_reduction_kg_per_year: float
199
  recommendations: List[OptimizationRecommendation]
200
  summary: Dict[str, str]
201
 
 
234
  # Predict
235
  prediction = model.predict(features)[0]
236
 
237
+ # Typical range for large centrifugal chillers (1000+ TR)
238
+ # Modern efficient: 0.45-0.55 kW/TR
239
+ # Older units: 0.60-0.80 kW/TR
240
+ prediction = np.clip(prediction, 0.40, 0.90)
241
 
242
  return float(prediction)
243
 
244
+ def calculate_total_power(kw_per_tr: float, cooling_load_tr: float) -> float:
245
+ """
246
+ Calculate total chiller plant power consumption for all chillers
247
+
248
+ Formula: Total Power (kW) = kW/TR × Cooling Load (TR)
249
+ """
250
+ return kw_per_tr * cooling_load_tr
251
+
252
+ def calculate_annual_energy_cost(kw_per_tr: float, avg_load_tr: float,
253
+ operating_hours: int = 3000,
254
+ electricity_rate: float = 0.12) -> dict:
255
+ """
256
+ Calculate annual energy cost and consumption
257
+ """
258
+ avg_power_kw = kw_per_tr * avg_load_tr
259
+ annual_kwh = avg_power_kw * operating_hours
260
+ annual_cost_usd = annual_kwh * electricity_rate
261
+
262
+ return {
263
+ "avg_power_kw": round(avg_power_kw, 1),
264
+ "annual_kwh": round(annual_kwh / 1000, 1),
265
+ "annual_cost_usd": round(annual_cost_usd, 0),
266
+ "operating_hours": operating_hours,
267
+ "electricity_rate": electricity_rate
268
+ }
269
+
270
+ def calculate_cop_from_kw_per_tr(kw_per_tr: float) -> float:
271
+ """
272
+ Convert kW/TR to COP (Coefficient of Performance)
273
+
274
+ Formula: COP = 3.516 / kW/TR
275
+ """
276
+ if kw_per_tr <= 0:
277
+ return 0
278
+ return 3.516 / kw_per_tr
279
+
280
  def get_efficiency_rating(kw_per_tr: float) -> str:
281
  """Get efficiency rating based on kW/TR value"""
282
  if kw_per_tr < 0.55:
 
320
 
321
  @app.get("/")
322
  async def root():
323
+ """Root endpoint with API information for large chiller plants"""
324
  feature_count = len(feature_names) if feature_names and isinstance(feature_names, list) else len(EXPECTED_FEATURES)
325
 
326
  return {
327
+ "service": "York Chiller Energy Optimizer - 4 Chiller Plant Edition",
328
  "model_type": "Random Forest Regressor",
329
  "version": "2.0.0",
330
+ "plant_size": "1000+ TR (4 centrifugal chillers)",
331
  "status": "online" if model is not None else "model_not_loaded",
332
  "model_info": {
333
  "loaded": model is not None,
 
339
  "endpoints": {
340
  "/": "This information",
341
  "/health": "Health check",
342
+ "/predict": "POST - Predict efficiency and power for 4-chiller plant",
343
+ "/optimize": "POST - Get optimization with annual savings ($ and CO2)",
344
+ "/plant-analysis": "POST - Detailed 4-chiller plant analysis"
345
+ },
346
+ "sample_4_chiller_input": {
347
+ "total_building_load": 1200,
348
+ "avg_chilled_water_rate": 800,
349
+ "avg_cooling_water_temp": 28,
350
+ "avg_outside_temp": 95,
351
+ "avg_dew_point": 65,
352
+ "avg_humidity": 60,
353
+ "avg_wind_speed": 8,
354
+ "avg_pressure": 29.9,
355
+ "hour": 14,
356
+ "day_of_week": 2,
357
+ "month": 7,
358
+ "day_of_year": 200,
359
+ "num_chillers_operating": 4,
360
+ "electricity_rate_usd_per_kwh": 0.12
361
+ },
362
+ "expected_output_sample": {
363
+ "total_power_kw": "~700-800 kW total",
364
+ "power_per_chiller_kw": "~175-200 kW each",
365
+ "annual_energy_cost": "$250,000-$350,000 at $0.12/kWh"
366
  },
367
  "interpretation": {
368
+ "kw_per_tr": "Lower is better for 1000+ TR plants: <0.55 = excellent",
369
+ "total_power_kw": "Actual plant power for all chillers combined",
370
+ "typical_power_for_1000tr": "500-700 kW at full load",
371
+ "potential_savings": "50-150 kW possible through optimization = $15k-45k/year"
 
372
  }
373
  }
374
 
 
389
 
390
  @app.post("/predict", response_model=PredictionResponse)
391
  async def predict_endpoint(input_data: ChillerInput):
392
+ """Predict efficiency and power for 4-chiller plant (1000+ TR)"""
393
  try:
394
  if model is None:
395
  raise HTTPException(status_code=503, detail="Model not loaded - check logs")
396
 
397
+ # Get predicted kW/TR
398
  kw_per_tr = predict_kw_per_tr(input_data)
399
+
400
+ # Calculate total plant power
401
+ total_power_kw = calculate_total_power(kw_per_tr, input_data.total_building_load)
402
+
403
+ # Get number of chillers (default 4)
404
+ num_chillers = input_data.num_chillers_operating or 4
405
+
406
+ # Per-chiller metrics
407
+ power_per_chiller = total_power_kw / num_chillers
408
+ load_per_chiller = input_data.total_building_load / num_chillers
409
+
410
+ # Calculate COP
411
+ cop = calculate_cop_from_kw_per_tr(kw_per_tr)
412
+
413
+ # Get efficiency rating
414
  rating = get_efficiency_rating(kw_per_tr)
415
 
416
+ # Calculate annual cost estimate
417
+ electricity_rate = input_data.electricity_rate_usd_per_kwh or 0.12
418
+ annual_cost = calculate_annual_energy_cost(
419
+ kw_per_tr,
420
+ input_data.total_building_load,
421
+ operating_hours=3000,
422
+ electricity_rate=electricity_rate
423
+ )
424
+
425
+ # Plant summary
426
+ plant_summary = {
427
+ "num_chillers": num_chillers,
428
+ "total_capacity_tr": round(input_data.total_building_load, 0),
429
+ "load_per_chiller_tr": round(load_per_chiller, 1),
430
+ "power_density_kw_per_100tr": round(kw_per_tr * 100, 2),
431
+ "chiller_type": "Centrifugal (1000+ TR scale)"
432
+ }
433
+
434
  feature_count = len(feature_names) if feature_names and isinstance(feature_names, list) else len(EXPECTED_FEATURES)
435
 
436
  return PredictionResponse(
437
  status="success",
438
  kw_per_tr=round(kw_per_tr, 4),
439
+ total_power_kw=round(total_power_kw, 1),
440
+ power_per_chiller_kw=round(power_per_chiller, 1),
441
+ cooling_load_tr=round(input_data.total_building_load, 1),
442
+ load_per_chiller_tr=round(load_per_chiller, 1),
443
+ cop=round(cop, 2),
444
  efficiency_rating=rating,
445
+ plant_summary=plant_summary,
446
+ annual_cost_estimate=annual_cost,
447
  features_used=feature_count,
448
  model_type="RandomForestRegressor",
449
  timestamp=datetime.now().isoformat()
 
454
 
455
  @app.post("/optimize", response_model=OptimizeResponse)
456
  async def optimize_endpoint(input_data: ChillerInput):
457
+ """Get optimization recommendations for large chiller plant"""
458
  try:
459
  if model is None:
460
  raise HTTPException(status_code=503, detail="Model not loaded - check logs")
461
 
462
+ num_chillers = input_data.num_chillers_operating or 4
463
+ electricity_rate = input_data.electricity_rate_usd_per_kwh or 0.12
464
+
465
+ # Current efficiency and power
466
+ current_kw_per_tr = predict_kw_per_tr(input_data)
467
+ current_power_kw = calculate_total_power(current_kw_per_tr, input_data.total_building_load)
468
 
469
  # Find optimal setpoint
470
+ optimal_sp, optimal_kw_per_tr = optimize_chw_setpoint(input_data)
471
+ optimal_power_kw = calculate_total_power(optimal_kw_per_tr, input_data.total_building_load)
472
 
473
  # Calculate savings
474
+ savings_pct = ((current_kw_per_tr - optimal_kw_per_tr) / current_kw_per_tr) * 100 if current_kw_per_tr > 0 else 0
475
  savings_pct = max(0, savings_pct)
476
 
477
+ # Power savings
478
+ power_savings_kw = current_power_kw - optimal_power_kw
479
+ power_savings_pct = (power_savings_kw / current_power_kw) * 100 if current_power_kw > 0 else 0
480
+
481
+ # Annual savings
482
+ annual_operating_hours = 3000
483
+ annual_savings_kwh = power_savings_kw * annual_operating_hours
484
+ annual_savings_usd = annual_savings_kwh * electricity_rate
485
+
486
+ # CO2 reduction (assuming 0.4 kg CO2 per kWh - US average)
487
+ co2_reduction_kg = annual_savings_kwh * 0.4
488
+
489
+ # Build recommendations for 4-chiller plant
490
  recommendations = []
491
 
492
+ # Setpoint optimization
493
  current_sp = input_data.current_chw_setpoint_c or 8.0
494
  if optimal_sp != current_sp and savings_pct > 1:
495
  recommendations.append(OptimizationRecommendation(
496
  action="CHW Setpoint Optimization",
497
  current_value=f"{current_sp:.1f}°C",
498
  recommended_value=f"{optimal_sp:.1f}°C",
499
+ expected_savings=f"{savings_pct:.1f}% ({power_savings_kw:.0f} kW, ${annual_savings_usd:,.0f}/year)",
500
  priority="HIGH" if savings_pct > 5 else "MEDIUM",
501
+ operator_action=f"Raise CHW setpoint to {optimal_sp:.1f}°C across all {num_chillers} chillers"
502
  ))
503
 
504
+ # Chiller sequencing for large plants
505
+ load_per_chiller = input_data.total_building_load / num_chillers
506
+
507
+ if input_data.total_building_load < 800:
508
  recommendations.append(OptimizationRecommendation(
509
  action="Chiller Sequencing",
510
+ current_value=f"{num_chillers} chillers operating",
511
+ recommended_value="3 chillers (or less)",
512
+ expected_savings="20-30% power reduction at low load",
513
  priority="HIGH",
514
+ operator_action=f"Sequentially shut down one chiller, adjust load on remaining {num_chillers-1} chillers"
515
  ))
516
  elif input_data.total_building_load > 1800:
517
  recommendations.append(OptimizationRecommendation(
518
+ action="Load Balancing",
519
+ current_value=f"{load_per_chiller:.0f} TR per chiller",
520
+ recommended_value="Verify all chillers are contributing equally",
521
+ expected_savings="5-10% efficiency improvement",
522
+ priority="MEDIUM",
523
+ operator_action="Check operating logs for load sharing between chillers"
524
+ ))
525
+
526
+ # Condenser water temperature optimization
527
+ if input_data.avg_cooling_water_temp < 24:
528
+ recommendations.append(OptimizationRecommendation(
529
+ action="Condenser Water Reset",
530
+ current_value=f"{input_data.avg_cooling_water_temp:.1f}°C",
531
+ recommended_value="Allow cooling tower to float higher",
532
+ expected_savings="3-8% pump energy reduction",
533
+ priority="MEDIUM",
534
+ operator_action="Reduce cooling tower fan speed or disable some cells"
535
  ))
536
 
537
  # Free cooling recommendation
538
  if input_data.avg_outside_temp < 50 and input_data.avg_humidity < 60:
539
+ estimated_savings_kw = current_power_kw * 0.35
540
+ estimated_savings_usd = estimated_savings_kw * annual_operating_hours * electricity_rate
541
  recommendations.append(OptimizationRecommendation(
542
+ action="Free Cooling Mode",
543
+ current_value="Mechanical cooling only",
544
+ recommended_value="Enable waterside economizer",
545
+ expected_savings=f"35% (approx {estimated_savings_kw:.0f} kW, ${estimated_savings_usd:,.0f}/year)",
546
  priority="HIGH",
547
+ operator_action="Open bypass valve for cooling tower to provide directly chilled water"
548
  ))
549
 
550
  # Efficiency rating
551
+ rating = get_efficiency_rating(current_kw_per_tr)
552
+ current_cop = calculate_cop_from_kw_per_tr(current_kw_per_tr)
553
+ optimal_cop = calculate_cop_from_kw_per_tr(optimal_kw_per_tr)
554
 
555
  summary = {
556
+ "plant_summary": f"{num_chillers} chillers, {input_data.total_building_load:.0f} TR total",
557
+ "current_efficiency": f"{current_kw_per_tr:.3f} kW/TR (COP: {current_cop:.2f})",
558
+ "current_power": f"{current_power_kw:.0f} kW ({current_power_kw/num_chillers:.0f} kW per chiller)",
559
+ "optimal_efficiency": f"{optimal_kw_per_tr:.3f} kW/TR (COP: {optimal_cop:.2f})",
560
+ "optimal_power": f"{optimal_power_kw:.0f} kW",
561
+ "power_savings": f"{power_savings_kw:.0f} kW ({savings_pct:.1f}%)",
562
+ "annual_savings_usd": f"${annual_savings_usd:,.0f}",
563
+ "co2_reduction": f"{co2_reduction_kg/1000:.1f} metric tons CO2/year",
564
  "efficiency_rating": rating,
565
+ "load_per_chiller": f"{load_per_chiller:.0f} TR",
566
+ "recommended_setpoint": f"{optimal_sp:.1f}°C",
567
+ "electricity_rate": f"${electricity_rate:.2f}/kWh"
568
  }
569
 
570
  return OptimizeResponse(
571
  timestamp=datetime.now().isoformat(),
572
+ current_kw_per_tr=round(current_kw_per_tr, 4),
573
+ current_total_power_kw=round(current_power_kw, 1),
574
+ optimal_kw_per_tr=round(optimal_kw_per_tr, 4),
575
+ optimal_total_power_kw=round(optimal_power_kw, 1),
576
  efficiency_improvement_pct=round(savings_pct, 2),
577
+ power_savings_kw=round(power_savings_kw, 1),
578
+ power_savings_pct=round(power_savings_pct, 2),
579
+ annual_savings_usd=round(annual_savings_usd, 0),
580
+ co2_reduction_kg_per_year=round(co2_reduction_kg, 0),
581
  recommendations=recommendations,
582
  summary=summary
583
  )
 
585
  except Exception as e:
586
  raise HTTPException(status_code=500, detail=str(e))
587
 
588
+ @app.post("/plant-analysis")
589
+ async def plant_analysis(input_data: ChillerInput):
590
+ """Detailed analysis for 4-chiller plant"""
591
+ try:
592
+ if model is None:
593
+ raise HTTPException(status_code=503, detail="Model not loaded")
594
+
595
+ kw_per_tr = predict_kw_per_tr(input_data)
596
+ num_chillers = input_data.num_chillers_operating or 4
597
+
598
+ # Calculate various metrics
599
+ total_power = kw_per_tr * input_data.total_building_load
600
+ power_per_chiller = total_power / num_chillers
601
+
602
+ # Compare to industry benchmarks
603
+ benchmarks = {
604
+ "excellent": 0.50,
605
+ "good": 0.60,
606
+ "average": 0.65,
607
+ "poor": 0.75
608
+ }
609
+
610
+ # Energy cost at different loads
611
+ load_levels = [50, 75, 100]
612
+ cost_analysis = []
613
+ for load_pct in load_levels:
614
+ load_tr = input_data.total_building_load * load_pct / 100
615
+ power_kw = kw_per_tr * load_tr
616
+ cost_analysis.append({
617
+ "load_pct": load_pct,
618
+ "load_tr": round(load_tr, 0),
619
+ "power_kw": round(power_kw, 0),
620
+ "power_per_chiller_kw": round(power_kw / num_chillers, 0)
621
+ })
622
+
623
+ return {
624
+ "timestamp": datetime.now().isoformat(),
625
+ "plant_configuration": {
626
+ "num_chillers": num_chillers,
627
+ "total_capacity_tr": input_data.total_building_load,
628
+ "current_load_tr": input_data.total_building_load,
629
+ "load_factor": "100%"
630
+ },
631
+ "performance_metrics": {
632
+ "kw_per_tr": round(kw_per_tr, 3),
633
+ "total_power_kw": round(total_power, 0),
634
+ "power_per_chiller_kw": round(power_per_chiller, 0),
635
+ "cop": round(calculate_cop_from_kw_per_tr(kw_per_tr), 2),
636
+ "vs_benchmark_excellent": f"{((kw_per_tr - benchmarks['excellent'])/benchmarks['excellent']*100):+.1f}%"
637
+ },
638
+ "cost_analysis": cost_analysis,
639
+ "recommendations": [
640
+ f"Target kW/TR < 0.60 for this plant size (currently {kw_per_tr:.3f})",
641
+ f"At full load, each chiller draws ~{power_per_chiller:.0f} kW",
642
+ "Review if all 4 chillers are needed at current load"
643
+ ]
644
+ }
645
+
646
+ except Exception as e:
647
+ raise HTTPException(status_code=500, detail=str(e))
648
+
649
  if __name__ == "__main__":
650
  import uvicorn
651
  uvicorn.run(app, host="0.0.0.0", port=7860)