hvac-agents / backend /api /plant_control_controller.py
subhan07's picture
HVAC FastAPI backend and O1-O20 agents (no frontend)
95c00ee verified
Raw History Blame Contribute Delete
11 kB
"""
PlantControlController: FastAPI Router exposing all REST endpoints for
Plant Control Parameter Optimizations:
- O5: Duct Static Pressure Reset
- O6–8: Unified Temperature Reset (HHW, CHW, CW modes)
- O9: Electronic Expansion Valve Retrofit Assessment
"""
from fastapi import APIRouter, HTTPException, Query
from typing import Dict, Any, List, Optional
from pydantic import BaseModel
from backend.services.plant_control_service import plant_control_service
from backend.services.plant_control_telemetry_service import plant_control_telemetry_service
from backend.services.plant_control_command_service import plant_control_command_service
from backend.services.plant_control_verification_service import plant_control_verification_service
from backend.services.plant_control_safety_service import plant_control_safety_service
from backend.agents.plant_control.o6_8_temperature_reset.engine import o6_8_agent
from backend.services.plant_control_provenance import stamp_plant_provenance
def _reset_opportunity(mode: str) -> str:
r_type = (mode or "CHW").upper().replace("_RESET", "")
if r_type == "HHW":
return "O6"
if r_type == "CW":
return "O8"
return "O7"
router = APIRouter(prefix="/api/agents/plant-control", tags=["Plant Control Parameter Optimizations"])
class CommandDispatchRequest(BaseModel):
target_setpoint: float
reset_type: Optional[str] = "CHW"
context: Optional[Dict[str, Any]] = None
# -------------------------------------------------------------
# High-Level Plant Control Fleet Endpoints
# -------------------------------------------------------------
@router.get("/state")
async def get_plant_control_state():
"""Returns fleet-wide optimization state, total kW shed, and opportunity status."""
return plant_control_service.get_dashboard_state()
@router.get("/telemetry")
async def get_all_plant_telemetry():
"""Returns standardized telemetry points across all plant control equipment."""
return plant_control_telemetry_service.get_all_points()
@router.get("/activity")
async def get_plant_activity(limit: int = Query(20, ge=1, le=100)):
"""Returns audit log history of plant control actions, safety checks, and verifications."""
return plant_control_service.get_activity_log(limit=limit)
@router.get("/safety/guardrails")
async def get_safety_guardrails():
"""Returns definitions of all 10 deterministic safety guardrails."""
return plant_control_safety_service.get_guardrail_definitions()
# -------------------------------------------------------------
# Opportunity 5: Duct Static Pressure Reset
# -------------------------------------------------------------
@router.get("/o5/state")
async def get_o5_state():
"""Returns live O5 Duct Static Pressure Reset state and candidate evaluation."""
return plant_control_service.get_o5_state()
@router.get("/o5/telemetry")
async def get_o5_telemetry():
"""Returns all standardized telemetry points for AHUs and downstream VAV boxes."""
return plant_control_telemetry_service.get_opportunity_telemetry("O5")
@router.get("/o5/decision")
async def get_o5_decision():
"""Returns active candidate recommendation and fan power savings prediction."""
state = plant_control_service.get_o5_state()
return {
"opportunity_code": "O5",
"current_setpoint": state["current_setpoint"],
"optimized_setpoint": state["optimized_setpoint"],
"pressure_reduction": state["pressure_reduction"],
"fan_power_shed_kw": state["power_shed_kw"],
"confidence": state["confidence"],
"safety_status": state["safety_status"],
"decision": state["decision"]
}
@router.get("/o5/history")
async def get_o5_history():
"""Returns 24-hour historical time-series for duct static pressure and fan power."""
return plant_control_service.get_o5_history()
@router.post("/o5/command")
async def dispatch_o5_command(req: CommandDispatchRequest):
"""Dispatches an optimized duct static pressure setpoint via BMS Gateway."""
try:
return plant_control_command_service.execute_command("O5", req.target_setpoint, req.context)
except ValueError as exc:
raise HTTPException(status_code=409, detail={"code": "DISPATCH_BLOCKED", "message": str(exc)})
@router.post("/o5/verify")
async def verify_o5_response():
"""Runs 15-minute M&V verification for O5 static pressure reset."""
return plant_control_verification_service.verify_opportunity("O5")
@router.post("/o5/rollback")
async def rollback_o5():
"""Rolls back O5 static pressure setpoint to design baseline."""
return plant_control_verification_service.rollback_opportunity("O5", "Manual Operator Rollback")
# -------------------------------------------------------------
# Opportunity 6–8: Unified Temperature Reset (HHW, CHW, CW)
# -------------------------------------------------------------
@router.get("/o6-8/state")
@router.get("/temperature-reset/state")
async def get_o6_8_state(mode: str = Query("CHW", description="Reset Mode: HHW, CHW, or CW")):
"""Returns normalized Temperature Reset state for selected mode (HHW, CHW, or CW)."""
return stamp_plant_provenance(o6_8_agent.optimize_mode(mode), _reset_opportunity(mode))
@router.get("/o6-8/modes")
@router.get("/temperature-reset/modes")
async def get_o6_8_all_modes():
"""Returns summary for all 3 temperature reset modes simultaneously."""
return o6_8_agent.get_all_modes_summary()
@router.get("/o6-8/telemetry")
@router.get("/temperature-reset/telemetry")
async def get_o6_8_telemetry(mode: str = Query("CHW")):
"""Returns telemetry points for selected reset mode loop."""
r_type = mode.upper().replace("_RESET", "")
opp_code = "O6" if r_type == "HHW" else ("O8" if r_type == "CW" else "O7")
return plant_control_telemetry_service.get_opportunity_telemetry(opp_code)
@router.get("/o6-8/decision")
@router.get("/temperature-reset/decision")
async def get_o6_8_decision(mode: str = Query("CHW")):
"""Returns normalized decision record for selected reset mode."""
res = o6_8_agent.optimize_mode(mode)
return {
"opportunity_id": "O6_8",
"reset_type": res["reset_type"],
"current_setpoint": res["current_setpoint"],
"optimized_setpoint": res["optimized_setpoint"],
"baseline_setpoint": res["baseline_setpoint"],
"power_impact": res["power_impact"],
"efficiency_impact": res["efficiency_impact"],
"confidence": res["confidence"],
"safety_status": res["status"],
"reason": res["reason"]
}
@router.post("/o6-8/command")
@router.post("/temperature-reset/command")
async def dispatch_o6_8_command(req: CommandDispatchRequest):
"""Dispatches a temperature reset setpoint for HHW, CHW, or CW."""
r_type = (req.reset_type or "CHW").upper().replace("_RESET", "")
opp_code = "O6" if r_type == "HHW" else ("O8" if r_type == "CW" else "O7")
try:
return plant_control_command_service.execute_command(opp_code, req.target_setpoint, req.context)
except ValueError as exc:
raise HTTPException(status_code=409, detail={"code": "DISPATCH_BLOCKED", "message": str(exc)})
@router.post("/o6-8/verify")
@router.post("/temperature-reset/verify")
async def verify_o6_8_response(mode: str = Query("CHW")):
"""Runs 15-minute M&V verification for temperature reset mode."""
r_type = mode.upper().replace("_RESET", "")
opp_code = "O6" if r_type == "HHW" else ("O8" if r_type == "CW" else "O7")
return plant_control_verification_service.verify_opportunity(opp_code)
@router.post("/o6-8/rollback")
@router.post("/temperature-reset/rollback")
async def rollback_o6_8(mode: str = Query("CHW")):
"""Rolls back temperature reset to baseline design setpoint."""
r_type = mode.upper().replace("_RESET", "")
opp_code = "O6" if r_type == "HHW" else ("O8" if r_type == "CW" else "O7")
return plant_control_verification_service.rollback_opportunity(opp_code, f"Rollback for {mode} Temperature Reset")
# -------------------------------------------------------------
# Backward-Compatibility Routes for O6, O7, O8
# -------------------------------------------------------------
@router.get("/o6/state")
async def get_o6_state():
return stamp_plant_provenance(o6_8_agent.optimize_mode("HHW"), "O6")
@router.get("/o6/telemetry")
async def get_o6_telemetry():
return plant_control_telemetry_service.get_opportunity_telemetry("O6")
@router.get("/o6/decision")
async def get_o6_decision():
return get_o6_8_decision("HHW")
@router.get("/o6/history")
async def get_o6_history():
return plant_control_service.get_o6_history()
@router.get("/o7/state")
async def get_o7_state():
return stamp_plant_provenance(o6_8_agent.optimize_mode("CHW"), "O7")
@router.get("/o7/telemetry")
async def get_o7_telemetry():
return plant_control_telemetry_service.get_opportunity_telemetry("O7")
@router.get("/o7/decision")
async def get_o7_decision():
return get_o6_8_decision("CHW")
@router.get("/o7/history")
async def get_o7_history():
return plant_control_service.get_o7_history()
@router.get("/o8/state")
async def get_o8_state():
return stamp_plant_provenance(o6_8_agent.optimize_mode("CW"), "O8")
@router.get("/o8/telemetry")
async def get_o8_telemetry():
return plant_control_telemetry_service.get_opportunity_telemetry("O8")
@router.get("/o8/decision")
async def get_o8_decision():
return get_o6_8_decision("CW")
@router.get("/o8/history")
async def get_o8_history():
return plant_control_service.get_o8_history()
# -------------------------------------------------------------
# Opportunity 9: Electronic Expansion Valve Retrofit
# -------------------------------------------------------------
@router.get("/o9/assessment")
@router.get("/o9/state")
async def get_o9_assessment():
"""Returns comprehensive thermodynamic and economic retrofit feasibility assessment."""
return plant_control_service.get_o9_assessment()
@router.get("/o9/telemetry")
async def get_o9_telemetry():
"""Returns live suction, superheat, and evaporating state telemetry."""
return plant_control_telemetry_service.get_opportunity_telemetry("O9")
@router.get("/o9/history")
async def get_o9_history():
"""Returns superheat stability and hunting amplitude comparison curves."""
return plant_control_service.get_o9_history()
@router.post("/o9/recalculate")
async def recalculate_o9_feasibility(utility_rate: float = Query(0.12)):
"""Recalculates capital payback and ROI based on updated utility rates."""
from backend.agents.plant_control.o9_electronic_expansion_valve.engine import o9_agent
return o9_agent.evaluate_retrofit_feasibility(utility_rate_per_kwh=utility_rate)