k96beni commited on
Commit
162ede3
·
verified ·
1 Parent(s): 8e0fd7e

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +436 -13
src/streamlit_app.py CHANGED
@@ -1,20 +1,443 @@
1
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- # Set page title
4
  st.set_page_config(
5
- page_title="Simple Streamlit Test",
6
- page_icon=""
 
 
7
  )
8
 
9
- # Main page content
10
- st.title("Simple Streamlit Test App")
11
- st.write("This is a minimal Streamlit app to test that everything is working correctly.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- # Add a simple interactive element
14
- if st.button("Click me!"):
15
- st.success("Button clicked!")
16
- st.balloons()
17
 
18
- # Display some basic information
19
- st.subheader("Next Steps")
20
- st.info("Once this app is working, we can replace it with the full dashboard application.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import pandas as pd
3
+ import plotly.express as px
4
+ import plotly.graph_objects as go
5
+ from plotly.subplots import make_subplots
6
+ import numpy as np
7
+ import tempfile
8
+ import os
9
+ from datetime import datetime
10
+ from reportlab.lib.pagesizes import letter, A4
11
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, Table, TableStyle
12
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
13
+ from reportlab.lib import colors
14
+ from reportlab.lib.units import inch
15
+ import io
16
+ import base64
17
+ from PIL import Image as PILImage
18
+ import calendar
19
 
20
+ # Set page configuration
21
  st.set_page_config(
22
+ page_title="Charging Outlets Dashboard",
23
+ page_icon="",
24
+ layout="wide",
25
+ initial_sidebar_state="expanded"
26
  )
27
 
28
+ # Custom CSS for styling
29
+ st.markdown("""
30
+ <style>
31
+ .main {
32
+ padding: 2rem;
33
+ }
34
+ .stTabs [data-baseweb="tab-list"] {
35
+ gap: 2px;
36
+ }
37
+ .stTabs [data-baseweb="tab"] {
38
+ height: 50px;
39
+ white-space: pre-wrap;
40
+ border-radius: 4px 4px 0px 0px;
41
+ }
42
+ h1, h2, h3 {
43
+ color: #2c3e50;
44
+ }
45
+ .metric-card {
46
+ background-color: #f8f9fa;
47
+ border-radius: 8px;
48
+ padding: 20px;
49
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
50
+ text-align: center;
51
+ }
52
+ .metric-value {
53
+ font-size: 2.5rem;
54
+ font-weight: bold;
55
+ color: #3498db;
56
+ }
57
+ .metric-label {
58
+ font-size: 1.2rem;
59
+ color: #7f8c8d;
60
+ }
61
+ </style>
62
+ """, unsafe_allow_html=True)
63
 
64
+ # Page title
65
+ st.title(" Charging Outlets Analytics Dashboard")
66
+ st.markdown("Upload your data files to analyze charging outlet performance and utilization.")
 
67
 
68
+ # File upload section
69
+ st.sidebar.header("Upload Data Files")
70
+ sessions_file = st.sidebar.file_uploader("Upload Sessions.xlsx", type=["xlsx"])
71
+ overview_file = st.sidebar.file_uploader("Upload Overview.xlsx", type=["xlsx"])
72
+
73
+ # Function to preprocess data
74
+ def preprocess_data(sessions_df, overview_df):
75
+ # Convert date columns to datetime
76
+ sessions_df['Startad'] = pd.to_datetime(sessions_df['Startad'])
77
+ sessions_df['Avslutad'] = pd.to_datetime(sessions_df['Avslutad'])
78
+
79
+ # Extract year and month for analysis
80
+ sessions_df['Year'] = sessions_df['Startad'].dt.year
81
+ sessions_df['Month'] = sessions_df['Startad'].dt.month
82
+ sessions_df['Month_Name'] = sessions_df['Startad'].dt.strftime('%b')
83
+ sessions_df['Year_Month'] = sessions_df['Startad'].dt.strftime('%Y-%m')
84
+
85
+ # Calculate session duration in hours
86
+ sessions_df['Duration_Hours'] = (sessions_df['Avslutad'] - sessions_df['Startad']).dt.total_seconds() / 3600
87
+
88
+ # Clean numeric columns
89
+ if isinstance(sessions_df['Laddat (kWh)'].iloc[0], str):
90
+ sessions_df['Laddat (kWh)'] = sessions_df['Laddat (kWh)'].str.replace(',', '.').astype(float)
91
+
92
+ if isinstance(sessions_df['Kostnad (exkl)'].iloc[0], str):
93
+ sessions_df['Kostnad (exkl)'] = sessions_df['Kostnad (exkl)'].str.replace(',', '.').astype(float)
94
+
95
+ # Ensure outlet numbers are integers
96
+ sessions_df['Uttag'] = pd.to_numeric(sessions_df['Uttag'], errors='coerce').fillna(0).astype(int)
97
+
98
+ return sessions_df, overview_df
99
+
100
+ # Function to calculate metrics
101
+ def calculate_metrics(sessions_df, overview_df):
102
+ metrics = {}
103
+
104
+ # Get unique areas
105
+ unique_areas = sessions_df['Område'].unique()
106
+ metrics['unique_areas'] = unique_areas
107
+ metrics['area_count'] = len(unique_areas)
108
+
109
+ # Calculate outlets per area
110
+ outlets_per_area = sessions_df.groupby('Område')['Uttag'].nunique().reset_index()
111
+ outlets_per_area.columns = ['Område', 'Number_of_Outlets']
112
+ metrics['outlets_per_area'] = outlets_per_area
113
+
114
+ # Calculate kWh per month per area
115
+ kwh_per_month_area = sessions_df.groupby(['Område', 'Year_Month'])['Laddat (kWh)'].sum().reset_index()
116
+ metrics['kwh_per_month_area'] = kwh_per_month_area
117
+
118
+ # Calculate kWh per outlet per month per area
119
+ kwh_outlet_month_area = sessions_df.groupby(['Område', 'Year_Month', 'Uttag'])['Laddat (kWh)'].sum().reset_index()
120
+ metrics['kwh_outlet_month_area'] = kwh_outlet_month_area
121
+
122
+ # Calculate utilization (this is simplified and might need adjustment)
123
+ # First, get the total possible outlet days for each area (outlets × days in month)
124
+ all_months = sessions_df['Year_Month'].unique()
125
+ all_areas = sessions_df['Område'].unique()
126
+
127
+ utilization_data = []
128
+
129
+ for area in all_areas:
130
+ area_outlets = sessions_df[sessions_df['Område'] == area]['Uttag'].nunique()
131
+
132
+ for ym in all_months:
133
+ year, month = map(int, ym.split('-'))
134
+ days_in_month = calendar.monthrange(year, month)[1]
135
+ total_outlet_days = area_outlets * days_in_month
136
+
137
+ # Count actual used outlet days
138
+ area_month_data = sessions_df[(sessions_df['Område'] == area) &
139
+ (sessions_df['Year_Month'] == ym)]
140
+
141
+ # Get unique (outlet, day) combinations
142
+ area_month_data['Day'] = area_month_data['Startad'].dt.day
143
+ used_outlet_days = area_month_data.groupby(['Uttag', 'Day']).size().reset_index().shape[0]
144
+
145
+ utilization = used_outlet_days / total_outlet_days if total_outlet_days > 0 else 0
146
+
147
+ utilization_data.append({
148
+ 'Område': area,
149
+ 'Year_Month': ym,
150
+ 'Used_Outlet_Days': used_outlet_days,
151
+ 'Total_Outlet_Days': total_outlet_days,
152
+ 'Utilization': utilization
153
+ })
154
+
155
+ metrics['utilization'] = pd.DataFrame(utilization_data)
156
+
157
+ # Total kWh and sessions
158
+ metrics['total_kwh'] = sessions_df['Laddat (kWh)'].sum()
159
+ metrics['total_sessions'] = len(sessions_df)
160
+ metrics['avg_kwh_per_session'] = metrics['total_kwh'] / metrics['total_sessions'] if metrics['total_sessions'] > 0 else 0
161
+
162
+ return metrics
163
+
164
+ # Function to create plotly figures
165
+ def create_visualizations(metrics):
166
+ figures = {}
167
+
168
+ # 1. Bar chart: Number of outlets per area
169
+ fig_outlets = px.bar(
170
+ metrics['outlets_per_area'],
171
+ x='Område',
172
+ y='Number_of_Outlets',
173
+ title='Number of Outlets per Area',
174
+ color='Number_of_Outlets',
175
+ color_continuous_scale='Blues',
176
+ labels={'Number_of_Outlets': 'Number of Outlets', 'Område': 'Area'}
177
+ )
178
+ fig_outlets.update_layout(xaxis_tickangle=-45)
179
+ figures['outlets_per_area'] = fig_outlets
180
+
181
+ # 2. Line chart: kWh per month per area
182
+ fig_kwh = px.line(
183
+ metrics['kwh_per_month_area'],
184
+ x='Year_Month',
185
+ y='Laddat (kWh)',
186
+ color='Område',
187
+ title='kWh per Month per Area',
188
+ labels={'Laddat (kWh)': 'kWh', 'Year_Month': 'Month', 'Område': 'Area'}
189
+ )
190
+ fig_kwh.update_layout(xaxis_tickangle=-45)
191
+ figures['kwh_per_month'] = fig_kwh
192
+
193
+ # 3. Heatmap: Utilization per area per month
194
+ pivot_util = metrics['utilization'].pivot(
195
+ index='Område',
196
+ columns='Year_Month',
197
+ values='Utilization'
198
+ ).fillna(0)
199
+
200
+ fig_util = px.imshow(
201
+ pivot_util,
202
+ labels=dict(x='Month', y='Area', color='Utilization'),
203
+ x=pivot_util.columns,
204
+ y=pivot_util.index,
205
+ color_continuous_scale='Viridis',
206
+ title='Outlet Utilization Heatmap (Used Outlet Days / Total Outlet Days)'
207
+ )
208
+ fig_util.update_layout(xaxis_tickangle=-45)
209
+ figures['utilization'] = fig_util
210
+
211
+ # 4. Box plot: kWh per outlet per month per area
212
+ fig_kwh_outlet = px.box(
213
+ metrics['kwh_outlet_month_area'],
214
+ x='Område',
215
+ y='Laddat (kWh)',
216
+ color='Year_Month',
217
+ title='kWh per Outlet Distribution by Area and Month',
218
+ labels={'Laddat (kWh)': 'kWh', 'Område': 'Area', 'Year_Month': 'Month'}
219
+ )
220
+ fig_kwh_outlet.update_layout(xaxis_tickangle=-45)
221
+ figures['kwh_per_outlet'] = fig_kwh_outlet
222
+
223
+ return figures
224
+
225
+ # Function to create PDF report
226
+ def generate_pdf(metrics, figures):
227
+ buffer = io.BytesIO()
228
+ doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=72, leftMargin=72, topMargin=72, bottomMargin=72)
229
+ styles = getSampleStyleSheet()
230
+
231
+ # Create a list to hold the PDF elements
232
+ elements = []
233
+
234
+ # Add title
235
+ title_style = styles["Title"]
236
+ elements.append(Paragraph("Charging Outlets Analytics Report", title_style))
237
+ elements.append(Spacer(1, 20))
238
+
239
+ # Add date
240
+ date_style = styles["Normal"]
241
+ date_style.alignment = 1 # Center alignment
242
+ elements.append(Paragraph(f"Report generated on {datetime.now().strftime('%Y-%m-%d %H:%M')}", date_style))
243
+ elements.append(Spacer(1, 30))
244
+
245
+ # Add summary metrics
246
+ elements.append(Paragraph("Key Metrics Summary", styles["Heading2"]))
247
+ elements.append(Spacer(1, 10))
248
+
249
+ # Create a table with key metrics
250
+ data = [
251
+ ["Metric", "Value"],
252
+ ["Total Areas", metrics['area_count']],
253
+ ["Total kWh Charged", f"{metrics['total_kwh']:.2f}"],
254
+ ["Total Charging Sessions", metrics['total_sessions']],
255
+ ["Average kWh per Session", f"{metrics['avg_kwh_per_session']:.2f}"]
256
+ ]
257
+
258
+ t = Table(data, colWidths=[200, 200])
259
+ t.setStyle(TableStyle([
260
+ ('BACKGROUND', (0, 0), (1, 0), colors.lightblue),
261
+ ('TEXTCOLOR', (0, 0), (1, 0), colors.whitesmoke),
262
+ ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
263
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
264
+ ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
265
+ ('BACKGROUND', (0, 1), (-1, -1), colors.beige),
266
+ ('GRID', (0, 0), (-1, -1), 1, colors.black)
267
+ ]))
268
+
269
+ elements.append(t)
270
+ elements.append(Spacer(1, 30))
271
+
272
+ # Add visualizations
273
+ for name, fig in figures.items():
274
+ # Add section title
275
+ elements.append(Paragraph(fig.layout.title.text, styles["Heading2"]))
276
+ elements.append(Spacer(1, 10))
277
+
278
+ # Save the Plotly figure to a temporary file and add it to the PDF
279
+ img_bytes = fig.to_image(format="png", width=700, height=500, scale=1)
280
+ img_stream = io.BytesIO(img_bytes)
281
+ img = PILImage.open(img_stream)
282
+ img_width = 450
283
+ img_height = int(img_width * img.height / img.width)
284
+ elements.append(Image(img_stream, width=img_width, height=img_height))
285
+ elements.append(Spacer(1, 20))
286
+
287
+ # Build the PDF
288
+ doc.build(elements)
289
+ buffer.seek(0)
290
+ return buffer
291
+
292
+ # Main application logic
293
+ if sessions_file is not None and overview_file is not None:
294
+ try:
295
+ # Read the uploaded files
296
+ sessions_df = pd.read_excel(sessions_file)
297
+ overview_df = pd.read_excel(overview_file)
298
+
299
+ # Display data loading success message
300
+ st.sidebar.success("Data loaded successfully!")
301
+
302
+ # Show data filtering options in sidebar
303
+ st.sidebar.header("Filter Data")
304
+
305
+ # Preprocess data
306
+ sessions_df, overview_df = preprocess_data(sessions_df, overview_df)
307
+
308
+ # Get unique areas for filtering
309
+ all_areas = sorted(sessions_df['Område'].unique())
310
+
311
+ # Area selection dropdown
312
+ selected_areas = st.sidebar.multiselect(
313
+ "Select Areas",
314
+ options=all_areas,
315
+ default=all_areas
316
+ )
317
+
318
+ # Filter data based on selection
319
+ if selected_areas:
320
+ filtered_sessions = sessions_df[sessions_df['Område'].isin(selected_areas)]
321
+ else:
322
+ filtered_sessions = sessions_df # Use all data if nothing selected
323
+
324
+ # Calculate metrics
325
+ metrics = calculate_metrics(filtered_sessions, overview_df)
326
+
327
+ # Create visualizations
328
+ figures = create_visualizations(metrics)
329
+
330
+ # Create tabs for different dashboard views
331
+ tab1, tab2, tab3 = st.tabs(["Key Metrics", "Utilization Analysis", "Energy Consumption"])
332
+
333
+ with tab1:
334
+ st.header("Key Performance Metrics")
335
+
336
+ # Key metrics in cards
337
+ col1, col2, col3 = st.columns(3)
338
+
339
+ with col1:
340
+ st.markdown(f"""
341
+ <div class="metric-card">
342
+ <div class="metric-value">{metrics['area_count']}</div>
343
+ <div class="metric-label">Total Areas</div>
344
+ </div>
345
+ """, unsafe_allow_html=True)
346
+
347
+ with col2:
348
+ st.markdown(f"""
349
+ <div class="metric-card">
350
+ <div class="metric-value">{metrics['total_sessions']:,}</div>
351
+ <div class="metric-label">Total Sessions</div>
352
+ </div>
353
+ """, unsafe_allow_html=True)
354
+
355
+ with col3:
356
+ st.markdown(f"""
357
+ <div class="metric-card">
358
+ <div class="metric-value">{metrics['total_kwh']:,.2f}</div>
359
+ <div class="metric-label">Total kWh</div>
360
+ </div>
361
+ """, unsafe_allow_html=True)
362
+
363
+ st.subheader("Number of Outlets per Area")
364
+ st.plotly_chart(figures['outlets_per_area'], use_container_width=True)
365
+
366
+ st.subheader("Monthly Energy Consumption by Area")
367
+ st.plotly_chart(figures['kwh_per_month'], use_container_width=True)
368
+
369
+ with tab2:
370
+ st.header("Utilization Analysis")
371
+ st.markdown("""
372
+ This heatmap shows the utilization rate of charging outlets, calculated as:
373
+
374
+ **Utilization = Used Outlet Days / Total Outlet Days**
375
+
376
+ Where Total Outlet Days = Number of Outlets × Days in Month
377
+ """)
378
+
379
+ st.plotly_chart(figures['utilization'], use_container_width=True)
380
+
381
+ # Display utilization data table
382
+ st.subheader("Utilization Data Table")
383
+ st.dataframe(
384
+ metrics['utilization'][['Område', 'Year_Month', 'Used_Outlet_Days', 'Total_Outlet_Days', 'Utilization']]
385
+ .sort_values(['Område', 'Year_Month'])
386
+ .style.format({'Utilization': '{:.2%}'})
387
+ )
388
+
389
+ with tab3:
390
+ st.header("Energy Consumption Analysis")
391
+
392
+ st.subheader("kWh per Outlet Distribution")
393
+ st.plotly_chart(figures['kwh_per_outlet'], use_container_width=True)
394
+
395
+ # Additional insights section
396
+ st.subheader("Energy Consumption Insights")
397
+
398
+ # Average kWh per session by area
399
+ avg_kwh_per_session = filtered_sessions.groupby('Område')['Laddat (kWh)'].mean().reset_index()
400
+ avg_kwh_per_session.columns = ['Område', 'Average kWh per Session']
401
+
402
+ fig_avg_kwh = px.bar(
403
+ avg_kwh_per_session,
404
+ x='Område',
405
+ y='Average kWh per Session',
406
+ color='Average kWh per Session',
407
+ color_continuous_scale='Viridis',
408
+ labels={'Average kWh per Session': 'Avg kWh per Session', 'Område': 'Area'}
409
+ )
410
+ fig_avg_kwh.update_layout(xaxis_tickangle=-45)
411
+
412
+ st.plotly_chart(fig_avg_kwh, use_container_width=True)
413
+
414
+ # Generate PDF button
415
+ st.sidebar.header("Export Report")
416
+ if st.sidebar.button("Generate PDF Report"):
417
+ with st.spinner("Generating PDF report..."):
418
+ pdf_buffer = generate_pdf(metrics, figures)
419
+
420
+ # Create download link
421
+ b64_pdf = base64.b64encode(pdf_buffer.read()).decode()
422
+ href = f'<a href="data:application/pdf;base64,{b64_pdf}" download="charging_outlets_report.pdf">Download PDF Report</a>'
423
+ st.sidebar.markdown(href, unsafe_allow_html=True)
424
+ st.sidebar.success("PDF generated successfully!")
425
+
426
+ except Exception as e:
427
+ st.error(f"Error processing data: {e}")
428
+ st.exception(e)
429
+ else:
430
+ # Show instructions when no files are uploaded
431
+ st.info("Please upload the required Excel files to begin the analysis.")
432
+
433
+ # Show example visualizations or instructions
434
+ st.header("Dashboard Preview")
435
+ st.markdown("""
436
+ This dashboard will help you analyze charging outlet performance with:
437
+
438
+ 1. **Key Metrics** - Number of outlets per area and energy consumption over time
439
+ 2. **Utilization Analysis** - Heatmap showing outlet usage patterns
440
+ 3. **Energy Consumption** - Detailed breakdowns of energy usage by outlet
441
+
442
+ You can filter by specific areas and generate PDF reports with all visualizations.
443
+ """)