grid_demo / app.py
OlamideKayode's picture
Upload app.py
331907c verified
raw
history blame
6.97 kB
import os
import time
import pandas as pd
import numpy as np
import joblib
import requests
import streamlit as st
from streamlit_autorefresh import st_autorefresh
from sklearn.metrics import mean_absolute_error
import plotly.express as px
# Auto-refresh every 5 seconds
st_autorefresh(interval=5000, key="refresh")
# Load model
@st.cache_resource
def load_model():
return joblib.load("rf_model.pkl")
model = load_model()
# Supabase config
SUPABASE_URL = os.environ["SUPABASE_URL"]
SUPABASE_KEY = os.environ["SUPABASE_KEY"]
TABLE = "smart_meter_readings_1year"
# Initialize session state
if "row_index" not in st.session_state:
st.session_state.row_index = 0
if "history" not in st.session_state:
st.session_state.history = pd.DataFrame()
# Fetch all data
@st.cache_data
def fetch_all_data():
url = f"{SUPABASE_URL}/rest/v1/{TABLE}?select=*&order=timestamp.asc"
headers = {
"apikey": SUPABASE_KEY,
"Authorization": f"Bearer {SUPABASE_KEY}"
}
r = requests.get(url, headers=headers)
if r.ok:
return pd.DataFrame(r.json())
else:
st.error(f"โŒ Error fetching data: {r.status_code}")
return pd.DataFrame()
df_all = fetch_all_data()
# Debug sidebar
st.sidebar.title("๐Ÿ›  Debug Info")
st.sidebar.write("Row index:", st.session_state.row_index)
st.sidebar.write("Total rows:", len(df_all))
if not df_all.empty and st.session_state.row_index < len(df_all):
st.sidebar.write("Next row:", df_all.iloc[st.session_state.row_index].to_dict())
# Get next row
def get_next_row():
if st.session_state.row_index < len(df_all):
row = df_all.iloc[[st.session_state.row_index]]
st.session_state.row_index += 1
return row
return pd.DataFrame()
# Feature engineering
def engineer(df):
if pd.api.types.is_numeric_dtype(df["timestamp"]):
df["datetime"] = pd.to_datetime(df["timestamp"], unit="s")
else:
df["datetime"] = pd.to_datetime(df["timestamp"])
df = df.sort_values("datetime")
df["hour_of_day"] = df["datetime"].dt.hour
df["lag_30min"] = df["power_consumption_kwh"].shift(1)
df["lag_1h"] = df["power_consumption_kwh"].shift(2)
df["rolling_avg_1h"] = df["power_consumption_kwh"].rolling(2).mean()
df["rolling_avg_2h"] = df["power_consumption_kwh"].rolling(4).mean()
df["is_weekend"] = df["datetime"].dt.weekday >= 5
df["hour_sin"] = np.sin(2 * np.pi * df["hour_of_day"] / 24)
df["hour_cos"] = np.cos(2 * np.pi * df["hour_of_day"] / 24)
df = pd.get_dummies(df, columns=["property_type", "region"], drop_first=False)
expected_features = [
'lag_30min', 'lag_1h', 'rolling_avg_1h', 'rolling_avg_2h',
'hour_of_day', 'is_weekend', 'hour_sin', 'hour_cos',
'temperature_c', 'ev_owner', 'solar_installed',
'property_type_commercial', 'property_type_residential',
'region_north', 'region_south', 'region_east', 'region_west'
]
for col in expected_features:
if col not in df.columns:
df[col] = 0
return df
# Forecast ahead logic (5 steps, 30min intervals)
def forecast_next(df, model, steps=5):
forecasts = []
df_copy = df.copy()
expected_features = model.feature_names_in_.tolist()
for i in range(steps):
df_copy = engineer(df_copy).dropna()
# Ensure columns are aligned to model input
input_row = df_copy.iloc[[-1]][expected_features]
y_pred = model.predict(input_row)[0]
# Update the last row's power consumption with prediction
df_copy.at[df_copy.index[-1], "power_consumption_kwh"] = y_pred
# Prepare the next timestamp: add 1800 seconds (30 mins)
next_timestamp = df_copy.iloc[-1]["timestamp"] + 1800
# Prepare new row: copy last row features but update timestamp and power consumption
new_row = df_copy.iloc[-1].copy()
new_row["power_consumption_kwh"] = y_pred
new_row["timestamp"] = next_timestamp
# Append new row safely
df_copy = pd.concat([df_copy, pd.DataFrame([new_row])], ignore_index=True)
forecasts.append({"timestamp": next_timestamp, "forecast_kwh": y_pred})
return pd.DataFrame(forecasts)
# UI Layout
st.set_page_config(layout="wide")
st.title("โšก Gridflux: Real-Time Smart Meter Dashboard")
# Layout structure
col1, col2 = st.columns([2, 1])
# Data ingestion and prediction
new_row = get_next_row()
if not new_row.empty:
st.session_state.history = pd.concat([st.session_state.history, new_row], ignore_index=True)
df_feat = engineer(st.session_state.history).dropna()
if not df_feat.empty:
# Align features exactly to what model expects
expected_features = model.feature_names_in_.tolist()
for col in expected_features:
if col not in df_feat.columns:
df_feat[col] = 0
latest_input = df_feat[expected_features].iloc[[-1]]
prediction = model.predict(latest_input)[0]
actual = new_row["power_consumption_kwh"].values[0]
mae = mean_absolute_error([actual], [prediction])
with col1:
st.subheader("๐Ÿ“Š Real-Time Power Usage")
chart_df = st.session_state.history.copy()
chart_df["datetime"] = pd.to_datetime(chart_df["timestamp"])
chart_df.set_index("datetime", inplace=True)
st.line_chart(chart_df["power_consumption_kwh"], use_container_width=True)
st.subheader("๐Ÿ”ฎ Forecast (Next 2.5 Hours)")
future_df = forecast_next(df_feat, model, steps=5)
future_df["datetime"] = pd.to_datetime(future_df["timestamp"])
fig = px.line(future_df, x="datetime", y="forecast_kwh", title="Forecasted Power Usage (Next 2.5h)")
st.plotly_chart(fig, use_container_width=True)
with col2:
st.metric("๐Ÿ”ฎ Predicted Power Usage (kWh)", f"{prediction:.3f}")
st.metric("โœ… Actual Power Usage (kWh)", f"{actual:.3f}")
st.metric("๐Ÿ“ MAE", f"{mae:.3f}")
st.subheader("๐ŸŒ Region-Wise Forecast")
for region in ["east", "west", "north", "south"]:
regional = df_feat[df_feat[f"region_{region}"] == 1]
if not regional.empty:
preds = model.predict(regional[expected_features])
st.write(f"**{region.title()} Region**: Avg Forecast: {np.mean(preds):.3f} kWh")
st.subheader("๐Ÿ  Property Type Forecast")
for region in ["east", "west", "north", "south"]:
for ptype in ["commercial", "residential"]:
filtered = df_feat[(df_feat[f"region_{region}"] == 1) & (df_feat[f"property_type_{ptype}"] == 1)]
if not filtered.empty:
preds = model.predict(filtered[expected_features])
st.write(f"{region.title()} / {ptype.title()}: {np.mean(preds):.2f} kWh")
else:
st.success("โœ… All data processed.")