from fastapi import APIRouter, Depends, Query from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession from core.database import get_session router = APIRouter() ROOMS = { "sg-01": [ {"room_id": "hall-a", "racks": [f"SG1A01.{i:02d}" for i in range(1, 21)] + [f"SG1A02.{i:02d}" for i in range(1, 21)], "crac_id": "crac-01"}, {"room_id": "hall-b", "racks": [f"SG1B01.{i:02d}" for i in range(1, 21)] + [f"SG1B02.{i:02d}" for i in range(1, 21)], "crac_id": "crac-02"}, ] } # Rated capacity config — would be per-asset configurable in production RACK_POWER_CAPACITY_KW = 10.0 # max kW per rack ROOM_POWER_CAPACITY_KW = 400.0 # 40 racks × 10 kW CRAC_COOLING_CAPACITY_KW = 160.0 # rated cooling per CRAC RACK_U_TOTAL = 42 @router.get("/summary") async def capacity_summary( site_id: str = Query(...), session: AsyncSession = Depends(get_session), ): """Per-rack and per-room capacity: power used vs rated, cooling load vs rated, rack space.""" result = await session.execute(text(""" SELECT DISTINCT ON (sensor_id) rack_id, room_id, sensor_type, value FROM readings WHERE site_id = :site_id AND sensor_type IN ('power_kw', 'temperature') AND rack_id IS NOT NULL AND recorded_at > NOW() - INTERVAL '10 minutes' ORDER BY sensor_id, recorded_at DESC """), {"site_id": site_id}) rows = result.mappings().all() # Index: rack_id → {power_kw, temperature, room_id} rack_idx: dict[str, dict] = {} for row in rows: rid = row["rack_id"] if rid not in rack_idx: rack_idx[rid] = {"room_id": row["room_id"]} if row["sensor_type"] == "power_kw": rack_idx[rid]["power_kw"] = round(float(row["value"]), 2) elif row["sensor_type"] == "temperature": rack_idx[rid]["temperature"] = round(float(row["value"]), 1) rooms_out = [] racks_out = [] for room in ROOMS.get(site_id, []): room_id = room["room_id"] room_power = 0.0 populated = 0 for rack_id in room["racks"]: d = rack_idx.get(rack_id, {}) power = d.get("power_kw") temp = d.get("temperature") if power is not None: room_power += power populated += 1 power_pct = round((power / RACK_POWER_CAPACITY_KW) * 100, 1) if power is not None else None racks_out.append({ "rack_id": rack_id, "room_id": room_id, "power_kw": power, "power_capacity_kw": RACK_POWER_CAPACITY_KW, "power_pct": power_pct, "temp": temp, }) room_power = round(room_power, 2) rooms_out.append({ "room_id": room_id, "power": { "used_kw": room_power, "capacity_kw": ROOM_POWER_CAPACITY_KW, "pct": round((room_power / ROOM_POWER_CAPACITY_KW) * 100, 1), "headroom_kw": round(ROOM_POWER_CAPACITY_KW - room_power, 2), }, "cooling": { "load_kw": room_power, # IT power ≈ heat generated "capacity_kw": CRAC_COOLING_CAPACITY_KW, "pct": round(min(100.0, (room_power / CRAC_COOLING_CAPACITY_KW) * 100), 1), "headroom_kw": round(max(0.0, CRAC_COOLING_CAPACITY_KW - room_power), 2), }, "space": { "racks_total": len(room["racks"]), "racks_populated": populated, "pct": round((populated / len(room["racks"])) * 100, 1), }, }) return { "site_id": site_id, "config": { "rack_power_kw": RACK_POWER_CAPACITY_KW, "room_power_kw": ROOM_POWER_CAPACITY_KW, "crac_cooling_kw": CRAC_COOLING_CAPACITY_KW, "rack_u_total": RACK_U_TOTAL, }, "rooms": rooms_out, "racks": racks_out, }