MyEnny commited on
Commit
4e97069
ยท
verified ยท
1 Parent(s): 7743726

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +306 -0
app.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ from datetime import datetime, timedelta
7
+ from joblib import load
8
+ import requests
9
+ import pytz
10
+ import time
11
+
12
+
13
+ # Constants
14
+ SUPABASE_URL = "https://ubbyirdtynaerjodadal.supabase.co"
15
+ SUPABASE_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InViYnlpcmR0eW5hZXJqb2RhZGFsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI0OTIyNjcsImV4cCI6MjA2ODA2ODI2N30.iTHJ18BZED_gE5VyZrBp7YWiy6NNzsA1YdqeazFtxZI"
16
+ TABLE = "smart_meter_readings_1year"
17
+ TIMEZONE = pytz.timezone("Europe/London")
18
+ now = pd.Timestamp.now(tz='UTC')
19
+
20
+ def auto_refresh(interval_seconds=60):
21
+ time.sleep(interval_seconds)
22
+ st.rerun()
23
+
24
+ st.set_page_config(page_title="Electric Grid Dashboard", layout="wide")
25
+
26
+ @st.cache_data(ttl=120)
27
+ def load_data():
28
+ url = f"{SUPABASE_URL}/rest/v1/{TABLE}?timestamp=lt.{datetime.now().isoformat()}"
29
+ headers = {
30
+ "apikey": SUPABASE_API_KEY,
31
+ "Authorization": f"Bearer {SUPABASE_API_KEY}"
32
+ }
33
+ res = requests.get(url, headers=headers)
34
+ if res.status_code != 200:
35
+ st.error(f"Failed to fetch data: {res.status_code}")
36
+ st.stop()
37
+ df = pd.DataFrame(res.json())
38
+ df['datetime'] = pd.to_datetime(df['timestamp'], utc=True)
39
+ df['hour_of_day'] = df['datetime'].dt.hour
40
+ df = df.set_index('datetime')
41
+ df.sort_index(inplace=True)
42
+ df['date'] = df.index.date
43
+ df['week'] = df.index.isocalendar().week
44
+ df['day_of_week'] = df.index.day_name()
45
+ df['hour_sin'] = np.sin(2 * np.pi * df['hour_of_day'] / 24)
46
+ df['hour_cos'] = np.cos(2 * np.pi * df['hour_of_day'] / 24)
47
+ df['lag_30mins'] = df['power_consumption_kwh'].shift(1)
48
+ df['lag_1hr'] = df['power_consumption_kwh'].shift(2)
49
+ df['roll_mean_1hr'] = df['power_consumption_kwh'].shift(1).rolling(2).mean()
50
+ df['roll_mean_2hr'] = df['power_consumption_kwh'].shift(1).rolling(4).mean()
51
+ df[['lag_30mins', 'lag_1hr', 'roll_mean_1hr', 'roll_mean_2hr']] = df[[
52
+ 'lag_30mins', 'lag_1hr', 'roll_mean_1hr', 'roll_mean_2hr'
53
+ ]].ffill().fillna(0)
54
+
55
+
56
+ df = df.drop(columns=['date', 'hour_of_day'])
57
+ df = pd.get_dummies(df, columns=['region', 'property_type', 'day_of_week'], drop_first=False)
58
+ df = df.astype({col: 'int' for col in df.select_dtypes('bool').columns})
59
+ return df
60
+
61
+ def main():
62
+ # Load data and model
63
+ data = load_data()
64
+ model = load('rf_model.joblib')
65
+
66
+ # Generate forecasts
67
+ features = data.drop(columns=['power_consumption_kwh', 'timestamp'], errors='ignore')
68
+ data['forecast'] = model.predict(features[model.feature_names_in_])
69
+
70
+ # Calculate performance metrics
71
+ latest_data = data.loc[data.index > pd.Timestamp.now(tz='UTC') - pd.Timedelta('1D')]
72
+ rmse = np.sqrt((latest_data['power_consumption_kwh'] - latest_data['forecast'])**2).mean()
73
+ mae = (latest_data['power_consumption_kwh'] - latest_data['forecast']).abs().mean()
74
+ current_error = (data['power_consumption_kwh'].iloc[-1] - data['forecast'].iloc[-1]) / data['power_consumption_kwh'].iloc[-1] * 100
75
+
76
+ # Title and description
77
+ st.title("๐ŸŒก๏ธ Real-Time Energy Dashboard")
78
+ st.markdown("Monitoring power consumption, environmental factors, and forecast accuracy across regions")
79
+
80
+ # Sidebar filters
81
+ st.sidebar.header("Filter Options")
82
+ # Build readable region and property_type filters
83
+ region_columns = list(data.filter(like='region_').columns)
84
+ region_labels = ['All'] + [col.replace('region_', '') for col in region_columns]
85
+ region = st.sidebar.selectbox("Region", region_labels)
86
+
87
+ property_columns = list(data.filter(like='property_type_').columns)
88
+ property_labels = ['All'] + [col.replace('property_type_', '') for col in property_columns]
89
+ property_selection = st.sidebar.selectbox("Property Type", property_labels)
90
+
91
+ time_range = st.sidebar.select_slider("Time Range",
92
+ options=['1h', '6h', '12h', '1D', '1W'],
93
+ value='12h')
94
+ filtered_data = data.copy()
95
+
96
+ # Apply region filter
97
+ if region != 'All':
98
+ region_col = f"region_{region}"
99
+ if region_col in filtered_data.columns:
100
+ filtered_data = filtered_data[filtered_data[region_col] == 1]
101
+
102
+ # Apply property_type filter
103
+ if property_selection != 'All':
104
+ property_col = f"property_type_{property_selection}"
105
+ if property_col in filtered_data.columns:
106
+ filtered_data = filtered_data[filtered_data[property_col] == 1]
107
+
108
+ # Apply time filter
109
+ filtered_data = filtered_data.loc[filtered_data.index > now - pd.Timedelta(time_range)]
110
+ #filtered_data = filtered_data.loc[filtered_data.index > pd.Timestamp.now(tz='UTC') - pd.Timedelta(time_range)]
111
+
112
+ # Current metrics
113
+ current = filtered_data.iloc[-1]
114
+ # show metrics here
115
+ st.subheader("๐Ÿ“Š Current Energy Status")
116
+ col1, col2, col3, col4 = st.columns(4)
117
+ col1.metric("Power Consumption", f"{current['power_consumption_kwh']:.2f} kWh",
118
+ delta=f"{current_error:.1f}% error", delta_color="inverse")
119
+ col2.metric("Voltage", f"{current['voltage']:.1f} V")
120
+ col3.metric("Temperature", f"{current['temperature_c']:.1f}ยฐC")
121
+ col4.metric("Humidity", f"{current['humidity_pct']:.1f}%")
122
+
123
+ # --- 2-Hour Forecast ---
124
+ st.subheader("๐Ÿ”ฎ Next 2 Hours Forecast")
125
+
126
+ latest_row = data.iloc[-1:].copy()
127
+ forecast_steps = []
128
+ timestamps = []
129
+
130
+ for i in range(1, 5): # 4 steps = next 2 hours (30-min intervals)
131
+ future_time = latest_row.index[0] + timedelta(minutes=30 * i)
132
+ timestamps.append(future_time)
133
+
134
+ hour = future_time.hour
135
+ hour_sin = np.sin(2 * np.pi * hour / 24)
136
+ hour_cos = np.cos(2 * np.pi * hour / 24)
137
+
138
+ new_row = latest_row.copy()
139
+ new_row.index = [future_time]
140
+ new_row['hour_sin'] = hour_sin
141
+ new_row['hour_cos'] = hour_cos
142
+
143
+ # Lags and rolling values
144
+ if i == 1:
145
+ lag_30 = latest_row['power_consumption_kwh'].values[0]
146
+ lag_1hr = latest_row['lag_30mins'].values[0]
147
+ roll_1hr = np.mean([lag_30, lag_1hr])
148
+ roll_2hr = np.mean([lag_30, lag_1hr, latest_row['lag_1hr'].values[0], latest_row['roll_mean_1hr'].values[0]])
149
+ else:
150
+ lag_30 = forecast_steps[-1]
151
+ lag_1hr = forecast_steps[-2] if i > 2 else latest_row['power_consumption_kwh'].values[0]
152
+ roll_1hr = np.mean([lag_30, lag_1hr])
153
+ roll_2hr = np.mean(forecast_steps[-3:] + [lag_1hr]) if i > 3 else roll_1hr
154
+
155
+ new_row['lag_30mins'] = lag_30
156
+ new_row['lag_1hr'] = lag_1hr
157
+ new_row['roll_mean_1hr'] = roll_1hr
158
+ new_row['roll_mean_2hr'] = roll_2hr
159
+
160
+ X_future = new_row[model.feature_names_in_]
161
+ y_pred = model.predict(X_future)[0]
162
+ forecast_steps.append(y_pred)
163
+
164
+ # Format forecast results
165
+ forecast_df = pd.DataFrame({
166
+ "datetime": timestamps,
167
+ "forecast_kwh": forecast_steps
168
+ }).set_index("datetime")
169
+
170
+ # --- Display 30 min / 1 hr / 2 hr Forecast ---
171
+ col1, col2, col3 = st.columns(3)
172
+ col1.metric("In 30 mins", f"{forecast_steps[0]:.2f} kWh", timestamps[0].strftime('%H:%M'))
173
+ col2.metric("In 1 hour", f"{forecast_steps[1]:.2f} kWh", timestamps[1].strftime('%H:%M'))
174
+ col3.metric("In 2 hours", f"{forecast_steps[3]:.2f} kWh", timestamps[3].strftime('%H:%M'))
175
+
176
+ # Plot forecast
177
+ fig_forecast = go.Figure()
178
+ fig_forecast.add_trace(go.Scatter(x=forecast_df.index, y=forecast_df['forecast_kwh'],
179
+ mode='lines+markers', name="Forecast"))
180
+ fig_forecast.update_layout(title="2-Hour Ahead Forecast", xaxis_title="Time", yaxis_title="kWh")
181
+ st.plotly_chart(fig_forecast, use_container_width=True)
182
+
183
+ # Performance metrics
184
+ # Model Performance: Current and 12-Hour Highs/Lows ---
185
+ st.subheader("๐Ÿ“ Model Performance (Last 12 Hours, 30-Min Intervals)")
186
+
187
+ # Step 1: Prepare error columns
188
+ perf_df = data[['power_consumption_kwh', 'forecast']].copy()
189
+ perf_df['error'] = perf_df['power_consumption_kwh'] - perf_df['forecast']
190
+ perf_df['abs_error'] = perf_df['error'].abs()
191
+ perf_df['squared_error'] = perf_df['error']**2
192
+
193
+ # Step 2: Resample into 30-min intervals
194
+ interval_perf = perf_df.resample('30min').agg({
195
+ 'squared_error': 'mean',
196
+ 'abs_error': 'mean'
197
+ }).dropna()
198
+
199
+ # Limit to last 12 hours
200
+ end_time = interval_perf.index.max()
201
+ start_time = end_time -timedelta(hours=12)
202
+ last_12h_perf = interval_perf.loc[start_time:end_time].copy()
203
+ last_12h_perf['RMSE'] = np.sqrt(last_12h_perf['squared_error'])
204
+ last_12h_perf['MAE'] = last_12h_perf['abs_error']
205
+ last_12h_perf = last_12h_perf[['RMSE', 'MAE']]
206
+
207
+ # Step 3: Current metrics
208
+ current_rmse = last_12h_perf['RMSE'].iloc[-1]
209
+ current_mae = last_12h_perf['MAE'].iloc[-1]
210
+ current_time = last_12h_perf.index[-1].strftime('%Y-%m-%d %H:%M')
211
+
212
+ # Step 4: Highs and lows
213
+ lowest_rmse = last_12h_perf['RMSE'].min()
214
+ lowest_rmse_time = last_12h_perf['RMSE'].idxmin().strftime('%Y-%m-%d %H:%M')
215
+
216
+ highest_rmse = last_12h_perf['RMSE'].max()
217
+ highest_rmse_time = last_12h_perf['RMSE'].idxmax().strftime('%Y-%m-%d %H:%M')
218
+
219
+ lowest_mae = last_12h_perf['MAE'].min()
220
+ lowest_mae_time = last_12h_perf['MAE'].idxmin().strftime('%Y-%m-%d %H:%M')
221
+
222
+ highest_mae = last_12h_perf['MAE'].max()
223
+ highest_mae_time = last_12h_perf['MAE'].idxmax().strftime('%Y-%m-%d %H:%M')
224
+
225
+ # Step 5: Display
226
+ col1, col2 = st.columns(2)
227
+ col1.metric("Current RMSE", f"{current_rmse:.3f} kWh", current_time)
228
+ col2.metric("Current MAE", f"{current_mae:.3f} kWh", current_time)
229
+
230
+ col3, col4, col5, col6 = st.columns(4)
231
+ col3.metric("๐Ÿ”ฝ Lowest RMSE (12h)", f"{lowest_rmse:.3f} kWh", lowest_rmse_time)
232
+ col4.metric("๐Ÿ”ผ Highest RMSE (12h)", f"{highest_rmse:.3f} kWh", highest_rmse_time)
233
+ col5.metric("๐Ÿ”ฝ Lowest MAE (12h)", f"{lowest_mae:.3f} kWh", lowest_mae_time)
234
+ col6.metric("๐Ÿ”ผ Highest MAE (12h)", f"{highest_mae:.3f} kWh", highest_mae_time)
235
+
236
+
237
+ st.subheader("๐Ÿ“ˆ RMSE and MAE over the Last 12 Hours")
238
+ fig_errors = px.line(
239
+ last_12h_perf,
240
+ x=last_12h_perf.index,
241
+ y=['RMSE', 'MAE'],
242
+ labels={'value': 'Error (kWh)', 'variable': 'Metric', 'datetime': 'Time'},
243
+ title="Model Error Metrics (30-Min Intervals)"
244
+ )
245
+ fig_errors.update_layout(
246
+ xaxis_title="Time",
247
+ yaxis_title="kWh",
248
+ template="plotly_white",
249
+ legend_title="Metric",
250
+ height=350
251
+ )
252
+ st.plotly_chart(fig_errors, use_container_width=True)
253
+
254
+ # Main content tabs
255
+ tab1, tab2, tab3 = st.tabs(["Consumption Trends", "Regional Analysis", "Environmental Factors"])
256
+
257
+ with tab1:
258
+ fig1 = px.line(filtered_data, x=filtered_data.index,
259
+ y=['power_consumption_kwh', 'forecast'],
260
+ title="Power Consumption vs Forecast")
261
+ st.plotly_chart(fig1, use_container_width=True)
262
+
263
+ # Hourly pattern
264
+ numeric_cols = filtered_data.select_dtypes(include=[np.number]).columns
265
+ hourly = filtered_data[numeric_cols].groupby(filtered_data.index.hour).mean()
266
+ fig2 = px.bar(hourly, x=hourly.index, y='power_consumption_kwh',
267
+ title="Average Hourly Consumption Pattern")
268
+ st.plotly_chart(fig2, use_container_width=True)
269
+
270
+ with tab2:
271
+ if 'region' in data.columns:
272
+ region_breakdown = data.groupby('region')['power_consumption_kwh'].sum().reset_index()
273
+ fig3 = px.pie(region_breakdown, names='region', values='power_consumption_kwh',
274
+ title="Regional Consumption Share")
275
+ st.plotly_chart(fig3, use_container_width=True)
276
+
277
+ # Regional comparison
278
+ if len(data.filter(like='region_').columns) > 0:
279
+ region_cols = data.filter(like='region_').columns
280
+ region_avg = data[region_cols].mean().reset_index()
281
+ region_avg.columns = ['Region', 'Avg Consumption']
282
+ fig4 = px.bar(region_avg, x='Region', y='Avg Consumption',
283
+ title="Average Consumption by Region")
284
+ st.plotly_chart(fig4, use_container_width=True)
285
+
286
+ with tab3:
287
+ fig5 = px.line(filtered_data, x=filtered_data.index,
288
+ y=['temperature_c', 'humidity_pct'],
289
+ title="Temperature & Humidity Trends")
290
+ st.plotly_chart(fig5, use_container_width=True)
291
+
292
+ fig6 = px.scatter(filtered_data, x='temperature_c', y='power_consumption_kwh',
293
+ color='voltage', size='humidity_pct',
294
+ title="Consumption vs Temperature (Colored by Voltage)")
295
+ st.plotly_chart(fig6, use_container_width=True)
296
+
297
+
298
+ # Footer
299
+ st.markdown("---")
300
+ st.markdown('Developed by Opeyemi Abodunrin')
301
+ st.markdown(f"Last updated: {datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S')}")
302
+ st.markdown("ยฉ 2025 Electric Forecast (Demonstration Purpose)")
303
+
304
+ if __name__ == "__main__":
305
+ main()
306
+ auto_refresh(60)