Spaces:
Sleeping
Sleeping
import numpy as np | |
import pandas as pd | |
import matplotlib.pyplot as plt | |
import streamlit as st | |
from typing import Dict, Tuple | |
# Constants | |
ONE_BR_UNITS = 23 | |
TWO_BR_UNITS = 45 | |
SOLAR_PANEL_RATING = 625 # W | |
SOLAR_PANEL_COST = 13000 # KSH per panel | |
BATTERY_CAPACITY = 200 # Ah | |
BATTERY_VOLTAGE = 12 # V | |
BATTERY_COST = 39000 # KSH per battery | |
SYSTEM_LOSSES = 0.20 # 20% system losses | |
GRID_COST_PER_KWH = 25 # KSH | |
FEED_IN_TARIFF = 12 # KSH per kWh sold back to grid | |
# Consumption estimates (kWh/month) | |
ONE_BR_CONSUMPTION = 250 | |
TWO_BR_CONSUMPTION = 400 | |
COMMON_AREA_CONSUMPTION = 1500 # For entire complex | |
def initialize_session_state(): | |
"""Initialize session state variables""" | |
if "solar_panels" not in st.session_state: | |
st.session_state.solar_panels = 100 | |
if "batteries" not in st.session_state: | |
st.session_state.batteries = 50 | |
def calculate_consumption(one_br_occupancy: float, two_br_occupancy: float) -> float: | |
"""Calculate total monthly consumption based on occupancy rates""" | |
total_consumption = ( | |
one_br_occupancy * ONE_BR_UNITS * ONE_BR_CONSUMPTION | |
+ two_br_occupancy * TWO_BR_UNITS * TWO_BR_CONSUMPTION | |
+ COMMON_AREA_CONSUMPTION | |
) | |
return total_consumption | |
def solar_production(panel_count: int, sun_hours: float = 5) -> float: | |
"""Calculate daily solar production considering losses""" | |
daily_production = ( | |
panel_count * SOLAR_PANEL_RATING * sun_hours * (1 - SYSTEM_LOSSES) / 1000 | |
) # kWh | |
monthly_production = daily_production * 30 | |
return monthly_production | |
def battery_storage(battery_count: int) -> float: | |
"""Calculate usable battery storage considering losses""" | |
total_capacity = battery_count * BATTERY_CAPACITY * BATTERY_VOLTAGE / 1000 # kWh | |
usable_capacity = total_capacity * (1 - SYSTEM_LOSSES) | |
return usable_capacity | |
def financial_analysis( | |
monthly_consumption: float, | |
solar_production: float, | |
battery_capacity: float, | |
panel_count: int, | |
battery_count: int, | |
) -> Dict[str, float]: | |
""" | |
Calculate financial metrics including costs, savings, and ROI | |
""" | |
# Initial investment | |
panel_cost = panel_count * SOLAR_PANEL_COST | |
battery_cost = battery_count * BATTERY_COST | |
total_investment = panel_cost + battery_cost | |
# Energy calculations | |
solar_used = min(solar_production, monthly_consumption) | |
excess_solar = max(solar_production - monthly_consumption, 0) | |
grid_purchased = max(monthly_consumption - solar_used, 0) | |
# Battery can store excess or reduce grid purchases | |
battery_stored = min(excess_solar, battery_capacity) | |
battery_used = min(grid_purchased, battery_capacity) | |
# Final energy flows | |
final_grid_purchased = max(grid_purchased - battery_used, 0) | |
final_excess_solar = max(excess_solar - battery_stored, 0) | |
# Financial calculations | |
grid_cost = final_grid_purchased * GRID_COST_PER_KWH | |
feed_in_income = final_excess_solar * FEED_IN_TARIFF | |
savings = (monthly_consumption * GRID_COST_PER_KWH) - grid_cost + feed_in_income | |
return { | |
"total_investment": total_investment, | |
"monthly_savings": savings, | |
"annual_savings": savings * 12, | |
"simple_payback_years": ( | |
total_investment / (savings * 12) if savings > 0 else float("inf") | |
), | |
"grid_purchased": final_grid_purchased, | |
"excess_solar": final_excess_solar, | |
"battery_utilization": ( | |
(battery_used + battery_stored) / battery_capacity | |
if battery_capacity > 0 | |
else 0 | |
), | |
"solar_coverage": ( | |
solar_used / monthly_consumption if monthly_consumption > 0 else 0 | |
), | |
} | |
def plot_scenario_comparison(results: Dict[str, Dict[str, float]]): | |
"""Plot comparison of different occupancy scenarios""" | |
scenarios = list(results.keys()) | |
# Prepare data for plotting | |
metrics = { | |
"Monthly Consumption (kWh)": [results[s]["consumption"] for s in scenarios], | |
"Solar Production (kWh)": [results[s]["solar_production"] for s in scenarios], | |
"Grid Purchased (kWh)": [ | |
results[s]["financials"]["grid_purchased"] for s in scenarios | |
], | |
"Excess Solar (kWh)": [ | |
results[s]["financials"]["excess_solar"] for s in scenarios | |
], | |
} | |
# Create figure | |
fig, axes = plt.subplots(2, 2, figsize=(15, 12)) | |
axes = axes.flatten() | |
for i, (title, values) in enumerate(metrics.items()): | |
axes[i].bar(scenarios, values, color=plt.cm.tab20(i)) | |
axes[i].set_title(title) | |
axes[i].tick_params(axis="x", rotation=45) | |
# Add value labels | |
for j, v in enumerate(values): | |
axes[i].text(j, v * 1.02, f"{v:,.0f}", ha="center", va="bottom") | |
plt.tight_layout() | |
st.pyplot(fig) | |
def plot_financial_comparison(results: Dict[str, Dict[str, float]]): | |
"""Plot financial comparison across scenarios""" | |
scenarios = list(results.keys()) | |
# Prepare financial data | |
financial_metrics = { | |
"Monthly Savings (Ksh)": [ | |
results[s]["financials"]["monthly_savings"] for s in scenarios | |
], | |
"Solar Coverage (%)": [ | |
results[s]["financials"]["solar_coverage"] * 100 for s in scenarios | |
], | |
"Payback Period (Years)": [ | |
results[s]["financials"]["simple_payback_years"] for s in scenarios | |
], | |
"Battery Utilization (%)": [ | |
results[s]["financials"]["battery_utilization"] * 100 for s in scenarios | |
], | |
} | |
# Create figure | |
fig, axes = plt.subplots(2, 2, figsize=(15, 12)) | |
axes = axes.flatten() | |
for i, (title, values) in enumerate(financial_metrics.items()): | |
if title == "Payback Period (Years)": | |
# For payback period, we'll do a horizontal bar chart | |
axes[i].barh(scenarios, values, color=plt.cm.tab20(3)) | |
axes[i].set_xlabel(title) | |
# Add value labels | |
for j, v in enumerate(values): | |
if np.isfinite(v): | |
axes[i].text(v * 1.02, j, f"{v:.1f}", va="center") | |
else: | |
axes[i].text(0, j, "Never", va="center") | |
else: | |
axes[i].bar(scenarios, values, color=plt.cm.tab20(i + 4)) | |
axes[i].set_title(title) | |
axes[i].tick_params(axis="x", rotation=45) | |
# Add value labels | |
for j, v in enumerate(values): | |
axes[i].text(j, v * 1.02, f"{v:,.1f}", ha="center", va="bottom") | |
plt.tight_layout() | |
st.pyplot(fig) | |
def main(): | |
st.set_page_config( | |
page_title="Apartment Complex Solar Analysis", page_icon="🏢", layout="wide" | |
) | |
# Initialize session state | |
initialize_session_state() | |
# Main title and description | |
st.title("🏢 Apartment Complex Solar Energy Analysis") | |
st.markdown( | |
""" | |
This tool analyzes solar energy potential for a complex with: | |
- 45 two-bedroom units | |
- 23 one-bedroom units | |
Comparing three specific occupancy scenarios with 2BR at 100% occupancy and varying 1BR occupancy. | |
""" | |
) | |
# Sidebar for system configuration | |
with st.sidebar: | |
st.header("System Configuration") | |
st.session_state.solar_panels = st.number_input( | |
"Number of Solar Panels", | |
min_value=0, | |
max_value=1000, | |
value=st.session_state.solar_panels, | |
step=1, | |
) | |
st.session_state.batteries = st.number_input( | |
"Number of Batteries", | |
min_value=0, | |
max_value=500, | |
value=st.session_state.batteries, | |
step=1, | |
) | |
st.markdown("---") | |
st.markdown( | |
f"**Panel Specifications:** {SOLAR_PANEL_RATING}W @ Ksh{SOLAR_PANEL_COST:,} each" | |
) | |
st.markdown( | |
f"**Battery Specifications:** {BATTERY_CAPACITY}Ah @ Ksh{BATTERY_COST:,} each" | |
) | |
st.markdown(f"**System Losses:** {SYSTEM_LOSSES*100:.0f}%") | |
# Define the specific scenarios | |
scenarios = { | |
"1BR: 0%, 2BR: 100%": {"1br": 0.0, "2br": 1.0}, | |
"1BR: 25%, 2BR: 100%": {"1br": 0.25, "2br": 1.0}, | |
"1BR: 50%, 2BR: 100%": {"1br": 0.5, "2br": 1.0}, | |
} | |
# Calculate results for each scenario | |
results = {} | |
for scenario, occupancy in scenarios.items(): | |
# Calculate consumption and production | |
consumption = calculate_consumption(occupancy["1br"], occupancy["2br"]) | |
production = solar_production(st.session_state.solar_panels) | |
storage = battery_storage(st.session_state.batteries) | |
# Financial analysis | |
financials = financial_analysis( | |
consumption, | |
production, | |
storage, | |
st.session_state.solar_panels, | |
st.session_state.batteries, | |
) | |
results[scenario] = { | |
"consumption": consumption, | |
"solar_production": production, | |
"battery_capacity": storage, | |
"financials": financials, | |
} | |
# Display system summary | |
st.subheader("System Summary") | |
col1, col2, col3, col4 = st.columns(4) | |
col1.metric("Total Solar Panels", st.session_state.solar_panels) | |
col2.metric("Total Batteries", st.session_state.batteries) | |
col3.metric( | |
"Total Investment", | |
f"Ksh{(st.session_state.solar_panels * SOLAR_PANEL_COST + st.session_state.batteries * BATTERY_COST):,}", | |
) | |
col4.metric( | |
"Total Solar Capacity", | |
f"{st.session_state.solar_panels * SOLAR_PANEL_RATING / 1000:.1f} kW", | |
) | |
# Display scenario comparison | |
st.subheader("Scenario Comparison: Energy Flows") | |
plot_scenario_comparison(results) | |
st.subheader("Scenario Comparison: Financial Metrics") | |
plot_financial_comparison(results) | |
# Detailed results for each scenario | |
st.subheader("Detailed Results by Scenario") | |
for scenario, data in results.items(): | |
with st.expander(f"Scenario: {scenario}"): | |
col1, col2, col3 = st.columns(3) | |
# Energy metrics | |
col1.metric("Monthly Consumption", f"{data['consumption']:,.0f} kWh") | |
col2.metric("Solar Production", f"{data['solar_production']:,.0f} kWh") | |
col3.metric("Battery Capacity", f"{data['battery_capacity']:,.1f} kWh") | |
# Financial metrics | |
col1.metric( | |
"Monthly Savings", f"Ksh{data['financials']['monthly_savings']:,.0f}" | |
) | |
col2.metric( | |
"Annual Savings", f"Ksh{data['financials']['annual_savings']:,.0f}" | |
) | |
payback = data["financials"]["simple_payback_years"] | |
payback_text = f"{payback:.1f} years" if np.isfinite(payback) else "Never" | |
col3.metric("Simple Payback Period", payback_text) | |
# Energy flow details | |
st.markdown("#### Energy Flow Details") | |
flow_data = { | |
"Metric": [ | |
"Grid Purchased", | |
"Excess Solar", | |
"Solar Coverage", | |
"Battery Utilization", | |
], | |
"Value": [ | |
f"{data['financials']['grid_purchased']:,.0f} kWh", | |
f"{data['financials']['excess_solar']:,.0f} kWh", | |
f"{data['financials']['solar_coverage']*100:.1f}%", | |
f"{data['financials']['battery_utilization']*100:.1f}%", | |
], | |
} | |
st.table(pd.DataFrame(flow_data)) | |
if __name__ == "__main__": | |
main() | |