Spaces:
Sleeping
Sleeping
Commit
·
d1cd9d0
1
Parent(s):
f2a4aec
refined
Browse files
app.py
CHANGED
@@ -9,264 +9,202 @@ from typing import Dict
|
|
9 |
ONE_BR_UNITS = 23
|
10 |
TWO_BR_UNITS = 45
|
11 |
SOLAR_PANEL_RATING = 625 # W
|
12 |
-
SOLAR_PANEL_COST = 13000 # KSH per panel
|
13 |
BATTERY_CAPACITY = 200 # Ah
|
14 |
BATTERY_VOLTAGE = 12 # V
|
15 |
-
|
16 |
-
|
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():
|
28 |
"""Initialize session state variables"""
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
34 |
|
35 |
-
def
|
36 |
-
|
37 |
-
) -> float:
|
38 |
-
"""Calculate total monthly consumption based on occupancy rates"""
|
39 |
return (
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
)
|
44 |
-
|
45 |
-
|
46 |
-
def solar_production(panel_count: int, sun_hours: float = 5) -> float:
|
47 |
-
"""Calculate daily solar production considering losses"""
|
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
|
55 |
-
"""Calculate
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
solar_used = min(production, consumption)
|
65 |
-
|
66 |
-
|
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 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
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 |
-
|
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("
|
127 |
-
|
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.
|
140 |
-
|
141 |
-
)
|
142 |
-
st.
|
143 |
-
|
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 |
-
"
|
158 |
-
"
|
159 |
-
"
|
160 |
}
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
194 |
with col1:
|
195 |
-
|
|
|
|
|
|
|
|
|
196 |
with col2:
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
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 |
-
palette="viridis",
|
217 |
-
)
|
218 |
-
plt.title("Energy Flow Composition")
|
219 |
-
plt.ylabel("Monthly Energy (kWh)")
|
220 |
-
plt.xticks(rotation=45)
|
221 |
-
st.pyplot(plt)
|
222 |
-
|
223 |
-
# Detailed data table
|
224 |
-
st.subheader("Detailed Financial Analysis")
|
225 |
-
display_df = df[
|
226 |
-
[
|
227 |
-
"Scenario",
|
228 |
-
"Common Area Usage",
|
229 |
-
"consumption",
|
230 |
-
"production",
|
231 |
-
"solar_coverage",
|
232 |
-
"grid_purchased",
|
233 |
-
"monthly_savings",
|
234 |
-
"extra_grid_cost_saved",
|
235 |
-
"profit_margin",
|
236 |
-
]
|
237 |
-
].copy()
|
238 |
-
|
239 |
-
display_df.columns = [
|
240 |
-
"Scenario",
|
241 |
-
"Common Area",
|
242 |
-
"Consumption (kWh)",
|
243 |
-
"Production (kWh)",
|
244 |
-
"Solar Coverage (%)",
|
245 |
-
"Grid Purchased (kWh)",
|
246 |
-
"Savings (Ksh)",
|
247 |
-
"Extra Cost Saved (Ksh)",
|
248 |
-
"Profit Margin (%)",
|
249 |
-
]
|
250 |
-
|
251 |
-
# Format numeric columns
|
252 |
-
for col in display_df.columns[2:]:
|
253 |
-
display_df[col] = display_df[col].apply(lambda x: f"{x:,.1f}")
|
254 |
-
|
255 |
-
st.dataframe(display_df, hide_index=True)
|
256 |
-
|
257 |
-
# Savings potential highlight
|
258 |
-
max_saving = df["extra_grid_cost_saved"].max()
|
259 |
-
best_case = df[df["extra_grid_cost_saved"] == max_saving].iloc[0]
|
260 |
-
|
261 |
-
st.success(
|
262 |
-
f"""
|
263 |
-
**Maximum Extra Cost Savings Potential:**
|
264 |
-
Ksh {max_saving:,.2f}/month in scenario:
|
265 |
-
{best_case['Scenario']} with {best_case['Common Area Usage']}
|
266 |
-
(Current profit margin: {best_case['profit_margin']:.1f}%)
|
267 |
-
"""
|
268 |
-
)
|
269 |
-
|
270 |
|
271 |
if __name__ == "__main__":
|
272 |
main()
|
|
|
9 |
ONE_BR_UNITS = 23
|
10 |
TWO_BR_UNITS = 45
|
11 |
SOLAR_PANEL_RATING = 625 # W
|
|
|
12 |
BATTERY_CAPACITY = 200 # Ah
|
13 |
BATTERY_VOLTAGE = 12 # V
|
14 |
+
SYSTEM_LOSSES = 0.20
|
15 |
+
FEED_IN_TARIFF = 12
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
+
# Lighting specifications
|
18 |
+
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 = {
|
25 |
+
'solar_panels': 100,
|
26 |
+
'batteries': 50,
|
27 |
+
'panel_price': 13000,
|
28 |
+
'battery_price': 39000,
|
29 |
+
'grid_price': 28.44
|
30 |
+
}
|
31 |
+
for key, value in defaults.items():
|
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 (
|
38 |
+
(occupancy_1br * ONE_BR_UNITS * LIGHTS_1BR * LIGHT_POWER / 1000) +
|
39 |
+
(occupancy_2br * TWO_BR_UNITS * LIGHTS_2BR * LIGHT_POWER / 1000)
|
40 |
+
) * 24 # Daily kWh
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
|
42 |
+
def calculate_appliance_consumption(occupancy_1br: float, occupancy_2br: float) -> float:
|
43 |
+
"""Calculate daily appliance consumption"""
|
44 |
+
return (
|
45 |
+
(occupancy_1br * ONE_BR_UNITS * (250 - (LIGHTS_1BR * LIGHT_POWER * 24 / 1000))) +
|
46 |
+
(occupancy_2br * TWO_BR_UNITS * (400 - (LIGHTS_2BR * LIGHT_POWER * 24 / 1000)))
|
47 |
+
) # Daily kWh
|
48 |
+
|
49 |
+
def total_consumption(occupancy_1br: float, occupancy_2br: float, common_area: float) -> float:
|
50 |
+
"""Calculate total monthly consumption"""
|
51 |
+
lighting = calculate_lighting_consumption(occupancy_1br, occupancy_2br)
|
52 |
+
appliances = calculate_appliance_consumption(occupancy_1br, occupancy_2br)
|
53 |
+
return (lighting + appliances + common_area) * 30 # Monthly kWh
|
54 |
+
|
55 |
+
def solar_production(panels: int) -> float:
|
56 |
+
"""Monthly solar production with losses"""
|
57 |
+
return panels * SOLAR_PANEL_RATING * 5 * 0.8 * 30 / 1000 # 5 sun hours
|
58 |
+
|
59 |
+
def battery_storage(batteries: int) -> float:
|
60 |
+
"""Usable battery capacity"""
|
61 |
+
return batteries * BATTERY_CAPACITY * BATTERY_VOLTAGE * 0.8 / 1000
|
62 |
+
|
63 |
+
def financial_analysis(consumption: float, production: float, storage: float) -> Dict:
|
64 |
+
"""Detailed financial calculations"""
|
65 |
solar_used = min(production, consumption)
|
66 |
+
grid_purchased = max(consumption - solar_used - storage, 0)
|
67 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
return {
|
69 |
+
'solar_contribution': solar_used / consumption * 100,
|
70 |
+
'grid_dependency': grid_purchased / consumption * 100,
|
71 |
+
'monthly_savings': (consumption * st.session_state.grid_price) -
|
72 |
+
(grid_purchased * st.session_state.grid_price),
|
73 |
+
'payback_period': (st.session_state.solar_panels * st.session_state.panel_price +
|
74 |
+
st.session_state.batteries * st.session_state.battery_price) /
|
75 |
+
((consumption - grid_purchased) * st.session_state.grid_price * 12)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
}
|
77 |
|
78 |
+
def create_consumption_breakdown(occupancy_1br: float, occupancy_2br: float, common_area: float):
|
79 |
+
"""Create detailed consumption breakdown"""
|
80 |
+
breakdown = {
|
81 |
+
'Lighting': calculate_lighting_consumption(occupancy_1br, occupancy_2br) * 30,
|
82 |
+
'Appliances': calculate_appliance_consumption(occupancy_1br, occupancy_2br) * 30,
|
83 |
+
'Common Areas': common_area * 30
|
84 |
+
}
|
85 |
+
return pd.DataFrame.from_dict(breakdown, orient='index', columns=['kWh'])
|
86 |
|
87 |
+
# Streamlit Interface
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
def main():
|
89 |
+
st.set_page_config("Solar Analysis Suite", "🌞", "wide")
|
|
|
|
|
90 |
initialize_session_state()
|
91 |
+
|
92 |
+
st.title("🌞 Advanced Solar Performance Analyzer")
|
93 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
with st.sidebar:
|
95 |
st.header("System Configuration")
|
96 |
+
st.number_input("Solar Panels", 1, 1000, key='solar_panels')
|
97 |
+
st.number_input("Batteries", 0, 500, key='batteries')
|
98 |
+
st.number_input("Panel Price (Ksh)", 1000, 50000, key='panel_price')
|
99 |
+
st.number_input("Battery Price (Ksh)", 5000, 100000, key='battery_price')
|
100 |
+
st.number_input("Grid Price (Ksh/kWh)", 10.0, 50.0, key='grid_price')
|
101 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
scenarios = {
|
103 |
+
"Low Occupancy": {"1br": 0.0, "2br": 1.0, "common": 5.904},
|
104 |
+
"Medium Occupancy": {"1br": 0.25, "2br": 1.0, "common": 5.904},
|
105 |
+
"High Occupancy": {"1br": 0.5, "2br": 1.0, "common": 5.904}
|
106 |
}
|
107 |
+
|
108 |
+
analysis_data = []
|
109 |
+
for name, params in scenarios.items():
|
110 |
+
consumption = total_consumption(
|
111 |
+
params["1br"], params["2br"], params["common"]
|
112 |
+
)
|
113 |
+
production = solar_production(st.session_state.solar_panels)
|
114 |
+
storage = battery_storage(st.session_state.batteries)
|
115 |
+
financials = financial_analysis(consumption, production, storage)
|
116 |
+
|
117 |
+
analysis_data.append({
|
118 |
+
"Scenario": name,
|
119 |
+
"Consumption": consumption,
|
120 |
+
"Production": production,
|
121 |
+
**financials
|
122 |
+
})
|
123 |
+
|
124 |
+
df = pd.DataFrame(analysis_data)
|
125 |
+
|
126 |
+
# Energy Flow Analysis
|
127 |
+
st.header("Energy Flow Composition")
|
128 |
+
fig, ax = plt.subplots(figsize=(10, 6))
|
129 |
+
sns.barplot(df.melt(id_vars=['Scenario'],
|
130 |
+
value_vars=['solar_contribution', 'grid_dependency'],
|
131 |
+
x='Scenario', y='value', hue='variable', ax=ax)
|
132 |
+
ax.set_ylabel("Percentage (%)")
|
133 |
+
ax.set_title("Energy Contribution Breakdown")
|
134 |
+
st.pyplot(fig)
|
135 |
+
|
136 |
+
with st.expander("🔍 Energy Flow Interpretation"):
|
137 |
+
st.markdown("""
|
138 |
+
**Understanding the Chart:**
|
139 |
+
- **Solar Contribution**: Percentage of total energy needs met by solar production
|
140 |
+
- **Grid Dependency**: Remaining energy required from grid
|
141 |
+
- Ideal scenario shows high solar contribution with minimal grid dependency
|
142 |
+
""")
|
143 |
+
|
144 |
+
# Financial Analysis
|
145 |
+
st.header("Financial Performance Metrics")
|
146 |
+
|
147 |
+
# Metric 1: Monthly Savings
|
148 |
+
fig1, ax1 = plt.subplots(figsize=(10, 4))
|
149 |
+
sns.barplot(data=df, x='Scenario', y='monthly_savings', ax=ax1)
|
150 |
+
ax1.set_title("Monthly Cost Savings")
|
151 |
+
ax1.set_ylabel("Ksh")
|
152 |
+
st.pyplot(fig1)
|
153 |
+
|
154 |
+
with st.expander("💵 Savings Analysis"):
|
155 |
+
st.markdown(f"""
|
156 |
+
**Key Observations:**
|
157 |
+
- Savings calculated as: `(Total Consumption × Grid Price) - (Grid Purchased × Grid Price)`
|
158 |
+
- Current Grid Price: Ksh {st.session_state.grid_price}/kWh
|
159 |
+
- Maximum potential savings limited by solar production capacity
|
160 |
+
""")
|
161 |
+
st.table(df[['Scenario', 'Consumption', 'Production', 'monthly_savings']])
|
162 |
+
|
163 |
+
# Metric 2: Payback Period
|
164 |
+
fig2, ax2 = plt.subplots(figsize=(10, 4))
|
165 |
+
sns.barplot(data=df, x='Scenario', y='payback_period', ax=ax2)
|
166 |
+
ax2.set_title("System Payback Period")
|
167 |
+
ax2.set_ylabel("Years")
|
168 |
+
st.pyplot(fig2)
|
169 |
+
|
170 |
+
with st.expander("⏳ Payback Explanation"):
|
171 |
+
st.markdown(f"""
|
172 |
+
**Calculation Methodology:**
|
173 |
+
- Total Investment: (Panels × {st.session_state.panel_price:,}Ksh) + (Batteries × {st.session_state.battery_price:,}Ksh)
|
174 |
+
- Annual Savings: Monthly Savings × 12
|
175 |
+
- Payback Period = Total Investment / Annual Savings
|
176 |
+
""")
|
177 |
+
st.table(df[['Scenario', 'payback_period']])
|
178 |
+
|
179 |
+
# Consumption Breakdown
|
180 |
+
st.header("Detailed Consumption Analysis")
|
181 |
+
scenario_select = st.selectbox("Select Scenario", list(scenarios.keys()))
|
182 |
+
|
183 |
+
selected_params = scenarios[scenario_select]
|
184 |
+
breakdown_df = create_consumption_breakdown(
|
185 |
+
selected_params["1br"],
|
186 |
+
selected_params["2br"],
|
187 |
+
selected_params["common"]
|
188 |
+
)
|
189 |
+
|
190 |
+
col1, col2 = st.columns(2)
|
191 |
with col1:
|
192 |
+
st.subheader("Energy Composition")
|
193 |
+
fig3, ax3 = plt.subplots()
|
194 |
+
breakdown_df.plot.pie(y='kWh', ax=ax3, autopct='%1.1f%%')
|
195 |
+
st.pyplot(fig3)
|
196 |
+
|
197 |
with col2:
|
198 |
+
st.subheader("Component Breakdown")
|
199 |
+
st.table(breakdown_df)
|
200 |
+
|
201 |
+
analysis_text = f"""
|
202 |
+
**Key Insights for {scenario_select}:**
|
203 |
+
- Lighting contributes {breakdown_df.loc['Lighting', 'kWh']/breakdown_df.sum().values[0]*100:.1f}% of total consumption
|
204 |
+
- Common areas account for {breakdown_df.loc['Common Areas', 'kWh']:.0f}kWh monthly
|
205 |
+
- 2BR units dominate appliance usage at {selected_params['2br']*100}% occupancy
|
206 |
+
"""
|
207 |
+
st.markdown(analysis_text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
208 |
|
209 |
if __name__ == "__main__":
|
210 |
main()
|