solar_savings / app.py
charagu-eric's picture
redesign
7c4cea5
raw
history blame
11.9 kB
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()