smksean commited on
Commit
2e45c28
·
verified ·
1 Parent(s): 394a27e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +476 -50
app.py CHANGED
@@ -6,9 +6,23 @@ import joblib
6
  import requests
7
  import streamlit as st
8
  from streamlit_autorefresh import st_autorefresh
 
 
 
 
 
 
9
 
10
- # Auto-refresh every 5 seconds
11
- st_autorefresh(interval=5000, key="refresh")
 
 
 
 
 
 
 
 
12
 
13
  # Load model
14
  @st.cache_resource
@@ -27,6 +41,14 @@ if "row_index" not in st.session_state:
27
  st.session_state.row_index = 0
28
  if "history" not in st.session_state:
29
  st.session_state.history = pd.DataFrame()
 
 
 
 
 
 
 
 
30
 
31
  # Fetch all data
32
  @st.cache_data
@@ -45,34 +67,24 @@ def fetch_all_data():
45
 
46
  df_all = fetch_all_data()
47
 
48
- # Debug sidebar
49
- st.sidebar.title("🛠 Debug Info")
50
- st.sidebar.write("Row index:", st.session_state.row_index)
51
- st.sidebar.write("Total rows:", len(df_all))
52
- if not df_all.empty and st.session_state.row_index < len(df_all):
53
- st.sidebar.write("Next row:", df_all.iloc[st.session_state.row_index].to_dict())
54
-
55
- # Get next row
56
- def get_next_row():
57
- if st.session_state.row_index < len(df_all):
58
- row = df_all.iloc[[st.session_state.row_index]]
59
- st.session_state.row_index += 1
60
- return row
61
- return pd.DataFrame()
62
-
63
  # Feature engineering
64
  def engineer(df):
 
 
 
 
 
65
  # Handle timestamp
66
  if pd.api.types.is_numeric_dtype(df["timestamp"]):
67
  df["datetime"] = pd.to_datetime(df["timestamp"], unit="s")
68
  else:
69
  df["datetime"] = pd.to_datetime(df["timestamp"])
70
-
71
  df["hour_of_day"] = df["datetime"].dt.hour
72
  df["lag_30min"] = df["power_consumption_kwh"].shift(1)
73
  df["lag_1h"] = df["power_consumption_kwh"].shift(2)
74
- df["rolling_avg_1h"] = df["power_consumption_kwh"].rolling(2).mean()
75
- df["rolling_avg_2h"] = df["power_consumption_kwh"].rolling(4).mean()
76
  df["is_weekend"] = df["datetime"].dt.weekday >= 5
77
  df["hour_sin"] = np.sin(2 * np.pi * df["hour_of_day"] / 24)
78
  df["hour_cos"] = np.cos(2 * np.pi * df["hour_of_day"] / 24)
@@ -82,10 +94,8 @@ def engineer(df):
82
 
83
  # Ensure all expected features exist
84
  expected_features = [
85
- 'lag_30min', 'lag_1h',
86
- 'rolling_avg_1h', 'rolling_avg_2h',
87
- 'hour_of_day', 'is_weekend',
88
- 'hour_sin', 'hour_cos',
89
  'temperature_c', 'ev_owner', 'solar_installed',
90
  'property_type_commercial', 'property_type_residential',
91
  'region_north', 'region_south', 'region_east', 'region_west'
@@ -97,37 +107,453 @@ def engineer(df):
97
 
98
  return df
99
 
100
- # UI layout
101
- st.title("⚡ Gridflux: Live Smart-Meter Forecast")
102
-
103
- placeholder_chart = st.empty()
104
- placeholder_metric = st.empty()
105
-
106
- new_row = get_next_row()
107
-
108
- if not new_row.empty:
109
- st.session_state.history = pd.concat([st.session_state.history, new_row], ignore_index=True)
110
- df_feat = engineer(st.session_state.history).dropna()
111
-
112
- if not df_feat.empty:
113
- latest_input = df_feat.iloc[[-1]][[
114
- 'lag_30min', 'lag_1h',
115
- 'rolling_avg_1h', 'rolling_avg_2h',
116
- 'hour_of_day', 'is_weekend',
117
- 'hour_sin', 'hour_cos',
118
  'temperature_c', 'ev_owner', 'solar_installed',
119
  'property_type_commercial', 'property_type_residential',
120
  'region_north', 'region_south', 'region_east', 'region_west'
121
  ]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
- prediction = model.predict(latest_input)[0]
 
124
 
125
- # Show outputs
126
- chart_df = st.session_state.history.copy()
127
- chart_df["datetime"] = pd.to_datetime(chart_df["timestamp"])
128
- chart_df.set_index("datetime", inplace=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
- placeholder_chart.line_chart(chart_df["power_consumption_kwh"])
131
- placeholder_metric.metric("🔮 Predicted Power Usage (kWh)", f"{prediction:.3f}")
132
  else:
133
- st.success("✅ All data processed.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  import requests
7
  import streamlit as st
8
  from streamlit_autorefresh import st_autorefresh
9
+ import plotly.express as px
10
+ import plotly.graph_objects as go
11
+ from plotly.subplots import make_subplots
12
+ from sklearn.metrics import mean_squared_error, mean_absolute_error
13
+ import warnings
14
+ warnings.filterwarnings('ignore')
15
 
16
+ # Page configuration
17
+ st.set_page_config(
18
+ page_title="Gridflux Smart Meter Dashboard",
19
+ page_icon="⚡",
20
+ layout="wide",
21
+ initial_sidebar_state="expanded"
22
+ )
23
+
24
+ # Auto-refresh every 2 seconds
25
+ st_autorefresh(interval=2000, key="refresh")
26
 
27
  # Load model
28
  @st.cache_resource
 
41
  st.session_state.row_index = 0
42
  if "history" not in st.session_state:
43
  st.session_state.history = pd.DataFrame()
44
+ if "performance_metrics" not in st.session_state:
45
+ st.session_state.performance_metrics = pd.DataFrame()
46
+ if "evaluation_count" not in st.session_state:
47
+ st.session_state.evaluation_count = 0
48
+ if "temp_predictions" not in st.session_state:
49
+ st.session_state.temp_predictions = []
50
+ if "temp_actuals" not in st.session_state:
51
+ st.session_state.temp_actuals = []
52
 
53
  # Fetch all data
54
  @st.cache_data
 
67
 
68
  df_all = fetch_all_data()
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  # Feature engineering
71
  def engineer(df):
72
+ if df.empty:
73
+ return df
74
+
75
+ df = df.copy()
76
+
77
  # Handle timestamp
78
  if pd.api.types.is_numeric_dtype(df["timestamp"]):
79
  df["datetime"] = pd.to_datetime(df["timestamp"], unit="s")
80
  else:
81
  df["datetime"] = pd.to_datetime(df["timestamp"])
82
+
83
  df["hour_of_day"] = df["datetime"].dt.hour
84
  df["lag_30min"] = df["power_consumption_kwh"].shift(1)
85
  df["lag_1h"] = df["power_consumption_kwh"].shift(2)
86
+ df['rolling_avg_1h'] = df['power_consumption_kwh'].rolling(2).mean().shift(1)
87
+ df['rolling_avg_2h'] = df['power_consumption_kwh'].rolling(4).mean().shift(1)
88
  df["is_weekend"] = df["datetime"].dt.weekday >= 5
89
  df["hour_sin"] = np.sin(2 * np.pi * df["hour_of_day"] / 24)
90
  df["hour_cos"] = np.cos(2 * np.pi * df["hour_of_day"] / 24)
 
94
 
95
  # Ensure all expected features exist
96
  expected_features = [
97
+ 'lag_30min', 'lag_1h', 'rolling_avg_1h', 'rolling_avg_2h',
98
+ 'hour_of_day', 'is_weekend', 'hour_sin', 'hour_cos',
 
 
99
  'temperature_c', 'ev_owner', 'solar_installed',
100
  'property_type_commercial', 'property_type_residential',
101
  'region_north', 'region_south', 'region_east', 'region_west'
 
107
 
108
  return df
109
 
110
+ # Multi-step forecasting function
111
+ def forecast_future(df_feat, model, steps=4):
112
+ """Forecast multiple steps into the future using lag features"""
113
+ if df_feat.empty:
114
+ return []
115
+
116
+ forecasts = []
117
+ current_data = df_feat.iloc[-1:].copy()
118
+
119
+ for step in range(steps):
120
+ features = current_data[[
121
+ 'lag_30min', 'lag_1h', 'rolling_avg_1h', 'rolling_avg_2h',
122
+ 'hour_of_day', 'is_weekend', 'hour_sin', 'hour_cos',
 
 
 
 
 
123
  'temperature_c', 'ev_owner', 'solar_installed',
124
  'property_type_commercial', 'property_type_residential',
125
  'region_north', 'region_south', 'region_east', 'region_west'
126
  ]]
127
+
128
+ prediction = model.predict(features)[0]
129
+ forecasts.append(prediction)
130
+
131
+ # Update features for next step
132
+ current_data = current_data.copy()
133
+ current_data['lag_1h'] = current_data['lag_30min'].values[0]
134
+ current_data['lag_30min'] = prediction
135
+ current_data['rolling_avg_1h'] = (current_data['lag_30min'].values[0] + current_data['lag_1h'].values[0]) / 2
136
+ current_data['rolling_avg_2h'] = prediction
137
+
138
+ # Update time-based features
139
+ current_hour = current_data['hour_of_day'].values[0]
140
+ next_hour = (current_hour + 1) % 24
141
+ current_data['hour_of_day'] = next_hour
142
+ current_data['hour_sin'] = np.sin(2 * np.pi * next_hour / 24)
143
+ current_data['hour_cos'] = np.cos(2 * np.pi * next_hour / 24)
144
+
145
+ return forecasts
146
+
147
+ # Performance evaluation with batch processing
148
+ def update_performance_metrics(actual, predicted):
149
+ """Update performance metrics every 10 evaluations"""
150
+ st.session_state.temp_actuals.append(actual)
151
+ st.session_state.temp_predictions.append(predicted)
152
+ st.session_state.evaluation_count += 1
153
+
154
+ # Calculate metrics every 10 evaluations
155
+ if st.session_state.evaluation_count % 10 == 0:
156
+ if len(st.session_state.temp_actuals) >= 10:
157
+ rmse = np.sqrt(mean_squared_error(st.session_state.temp_actuals, st.session_state.temp_predictions))
158
+ mae = mean_absolute_error(st.session_state.temp_actuals, st.session_state.temp_predictions)
159
+
160
+ # Store metrics
161
+ new_metric = pd.DataFrame({
162
+ 'timestamp': [pd.Timestamp.now()],
163
+ 'rmse': [rmse],
164
+ 'mae': [mae],
165
+ 'batch_size': [len(st.session_state.temp_actuals)]
166
+ })
167
+
168
+ st.session_state.performance_metrics = pd.concat([
169
+ st.session_state.performance_metrics, new_metric
170
+ ], ignore_index=True)
171
+
172
+ # Clear temporary storage
173
+ st.session_state.temp_actuals = []
174
+ st.session_state.temp_predictions = []
175
+
176
+ return rmse, mae
177
+
178
+ return None, None
179
+
180
+ # Get next row
181
+ def get_next_row():
182
+ if st.session_state.row_index < len(df_all):
183
+ row = df_all.iloc[[st.session_state.row_index]]
184
+ st.session_state.row_index += 1
185
+ return row
186
+ return pd.DataFrame()
187
+
188
+ # UI Layout
189
+ st.title("⚡ Gridflux: Smart Meter Forecasting Dashboard")
190
+ st.markdown("*Real-time power consumption forecasting and monitoring system*")
191
+
192
+ # Sidebar
193
+ st.sidebar.header("📊 System Status")
194
+ st.sidebar.metric("Records Processed", st.session_state.row_index)
195
+ st.sidebar.metric("Evaluations", st.session_state.evaluation_count)
196
+ st.sidebar.metric("Performance Batches", len(st.session_state.performance_metrics))
197
 
198
+ # Main processing
199
+ new_row = get_next_row()
200
 
201
+ if not new_row.empty:
202
+ st.session_state.history = pd.concat([st.session_state.history, new_row], ignore_index=True)
203
+
204
+ # Create tabs
205
+ tab1, tab2, tab3 = st.tabs(["🔮 Regional Forecasting", "📈 Performance Monitor", "🔄 Usage Patterns"])
206
+
207
+ with tab1:
208
+ st.header("Multi-Step Forecasting by Region & Property Type")
209
+ st.markdown("*Forecasting 2 hours ahead (30min intervals) for each region and property type combination*")
210
+
211
+ regions = ['north', 'south', 'east', 'west']
212
+ property_types = ['residential', 'commercial']
213
+
214
+ # Create forecast grid
215
+ for region in regions:
216
+ st.subheader(f"🌍 {region.upper()} Region")
217
+
218
+ region_data = st.session_state.history[st.session_state.history['region'] == region]
219
+
220
+ if not region_data.empty:
221
+ col1, col2 = st.columns(2)
222
+
223
+ for idx, prop_type in enumerate(property_types):
224
+ subset = region_data[region_data['property_type'] == prop_type]
225
+
226
+ if not subset.empty and len(subset) > 2:
227
+ df_feat = engineer(subset).dropna()
228
+
229
+ if not df_feat.empty:
230
+ # Get forecasts
231
+ forecasts = forecast_future(df_feat, model, steps=4)
232
+
233
+ # Display in appropriate column
234
+ with col1 if idx == 0 else col2:
235
+ st.markdown(f"**🏠 {prop_type.capitalize()} Properties**")
236
+
237
+ if forecasts:
238
+ # Create forecast metrics in a nice layout
239
+ forecast_col1, forecast_col2 = st.columns(2)
240
+
241
+ with forecast_col1:
242
+ st.metric("30min Ahead", f"{forecasts[0]:.3f} kWh",
243
+ delta=f"{forecasts[0] - df_feat['power_consumption_kwh'].iloc[-1]:.3f}")
244
+ st.metric("1.5h Ahead", f"{forecasts[2]:.3f} kWh")
245
+
246
+ with forecast_col2:
247
+ st.metric("1h Ahead", f"{forecasts[1]:.3f} kWh")
248
+ st.metric("2h Ahead", f"{forecasts[3]:.3f} kWh")
249
+
250
+ # Create mini forecast chart
251
+ chart_data = subset.copy()
252
+ chart_data["datetime"] = pd.to_datetime(chart_data["timestamp"])
253
+
254
+ # Get last few points for context
255
+ recent_data = chart_data.tail(10)
256
+
257
+ fig = go.Figure()
258
+
259
+ # Historical data
260
+ fig.add_trace(go.Scatter(
261
+ x=recent_data["datetime"],
262
+ y=recent_data["power_consumption_kwh"],
263
+ mode='lines+markers',
264
+ name='Historical',
265
+ line=dict(color='blue', width=2)
266
+ ))
267
+
268
+ # Forecast data
269
+ last_time = recent_data["datetime"].iloc[-1]
270
+ future_times = pd.date_range(
271
+ start=last_time + pd.Timedelta(minutes=30),
272
+ periods=4, freq='30min'
273
+ )
274
+
275
+ fig.add_trace(go.Scatter(
276
+ x=future_times,
277
+ y=forecasts,
278
+ mode='lines+markers',
279
+ name='Forecast',
280
+ line=dict(color='red', dash='dash', width=2)
281
+ ))
282
+
283
+ fig.update_layout(
284
+ title=f"{region.title()} {prop_type.title()} - Forecast",
285
+ xaxis_title="Time",
286
+ yaxis_title="Power (kWh)",
287
+ height=300,
288
+ showlegend=True
289
+ )
290
+
291
+ st.plotly_chart(fig, use_container_width=True)
292
+
293
+ # Update performance metrics
294
+ if len(df_feat) > 1:
295
+ actual = df_feat['power_consumption_kwh'].iloc[-1]
296
+ predicted = forecasts[0] # Use 30min forecast
297
+ update_performance_metrics(actual, predicted)
298
+
299
+ else:
300
+ st.info("Insufficient data for forecasting")
301
+ else:
302
+ with col1 if idx == 0 else col2:
303
+ st.markdown(f"**🏠 {prop_type.capitalize()} Properties**")
304
+ st.info("No data available")
305
+ else:
306
+ st.info(f"No data available for {region.upper()} region")
307
+
308
+ st.divider()
309
+
310
+ with tab2:
311
+ st.header("Real-Time Model Performance")
312
+ st.markdown("*Performance metrics calculated every 10 evaluations to ensure statistical significance*")
313
+
314
+ # Current batch status
315
+ batch_progress = st.session_state.evaluation_count % 10
316
+ st.progress(batch_progress / 10, text=f"Current batch: {batch_progress}/10 evaluations")
317
+
318
+ if len(st.session_state.performance_metrics) > 0:
319
+ # Latest metrics
320
+ latest_metrics = st.session_state.performance_metrics.iloc[-1]
321
+
322
+ col1, col2, col3, col4 = st.columns(4)
323
+
324
+ with col1:
325
+ st.metric("Latest RMSE", f"{latest_metrics['rmse']:.4f}")
326
+ with col2:
327
+ st.metric("Latest MAE", f"{latest_metrics['mae']:.4f}")
328
+ with col3:
329
+ st.metric("Batch Size", f"{int(latest_metrics['batch_size'])}")
330
+ with col4:
331
+ st.metric("Total Batches", len(st.session_state.performance_metrics))
332
+
333
+ # Performance trends
334
+ st.subheader("📊 Performance Trends Over Time")
335
+
336
+ if len(st.session_state.performance_metrics) > 1:
337
+ fig = make_subplots(
338
+ rows=2, cols=1,
339
+ subplot_titles=('Root Mean Square Error (RMSE)', 'Mean Absolute Error (MAE)'),
340
+ shared_xaxes=True,
341
+ vertical_spacing=0.1
342
+ )
343
+
344
+ # RMSE plot
345
+ fig.add_trace(
346
+ go.Scatter(
347
+ x=st.session_state.performance_metrics['timestamp'],
348
+ y=st.session_state.performance_metrics['rmse'],
349
+ mode='lines+markers',
350
+ name='RMSE',
351
+ line=dict(color='#ff6b6b', width=3),
352
+ marker=dict(size=8)
353
+ ),
354
+ row=1, col=1
355
+ )
356
+
357
+ # MAE plot
358
+ fig.add_trace(
359
+ go.Scatter(
360
+ x=st.session_state.performance_metrics['timestamp'],
361
+ y=st.session_state.performance_metrics['mae'],
362
+ mode='lines+markers',
363
+ name='MAE',
364
+ line=dict(color='#4ecdc4', width=3),
365
+ marker=dict(size=8)
366
+ ),
367
+ row=2, col=1
368
+ )
369
+
370
+ fig.update_layout(
371
+ height=500,
372
+ title_text="Model Performance Monitoring",
373
+ showlegend=False
374
+ )
375
+
376
+ fig.update_xaxes(title_text="Time", row=2, col=1)
377
+ fig.update_yaxes(title_text="RMSE", row=1, col=1)
378
+ fig.update_yaxes(title_text="MAE", row=2, col=1)
379
+
380
+ st.plotly_chart(fig, use_container_width=True)
381
+
382
+ # Performance summary
383
+ st.subheader("📈 Performance Summary")
384
+
385
+ col1, col2 = st.columns(2)
386
+
387
+ with col1:
388
+ st.markdown("**RMSE Statistics**")
389
+ st.metric("Average", f"{st.session_state.performance_metrics['rmse'].mean():.4f}")
390
+ st.metric("Best (Lowest)", f"{st.session_state.performance_metrics['rmse'].min():.4f}")
391
+ st.metric("Std Deviation", f"{st.session_state.performance_metrics['rmse'].std():.4f}")
392
+
393
+ with col2:
394
+ st.markdown("**MAE Statistics**")
395
+ st.metric("Average", f"{st.session_state.performance_metrics['mae'].mean():.4f}")
396
+ st.metric("Best (Lowest)", f"{st.session_state.performance_metrics['mae'].min():.4f}")
397
+ st.metric("Std Deviation", f"{st.session_state.performance_metrics['mae'].std():.4f}")
398
+ else:
399
+ st.info("🔄 Collecting data... Performance metrics will appear after 10 evaluations")
400
+
401
+ with tab3:
402
+ st.header("Power Usage Patterns & Cycles")
403
+ st.markdown("*Understanding power consumption patterns across different regions and time periods*")
404
+
405
+ if len(st.session_state.history) > 0:
406
+ # Prepare data
407
+ cycle_data = st.session_state.history.copy()
408
+ cycle_data["datetime"] = pd.to_datetime(cycle_data["timestamp"])
409
+ cycle_data["hour"] = cycle_data["datetime"].dt.hour
410
+ cycle_data["day_of_week"] = cycle_data["datetime"].dt.day_name()
411
+ cycle_data["is_weekend"] = cycle_data["datetime"].dt.weekday >= 5
412
+
413
+ # Hourly patterns by region
414
+ st.subheader("⏰ 24-Hour Usage Patterns by Region")
415
+
416
+ hourly_usage = cycle_data.groupby(['region', 'hour'])['power_consumption_kwh'].mean().reset_index()
417
+
418
+ fig = px.line(
419
+ hourly_usage,
420
+ x='hour',
421
+ y='power_consumption_kwh',
422
+ color='region',
423
+ title='Average Power Consumption Throughout the Day',
424
+ labels={
425
+ 'hour': 'Hour of Day (24-hour format)',
426
+ 'power_consumption_kwh': 'Average Power Consumption (kWh)',
427
+ 'region': 'Region'
428
+ }
429
+ )
430
+
431
+ fig.update_layout(
432
+ xaxis=dict(tickmode='linear', tick0=0, dtick=2),
433
+ hovermode='x unified',
434
+ height=400
435
+ )
436
+
437
+ # Add annotations for typical usage periods
438
+ fig.add_vrect(x0=6, x1=9, fillcolor="yellow", opacity=0.2, annotation_text="Morning Peak")
439
+ fig.add_vrect(x0=17, x1=21, fillcolor="orange", opacity=0.2, annotation_text="Evening Peak")
440
+ fig.add_vrect(x0=22, x1=6, fillcolor="blue", opacity=0.1, annotation_text="Night/Low Usage")
441
+
442
+ st.plotly_chart(fig, use_container_width=True)
443
+
444
+ # Usage insights
445
+ st.subheader("🔍 Usage Insights")
446
+
447
+ col1, col2 = st.columns(2)
448
+
449
+ with col1:
450
+ st.markdown("**📊 Regional Summary**")
451
+
452
+ regional_stats = cycle_data.groupby('region')['power_consumption_kwh'].agg([
453
+ 'mean', 'std', 'min', 'max', 'count'
454
+ ]).round(3)
455
+
456
+ regional_stats.columns = ['Avg (kWh)', 'Std Dev', 'Min (kWh)', 'Max (kWh)', 'Data Points']
457
+ st.dataframe(regional_stats, use_container_width=True)
458
+
459
+ with col2:
460
+ st.markdown("**⏰ Peak Usage Times**")
461
+
462
+ # Find peak hours for each region
463
+ peak_hours = hourly_usage.loc[hourly_usage.groupby('region')['power_consumption_kwh'].idxmax()]
464
+ peak_display = peak_hours[['region', 'hour', 'power_consumption_kwh']].copy()
465
+ peak_display.columns = ['Region', 'Peak Hour', 'Peak Usage (kWh)']
466
+ peak_display['Peak Hour'] = peak_display['Peak Hour'].apply(lambda x: f"{x:02d}:00")
467
+ peak_display['Peak Usage (kWh)'] = peak_display['Peak Usage (kWh)'].round(3)
468
+
469
+ st.dataframe(peak_display.set_index('Region'), use_container_width=True)
470
+
471
+ # Weekend vs Weekday comparison
472
+ st.subheader("📅 Weekend vs Weekday Usage")
473
+
474
+ weekend_comparison = cycle_data.groupby(['region', 'is_weekend'])['power_consumption_kwh'].mean().reset_index()
475
+ weekend_comparison['period'] = weekend_comparison['is_weekend'].map({True: 'Weekend', False: 'Weekday'})
476
+
477
+ fig_weekend = px.bar(
478
+ weekend_comparison,
479
+ x='region',
480
+ y='power_consumption_kwh',
481
+ color='period',
482
+ title='Average Power Consumption: Weekday vs Weekend',
483
+ labels={
484
+ 'region': 'Region',
485
+ 'power_consumption_kwh': 'Average Power Consumption (kWh)'
486
+ },
487
+ barmode='group'
488
+ )
489
+
490
+ fig_weekend.update_layout(height=400)
491
+ st.plotly_chart(fig_weekend, use_container_width=True)
492
+
493
+ # Property type patterns
494
+ if 'property_type' in cycle_data.columns:
495
+ st.subheader("🏠 Property Type Usage Patterns")
496
+
497
+ prop_patterns = cycle_data.groupby(['property_type', 'hour'])['power_consumption_kwh'].mean().reset_index()
498
+
499
+ fig_prop = px.line(
500
+ prop_patterns,
501
+ x='hour',
502
+ y='power_consumption_kwh',
503
+ color='property_type',
504
+ title='Usage Patterns by Property Type',
505
+ labels={
506
+ 'hour': 'Hour of Day',
507
+ 'power_consumption_kwh': 'Average Power Consumption (kWh)',
508
+ 'property_type': 'Property Type'
509
+ }
510
+ )
511
+
512
+ fig_prop.update_layout(
513
+ xaxis=dict(tickmode='linear', tick0=0, dtick=2),
514
+ height=400
515
+ )
516
+
517
+ st.plotly_chart(fig_prop, use_container_width=True)
518
+ else:
519
+ st.info("📊 Collecting usage data... Patterns will appear as data accumulates")
520
 
 
 
521
  else:
522
+ st.success("✅ All data processed successfully!")
523
+
524
+ # Final summary
525
+ if len(st.session_state.history) > 0:
526
+ st.balloons()
527
+
528
+ st.header("📋 Processing Summary")
529
+
530
+ col1, col2, col3, col4 = st.columns(4)
531
+
532
+ with col1:
533
+ st.metric("Total Records", len(st.session_state.history))
534
+ with col2:
535
+ st.metric("Regions Covered", st.session_state.history['region'].nunique())
536
+ with col3:
537
+ st.metric("Property Types", st.session_state.history['property_type'].nunique())
538
+ with col4:
539
+ st.metric("Performance Evaluations", st.session_state.evaluation_count)
540
+
541
+ # Enhanced debug sidebar
542
+ with st.sidebar:
543
+ st.divider()
544
+
545
+ if st.checkbox("🔧 Show Debug Details"):
546
+ st.write("**Data Status:**")
547
+ st.write(f"- History shape: {st.session_state.history.shape}")
548
+ st.write(f"- Temp predictions: {len(st.session_state.temp_predictions)}")
549
+ st.write(f"- Temp actuals: {len(st.session_state.temp_actuals)}")
550
+
551
+ if not st.session_state.history.empty:
552
+ st.write("**Latest Record:**")
553
+ latest = st.session_state.history.iloc[-1]
554
+ st.json({
555
+ "region": latest.get('region', 'N/A'),
556
+ "property_type": latest.get('property_type', 'N/A'),
557
+ "power_consumption": f"{latest.get('power_consumption_kwh', 0):.3f} kWh",
558
+ "timestamp": str(latest.get('timestamp', 'N/A'))
559
+ })