charagu-eric commited on
Commit
e583e5b
·
1 Parent(s): 7c4cea5
Files changed (1) hide show
  1. app.py +179 -244
app.py CHANGED
@@ -1,8 +1,9 @@
1
  import numpy as np
2
  import pandas as pd
3
  import matplotlib.pyplot as plt
 
4
  import streamlit as st
5
- from typing import Dict, Tuple
6
 
7
  # Constants
8
  ONE_BR_UNITS = 23
@@ -13,13 +14,14 @@ BATTERY_CAPACITY = 200 # Ah
13
  BATTERY_VOLTAGE = 12 # V
14
  BATTERY_COST = 39000 # KSH per battery
15
  SYSTEM_LOSSES = 0.20 # 20% system losses
16
- GRID_COST_PER_KWH = 25 # KSH
17
  FEED_IN_TARIFF = 12 # KSH per kWh sold back to grid
18
 
19
- # Consumption estimates (kWh/month)
20
  ONE_BR_CONSUMPTION = 250
21
  TWO_BR_CONSUMPTION = 400
22
- COMMON_AREA_CONSUMPTION = 1500 # For entire complex
 
23
 
24
 
25
  def initialize_session_state():
@@ -30,14 +32,15 @@ def initialize_session_state():
30
  st.session_state.batteries = 50
31
 
32
 
33
- def calculate_consumption(one_br_occupancy: float, two_br_occupancy: float) -> float:
 
 
34
  """Calculate total monthly consumption based on occupancy rates"""
35
- total_consumption = (
36
  one_br_occupancy * ONE_BR_UNITS * ONE_BR_CONSUMPTION
37
  + two_br_occupancy * TWO_BR_UNITS * TWO_BR_CONSUMPTION
38
- + COMMON_AREA_CONSUMPTION
39
  )
40
- return total_consumption
41
 
42
 
43
  def solar_production(panel_count: int, sun_hours: float = 5) -> float:
@@ -45,293 +48,225 @@ def solar_production(panel_count: int, sun_hours: float = 5) -> float:
45
  daily_production = (
46
  panel_count * SOLAR_PANEL_RATING * sun_hours * (1 - SYSTEM_LOSSES) / 1000
47
  ) # kWh
48
- monthly_production = daily_production * 30
49
- return monthly_production
50
 
51
 
52
  def battery_storage(battery_count: int) -> float:
53
  """Calculate usable battery storage considering losses"""
54
  total_capacity = battery_count * BATTERY_CAPACITY * BATTERY_VOLTAGE / 1000 # kWh
55
- usable_capacity = total_capacity * (1 - SYSTEM_LOSSES)
56
- return usable_capacity
57
 
58
 
59
  def financial_analysis(
60
- monthly_consumption: float,
61
- solar_production: float,
62
- battery_capacity: float,
63
- panel_count: int,
64
- battery_count: int,
65
  ) -> Dict[str, float]:
66
- """
67
- Calculate financial metrics including costs, savings, and ROI
68
- """
69
- # Initial investment
70
- panel_cost = panel_count * SOLAR_PANEL_COST
71
- battery_cost = battery_count * BATTERY_COST
72
- total_investment = panel_cost + battery_cost
73
-
74
- # Energy calculations
75
- solar_used = min(solar_production, monthly_consumption)
76
- excess_solar = max(solar_production - monthly_consumption, 0)
77
- grid_purchased = max(monthly_consumption - solar_used, 0)
78
 
79
- # Battery can store excess or reduce grid purchases
80
- battery_stored = min(excess_solar, battery_capacity)
81
- battery_used = min(grid_purchased, battery_capacity)
82
 
83
- # Final energy flows
84
- final_grid_purchased = max(grid_purchased - battery_used, 0)
85
- final_excess_solar = max(excess_solar - battery_stored, 0)
86
 
87
- # Financial calculations
88
- grid_cost = final_grid_purchased * GRID_COST_PER_KWH
89
- feed_in_income = final_excess_solar * FEED_IN_TARIFF
90
- savings = (monthly_consumption * GRID_COST_PER_KWH) - grid_cost + feed_in_income
91
 
92
  return {
93
- "total_investment": total_investment,
94
- "monthly_savings": savings,
95
- "annual_savings": savings * 12,
96
- "simple_payback_years": (
97
- total_investment / (savings * 12) if savings > 0 else float("inf")
98
- ),
99
- "grid_purchased": final_grid_purchased,
100
- "excess_solar": final_excess_solar,
101
  "battery_utilization": (
102
- (battery_used + battery_stored) / battery_capacity
103
- if battery_capacity > 0
104
- else 0
105
  ),
106
- "solar_coverage": (
107
- solar_used / monthly_consumption if monthly_consumption > 0 else 0
108
- ),
109
- }
110
-
111
-
112
- def plot_scenario_comparison(results: Dict[str, Dict[str, float]]):
113
- """Plot comparison of different occupancy scenarios"""
114
- scenarios = list(results.keys())
115
-
116
- # Prepare data for plotting
117
- metrics = {
118
- "Monthly Consumption (kWh)": [results[s]["consumption"] for s in scenarios],
119
- "Solar Production (kWh)": [results[s]["solar_production"] for s in scenarios],
120
- "Grid Purchased (kWh)": [
121
- results[s]["financials"]["grid_purchased"] for s in scenarios
122
- ],
123
- "Excess Solar (kWh)": [
124
- results[s]["financials"]["excess_solar"] for s in scenarios
125
- ],
126
  }
127
 
128
- # Create figure
129
- fig, axes = plt.subplots(2, 2, figsize=(15, 12))
130
- axes = axes.flatten()
131
 
132
- for i, (title, values) in enumerate(metrics.items()):
133
- axes[i].bar(scenarios, values, color=plt.cm.tab20(i))
134
- axes[i].set_title(title)
135
- axes[i].tick_params(axis="x", rotation=45)
136
-
137
- # Add value labels
138
- for j, v in enumerate(values):
139
- axes[i].text(j, v * 1.02, f"{v:,.0f}", ha="center", va="bottom")
140
-
141
- plt.tight_layout()
142
- st.pyplot(fig)
143
-
144
-
145
- def plot_financial_comparison(results: Dict[str, Dict[str, float]]):
146
- """Plot financial comparison across scenarios"""
147
- scenarios = list(results.keys())
148
-
149
- # Prepare financial data
150
- financial_metrics = {
151
- "Monthly Savings (Ksh)": [
152
- results[s]["financials"]["monthly_savings"] for s in scenarios
153
- ],
154
- "Solar Coverage (%)": [
155
- results[s]["financials"]["solar_coverage"] * 100 for s in scenarios
156
- ],
157
- "Payback Period (Years)": [
158
- results[s]["financials"]["simple_payback_years"] for s in scenarios
159
- ],
160
- "Battery Utilization (%)": [
161
- results[s]["financials"]["battery_utilization"] * 100 for s in scenarios
162
- ],
163
- }
164
-
165
- # Create figure
166
- fig, axes = plt.subplots(2, 2, figsize=(15, 12))
167
- axes = axes.flatten()
168
-
169
- for i, (title, values) in enumerate(financial_metrics.items()):
170
- if title == "Payback Period (Years)":
171
- # For payback period, we'll do a horizontal bar chart
172
- axes[i].barh(scenarios, values, color=plt.cm.tab20(3))
173
- axes[i].set_xlabel(title)
174
-
175
- # Add value labels
176
- for j, v in enumerate(values):
177
- if np.isfinite(v):
178
- axes[i].text(v * 1.02, j, f"{v:.1f}", va="center")
179
- else:
180
- axes[i].text(0, j, "Never", va="center")
181
- else:
182
- axes[i].bar(scenarios, values, color=plt.cm.tab20(i + 4))
183
- axes[i].set_title(title)
184
- axes[i].tick_params(axis="x", rotation=45)
185
-
186
- # Add value labels
187
- for j, v in enumerate(values):
188
- axes[i].text(j, v * 1.02, f"{v:,.1f}", ha="center", va="bottom")
189
 
 
 
190
  plt.tight_layout()
191
- st.pyplot(fig)
192
 
193
 
194
  def main():
195
  st.set_page_config(
196
- page_title="Apartment Complex Solar Analysis", page_icon="🏢", layout="wide"
197
  )
198
-
199
- # Initialize session state
200
  initialize_session_state()
201
 
202
- # Main title and description
203
- st.title("🏢 Apartment Complex Solar Energy Analysis")
204
  st.markdown(
205
  """
206
- This tool analyzes solar energy potential for a complex with:
207
- - 45 two-bedroom units
208
- - 23 one-bedroom units
209
-
210
- Comparing three specific occupancy scenarios with 2BR at 100% occupancy and varying 1BR occupancy.
211
- """
212
  )
213
 
214
- # Sidebar for system configuration
215
  with st.sidebar:
216
  st.header("System Configuration")
217
  st.session_state.solar_panels = st.number_input(
218
- "Number of Solar Panels",
219
- min_value=0,
220
- max_value=1000,
221
- value=st.session_state.solar_panels,
222
- step=1,
223
  )
224
-
225
  st.session_state.batteries = st.number_input(
226
- "Number of Batteries",
227
- min_value=0,
228
- max_value=500,
229
- value=st.session_state.batteries,
230
- step=1,
231
  )
232
 
233
- st.markdown("---")
234
- st.markdown(
235
- f"**Panel Specifications:** {SOLAR_PANEL_RATING}W @ Ksh{SOLAR_PANEL_COST:,} each"
236
- )
237
  st.markdown(
238
- f"**Battery Specifications:** {BATTERY_CAPACITY}Ah @ Ksh{BATTERY_COST:,} each"
 
 
 
 
 
239
  )
240
- st.markdown(f"**System Losses:** {SYSTEM_LOSSES*100:.0f}%")
241
 
242
- # Define the specific scenarios
243
  scenarios = {
244
- "1BR: 0%, 2BR: 100%": {"1br": 0.0, "2br": 1.0},
245
- "1BR: 25%, 2BR: 100%": {"1br": 0.25, "2br": 1.0},
246
- "1BR: 50%, 2BR: 100%": {"1br": 0.5, "2br": 1.0},
247
  }
248
 
249
- # Calculate results for each scenario
250
- results = {}
251
- for scenario, occupancy in scenarios.items():
252
- # Calculate consumption and production
253
- consumption = calculate_consumption(occupancy["1br"], occupancy["2br"])
254
- production = solar_production(st.session_state.solar_panels)
255
- storage = battery_storage(st.session_state.batteries)
256
-
257
- # Financial analysis
258
- financials = financial_analysis(
259
- consumption,
260
- production,
261
- storage,
262
- st.session_state.solar_panels,
263
- st.session_state.batteries,
264
- )
265
-
266
- results[scenario] = {
267
- "consumption": consumption,
268
- "solar_production": production,
269
- "battery_capacity": storage,
270
- "financials": financials,
271
- }
272
-
273
- # Display system summary
274
- st.subheader("System Summary")
275
- col1, col2, col3, col4 = st.columns(4)
276
- col1.metric("Total Solar Panels", st.session_state.solar_panels)
277
- col2.metric("Total Batteries", st.session_state.batteries)
278
- col3.metric(
279
- "Total Investment",
280
- f"Ksh{(st.session_state.solar_panels * SOLAR_PANEL_COST + st.session_state.batteries * BATTERY_COST):,}",
281
- )
282
- col4.metric(
283
- "Total Solar Capacity",
284
- f"{st.session_state.solar_panels * SOLAR_PANEL_RATING / 1000:.1f} kW",
285
- )
286
-
287
- # Display scenario comparison
288
- st.subheader("Scenario Comparison: Energy Flows")
289
- plot_scenario_comparison(results)
290
-
291
- st.subheader("Scenario Comparison: Financial Metrics")
292
- plot_financial_comparison(results)
293
 
294
- # Detailed results for each scenario
295
- st.subheader("Detailed Results by Scenario")
 
 
 
 
 
296
 
297
- for scenario, data in results.items():
298
- with st.expander(f"Scenario: {scenario}"):
299
- col1, col2, col3 = st.columns(3)
300
 
301
- # Energy metrics
302
- col1.metric("Monthly Consumption", f"{data['consumption']:,.0f} kWh")
303
- col2.metric("Solar Production", f"{data['solar_production']:,.0f} kWh")
304
- col3.metric("Battery Capacity", f"{data['battery_capacity']:,.1f} kWh")
305
 
306
- # Financial metrics
307
- col1.metric(
308
- "Monthly Savings", f"Ksh{data['financials']['monthly_savings']:,.0f}"
309
- )
310
- col2.metric(
311
- "Annual Savings", f"Ksh{data['financials']['annual_savings']:,.0f}"
312
  )
313
 
314
- payback = data["financials"]["simple_payback_years"]
315
- payback_text = f"{payback:.1f} years" if np.isfinite(payback) else "Never"
316
- col3.metric("Simple Payback Period", payback_text)
317
-
318
- # Energy flow details
319
- st.markdown("#### Energy Flow Details")
320
- flow_data = {
321
- "Metric": [
322
- "Grid Purchased",
323
- "Excess Solar",
324
- "Solar Coverage",
325
- "Battery Utilization",
326
- ],
327
- "Value": [
328
- f"{data['financials']['grid_purchased']:,.0f} kWh",
329
- f"{data['financials']['excess_solar']:,.0f} kWh",
330
- f"{data['financials']['solar_coverage']*100:.1f}%",
331
- f"{data['financials']['battery_utilization']*100:.1f}%",
332
- ],
333
- }
334
- st.table(pd.DataFrame(flow_data))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
 
337
  if __name__ == "__main__":
 
1
  import numpy as np
2
  import pandas as pd
3
  import matplotlib.pyplot as plt
4
+ import seaborn as sns
5
  import streamlit as st
6
+ from typing import Dict
7
 
8
  # Constants
9
  ONE_BR_UNITS = 23
 
14
  BATTERY_VOLTAGE = 12 # V
15
  BATTERY_COST = 39000 # KSH per battery
16
  SYSTEM_LOSSES = 0.20 # 20% system losses
17
+ GRID_COST_PER_KWH = 28.44 # KSH
18
  FEED_IN_TARIFF = 12 # KSH per kWh sold back to grid
19
 
20
+ # Consumption scenarios (kWh/month)
21
  ONE_BR_CONSUMPTION = 250
22
  TWO_BR_CONSUMPTION = 400
23
+ COMMON_AREA_CURRENT = 1.974 # kWh
24
+ COMMON_AREA_INCREASED = 5.904 # kWh
25
 
26
 
27
  def initialize_session_state():
 
32
  st.session_state.batteries = 50
33
 
34
 
35
+ def calculate_consumption(
36
+ one_br_occupancy: float, two_br_occupancy: float, common_area: float
37
+ ) -> float:
38
  """Calculate total monthly consumption based on occupancy rates"""
39
+ return (
40
  one_br_occupancy * ONE_BR_UNITS * ONE_BR_CONSUMPTION
41
  + two_br_occupancy * TWO_BR_UNITS * TWO_BR_CONSUMPTION
42
+ + common_area * 30 # Convert daily to monthly
43
  )
 
44
 
45
 
46
  def solar_production(panel_count: int, sun_hours: float = 5) -> float:
 
48
  daily_production = (
49
  panel_count * SOLAR_PANEL_RATING * sun_hours * (1 - SYSTEM_LOSSES) / 1000
50
  ) # kWh
51
+ return daily_production * 30 # Monthly production
 
52
 
53
 
54
  def battery_storage(battery_count: int) -> float:
55
  """Calculate usable battery storage considering losses"""
56
  total_capacity = battery_count * BATTERY_CAPACITY * BATTERY_VOLTAGE / 1000 # kWh
57
+ return total_capacity * (1 - SYSTEM_LOSSES)
 
58
 
59
 
60
  def financial_analysis(
61
+ consumption: float, production: float, storage: float
 
 
 
 
62
  ) -> Dict[str, float]:
63
+ """Calculate financial metrics with detailed energy flows"""
64
+ solar_used = min(production, consumption)
65
+ excess_solar = max(production - consumption, 0)
66
+ grid_purchased = max(consumption - solar_used, 0)
 
 
 
 
 
 
 
 
67
 
68
+ # Battery operations
69
+ battery_stored = min(excess_solar, storage)
70
+ battery_used = min(grid_purchased, storage)
71
 
72
+ final_grid = max(grid_purchased - battery_used, 0)
73
+ final_excess = max(excess_solar - battery_stored, 0)
 
74
 
75
+ grid_cost = final_grid * GRID_COST_PER_KWH
76
+ feed_in_income = final_excess * FEED_IN_TARIFF
77
+ savings = (consumption * GRID_COST_PER_KWH) - grid_cost + feed_in_income
 
78
 
79
  return {
80
+ "consumption": consumption,
81
+ "production": production,
82
+ "solar_used": solar_used,
83
+ "excess_solar": final_excess,
84
+ "grid_purchased": final_grid,
 
 
 
85
  "battery_utilization": (
86
+ (battery_used + battery_stored) / storage if storage > 0 else 0
 
 
87
  ),
88
+ "solar_coverage": solar_used / consumption if consumption > 0 else 0,
89
+ "monthly_savings": savings,
90
+ "extra_grid_cost_saved": (3.93 * 30 * GRID_COST_PER_KWH)
91
+ - (final_grid * GRID_COST_PER_KWH),
92
+ "profit_margin": (feed_in_income + savings)
93
+ / (consumption * GRID_COST_PER_KWH)
94
+ * 100,
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  }
96
 
 
 
 
97
 
98
+ def plot_comparison(data: pd.DataFrame, metric: str, title: str):
99
+ """Create comparison plots using Seaborn"""
100
+ plt.figure(figsize=(10, 6))
101
+ ax = sns.barplot(data=data, x="Scenario", y=metric, hue="Common Area Usage")
102
+
103
+ # Add value annotations
104
+ for p in ax.patches:
105
+ ax.annotate(
106
+ f"{p.get_height():.1f}",
107
+ (p.get_x() + p.get_width() / 2.0, p.get_height()),
108
+ ha="center",
109
+ va="center",
110
+ xytext=(0, 10),
111
+ textcoords="offset points",
112
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
+ plt.title(title)
115
+ plt.xticks(rotation=45)
116
  plt.tight_layout()
117
+ st.pyplot(plt)
118
 
119
 
120
  def main():
121
  st.set_page_config(
122
+ page_title="Solar Profitability Analyzer", page_icon="📊", layout="wide"
123
  )
 
 
124
  initialize_session_state()
125
 
126
+ st.title("📊 Solar Profitability Analysis")
 
127
  st.markdown(
128
  """
129
+ Comparing current vs increased common area usage (1.974kWh vs 5.904kWh daily)
130
+ Grid cost: Ksh 28.44/kWh | Extra 3.93kWh would cost Ksh {:.2f} monthly
131
+ """.format(
132
+ 3.93 * 30 * GRID_COST_PER_KWH
133
+ )
 
134
  )
135
 
136
+ # System configuration
137
  with st.sidebar:
138
  st.header("System Configuration")
139
  st.session_state.solar_panels = st.number_input(
140
+ "Solar Panels", 0, 1000, st.session_state.solar_panels
 
 
 
 
141
  )
 
142
  st.session_state.batteries = st.number_input(
143
+ "Batteries", 0, 500, st.session_state.batteries
 
 
 
 
144
  )
145
 
 
 
 
 
146
  st.markdown(
147
+ f"""
148
+ **System Details:**
149
+ - Panel: {SOLAR_PANEL_RATING}W @ Ksh{SOLAR_PANEL_COST:,}
150
+ - Battery: {BATTERY_CAPACITY}Ah @ Ksh{BATTERY_COST:,}
151
+ - Losses: {SYSTEM_LOSSES*100:.0f}%
152
+ """
153
  )
 
154
 
155
+ # Occupancy scenarios
156
  scenarios = {
157
+ "1BR:0%, 2BR:100%": {"1br": 0.0, "2br": 1.0},
158
+ "1BR:25%, 2BR:100%": {"1br": 0.25, "2br": 1.0},
159
+ "1BR:50%, 2BR:100%": {"1br": 0.5, "2br": 1.0},
160
  }
161
 
162
+ # Common area scenarios
163
+ common_areas = {
164
+ "Current (1.974kWh)": COMMON_AREA_CURRENT,
165
+ "Increased (5.904kWh)": COMMON_AREA_INCREASED,
166
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
+ # Calculate all combinations
169
+ results = []
170
+ for scenario_name, occupancy in scenarios.items():
171
+ for area_name, area_usage in common_areas.items():
172
+ consumption = calculate_consumption(
173
+ occupancy["1br"], occupancy["2br"], area_usage
174
+ )
175
 
176
+ production = solar_production(st.session_state.solar_panels)
177
+ storage = battery_storage(st.session_state.batteries)
 
178
 
179
+ financials = financial_analysis(consumption, production, storage)
 
 
 
180
 
181
+ results.append(
182
+ {
183
+ "Scenario": scenario_name,
184
+ "Common Area Usage": area_name,
185
+ **financials,
186
+ }
187
  )
188
 
189
+ df = pd.DataFrame(results)
190
+
191
+ # Key metrics comparison
192
+ st.subheader("Performance Comparison")
193
+ col1, col2, col3 = st.columns(3)
194
+ with col1:
195
+ plot_comparison(df, "monthly_savings", "Monthly Savings (Ksh)")
196
+ with col2:
197
+ plot_comparison(df, "extra_grid_cost_saved", "Extra Grid Cost Saved (Ksh)")
198
+ with col3:
199
+ plot_comparison(df, "profit_margin", "Profit Margin (%)")
200
+
201
+ # Energy flow visualization
202
+ st.subheader("Energy Flow Analysis")
203
+ flow_df = df.melt(
204
+ id_vars=["Scenario", "Common Area Usage"],
205
+ value_vars=["solar_used", "grid_purchased", "excess_solar"],
206
+ var_name="Flow",
207
+ value_name="kWh",
208
+ )
209
+
210
+ plt.figure(figsize=(12, 6))
211
+ sns.barplot(
212
+ data=flow_df,
213
+ x="Scenario",
214
+ y="kWh",
215
+ hue="Flow",
216
+ style="Common Area Usage",
217
+ palette="viridis",
218
+ )
219
+ plt.title("Energy Flow Composition")
220
+ plt.ylabel("Monthly Energy (kWh)")
221
+ plt.xticks(rotation=45)
222
+ st.pyplot(plt)
223
+
224
+ # Detailed data table
225
+ st.subheader("Detailed Financial Analysis")
226
+ display_df = df[
227
+ [
228
+ "Scenario",
229
+ "Common Area Usage",
230
+ "consumption",
231
+ "production",
232
+ "solar_coverage",
233
+ "grid_purchased",
234
+ "monthly_savings",
235
+ "extra_grid_cost_saved",
236
+ "profit_margin",
237
+ ]
238
+ ].copy()
239
+
240
+ display_df.columns = [
241
+ "Scenario",
242
+ "Common Area",
243
+ "Consumption (kWh)",
244
+ "Production (kWh)",
245
+ "Solar Coverage (%)",
246
+ "Grid Purchased (kWh)",
247
+ "Savings (Ksh)",
248
+ "Extra Cost Saved (Ksh)",
249
+ "Profit Margin (%)",
250
+ ]
251
+
252
+ # Format numeric columns
253
+ for col in display_df.columns[2:]:
254
+ display_df[col] = display_df[col].apply(lambda x: f"{x:,.1f}")
255
+
256
+ st.dataframe(display_df, hide_index=True)
257
+
258
+ # Savings potential highlight
259
+ max_saving = df["extra_grid_cost_saved"].max()
260
+ best_case = df[df["extra_grid_cost_saved"] == max_saving].iloc[0]
261
+
262
+ st.success(
263
+ f"""
264
+ **Maximum Extra Cost Savings Potential:**
265
+ Ksh {max_saving:,.2f}/month in scenario:
266
+ {best_case['Scenario']} with {best_case['Common Area Usage']}
267
+ (Current profit margin: {best_case['profit_margin']:.1f}%)
268
+ """
269
+ )
270
 
271
 
272
  if __name__ == "__main__":