mabuseif commited on
Commit
fcd0cb7
·
verified ·
1 Parent(s): 9b4684d

Update data/climate_data.py

Browse files
Files changed (1) hide show
  1. data/climate_data.py +9 -259
data/climate_data.py CHANGED
@@ -1,172 +1,4 @@
1
- """
2
- ASHRAE 169 climate data module for HVAC Load Calculator.
3
- This module provides access to climate data for various locations based on ASHRAE 169 standard.
4
-
5
- Author: Dr Majed Abuseif
6
- Date: March 2025
7
- Version: 1.0.0
8
- """
9
-
10
- from typing import Dict, List, Any, Optional
11
- import pandas as pd
12
- import numpy as np
13
- import os
14
- import json
15
- from dataclasses import dataclass
16
- import streamlit as st
17
- import plotly.graph_objects as go
18
- from io import StringIO
19
-
20
- # Define paths
21
- DATA_DIR = os.path.dirname(os.path.abspath(__file__))
22
-
23
-
24
- @dataclass
25
- class ClimateLocation:
26
- """Class representing a climate location with ASHRAE 169 data."""
27
-
28
- id: str
29
- country: str
30
- state_province: str
31
- city: str
32
- latitude: float
33
- longitude: float
34
- elevation: float # meters
35
- climate_zone: str
36
- heating_degree_days: float # base 18°C
37
- cooling_degree_days: float # base 18°C
38
- winter_design_temp: float # 99.6% heating design temperature (°C)
39
- summer_design_temp_db: float # 0.4% cooling design dry-bulb temperature (°C)
40
- summer_design_temp_wb: float # 0.4% cooling design wet-bulb temperature (°C)
41
- summer_daily_range: float # Mean daily temperature range in summer (°C)
42
- monthly_temps: Dict[str, float] # Average monthly temperatures (°C)
43
- monthly_humidity: Dict[str, float] # Average monthly relative humidity (%)
44
-
45
- def to_dict(self) -> Dict[str, Any]:
46
- """Convert the climate location to a dictionary."""
47
- return {
48
- "id": self.id,
49
- "country": self.country,
50
- "state_province": self.state_province,
51
- "city": self.city,
52
- "latitude": self.latitude,
53
- "longitude": self.longitude,
54
- "elevation": self.elevation,
55
- "climate_zone": self.climate_zone,
56
- "heating_degree_days": self.heating_degree_days,
57
- "cooling_degree_days": self.cooling_degree_days,
58
- "winter_design_temp": self.winter_design_temp,
59
- "summer_design_temp_db": self.summer_design_temp_db,
60
- "summer_design_temp_wb": self.summer_design_temp_wb,
61
- "summer_daily_range": self.summer_daily_range,
62
- "monthly_temps": self.monthly_temps,
63
- "monthly_humidity": self.monthly_humidity
64
- }
65
-
66
-
67
- class ClimateData:
68
- """Class for managing ASHRAE 169 climate data."""
69
-
70
- def __init__(self):
71
- """Initialize climate data."""
72
- self.locations = {}
73
- self.countries = []
74
- self.country_states = {}
75
-
76
- def _group_locations_by_country_state(self) -> Dict[str, Dict[str, List[str]]]:
77
- """Group locations by country and state/province."""
78
- result = {}
79
- for loc in self.locations.values():
80
- if loc.country not in result:
81
- result[loc.country] = {}
82
- if loc.state_province not in result[loc.country]:
83
- result[loc.country][loc.state_province] = []
84
- result[loc.country][loc.state_province].append(loc.city)
85
- for country in result:
86
- for state in result[country]:
87
- result[country][state] = sorted(result[country][state])
88
- return result
89
-
90
- def add_location(self, location: ClimateLocation):
91
- """Add a new location to the dictionary."""
92
- self.locations[location.id] = location
93
- self.countries = sorted(list(set(loc.country for loc in self.locations.values())))
94
- self.country_states = self._group_locations_by_country_state()
95
-
96
- def display_climate_input(self, session_state: Dict[str, Any]):
97
- """Display form for manual input or EPW upload in Streamlit."""
98
- st.title("Climate Data")
99
-
100
- if not session_state.building_info.get("country") or not session_state.building_info.get("city"):
101
- st.warning("Please enter country and city in Building Information first.")
102
- st.button("Go to Building Information", on_click=lambda: setattr(session_state, "page", "Building Information"))
103
- return
104
-
105
- st.subheader(f"Location: {session_state.building_info['country']}, {session_state.building_info['city']}")
106
- tab1, tab2 = st.tabs(["Manual Input", "Upload EPW File"])
107
-
108
- # Manual Input Tab
109
- with tab1:
110
- with st.form("manual_climate_form"):
111
- col1, col2 = st.columns(2)
112
- with col1:
113
- id_input = st.text_input("Location ID", value=f"{session_state.building_info['country'][:2].upper()}-{session_state.building_info['city'][:3].upper()}")
114
- state_province = st.text_input("State/Province", value="N/A")
115
- latitude = st.number_input("Latitude", min_value=-90.0, max_value=90.0, value=0.0, step=0.1)
116
- longitude = st.number_input("Longitude", min_value=-180.0, max_value=180.0, value=0.0, step=0.1)
117
- elevation = st.number_input("Elevation (m)", min_value=0.0, value=0.0, step=10.0)
118
- climate_zone = st.selectbox("Climate Zone", ["0A", "0B", "1A", "1B", "2A", "2B", "3A", "3B", "3C", "4A", "4B", "4C", "5A", "5B", "5C", "6A", "6B", "7", "8"])
119
-
120
- with col2:
121
- hdd = st.number_input("Heating Degree Days (base 18°C)", min_value=0.0, value=0.0, step=100.0)
122
- cdd = st.number_input("Cooling Degree Days (base 18°C)", min_value=0.0, value=0.0, step=100.0)
123
- winter_design_temp = st.number_input("Winter Design Temp (99.6%) (°C)", min_value=-50.0, max_value=20.0, value=0.0, step=0.5)
124
- summer_design_temp_db = st.number_input("Summer Design Temp DB (0.4%) (°C)", min_value=0.0, max_value=50.0, value=35.0, step=0.5)
125
- summer_design_temp_wb = st.number_input("Summer Design Temp WB (0.4%) (°C)", min_value=0.0, max_value=40.0, value=25.0, step=0.5)
126
- summer_daily_range = st.number_input("Summer Daily Range (°C)", min_value=0.0, value=5.0, step=0.5)
127
-
128
- st.subheader("Monthly Data")
129
- monthly_temps = {}
130
- monthly_humidity = {}
131
- month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
132
- col1, col2 = st.columns(2)
133
- with col1:
134
- for month in month_names[:6]:
135
- monthly_temps[month] = st.number_input(f"{month} Temp (°C)", min_value=-50.0, max_value=50.0, value=20.0, step=0.5, key=f"temp_{month}")
136
- with col2:
137
- for month in month_names[6:]:
138
- monthly_temps[month] = st.number_input(f"{month} Temp (°C)", min_value=-50.0, max_value=50.0, value=20.0, step=0.5, key=f"temp_{month}")
139
- col1, col2 = st.columns(2)
140
- with col1:
141
- for month in month_names[:6]:
142
- monthly_humidity[month] = st.number_input(f"{month} Humidity (%)", min_value=0.0, max_value=100.0, value=50.0, step=5.0, key=f"hum_{month}")
143
- with col2:
144
- for month in month_names[6:]:
145
- monthly_humidity[month] = st.number_input(f"{month} Humidity (%)", min_value=0.0, max_value=100.0, value=50.0, step=5.0, key=f"hum_{month}")
146
-
147
- if st.form_submit_button("Save Climate Data"):
148
- location = ClimateLocation(
149
- id=id_input,
150
- country=session_state.building_info["country"],
151
- state_province=state_province,
152
- city=session_state.building_info["city"],
153
- latitude=latitude,
154
- longitude=longitude,
155
- elevation=elevation,
156
- climate_zone=climate_zone,
157
- heating_degree_days=hdd,
158
- cooling_degree_days=cdd,
159
- winter_design_temp=winter_design_temp,
160
- summer_design_temp_db=summer_design_temp_db,
161
- summer_design_temp_wb=summer_design_temp_wb,
162
- summer_daily_range=summer_daily_range,
163
- monthly_temps=monthly_temps,
164
- monthly_humidity=monthly_humidity
165
- )
166
- self.add_location(location)
167
- st.success("Climate data saved manually!")
168
- self.display_design_conditions(location)
169
- self.visualize_data(location, epw_data=None)
170
 
171
  # EPW Upload Tab
172
  with tab2:
@@ -191,11 +23,11 @@ class ClimateData:
191
  for col in epw_data.columns:
192
  epw_data[col] = pd.to_numeric(epw_data[col], errors='coerce')
193
 
194
- # Extract key columns (corrected humidity to column 21)
195
  months = epw_data[1].values # Month
196
- dry_bulb = epw_data[6].values # Dry-bulb temperature (°C)
197
- wet_bulb = epw_data[8].values # Wet-bulb temperature (°C)
198
- humidity = epw_data[21].values # Relative humidity (%) - corrected from 9
199
 
200
  # Check for critical NaN issues
201
  if np.all(np.isnan(dry_bulb)) or np.all(np.isnan(humidity)):
@@ -253,62 +85,7 @@ class ClimateData:
253
  except Exception as e:
254
  st.error(f"Error processing EPW file: {str(e)}. Ensure it has 8760 hourly records and correct format.")
255
 
256
- # Navigation buttons
257
- col1, col2 = st.columns(2)
258
- with col1:
259
- st.button("Back to Building Information", on_click=lambda: setattr(session_state, "page", "Building Information"))
260
- with col2:
261
- if self.locations:
262
- st.button("Continue to Building Components", on_click=lambda: setattr(session_state, "page", "Building Components"))
263
- else:
264
- st.button("Continue to Building Components", disabled=True)
265
-
266
- def display_design_conditions(self, location: ClimateLocation):
267
- """Display a table of design conditions for calculations."""
268
- st.subheader("Design Conditions for HVAC Calculations")
269
- design_data = pd.DataFrame({
270
- "Parameter": [
271
- "Climate Zone",
272
- "Heating Degree Days (base 18°C)",
273
- "Cooling Degree Days (base 18°C)",
274
- "Winter Design Temperature (99.6%)",
275
- "Summer Design Dry-Bulb Temp (0.4%)",
276
- "Summer Design Wet-Bulb Temp (0.4%)",
277
- "Summer Daily Temperature Range"
278
- ],
279
- "Value": [
280
- location.climate_zone,
281
- f"{location.heating_degree_days} HDD",
282
- f"{location.cooling_degree_days} CDD",
283
- f"{location.winter_design_temp} °C",
284
- f"{location.summer_design_temp_db} °C",
285
- f"{location.summer_design_temp_wb} °C",
286
- f"{location.summer_daily_range} °C"
287
- ]
288
- })
289
- st.table(design_data)
290
-
291
- @staticmethod
292
- def assign_climate_zone(hdd: float, cdd: float, avg_humidity: float) -> str:
293
- """Assign ASHRAE 169 climate zone based on HDD, CDD, and humidity."""
294
- if cdd > 10000:
295
- return "0A" if avg_humidity > 60 else "0B"
296
- elif cdd > 5000:
297
- return "1A" if avg_humidity > 60 else "1B"
298
- elif cdd > 2500:
299
- return "2A" if avg_humidity > 60 else "2B"
300
- elif hdd < 2000 and cdd > 1000:
301
- return "3A" if avg_humidity > 60 else "3B" if avg_humidity < 40 else "3C"
302
- elif hdd < 3000:
303
- return "4A" if avg_humidity > 60 else "4B" if avg_humidity < 40 else "4C"
304
- elif hdd < 4000:
305
- return "5A" if avg_humidity > 60 else "5B" if avg_humidity < 40 else "5C"
306
- elif hdd < 5000:
307
- return "6A" if avg_humidity > 60 else "6B"
308
- elif hdd < 7000:
309
- return "7"
310
- else:
311
- return "8"
312
 
313
  @staticmethod
314
  def visualize_data(location: ClimateLocation, epw_data: Optional[pd.DataFrame] = None):
@@ -333,7 +110,7 @@ class ClimateData:
333
 
334
  # Add min/max for EPW data only
335
  if epw_data is not None:
336
- dry_bulb = epw_data[6].values
337
  month_col = epw_data[1].values
338
  temps_min = []
339
  temps_max = []
@@ -382,7 +159,7 @@ class ClimateData:
382
 
383
  # Add min/max for EPW data only
384
  if epw_data is not None:
385
- humidity = epw_data[21].values
386
  humidity_min = []
387
  humidity_max = []
388
  for i in range(1, 13):
@@ -417,31 +194,4 @@ class ClimateData:
417
  )
418
  st.plotly_chart(fig_hum, use_container_width=True)
419
 
420
- def export_to_json(self, file_path: str) -> None:
421
- """Export all climate data to a JSON file."""
422
- data = {loc_id: loc.to_dict() for loc_id, loc in self.locations.items()}
423
- with open(file_path, 'w') as f:
424
- json.dump(data, f, indent=4)
425
-
426
- @classmethod
427
- def from_json(cls, file_path: str) -> 'ClimateData':
428
- """Create a ClimateData instance from a JSON file."""
429
- with open(file_path, 'r') as f:
430
- data = json.load(f)
431
- climate_data = cls()
432
- climate_data.locations = {}
433
- for loc_id, loc_dict in data.items():
434
- climate_data.locations[loc_id] = ClimateLocation(**loc_dict)
435
- climate_data.countries = sorted(list(set(loc.country for loc in climate_data.locations.values())))
436
- climate_data.country_states = climate_data._group_locations_by_country_state()
437
- return climate_data
438
-
439
-
440
- if __name__ == "__main__":
441
- if "building_info" not in st.session_state:
442
- st.session_state.building_info = {"country": "Iceland", "city": "Reykjavik"}
443
- if "page" not in st.session_state:
444
- st.session_state.page = "Climate Data"
445
-
446
- climate_data = ClimateData()
447
- climate_data.display_climate_input(st.session_state)
 
1
+ # ... (Previous code unchanged up to the EPW Upload Tab) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  # EPW Upload Tab
4
  with tab2:
 
23
  for col in epw_data.columns:
24
  epw_data[col] = pd.to_numeric(epw_data[col], errors='coerce')
25
 
26
+ # Extract key columns (using your specified indices: 7, 8, 9)
27
  months = epw_data[1].values # Month
28
+ dry_bulb = epw_data[7].values # Dry-bulb temperature (°C) - changed from 6 to 7
29
+ wet_bulb = epw_data[8].values # Wet-bulb temperature (°C) - unchanged
30
+ humidity = epw_data[9].values # Relative humidity (%) - changed from 21 to 9
31
 
32
  # Check for critical NaN issues
33
  if np.all(np.isnan(dry_bulb)) or np.all(np.isnan(humidity)):
 
85
  except Exception as e:
86
  st.error(f"Error processing EPW file: {str(e)}. Ensure it has 8760 hourly records and correct format.")
87
 
88
+ # ... (Rest of the code unchanged up to visualize_data) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  @staticmethod
91
  def visualize_data(location: ClimateLocation, epw_data: Optional[pd.DataFrame] = None):
 
110
 
111
  # Add min/max for EPW data only
112
  if epw_data is not None:
113
+ dry_bulb = epw_data[7].values # Changed from 6 to 7
114
  month_col = epw_data[1].values
115
  temps_min = []
116
  temps_max = []
 
159
 
160
  # Add min/max for EPW data only
161
  if epw_data is not None:
162
+ humidity = epw_data[9].values # Changed from 21 to 9
163
  humidity_min = []
164
  humidity_max = []
165
  for i in range(1, 13):
 
194
  )
195
  st.plotly_chart(fig_hum, use_container_width=True)
196
 
197
+ # ... (Rest of the code unchanged) ...