charagu-eric commited on
Commit
8879278
·
1 Parent(s): 704235d

new lights

Browse files
Files changed (4) hide show
  1. .gitattributes +35 -35
  2. README.md +12 -12
  3. app.py +331 -332
  4. requirements.txt +5 -5
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,12 +1,12 @@
1
- ---
2
- title: Solar Savings
3
- emoji: ⚡
4
- colorFrom: yellow
5
- colorTo: red
6
- sdk: streamlit
7
- sdk_version: 1.44.1
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ ---
2
+ title: Solar Savings
3
+ emoji: ⚡
4
+ colorFrom: yellow
5
+ colorTo: red
6
+ sdk: streamlit
7
+ sdk_version: 1.44.1
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py CHANGED
@@ -1,332 +1,331 @@
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": 0.006 * 5, "usage_hours": 5, "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__":
331
- main()
332
-
 
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__":
331
+ main()
 
requirements.txt CHANGED
@@ -1,6 +1,6 @@
1
- streamlit
2
- polars
3
- xarray
4
- seaborn
5
- matplotlib
6
  numpy
 
1
+ streamlit
2
+ polars
3
+ xarray
4
+ seaborn
5
+ matplotlib
6
  numpy