Spaces:
Sleeping
Sleeping
Commit
·
7c4cea5
1
Parent(s):
8879278
redesign
Browse files
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 |
-
|
|
|
|
|
|
|
8 |
|
9 |
# Constants
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
#
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
|
26 |
|
27 |
def initialize_session_state():
|
28 |
-
"""Initialize
|
29 |
-
if "
|
30 |
-
st.session_state.
|
31 |
-
if "
|
32 |
-
st.session_state.
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
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 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
|
100 |
return {
|
101 |
-
"
|
102 |
-
"
|
103 |
-
"
|
104 |
-
"
|
105 |
-
|
106 |
-
|
107 |
-
"
|
108 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
}
|
110 |
|
111 |
|
112 |
-
def
|
113 |
-
"""
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
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 |
-
|
149 |
-
|
150 |
-
|
151 |
|
152 |
-
|
153 |
-
|
|
|
|
|
154 |
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
)
|
159 |
|
160 |
-
|
161 |
-
|
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 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
186 |
st.pyplot(fig)
|
187 |
|
188 |
|
189 |
def main():
|
190 |
st.set_page_config(
|
191 |
-
page_title="
|
192 |
)
|
193 |
|
194 |
-
# Initialize session state
|
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("
|
201 |
-
st.markdown(
|
202 |
-
|
203 |
-
|
204 |
-
|
|
|
|
|
|
|
|
|
|
|
205 |
|
206 |
-
# Sidebar for
|
207 |
with st.sidebar:
|
208 |
-
st.header("Configuration")
|
209 |
-
st.session_state.
|
210 |
-
"Solar
|
211 |
-
min_value=0
|
212 |
-
max_value=
|
213 |
-
value=
|
214 |
-
step=
|
215 |
-
format="%.1f",
|
216 |
)
|
217 |
|
218 |
-
st.session_state.
|
219 |
-
"
|
220 |
-
min_value=0
|
221 |
-
max_value=
|
222 |
-
value=
|
223 |
-
step=
|
224 |
)
|
225 |
|
226 |
st.markdown("---")
|
227 |
-
st.
|
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.
|
263 |
-
"
|
264 |
-
f"{(data['solar_consumption'] / data['total_consumption']) * 100:.1f}%",
|
265 |
)
|
|
|
266 |
|
267 |
-
#
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
)
|
279 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
280 |
)
|
281 |
|
282 |
-
#
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
st.
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
""
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
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__":
|