charagu-eric commited on
Commit
7c4cea5
·
1 Parent(s): 8879278
Files changed (1) hide show
  1. app.py +293 -286
app.py CHANGED
@@ -1,330 +1,337 @@
1
- import streamlit as st
2
- import polars as pl
3
- import xarray as xr
4
- import seaborn as sns
5
- import matplotlib.pyplot as plt
6
  import numpy as np
7
- from typing import Dict, List, Tuple
 
 
 
8
 
9
  # Constants
10
- GRID_PRICE = 28.44 # Ksh/kWh
11
- TIME_RESOLUTION = 24 # hours in a day
12
-
13
- # Appliance data structure
14
- APPLIANCES = {
15
- "Lights": {"power_kw": 2088, "usage_hours": 16, "grid_only": False},
16
- "TV": {"power_kw": 0.08, "usage_hours": 5, "grid_only": False},
17
- "Fridge": {"power_kw": 0.8, "usage_hours": 24, "grid_only": False},
18
- "Computer": {"power_kw": 0.15, "usage_hours": 9, "grid_only": False},
19
- # "Water Pump": {"power_kw": 0.5, "usage_hours": 2, "grid_only": False},
20
- "Instant Shower": {"power_kw": 5.0, "usage_hours": 0.5, "grid_only": True},
21
- "Electric Kettle": {"power_kw": 1.5, "usage_hours": 0.25, "grid_only": True},
22
- "Microwave": {"power_kw": 2.0, "usage_hours": 0.2, "grid_only": True},
23
- # "Oven": {"power_kw": 1.0, "usage_hours": 0.3, "grid_only": True},
24
- }
25
 
26
 
27
  def initialize_session_state():
28
- """Initialize all session state variables"""
29
- if "solar_price" not in st.session_state:
30
- st.session_state.solar_price = 10.0
31
- if "solar_ratio" not in st.session_state:
32
- st.session_state.solar_ratio = 0.5
33
- if "appliance_data" not in st.session_state:
34
- st.session_state.appliance_data = None
35
-
36
-
37
- def create_appliance_dataframe() -> pl.DataFrame:
38
- """Create a Polars DataFrame from the appliance data"""
39
- data = []
40
- for name, specs in APPLIANCES.items():
41
- data.append(
42
- {
43
- "appliance": name,
44
- "power_kw": specs["power_kw"],
45
- "usage_hours": specs["usage_hours"],
46
- "grid_only": specs["grid_only"],
47
- "daily_kwh": specs["power_kw"] * specs["usage_hours"],
48
- }
49
- )
50
- return pl.DataFrame(data)
51
-
52
-
53
- def create_time_series_dataset(df: pl.DataFrame) -> xr.Dataset:
54
- """Create an xarray Dataset with time series data"""
55
- hours = np.arange(TIME_RESOLUTION)
56
- appliances = df["appliance"].to_list()
57
-
58
- # Create empty arrays
59
- power_usage = xr.DataArray(
60
- np.zeros((len(appliances), TIME_RESOLUTION)),
61
- dims=("appliance", "hour"),
62
- coords={"appliance": appliances, "hour": hours},
63
  )
64
-
65
- # Fill with actual usage patterns (simplified - could be enhanced)
66
- for i, row in enumerate(df.iter_rows(named=True)):
67
- if row["usage_hours"] > 0:
68
- usage_start = 8 # assuming morning start
69
- usage_end = min(usage_start + int(row["usage_hours"]), TIME_RESOLUTION)
70
- power_usage[i, usage_start:usage_end] = row["power_kw"]
71
-
72
- return xr.Dataset(
73
- {
74
- "power_usage": power_usage,
75
- "daily_kwh": xr.DataArray(df["daily_kwh"].to_numpy(), dims="appliance"),
76
- "grid_only": xr.DataArray(df["grid_only"].to_numpy(), dims="appliance"),
77
- }
78
- )
79
-
80
-
81
- def calculate_consumption(ds: xr.Dataset, solar_ratio: float) -> Dict:
82
- """Calculate consumption breakdown and costs"""
83
- # Separate grid-only and mixed appliances
84
- mixed_mask = ~ds["grid_only"]
85
- grid_only_mask = ds["grid_only"]
86
-
87
- # Calculate consumptions
88
- grid_only_consumption = ds["daily_kwh"].where(grid_only_mask, 0).sum().item()
89
- total_mixed_consumption = ds["daily_kwh"].where(mixed_mask, 0).sum().item()
90
-
91
- solar_consumption = total_mixed_consumption * solar_ratio
92
- grid_mixed_consumption = total_mixed_consumption * (1 - solar_ratio)
93
- total_grid_consumption = grid_mixed_consumption + grid_only_consumption
94
-
95
- # Calculate costs
96
- grid_cost = total_grid_consumption * GRID_PRICE
97
- solar_cost = solar_consumption * st.session_state.solar_price
98
- total_cost = grid_cost + solar_cost
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  return {
101
- "solar_consumption": solar_consumption,
102
- "grid_consumption": total_grid_consumption,
103
- "grid_only_consumption": grid_only_consumption,
104
- "total_consumption": total_mixed_consumption + grid_only_consumption,
105
- "grid_cost": grid_cost,
106
- "solar_cost": solar_cost,
107
- "total_cost": total_cost,
108
- "hourly_power": ds["power_usage"],
 
 
 
 
 
 
 
 
109
  }
110
 
111
 
112
- def find_optimal_solar_price(solar_ratio: float, years: int = 5) -> float:
113
- """Calculate optimal solar price considering payback period"""
114
- # Simplified model - in reality would need installation costs, etc.
115
- # Assumes you want payback within 'years' years
116
- daily_savings = GRID_PRICE - (GRID_PRICE * 0.8) # 20% savings target
117
- return max(0, GRID_PRICE - (daily_savings / years))
118
-
119
-
120
- def plot_consumption_breakdown(data: Dict):
121
- """Create consumption breakdown visualization"""
122
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
123
-
124
- # Pie chart
125
- labels = ["Solar", "Grid (mixed)", "Grid (high load)"]
126
- sizes = [
127
- data["solar_consumption"],
128
- data["grid_consumption"] - data["grid_only_consumption"],
129
- data["grid_only_consumption"],
130
- ]
131
- ax1.pie(sizes, labels=labels, autopct="%1.1f%%", startangle=90)
132
- ax1.set_title("Energy Source Breakdown")
133
-
134
- # Cost comparison bar plot
135
- cost_df = pl.DataFrame(
136
- {
137
- "source": ["Solar", "Grid", "Total"],
138
- "cost": [data["solar_cost"], data["grid_cost"], data["total_cost"]],
139
- }
140
- )
141
- sns.barplot(data=cost_df.to_pandas(), x="source", y="cost", ax=ax2)
142
- ax2.set_title("Daily Cost Comparison")
143
- ax2.set_ylabel("Cost (Ksh)")
144
-
145
- st.pyplot(fig)
146
-
147
 
148
- def plot_hourly_usage(hourly_data: xr.DataArray):
149
- """Plot hourly power usage patterns"""
150
- fig, ax = plt.subplots(figsize=(10, 5))
151
 
152
- # Convert to DataFrame for Seaborn
153
- df = hourly_data.to_dataframe(name="power_kw").reset_index()
 
 
154
 
155
- # Plot grid-only vs solar-capable appliances separately
156
- grid_only_mask = df["appliance"].isin(
157
- [name for name, specs in APPLIANCES.items() if specs["grid_only"]]
158
- )
159
 
160
- sns.lineplot(
161
- data=df[~grid_only_mask],
162
- x="hour",
163
- y="power_kw",
164
- hue="appliance",
165
- ax=ax,
166
- palette="crest",
167
- legend=True,
168
- )
169
 
170
- sns.lineplot(
171
- data=df[grid_only_mask],
172
- x="hour",
173
- y="power_kw",
174
- hue="appliance",
175
- ax=ax,
176
- palette="flare",
177
- linestyle="--",
178
- legend=True,
179
- )
180
 
181
- ax.set_title("Hourly Power Consumption Patterns")
182
- ax.set_xlabel("Hour of Day")
183
- ax.set_ylabel("Power (kW)")
184
- ax.legend(title="Appliance", bbox_to_anchor=(1.05, 1), loc="upper left")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  st.pyplot(fig)
187
 
188
 
189
  def main():
190
  st.set_page_config(
191
- page_title="Solar vs Grid Consumption Analyzer", page_icon="☀️", layout="wide"
192
  )
193
 
194
- # Initialize session state and data structures
195
  initialize_session_state()
196
- appliance_df = create_appliance_dataframe()
197
- ds = create_time_series_dataset(appliance_df)
198
 
199
  # Main title and description
200
- st.title("☀️ Solar vs National Grid Consumption Analyzer")
201
- st.markdown("""
202
- Analyze the financial impact of different solar/grid consumption ratios
203
- and optimize your energy costs.
204
- """)
 
 
 
 
 
205
 
206
- # Sidebar for inputs
207
  with st.sidebar:
208
- st.header("Configuration")
209
- st.session_state.solar_ratio = st.slider(
210
- "Solar/Grid Consumption Ratio",
211
- min_value=0.0,
212
- max_value=1.0,
213
- value=0.5,
214
- step=0.1,
215
- format="%.1f",
216
  )
217
 
218
- st.session_state.solar_price = st.number_input(
219
- "Custom Solar Price (Ksh/kWh)",
220
- min_value=0.0,
221
- max_value=float(GRID_PRICE),
222
- value=10.0,
223
- step=0.1,
224
  )
225
 
226
  st.markdown("---")
227
- st.metric("Current Grid Price", f"{GRID_PRICE} Ksh/kWh")
228
-
229
- # Calculate consumption and costs
230
- data = calculate_consumption(ds, st.session_state.solar_ratio)
231
- optimal_price = find_optimal_solar_price(st.session_state.solar_ratio)
232
-
233
- # Main content columns
234
- col1, col2, col3 = st.columns(3)
235
- with col1:
236
- st.metric("Total Daily Consumption", f"{data['total_consumption']:.2f} kWh")
237
- with col2:
238
- st.metric("Your Solar Price", f"{st.session_state.solar_price} Ksh/kWh")
239
- with col3:
240
- st.metric("Suggested Optimal Price", f"{optimal_price:.2f} Ksh/kWh")
241
-
242
- # Visualization section
243
- st.markdown("---")
244
- st.subheader("Consumption Analysis")
245
- plot_consumption_breakdown(data)
246
-
247
- st.subheader("Hourly Usage Patterns")
248
- plot_hourly_usage(data["hourly_power"])
249
-
250
- # Financial analysis
251
- st.subheader("Financial Impact")
252
- savings = (GRID_PRICE * data["total_consumption"]) - data["total_cost"]
253
- col1, col2 = st.columns(2)
254
- with col1:
255
- st.metric("Daily Savings vs All-Grid", f"{savings:.2f} Ksh")
256
- st.metric("Annual Savings Potential", f"{savings * 365:.2f} Ksh")
257
- with col2:
258
- st.metric(
259
- "Grid Dependency",
260
- f"{(data['grid_consumption'] / data['total_consumption']) * 100:.1f}%",
261
  )
262
- st.metric(
263
- "Solar Viability",
264
- f"{(data['solar_consumption'] / data['total_consumption']) * 100:.1f}%",
265
  )
 
266
 
267
- # Appliance details table
268
- st.subheader("Appliance Details")
269
- st.dataframe(
270
- appliance_df.select(
271
- [
272
- pl.col("appliance"),
273
- pl.col("power_kw").alias("Power (kW)"),
274
- pl.col("usage_hours").alias("Usage (hrs)"),
275
- pl.col("daily_kwh").alias("Daily (kWh)"),
276
- pl.col("grid_only").alias("Grid Only"),
277
- ]
278
- ),
279
- use_container_width=True,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  )
281
 
282
- # Grid-only appliances warning
283
- grid_only_appliances = [
284
- name for name, specs in APPLIANCES.items() if specs["grid_only"]
285
- ]
286
- st.warning(f"""
287
- **Grid-Only Appliances:** These high-load devices cannot be solar-powered:
288
- {", ".join(grid_only_appliances)}.
289
- They represent fixed grid costs of {data["grid_only_consumption"]:.2f} kWh/day.
290
- """)
291
-
292
- # Advanced analysis expander
293
- with st.expander("Advanced Analysis"):
294
- st.write("""
295
- ### Detailed Financial Modeling
296
-
297
- For a more accurate financial analysis, consider:
298
- - Solar installation costs
299
- - Maintenance expenses
300
- - Battery storage requirements
301
- - Grid feed-in tariffs
302
- - Equipment degradation over time
303
- """)
304
-
305
- # Sensitivity analysis
306
- st.write("### Price Sensitivity Analysis")
307
- solar_prices = np.linspace(0, GRID_PRICE, 20)
308
- costs = []
309
- for price in solar_prices:
310
- temp_data = calculate_consumption(ds, st.session_state.solar_ratio)
311
- temp_data["solar_price"] = price
312
- costs.append(temp_data["total_cost"])
313
-
314
- fig, ax = plt.subplots()
315
- sns.lineplot(x=solar_prices, y=costs, ax=ax)
316
- ax.set_xlabel("Solar Price (Ksh/kWh)")
317
- ax.set_ylabel("Total Daily Cost (Ksh)")
318
- ax.axvline(x=optimal_price, color="r", linestyle="--", label="Optimal Price")
319
- ax.legend()
320
- st.pyplot(fig)
321
-
322
- # Footer
323
- st.markdown("---")
324
- st.caption("""
325
- *Note: This analysis provides estimates only. Consult with a solar energy professional
326
- for accurate system sizing and financial projections.*
327
- """)
 
 
328
 
329
 
330
  if __name__ == "__main__":
 
 
 
 
 
 
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
9
+ TWO_BR_UNITS = 45
10
+ SOLAR_PANEL_RATING = 625 # W
11
+ SOLAR_PANEL_COST = 13000 # KSH per panel
12
+ 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():
26
+ """Initialize session state variables"""
27
+ if "solar_panels" not in st.session_state:
28
+ st.session_state.solar_panels = 100
29
+ if "batteries" not in st.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:
44
+ """Calculate daily solar production considering losses"""
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__":