from fastapi import FastAPI from pydantic import BaseModel import numpy as np import pandas as pd import joblib from supabase import create_client import os from dotenv import load_dotenv from datetime import datetime # ---------------- LOAD ENV ---------------- load_dotenv() SUPABASE_URL = os.getenv("SUPABASE_URL") SUPABASE_KEY = os.getenv("SUPABASE_KEY") supabase = create_client(SUPABASE_URL, SUPABASE_KEY) # ---------------- LOAD MODEL ---------------- model = joblib.load("xgb_model.pkl") scaler = joblib.load("xgb_scaler.pkl") le = joblib.load("label_encoder.pkl") # ---------------- FASTAPI ---------------- app = FastAPI(title="Energy Monitor API") # ---------------- INPUT SCHEMA ---------------- class Features(BaseModel): mean: float max: float min: float std: float range: float peak_count: int slope: float power: float relay: bool # ---------------- FEATURE ENGINEERING ---------------- def transform_features(data: Features): mean = data.mean mx = data.max mn = data.min std = data.std rng = data.range peak = data.peak_count slope = data.slope energy = data.power * (40 * 0.04) / 3600 ratio = mx / (mn + 1) cv = std / (mean + 1) peak_ratio = peak / 40 delta_mean = std * 0.8 power_density = mean / (rng + 1) return [ mean, mx, mn, std, rng, peak, slope, energy, ratio, cv, peak_ratio, delta_mean, power_density ] # ---------------- ROOT ---------------- @app.get("/") def root(): return {"message": "Energy API running 🚀"} # ---------------- HEALTH ---------------- @app.get("/health") def health(): return {"status": "ok"} # ---------------- PREDICT ---------------- @app.post("/predict") def predict(data: Features): if not data.relay: # No power → skip prediction appliance = "Overloaded" confidence = 1.0 energy = 0.0 # no energy usage # store in DB supabase.table("energy_logs").insert({ "timestamp": datetime.utcnow().isoformat(), "appliance": appliance, "confidence": confidence, "mean": data.mean, "max": data.max, "min": data.min, "std": data.std, "range": data.range, "peak_count": data.peak_count, "slope": data.slope, "energy": energy, "power": 0.0, "relay": data.relay }).execute() return { "appliance": appliance, "confidence": confidence, "relay": data.relay } features = transform_features(data) columns = [ "mean","max","min","std","range", "peak_count","slope", "energy","ratio", "cv","peak_ratio", "delta_mean","power_density" ] df = pd.DataFrame([features], columns=columns) X = scaler.transform(df) probs = model.predict_proba(X)[0] pred_index = np.argmax(probs) appliance = le.inverse_transform([pred_index])[0] confidence = float(np.max(probs)) # 🔥 Optional threshold (lower to reduce Unknown) if confidence < 0.3: appliance = "Unknown" # ---------------- ENERGY CALC ---------------- time_seconds = 40 * 0.04 # 40 samples × 40ms = 1.6 sec energy = (data.power * time_seconds) / 3600.0 # ---------------- STORE IN SUPABASE ---------------- supabase.table("energy_logs").insert({ "timestamp": datetime.utcnow().isoformat(), "appliance": appliance, "confidence": confidence, "mean": data.mean, "max": data.max, "min": data.min, "std": data.std, "range": data.range, "peak_count": data.peak_count, "slope": data.slope, "energy": energy, "power": data.power, "relay": data.relay, }).execute() return { "appliance": appliance, "confidence": round(confidence, 3), "relay": data.relay } # ---------------- GET LATEST ---------------- @app.get("/latest") def latest(): res = supabase.table("energy_logs") \ .select("*") \ .order("timestamp", desc=True) \ .limit(1) \ .execute() return res.data[0] if res.data else {} # ---------------- GET HISTORY ---------------- @app.get("/history") def history(): res = supabase.table("energy_logs") \ .select("*") \ .order("timestamp", desc=True) \ .limit(50) \ .execute() return res.data # ---------------- MONTHLY ENERGY ---------------- @app.get("/monthly-energy") def monthly_energy(): res = supabase.table("energy_logs") \ .select("energy") \ .execute() total = sum([r["energy"] for r in res.data]) return { "total_energy": total } # ---------------- SERVER ---------------- if __name__ == "__main__": import uvicorn uvicorn.run("app:app", host="0.0.0.0", port=7860)