charagu-eric commited on
Commit
413175c
·
1 Parent(s): 14c8553
Files changed (1) hide show
  1. app.py +247 -149
app.py CHANGED
@@ -19,6 +19,7 @@ LIGHTS_1BR = 5
19
  LIGHTS_2BR = 8
20
  LIGHT_POWER = 6 # Watts per light
21
 
 
22
  def initialize_session_state():
23
  """Initialize session state variables"""
24
  defaults = {
@@ -32,6 +33,7 @@ def initialize_session_state():
32
  if key not in st.session_state:
33
  st.session_state[key] = value
34
 
 
35
  def calculate_lighting_consumption(occupancy_1br: float, occupancy_2br: float) -> float:
36
  """Calculate daily lighting consumption"""
37
  return (
@@ -39,6 +41,7 @@ def calculate_lighting_consumption(occupancy_1br: float, occupancy_2br: float) -
39
  + (occupancy_2br * TWO_BR_UNITS * LIGHTS_2BR * LIGHT_POWER / 1000)
40
  ) * 6 # 6 hours per day
41
 
 
42
  def calculate_appliance_consumption(
43
  occupancy_1br: float, occupancy_2br: float
44
  ) -> float:
@@ -49,6 +52,7 @@ def calculate_appliance_consumption(
49
  occupancy_2br * TWO_BR_UNITS * (400 - (LIGHTS_2BR * LIGHT_POWER * 6 / 1000))
50
  ) # Daily kWh
51
 
 
52
  def total_consumption(
53
  occupancy_1br: float, occupancy_2br: float, common_area: float
54
  ) -> float:
@@ -57,41 +61,50 @@ def total_consumption(
57
  appliances = calculate_appliance_consumption(occupancy_1br, occupancy_2br)
58
  return (lighting + appliances + common_area) * 30 # Monthly kWh
59
 
 
60
  def solar_production(panels: int) -> float:
61
  """Monthly solar production with losses"""
62
- daily_production = panels * SOLAR_PANEL_RATING * 5 * (1 - SYSTEM_LOSSES) / 1000 # 5 sun hours
 
 
63
  return daily_production * 30 # Monthly kWh
64
 
 
65
  def battery_storage(batteries: int) -> float:
66
  """Usable battery capacity"""
67
  return batteries * BATTERY_CAPACITY * BATTERY_VOLTAGE * 0.8 / 1000 # kWh
68
 
 
69
  def financial_analysis(consumption: float, production: float, storage: float) -> Dict:
70
  """Detailed financial calculations"""
71
  solar_used = min(production, consumption)
72
  surplus = max(0, production - consumption)
73
  feed_in_revenue = surplus * FEED_IN_TARIFF / 100 # Convert to Ksh from cents/kWh
74
-
75
  # Account for battery storage
76
  grid_purchased = max(0, consumption - solar_used)
77
  if storage > 0:
78
  # Battery can offset some grid purchases
79
  grid_offset = min(grid_purchased, storage)
80
  grid_purchased -= grid_offset
81
-
82
- monthly_savings = (consumption * st.session_state.grid_price / 100) - (grid_purchased * st.session_state.grid_price / 100) + feed_in_revenue
83
-
 
 
 
 
84
  total_investment = (
85
  st.session_state.solar_panels * st.session_state.panel_price
86
  + st.session_state.batteries * st.session_state.battery_price
87
  )
88
-
89
  # Avoid division by zero
90
  if monthly_savings > 0:
91
  payback_years = total_investment / (monthly_savings * 12)
92
  else:
93
- payback_years = float('inf')
94
-
95
  return {
96
  "consumption": consumption,
97
  "production": production,
@@ -102,24 +115,28 @@ def financial_analysis(consumption: float, production: float, storage: float) ->
102
  "grid_purchased": grid_purchased,
103
  }
104
 
 
105
  def create_consumption_breakdown(
106
  occupancy_1br: float, occupancy_2br: float, common_area: float
107
  ):
108
  """Create detailed consumption breakdown"""
109
  breakdown = {
110
  "Lighting": calculate_lighting_consumption(occupancy_1br, occupancy_2br) * 30,
111
- "Appliances": calculate_appliance_consumption(occupancy_1br, occupancy_2br) * 30,
 
112
  "Common Areas": common_area * 30,
113
  }
114
  return pd.DataFrame.from_dict(breakdown, orient="index", columns=["kWh"])
115
 
 
116
  # Streamlit Interface
117
  def main():
118
  st.set_page_config(page_title="Solar Analysis Suite", page_icon="🌞", layout="wide")
119
  initialize_session_state()
120
-
121
  # Custom CSS
122
- st.markdown("""
 
123
  <style>
124
  .main .block-container {padding-top: 2rem;}
125
  h1, h2, h3 {color: #1E88E5;}
@@ -139,61 +156,99 @@ def main():
139
  color: white;
140
  }
141
  </style>
142
- """, unsafe_allow_html=True)
143
-
 
 
144
  # Header with logo
145
  col1, col2 = st.columns([1, 4])
146
  with col1:
147
  st.image("https://img.icons8.com/fluency/96/000000/sun.png", width=100)
148
  with col2:
149
  st.title("🌞 Advanced Solar Performance Analyzer")
150
- st.markdown("Optimize your apartment complex solar installation with data-driven insights")
151
-
 
 
152
  # Sidebar for system configuration
153
  with st.sidebar:
154
  st.header("System Configuration")
155
-
156
  # Add a nice header image
157
  st.image("https://img.icons8.com/color/96/000000/solar-panel.png", width=80)
158
-
159
  # Create tabs for different settings
160
  tab1, tab2 = st.tabs(["Hardware", "Pricing"])
161
-
162
  with tab1:
163
- st.slider("Number of Solar Panels", 1, 300, key="solar_panels", help="Each panel rated at 625W")
164
- st.slider("Number of Batteries", 0, 150, key="batteries", help="Each battery has 200Ah capacity at 12V")
165
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  with tab2:
167
- st.number_input("Panel Price (Ksh)", 1000, 50000, step=500, key="panel_price",
168
- help="Cost per solar panel")
169
- st.number_input("Battery Price (Ksh)", 5000, 100000, step=1000, key="battery_price",
170
- help="Cost per battery unit")
171
- st.number_input("Grid Price (Ksh/kWh)", 10.0, 50.0, step=0.1, key="grid_price",
172
- help="Current electricity price from the grid")
173
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  st.markdown("---")
175
- st.markdown("""
 
176
  📊 **System Totals**
177
  - **Total Panel Capacity**: {0:.1f} kW
178
  - **Total Battery Storage**: {1:.1f} kWh
179
  - **Total Investment**: {2:,.0f} Ksh
180
  """.format(
181
- st.session_state.solar_panels * SOLAR_PANEL_RATING / 1000,
182
- battery_storage(st.session_state.batteries),
183
- st.session_state.solar_panels * st.session_state.panel_price +
184
- st.session_state.batteries * st.session_state.battery_price
185
- ))
 
186
 
187
  # Main content
188
  # Create scenarios with varying occupancy levels
189
  scenarios = {}
190
-
191
  # Common area consumption remains constant
192
  common_area_consumption = 5.904 # kWh per day
193
-
194
  # Generate scenarios with different occupancy combinations
195
  occupancy_levels = [0.0, 0.25, 0.50, 0.75, 1.0]
196
-
197
  # Create scenarios for 1BR fixed, varying 2BR
198
  for br1_level in occupancy_levels:
199
  for br2_level in occupancy_levels:
@@ -201,13 +256,15 @@ def main():
201
  scenarios[scenario_name] = {
202
  "1br": br1_level,
203
  "2br": br2_level,
204
- "common": common_area_consumption
205
  }
206
-
207
  # Analysis tabs
208
  st.markdown("---")
209
- tab1, tab2, tab3 = st.tabs(["📊 Energy Analysis", "💰 Financial Metrics", "🔍 Detailed Breakdown"])
210
-
 
 
211
  # Prepare analysis data for all scenarios
212
  analysis_data = []
213
  for name, params in scenarios.items():
@@ -215,61 +272,64 @@ def main():
215
  production = solar_production(st.session_state.solar_panels)
216
  storage = battery_storage(st.session_state.batteries)
217
  financials = financial_analysis(consumption, production, storage)
218
- analysis_data.append({
219
- "Scenario": name,
220
- **financials
221
- })
222
-
223
  df = pd.DataFrame(analysis_data)
224
-
225
  # Tab 1: Energy Analysis
226
  with tab1:
227
  st.header("Energy Flow Analysis")
228
-
229
  # Allow filtering by 1BR occupancy
230
  one_br_filter = st.selectbox(
231
- "Filter by 1BR Occupancy",
232
  ["All"] + [f"{int(level*100)}%" for level in occupancy_levels],
233
- help="Filter scenarios by 1BR occupancy level"
234
  )
235
-
236
  # Filter the dataframe based on selection
237
  filtered_df = df
238
  if one_br_filter != "All":
239
  occupancy_value = int(one_br_filter.replace("%", ""))
240
  filtered_df = df[df["Scenario"].str.contains(f"1BR: {occupancy_value}%")]
241
-
242
  # Chart 1: Energy Balance
243
  st.subheader("Energy Balance by Scenario")
244
-
245
  energy_fig = plt.figure(figsize=(12, 7))
246
  ax = energy_fig.add_subplot(111)
247
-
248
  # Create data for stacked bar chart
249
  chart_data = filtered_df.copy()
250
  chart_data["grid_energy"] = chart_data["grid_purchased"]
251
- chart_data["solar_energy"] = chart_data["consumption"] - chart_data["grid_purchased"]
252
-
 
 
253
  # Create normalized stacked bar chart
254
  chart_data = chart_data.set_index("Scenario")
255
- energy_proportions = chart_data[["solar_energy", "grid_energy"]].div(chart_data["consumption"], axis=0) * 100
 
 
 
 
 
256
  energy_proportions = energy_proportions.reset_index()
257
-
258
  # Reshape for seaborn
259
  energy_melt = pd.melt(
260
- energy_proportions,
261
- id_vars=["Scenario"],
262
  value_vars=["solar_energy", "grid_energy"],
263
  var_name="Energy Source",
264
- value_name="Percentage"
265
  )
266
-
267
  # Rename for better labels
268
- energy_melt["Energy Source"] = energy_melt["Energy Source"].replace({
269
- "solar_energy": "Solar Generated",
270
- "grid_energy": "Grid Purchased"
271
- })
272
-
273
  # Plot with seaborn
274
  sns.set_theme(style="whitegrid")
275
  sns.barplot(
@@ -278,34 +338,42 @@ def main():
278
  y="Percentage",
279
  hue="Energy Source",
280
  palette=["#4CAF50", "#F44336"],
281
- ax=ax
282
  )
283
  ax.set_ylabel("Energy Contribution (%)")
284
  ax.set_title("Energy Source Distribution by Occupancy Scenario")
285
  plt.xticks(rotation=45, ha="right")
286
  plt.tight_layout()
287
  st.pyplot(energy_fig)
288
-
289
  # Detailed metrics
290
  col1, col2, col3 = st.columns(3)
291
  with col1:
292
  st.metric(
293
- "Avg. Solar Contribution",
294
  f"{filtered_df['solar_contribution'].mean():.1f}%",
295
- f"{filtered_df['solar_contribution'].mean() - 50:.1f}%" if filtered_df['solar_contribution'].mean() > 50 else f"{filtered_df['solar_contribution'].mean() - 50:.1f}%"
 
 
 
 
296
  )
297
  with col2:
298
  st.metric(
299
- "Avg. Grid Dependency",
300
  f"{filtered_df['grid_dependency'].mean():.1f}%",
301
- f"{50 - filtered_df['grid_dependency'].mean():.1f}%" if filtered_df['grid_dependency'].mean() < 50 else f"{50 - filtered_df['grid_dependency'].mean():.1f}%"
 
 
 
 
302
  )
303
  with col3:
304
  st.metric(
305
- "Production/Consumption Ratio",
306
- f"{(filtered_df['production'].mean() / filtered_df['consumption'].mean() * 100):.1f}%"
307
  )
308
-
309
  with st.expander("🔍 Energy Flow Interpretation"):
310
  st.markdown(
311
  """
@@ -320,90 +388,104 @@ def main():
320
  3. **Battery Storage**: Helps utilize excess daytime production for nighttime use
321
  """
322
  )
323
-
324
  # Tab 2: Financial Metrics
325
  with tab2:
326
  st.header("Financial Performance Analysis")
327
-
328
  # Allow filtering by 2BR occupancy
329
  two_br_filter = st.selectbox(
330
- "Filter by 2BR Occupancy",
331
  ["All"] + [f"{int(level*100)}%" for level in occupancy_levels],
332
- help="Filter scenarios by 2BR occupancy level"
333
  )
334
-
335
  # Filter the dataframe based on selection
336
  filtered_fin_df = df
337
  if two_br_filter != "All":
338
  occupancy_value = int(two_br_filter.replace("%", ""))
339
- filtered_fin_df = df[df["Scenario"].str.contains(f"2BR: {occupancy_value}%")]
340
-
 
 
341
  # Monthly Savings Chart
342
  st.subheader("Monthly Cost Savings")
343
-
344
  # Fix large values
345
- filtered_fin_df['monthly_savings_fixed'] = filtered_fin_df['monthly_savings'].clip(0, 100000)
346
-
 
 
347
  fig1, ax1 = plt.subplots(figsize=(12, 6))
348
  sns.barplot(
349
  data=filtered_fin_df,
350
  x="Scenario",
351
  y="monthly_savings_fixed",
352
  palette="viridis",
353
- ax=ax1
354
  )
355
  ax1.set_title("Monthly Cost Savings by Scenario")
356
  ax1.set_ylabel("Ksh")
357
  plt.xticks(rotation=45, ha="right")
358
  plt.tight_layout()
359
  st.pyplot(fig1)
360
-
361
  # Payback Period Chart
362
  st.subheader("System Payback Period")
363
-
364
  # Fix large values
365
- filtered_fin_df['payback_period_fixed'] = filtered_fin_df['payback_period'].clip(0, 30)
366
-
 
 
367
  fig2, ax2 = plt.subplots(figsize=(12, 6))
368
  sns.barplot(
369
  data=filtered_fin_df,
370
  x="Scenario",
371
  y="payback_period_fixed",
372
  palette="rocket_r",
373
- ax=ax2
374
  )
375
  ax2.set_title("Investment Payback Period by Scenario")
376
  ax2.set_ylabel("Years")
377
  plt.xticks(rotation=45, ha="right")
378
  plt.tight_layout()
379
  st.pyplot(fig2)
380
-
381
  # Financial summary metrics
382
  col1, col2, col3 = st.columns(3)
383
  with col1:
384
- avg_savings = filtered_fin_df['monthly_savings'].mean()
385
  st.metric(
386
- "Avg. Monthly Savings",
387
  f"{avg_savings:,.0f} Ksh",
388
- f"{avg_savings - df['monthly_savings'].mean():,.0f} Ksh" if avg_savings > df['monthly_savings'].mean() else f"{avg_savings - df['monthly_savings'].mean():,.0f} Ksh"
 
 
 
 
389
  )
390
  with col2:
391
- min_payback = filtered_fin_df['payback_period'].min()
392
  st.metric(
393
- "Best Payback Period",
394
  f"{min_payback:.1f} years",
395
- help="Shortest time to recover investment"
396
  )
397
  with col3:
398
- total_investment = (st.session_state.solar_panels * st.session_state.panel_price +
399
- st.session_state.batteries * st.session_state.battery_price)
400
- annual_roi = (avg_savings * 12 / total_investment) * 100 if total_investment > 0 else 0
 
 
 
 
 
 
401
  st.metric(
402
- "Annual ROI",
403
- f"{annual_roi:.1f}%",
404
- help="Annual Return on Investment"
405
  )
406
-
407
  with st.expander("💵 Financial Analysis Details"):
408
  st.markdown(
409
  f"""
@@ -421,109 +503,124 @@ def main():
421
  """
422
  )
423
  st.dataframe(
424
- filtered_fin_df[["Scenario", "consumption", "production", "monthly_savings", "payback_period"]].sort_values("monthly_savings", ascending=False),
425
- hide_index=True
 
 
 
 
 
 
 
 
426
  )
427
-
428
  # Tab 3: Detailed Breakdown
429
  with tab3:
430
  st.header("Consumption Breakdown Analysis")
431
-
432
  # Select specific scenario for detailed analysis
433
- scenario_select = st.selectbox("Select Specific Scenario", list(scenarios.keys()))
 
 
434
  selected_params = scenarios[scenario_select]
435
-
436
  # Create consumption breakdown
437
  breakdown_df = create_consumption_breakdown(
438
  selected_params["1br"], selected_params["2br"], selected_params["common"]
439
  )
440
-
441
  total_kwh = breakdown_df["kWh"].sum()
442
-
443
  # Add percentage column
444
  breakdown_df["Percentage"] = (breakdown_df["kWh"] / total_kwh * 100).round(1)
445
-
446
  col1, col2 = st.columns([2, 3])
447
-
448
  with col1:
449
  st.subheader("Energy Composition")
450
-
451
  # Create a more attractive pie chart
452
  fig3 = plt.figure(figsize=(8, 8))
453
  ax3 = fig3.add_subplot(111)
454
-
455
- colors = ['#FF9800', '#2196F3', '#4CAF50']
456
  explode = (0.1, 0, 0)
457
-
458
  wedges, texts, autotexts = ax3.pie(
459
- breakdown_df["kWh"],
460
- labels=breakdown_df.index,
461
- autopct='%1.1f%%',
462
  explode=explode,
463
  colors=colors,
464
  shadow=True,
465
  startangle=90,
466
- textprops={'fontsize': 12}
467
  )
468
-
469
  # Equal aspect ratio ensures that pie is drawn as a circle
470
- ax3.axis('equal')
471
  plt.tight_layout()
472
  st.pyplot(fig3)
473
-
474
  # Show total consumption
475
  st.metric(
476
- "Total Monthly Consumption",
477
  f"{total_kwh:.1f} kWh",
478
- help="Sum of all consumption components"
479
  )
480
-
481
  with col2:
482
  st.subheader("Detailed Component Analysis")
483
-
484
  # Show breakdown as a horizontal bar chart
485
  fig4 = plt.figure(figsize=(10, 5))
486
  ax4 = fig4.add_subplot(111)
487
-
488
  # Sort by consumption
489
  sorted_df = breakdown_df.sort_values("kWh", ascending=True)
490
-
491
  # Create horizontal bar chart
492
  bars = sns.barplot(
493
- y=sorted_df.index,
494
- x="kWh",
495
- data=sorted_df,
496
- palette=colors[::-1],
497
- ax=ax4
498
  )
499
-
500
  # Add data labels
501
  for i, v in enumerate(sorted_df["kWh"]):
502
- ax4.text(v + 5, i, f"{v:.1f} kWh ({sorted_df['Percentage'].iloc[i]}%)", va='center')
503
-
 
 
 
 
 
504
  ax4.set_title(f"Energy Consumption Breakdown - {scenario_select}")
505
  ax4.set_xlabel("Monthly Consumption (kWh)")
506
  ax4.set_ylabel("")
507
  plt.tight_layout()
508
  st.pyplot(fig4)
509
-
510
  # Add scenario details
511
- st.markdown(f"""
 
512
  **Scenario Details:**
513
  - 1BR Units Occupancy: {selected_params['1br']*100:.0f}% ({selected_params['1br']*ONE_BR_UNITS:.0f} units)
514
  - 2BR Units Occupancy: {selected_params['2br']*100:.0f}% ({selected_params['2br']*TWO_BR_UNITS:.0f} units)
515
  - Common Areas Consumption: {selected_params['common']*30:.1f} kWh/month
516
- """)
517
-
 
518
  # Insight box
519
- st.info(f"""
 
520
  **Key Insights for {scenario_select}:**
521
  - Lighting contributes {breakdown_df.loc['Lighting', 'Percentage']:.1f}% of total consumption
522
  - Common areas account for {breakdown_df.loc['Common Areas', 'Percentage']:.1f}% of the total
523
  - {'2BR units dominate consumption at ' + str(selected_params['2br']*100) + '% occupancy' if selected_params['2br'] > selected_params['1br'] else '1BR units are the primary consumers at ' + str(selected_params['1br']*100) + '% occupancy'}
524
  - Total potential solar offset: {min(solar_production(st.session_state.solar_panels)/total_kwh*100, 100):.1f}%
525
- """)
526
-
 
527
  # Footer
528
  st.markdown("---")
529
  st.markdown(
@@ -532,8 +629,9 @@ def main():
532
  <p>Solar Analysis Suite v1.0 | Developed with ❤️ for sustainable energy solutions</p>
533
  </div>
534
  """,
535
- unsafe_allow_html=True
536
  )
537
 
 
538
  if __name__ == "__main__":
539
  main()
 
19
  LIGHTS_2BR = 8
20
  LIGHT_POWER = 6 # Watts per light
21
 
22
+
23
  def initialize_session_state():
24
  """Initialize session state variables"""
25
  defaults = {
 
33
  if key not in st.session_state:
34
  st.session_state[key] = value
35
 
36
+
37
  def calculate_lighting_consumption(occupancy_1br: float, occupancy_2br: float) -> float:
38
  """Calculate daily lighting consumption"""
39
  return (
 
41
  + (occupancy_2br * TWO_BR_UNITS * LIGHTS_2BR * LIGHT_POWER / 1000)
42
  ) * 6 # 6 hours per day
43
 
44
+
45
  def calculate_appliance_consumption(
46
  occupancy_1br: float, occupancy_2br: float
47
  ) -> float:
 
52
  occupancy_2br * TWO_BR_UNITS * (400 - (LIGHTS_2BR * LIGHT_POWER * 6 / 1000))
53
  ) # Daily kWh
54
 
55
+
56
  def total_consumption(
57
  occupancy_1br: float, occupancy_2br: float, common_area: float
58
  ) -> float:
 
61
  appliances = calculate_appliance_consumption(occupancy_1br, occupancy_2br)
62
  return (lighting + appliances + common_area) * 30 # Monthly kWh
63
 
64
+
65
  def solar_production(panels: int) -> float:
66
  """Monthly solar production with losses"""
67
+ daily_production = (
68
+ panels * SOLAR_PANEL_RATING * 5 * (1 - SYSTEM_LOSSES) / 1000
69
+ ) # 5 sun hours
70
  return daily_production * 30 # Monthly kWh
71
 
72
+
73
  def battery_storage(batteries: int) -> float:
74
  """Usable battery capacity"""
75
  return batteries * BATTERY_CAPACITY * BATTERY_VOLTAGE * 0.8 / 1000 # kWh
76
 
77
+
78
  def financial_analysis(consumption: float, production: float, storage: float) -> Dict:
79
  """Detailed financial calculations"""
80
  solar_used = min(production, consumption)
81
  surplus = max(0, production - consumption)
82
  feed_in_revenue = surplus * FEED_IN_TARIFF / 100 # Convert to Ksh from cents/kWh
83
+
84
  # Account for battery storage
85
  grid_purchased = max(0, consumption - solar_used)
86
  if storage > 0:
87
  # Battery can offset some grid purchases
88
  grid_offset = min(grid_purchased, storage)
89
  grid_purchased -= grid_offset
90
+
91
+ monthly_savings = (
92
+ (consumption * st.session_state.grid_price / 100)
93
+ - (grid_purchased * st.session_state.grid_price / 100)
94
+ + feed_in_revenue
95
+ )
96
+
97
  total_investment = (
98
  st.session_state.solar_panels * st.session_state.panel_price
99
  + st.session_state.batteries * st.session_state.battery_price
100
  )
101
+
102
  # Avoid division by zero
103
  if monthly_savings > 0:
104
  payback_years = total_investment / (monthly_savings * 12)
105
  else:
106
+ payback_years = float("inf")
107
+
108
  return {
109
  "consumption": consumption,
110
  "production": production,
 
115
  "grid_purchased": grid_purchased,
116
  }
117
 
118
+
119
  def create_consumption_breakdown(
120
  occupancy_1br: float, occupancy_2br: float, common_area: float
121
  ):
122
  """Create detailed consumption breakdown"""
123
  breakdown = {
124
  "Lighting": calculate_lighting_consumption(occupancy_1br, occupancy_2br) * 30,
125
+ "Appliances": calculate_appliance_consumption(occupancy_1br, occupancy_2br)
126
+ * 30,
127
  "Common Areas": common_area * 30,
128
  }
129
  return pd.DataFrame.from_dict(breakdown, orient="index", columns=["kWh"])
130
 
131
+
132
  # Streamlit Interface
133
  def main():
134
  st.set_page_config(page_title="Solar Analysis Suite", page_icon="🌞", layout="wide")
135
  initialize_session_state()
136
+
137
  # Custom CSS
138
+ st.markdown(
139
+ """
140
  <style>
141
  .main .block-container {padding-top: 2rem;}
142
  h1, h2, h3 {color: #1E88E5;}
 
156
  color: white;
157
  }
158
  </style>
159
+ """,
160
+ unsafe_allow_html=True,
161
+ )
162
+
163
  # Header with logo
164
  col1, col2 = st.columns([1, 4])
165
  with col1:
166
  st.image("https://img.icons8.com/fluency/96/000000/sun.png", width=100)
167
  with col2:
168
  st.title("🌞 Advanced Solar Performance Analyzer")
169
+ st.markdown(
170
+ "Optimize your apartment complex solar installation with data-driven insights"
171
+ )
172
+
173
  # Sidebar for system configuration
174
  with st.sidebar:
175
  st.header("System Configuration")
176
+
177
  # Add a nice header image
178
  st.image("https://img.icons8.com/color/96/000000/solar-panel.png", width=80)
179
+
180
  # Create tabs for different settings
181
  tab1, tab2 = st.tabs(["Hardware", "Pricing"])
182
+
183
  with tab1:
184
+ st.number_input(
185
+ "Number of Solar Panels",
186
+ 1,
187
+ 300,
188
+ step=5,
189
+ key="solar_panels",
190
+ help="Each panel rated at 625W",
191
+ )
192
+ st.number_input(
193
+ "Number of Batteries",
194
+ 0,
195
+ 150,
196
+ step=5,
197
+ key="batteries",
198
+ help="Each battery has 200Ah capacity at 12V",
199
+ )
200
+
201
  with tab2:
202
+ st.number_input(
203
+ "Panel Price (Ksh)",
204
+ 1000,
205
+ 50000,
206
+ step=500,
207
+ key="panel_price",
208
+ help="Cost per solar panel",
209
+ )
210
+ st.number_input(
211
+ "Battery Price (Ksh)",
212
+ 5000,
213
+ 100000,
214
+ step=1000,
215
+ key="battery_price",
216
+ help="Cost per battery unit",
217
+ )
218
+ st.number_input(
219
+ "Grid Price (Ksh/kWh)",
220
+ 10.0,
221
+ 50.0,
222
+ step=0.1,
223
+ key="grid_price",
224
+ help="Current electricity price from the grid",
225
+ )
226
+
227
  st.markdown("---")
228
+ st.markdown(
229
+ """
230
  📊 **System Totals**
231
  - **Total Panel Capacity**: {0:.1f} kW
232
  - **Total Battery Storage**: {1:.1f} kWh
233
  - **Total Investment**: {2:,.0f} Ksh
234
  """.format(
235
+ st.session_state.solar_panels * SOLAR_PANEL_RATING / 1000,
236
+ battery_storage(st.session_state.batteries),
237
+ st.session_state.solar_panels * st.session_state.panel_price
238
+ + st.session_state.batteries * st.session_state.battery_price,
239
+ )
240
+ )
241
 
242
  # Main content
243
  # Create scenarios with varying occupancy levels
244
  scenarios = {}
245
+
246
  # Common area consumption remains constant
247
  common_area_consumption = 5.904 # kWh per day
248
+
249
  # Generate scenarios with different occupancy combinations
250
  occupancy_levels = [0.0, 0.25, 0.50, 0.75, 1.0]
251
+
252
  # Create scenarios for 1BR fixed, varying 2BR
253
  for br1_level in occupancy_levels:
254
  for br2_level in occupancy_levels:
 
256
  scenarios[scenario_name] = {
257
  "1br": br1_level,
258
  "2br": br2_level,
259
+ "common": common_area_consumption,
260
  }
261
+
262
  # Analysis tabs
263
  st.markdown("---")
264
+ tab1, tab2, tab3 = st.tabs(
265
+ ["📊 Energy Analysis", "💰 Financial Metrics", "🔍 Detailed Breakdown"]
266
+ )
267
+
268
  # Prepare analysis data for all scenarios
269
  analysis_data = []
270
  for name, params in scenarios.items():
 
272
  production = solar_production(st.session_state.solar_panels)
273
  storage = battery_storage(st.session_state.batteries)
274
  financials = financial_analysis(consumption, production, storage)
275
+ analysis_data.append({"Scenario": name, **financials})
276
+
 
 
 
277
  df = pd.DataFrame(analysis_data)
278
+
279
  # Tab 1: Energy Analysis
280
  with tab1:
281
  st.header("Energy Flow Analysis")
282
+
283
  # Allow filtering by 1BR occupancy
284
  one_br_filter = st.selectbox(
285
+ "Filter by 1BR Occupancy",
286
  ["All"] + [f"{int(level*100)}%" for level in occupancy_levels],
287
+ help="Filter scenarios by 1BR occupancy level",
288
  )
289
+
290
  # Filter the dataframe based on selection
291
  filtered_df = df
292
  if one_br_filter != "All":
293
  occupancy_value = int(one_br_filter.replace("%", ""))
294
  filtered_df = df[df["Scenario"].str.contains(f"1BR: {occupancy_value}%")]
295
+
296
  # Chart 1: Energy Balance
297
  st.subheader("Energy Balance by Scenario")
298
+
299
  energy_fig = plt.figure(figsize=(12, 7))
300
  ax = energy_fig.add_subplot(111)
301
+
302
  # Create data for stacked bar chart
303
  chart_data = filtered_df.copy()
304
  chart_data["grid_energy"] = chart_data["grid_purchased"]
305
+ chart_data["solar_energy"] = (
306
+ chart_data["consumption"] - chart_data["grid_purchased"]
307
+ )
308
+
309
  # Create normalized stacked bar chart
310
  chart_data = chart_data.set_index("Scenario")
311
+ energy_proportions = (
312
+ chart_data[["solar_energy", "grid_energy"]].div(
313
+ chart_data["consumption"], axis=0
314
+ )
315
+ * 100
316
+ )
317
  energy_proportions = energy_proportions.reset_index()
318
+
319
  # Reshape for seaborn
320
  energy_melt = pd.melt(
321
+ energy_proportions,
322
+ id_vars=["Scenario"],
323
  value_vars=["solar_energy", "grid_energy"],
324
  var_name="Energy Source",
325
+ value_name="Percentage",
326
  )
327
+
328
  # Rename for better labels
329
+ energy_melt["Energy Source"] = energy_melt["Energy Source"].replace(
330
+ {"solar_energy": "Solar Generated", "grid_energy": "Grid Purchased"}
331
+ )
332
+
 
333
  # Plot with seaborn
334
  sns.set_theme(style="whitegrid")
335
  sns.barplot(
 
338
  y="Percentage",
339
  hue="Energy Source",
340
  palette=["#4CAF50", "#F44336"],
341
+ ax=ax,
342
  )
343
  ax.set_ylabel("Energy Contribution (%)")
344
  ax.set_title("Energy Source Distribution by Occupancy Scenario")
345
  plt.xticks(rotation=45, ha="right")
346
  plt.tight_layout()
347
  st.pyplot(energy_fig)
348
+
349
  # Detailed metrics
350
  col1, col2, col3 = st.columns(3)
351
  with col1:
352
  st.metric(
353
+ "Avg. Solar Contribution",
354
  f"{filtered_df['solar_contribution'].mean():.1f}%",
355
+ (
356
+ f"{filtered_df['solar_contribution'].mean() - 50:.1f}%"
357
+ if filtered_df["solar_contribution"].mean() > 50
358
+ else f"{filtered_df['solar_contribution'].mean() - 50:.1f}%"
359
+ ),
360
  )
361
  with col2:
362
  st.metric(
363
+ "Avg. Grid Dependency",
364
  f"{filtered_df['grid_dependency'].mean():.1f}%",
365
+ (
366
+ f"{50 - filtered_df['grid_dependency'].mean():.1f}%"
367
+ if filtered_df["grid_dependency"].mean() < 50
368
+ else f"{50 - filtered_df['grid_dependency'].mean():.1f}%"
369
+ ),
370
  )
371
  with col3:
372
  st.metric(
373
+ "Production/Consumption Ratio",
374
+ f"{(filtered_df['production'].mean() / filtered_df['consumption'].mean() * 100):.1f}%",
375
  )
376
+
377
  with st.expander("🔍 Energy Flow Interpretation"):
378
  st.markdown(
379
  """
 
388
  3. **Battery Storage**: Helps utilize excess daytime production for nighttime use
389
  """
390
  )
391
+
392
  # Tab 2: Financial Metrics
393
  with tab2:
394
  st.header("Financial Performance Analysis")
395
+
396
  # Allow filtering by 2BR occupancy
397
  two_br_filter = st.selectbox(
398
+ "Filter by 2BR Occupancy",
399
  ["All"] + [f"{int(level*100)}%" for level in occupancy_levels],
400
+ help="Filter scenarios by 2BR occupancy level",
401
  )
402
+
403
  # Filter the dataframe based on selection
404
  filtered_fin_df = df
405
  if two_br_filter != "All":
406
  occupancy_value = int(two_br_filter.replace("%", ""))
407
+ filtered_fin_df = df[
408
+ df["Scenario"].str.contains(f"2BR: {occupancy_value}%")
409
+ ]
410
+
411
  # Monthly Savings Chart
412
  st.subheader("Monthly Cost Savings")
413
+
414
  # Fix large values
415
+ filtered_fin_df["monthly_savings_fixed"] = filtered_fin_df[
416
+ "monthly_savings"
417
+ ].clip(0, 100000)
418
+
419
  fig1, ax1 = plt.subplots(figsize=(12, 6))
420
  sns.barplot(
421
  data=filtered_fin_df,
422
  x="Scenario",
423
  y="monthly_savings_fixed",
424
  palette="viridis",
425
+ ax=ax1,
426
  )
427
  ax1.set_title("Monthly Cost Savings by Scenario")
428
  ax1.set_ylabel("Ksh")
429
  plt.xticks(rotation=45, ha="right")
430
  plt.tight_layout()
431
  st.pyplot(fig1)
432
+
433
  # Payback Period Chart
434
  st.subheader("System Payback Period")
435
+
436
  # Fix large values
437
+ filtered_fin_df["payback_period_fixed"] = filtered_fin_df[
438
+ "payback_period"
439
+ ].clip(0, 30)
440
+
441
  fig2, ax2 = plt.subplots(figsize=(12, 6))
442
  sns.barplot(
443
  data=filtered_fin_df,
444
  x="Scenario",
445
  y="payback_period_fixed",
446
  palette="rocket_r",
447
+ ax=ax2,
448
  )
449
  ax2.set_title("Investment Payback Period by Scenario")
450
  ax2.set_ylabel("Years")
451
  plt.xticks(rotation=45, ha="right")
452
  plt.tight_layout()
453
  st.pyplot(fig2)
454
+
455
  # Financial summary metrics
456
  col1, col2, col3 = st.columns(3)
457
  with col1:
458
+ avg_savings = filtered_fin_df["monthly_savings"].mean()
459
  st.metric(
460
+ "Avg. Monthly Savings",
461
  f"{avg_savings:,.0f} Ksh",
462
+ (
463
+ f"{avg_savings - df['monthly_savings'].mean():,.0f} Ksh"
464
+ if avg_savings > df["monthly_savings"].mean()
465
+ else f"{avg_savings - df['monthly_savings'].mean():,.0f} Ksh"
466
+ ),
467
  )
468
  with col2:
469
+ min_payback = filtered_fin_df["payback_period"].min()
470
  st.metric(
471
+ "Best Payback Period",
472
  f"{min_payback:.1f} years",
473
+ help="Shortest time to recover investment",
474
  )
475
  with col3:
476
+ total_investment = (
477
+ st.session_state.solar_panels * st.session_state.panel_price
478
+ + st.session_state.batteries * st.session_state.battery_price
479
+ )
480
+ annual_roi = (
481
+ (avg_savings * 12 / total_investment) * 100
482
+ if total_investment > 0
483
+ else 0
484
+ )
485
  st.metric(
486
+ "Annual ROI", f"{annual_roi:.1f}%", help="Annual Return on Investment"
 
 
487
  )
488
+
489
  with st.expander("💵 Financial Analysis Details"):
490
  st.markdown(
491
  f"""
 
503
  """
504
  )
505
  st.dataframe(
506
+ filtered_fin_df[
507
+ [
508
+ "Scenario",
509
+ "consumption",
510
+ "production",
511
+ "monthly_savings",
512
+ "payback_period",
513
+ ]
514
+ ].sort_values("monthly_savings", ascending=False),
515
+ hide_index=True,
516
  )
517
+
518
  # Tab 3: Detailed Breakdown
519
  with tab3:
520
  st.header("Consumption Breakdown Analysis")
521
+
522
  # Select specific scenario for detailed analysis
523
+ scenario_select = st.selectbox(
524
+ "Select Specific Scenario", list(scenarios.keys())
525
+ )
526
  selected_params = scenarios[scenario_select]
527
+
528
  # Create consumption breakdown
529
  breakdown_df = create_consumption_breakdown(
530
  selected_params["1br"], selected_params["2br"], selected_params["common"]
531
  )
532
+
533
  total_kwh = breakdown_df["kWh"].sum()
534
+
535
  # Add percentage column
536
  breakdown_df["Percentage"] = (breakdown_df["kWh"] / total_kwh * 100).round(1)
537
+
538
  col1, col2 = st.columns([2, 3])
539
+
540
  with col1:
541
  st.subheader("Energy Composition")
542
+
543
  # Create a more attractive pie chart
544
  fig3 = plt.figure(figsize=(8, 8))
545
  ax3 = fig3.add_subplot(111)
546
+
547
+ colors = ["#FF9800", "#2196F3", "#4CAF50"]
548
  explode = (0.1, 0, 0)
549
+
550
  wedges, texts, autotexts = ax3.pie(
551
+ breakdown_df["kWh"],
552
+ labels=breakdown_df.index,
553
+ autopct="%1.1f%%",
554
  explode=explode,
555
  colors=colors,
556
  shadow=True,
557
  startangle=90,
558
+ textprops={"fontsize": 12},
559
  )
560
+
561
  # Equal aspect ratio ensures that pie is drawn as a circle
562
+ ax3.axis("equal")
563
  plt.tight_layout()
564
  st.pyplot(fig3)
565
+
566
  # Show total consumption
567
  st.metric(
568
+ "Total Monthly Consumption",
569
  f"{total_kwh:.1f} kWh",
570
+ help="Sum of all consumption components",
571
  )
572
+
573
  with col2:
574
  st.subheader("Detailed Component Analysis")
575
+
576
  # Show breakdown as a horizontal bar chart
577
  fig4 = plt.figure(figsize=(10, 5))
578
  ax4 = fig4.add_subplot(111)
579
+
580
  # Sort by consumption
581
  sorted_df = breakdown_df.sort_values("kWh", ascending=True)
582
+
583
  # Create horizontal bar chart
584
  bars = sns.barplot(
585
+ y=sorted_df.index, x="kWh", data=sorted_df, palette=colors[::-1], ax=ax4
 
 
 
 
586
  )
587
+
588
  # Add data labels
589
  for i, v in enumerate(sorted_df["kWh"]):
590
+ ax4.text(
591
+ v + 5,
592
+ i,
593
+ f"{v:.1f} kWh ({sorted_df['Percentage'].iloc[i]}%)",
594
+ va="center",
595
+ )
596
+
597
  ax4.set_title(f"Energy Consumption Breakdown - {scenario_select}")
598
  ax4.set_xlabel("Monthly Consumption (kWh)")
599
  ax4.set_ylabel("")
600
  plt.tight_layout()
601
  st.pyplot(fig4)
602
+
603
  # Add scenario details
604
+ st.markdown(
605
+ f"""
606
  **Scenario Details:**
607
  - 1BR Units Occupancy: {selected_params['1br']*100:.0f}% ({selected_params['1br']*ONE_BR_UNITS:.0f} units)
608
  - 2BR Units Occupancy: {selected_params['2br']*100:.0f}% ({selected_params['2br']*TWO_BR_UNITS:.0f} units)
609
  - Common Areas Consumption: {selected_params['common']*30:.1f} kWh/month
610
+ """
611
+ )
612
+
613
  # Insight box
614
+ st.info(
615
+ f"""
616
  **Key Insights for {scenario_select}:**
617
  - Lighting contributes {breakdown_df.loc['Lighting', 'Percentage']:.1f}% of total consumption
618
  - Common areas account for {breakdown_df.loc['Common Areas', 'Percentage']:.1f}% of the total
619
  - {'2BR units dominate consumption at ' + str(selected_params['2br']*100) + '% occupancy' if selected_params['2br'] > selected_params['1br'] else '1BR units are the primary consumers at ' + str(selected_params['1br']*100) + '% occupancy'}
620
  - Total potential solar offset: {min(solar_production(st.session_state.solar_panels)/total_kwh*100, 100):.1f}%
621
+ """
622
+ )
623
+
624
  # Footer
625
  st.markdown("---")
626
  st.markdown(
 
629
  <p>Solar Analysis Suite v1.0 | Developed with ❤️ for sustainable energy solutions</p>
630
  </div>
631
  """,
632
+ unsafe_allow_html=True,
633
  )
634
 
635
+
636
  if __name__ == "__main__":
637
  main()