File size: 5,474 Bytes
95c00ee 1bd74e9 95c00ee | 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 | """Shared HVAC ML HTTP API. Training datasets are never LIVE_BMS."""
from __future__ import annotations
from typing import Any, Dict, Optional
from fastapi import APIRouter
from pydantic import BaseModel, Field
from backend.ml.prediction.service import list_models, model_status, predict
from backend.ml.registry.service import list_datasets, list_maps, opportunity_health, register_datasets
from backend.ml.training.pipeline import train_all
router = APIRouter(prefix="/api/ml", tags=["ml"])
class PredictBody(BaseModel):
opportunity_id: str
agent_id: Optional[str] = None
building_id: Optional[str] = None
features: Dict[str, Any] = Field(default_factory=dict)
@router.get("/datasets")
def get_datasets():
return {"datasets": list_datasets(), "source": "TRAINING_DATASET"}
@router.post("/datasets/register")
def post_register():
return {"datasets": register_datasets(), "source": "TRAINING_DATASET"}
@router.get("/datasets/{dataset_id}")
def get_dataset(dataset_id: str):
for row in list_datasets():
if row["id"] == dataset_id:
return row
return {"status": "NOT_FOUND", "id": dataset_id}
@router.post("/datasets/{dataset_id}/validate")
def post_validate(dataset_id: str):
rows = {r["id"]: r for r in list_datasets()}
row = rows.get(dataset_id)
if not row:
return {"status": "NOT_FOUND", "id": dataset_id}
return {"status": row["status"], "source": "TRAINING_DATASET", "dataset": row}
@router.get("/maps")
def get_maps(opportunity_id: Optional[str] = None):
return {"maps": list_maps(opportunity_id)}
@router.get("/health")
def get_health():
try:
return opportunity_health()
except Exception as exc:
from backend.ml.features.maps import AGENT_FOR, OPPORTUNITY_MAPS
maps = {m["opportunity_id"]: m for m in OPPORTUNITY_MAPS}
rows = []
for i in range(1, 21):
oid = f"O{i}"
primary = maps.get(oid)
trainable = bool(primary and primary.get("training_allowed") and primary.get("target_column"))
rows.append(
{
"opportunity_id": oid,
"agent_id": (primary or {}).get("agent_id") or AGENT_FOR.get(oid),
"dataset_id": (primary or {}).get("dataset_id"),
"dataset_name": None,
"dataset_status": None,
"dataset_quality": None,
"feature_map": (primary or {}).get("feature_map") or {},
"target": (primary or {}).get("target_column"),
"task_type": (primary or {}).get("task_type"),
"training_allowed": trainable,
"notes": (primary or {}).get("notes") or "No legitimate mapping.",
"missing_dataset": (primary or {}).get("missing_dataset"),
"model_id": None,
"model_version": None,
"status": "MODEL_NOT_TRAINABLE" if oid == "O10" or not trainable else "MODEL_NOT_AVAILABLE",
"validation_status": "N/A",
"metrics": None,
"last_trained": None,
"prediction_availability": "UNAVAILABLE",
"provenance": "TRAINING DATA",
"last_prediction": None,
"training_run": None,
}
)
return {"opportunities": rows, "source": "TRAINING_DATASET", "datasets": [], "degraded": type(exc).__name__}
@router.get("/models")
def get_models():
return {"models": list_models(), "source": "TRAINING_DATASET"}
@router.get("/models/{opportunity_id}")
def get_model(opportunity_id: str):
return model_status(opportunity_id)
@router.post("/train")
def post_train():
from backend.ml.features.maps import trainable_maps
return {"runs": train_all(), "trainable_maps": len(trainable_maps())}
@router.post("/predict")
def post_predict(body: PredictBody):
return predict(body.opportunity_id, features=body.features, agent_id=body.agent_id, building_id=body.building_id)
@router.post("/batch-predict")
def post_batch(body: Dict[str, Any]):
items = body.get("items") or []
return {"results": [predict(it.get("opportunity_id"), features=it.get("features") or {}, persist=False) for it in items]}
@router.get("/predictions/{opportunity_id}")
def get_predictions(opportunity_id: str):
from database.models_ml import MLPredictionDB
from database.session import SessionLocal
db = SessionLocal()
try:
rows = (
db.query(MLPredictionDB)
.filter_by(opportunity_id=opportunity_id.upper())
.order_by(MLPredictionDB.created_at.desc())
.limit(50)
.all()
)
return {
"predictions": [
{
"id": r.id,
"status": r.status,
"provenance": r.provenance,
"source": r.source,
"prediction": r.prediction_json,
"confidence": r.confidence,
"created_at": r.created_at.isoformat() if r.created_at else None,
}
for r in rows
]
}
finally:
db.close()
|