Update data/climate_data.py
Browse files- data/climate_data.py +170 -36
data/climate_data.py
CHANGED
|
@@ -18,6 +18,7 @@ import plotly.graph_objects as go
|
|
| 18 |
from io import StringIO
|
| 19 |
import pvlib
|
| 20 |
from datetime import datetime, timedelta
|
|
|
|
| 21 |
|
| 22 |
# Define paths
|
| 23 |
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
@@ -45,8 +46,9 @@ class ClimateLocation:
|
|
| 45 |
hourly_data: List[Dict] # Hourly data for integration with main.py
|
| 46 |
typical_extreme_periods: Dict[str, Dict] # Typical/extreme periods (summer/winter)
|
| 47 |
ground_temperatures: Dict[str, List[float]] # Monthly ground temperatures by depth
|
|
|
|
| 48 |
|
| 49 |
-
def __init__(self, epw_file: pd.DataFrame, typical_extreme_periods: Dict, ground_temperatures: Dict, **kwargs):
|
| 50 |
"""Initialize ClimateLocation with EPW file data and header information."""
|
| 51 |
self.id = kwargs.get("id")
|
| 52 |
self.country = kwargs.get("country")
|
|
@@ -57,6 +59,7 @@ class ClimateLocation:
|
|
| 57 |
self.elevation = kwargs.get("elevation")
|
| 58 |
self.typical_extreme_periods = typical_extreme_periods
|
| 59 |
self.ground_temperatures = ground_temperatures
|
|
|
|
| 60 |
|
| 61 |
# Extract columns from EPW data
|
| 62 |
months = pd.to_numeric(epw_file[1], errors='coerce').values
|
|
@@ -95,24 +98,25 @@ class ClimateLocation:
|
|
| 95 |
self.climate_zone = ClimateData.assign_climate_zone(self.heating_degree_days, self.cooling_degree_days, np.nanmean(humidity))
|
| 96 |
|
| 97 |
# Store hourly data with enhanced fields
|
| 98 |
-
self.hourly_data = [
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
| 100 |
"month": int(months[i]),
|
| 101 |
"day": int(days[i]),
|
| 102 |
"hour": int(hours[i]),
|
| 103 |
"dry_bulb": float(dry_bulb[i]),
|
| 104 |
-
"relative_humidity": float(humidity[i]),
|
| 105 |
-
"atmospheric_pressure": float(pressure[i]),
|
| 106 |
-
"global_horizontal_radiation": float(global_radiation[i]),
|
| 107 |
-
"wind_speed": float(wind_speed[i]),
|
| 108 |
-
"wind_direction": float(wind_direction[i])
|
| 109 |
}
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
]))
|
| 115 |
-
]
|
| 116 |
|
| 117 |
def to_dict(self) -> Dict[str, Any]:
|
| 118 |
"""Convert the climate location to a dictionary."""
|
|
@@ -135,7 +139,8 @@ class ClimateLocation:
|
|
| 135 |
"pressure": self.pressure,
|
| 136 |
"hourly_data": self.hourly_data,
|
| 137 |
"typical_extreme_periods": self.typical_extreme_periods,
|
| 138 |
-
"ground_temperatures": self.ground_temperatures
|
|
|
|
| 139 |
}
|
| 140 |
|
| 141 |
class ClimateData:
|
|
@@ -182,70 +187,102 @@ class ClimateData:
|
|
| 182 |
"id", "country", "city", "latitude", "longitude", "elevation",
|
| 183 |
"climate_zone", "heating_degree_days", "cooling_degree_days",
|
| 184 |
"winter_design_temp", "summer_design_temp_db", "summer_design_temp_wb",
|
| 185 |
-
"summer_daily_range", "wind_speed", "pressure", "hourly_data"
|
| 186 |
-
"typical_extreme_periods", "ground_temperatures"
|
| 187 |
]
|
| 188 |
|
| 189 |
for field in required_fields:
|
| 190 |
if field not in data:
|
|
|
|
| 191 |
return False
|
| 192 |
|
| 193 |
if not (-90 <= data["latitude"] <= 90 and -180 <= data["longitude"] <= 180):
|
|
|
|
| 194 |
return False
|
| 195 |
if data["elevation"] < 0:
|
|
|
|
| 196 |
return False
|
| 197 |
if data["climate_zone"] not in ["0A", "0B", "1A", "1B", "2A", "2B", "3A", "3B", "3C", "4A", "4B", "4C", "5A", "5B", "5C", "6A", "6B", "7", "8"]:
|
|
|
|
| 198 |
return False
|
| 199 |
if not (data["heating_degree_days"] >= 0 and data["cooling_degree_days"] >= 0):
|
|
|
|
| 200 |
return False
|
| 201 |
if not (-50 <= data["winter_design_temp"] <= 20):
|
|
|
|
| 202 |
return False
|
| 203 |
if not (0 <= data["summer_design_temp_db"] <= 50 and 0 <= data["summer_design_temp_wb"] <= 40):
|
|
|
|
| 204 |
return False
|
| 205 |
if data["summer_daily_range"] < 0:
|
|
|
|
| 206 |
return False
|
| 207 |
if not (0 <= data["wind_speed"] <= 20):
|
|
|
|
| 208 |
return False
|
| 209 |
if not (80000 <= data["pressure"] <= 110000):
|
|
|
|
| 210 |
return False
|
| 211 |
|
| 212 |
-
if not data["hourly_data"] or len(data["hourly_data"])
|
|
|
|
| 213 |
return False
|
| 214 |
for record in data["hourly_data"]:
|
| 215 |
if not (1 <= record["month"] <= 12):
|
|
|
|
| 216 |
return False
|
| 217 |
if not (1 <= record["day"] <= 31):
|
|
|
|
| 218 |
return False
|
| 219 |
if not (1 <= record["hour"] <= 24):
|
|
|
|
| 220 |
return False
|
| 221 |
if not (-50 <= record["dry_bulb"] <= 50):
|
|
|
|
| 222 |
return False
|
| 223 |
if not (0 <= record["relative_humidity"] <= 100):
|
|
|
|
| 224 |
return False
|
| 225 |
if not (80000 <= record["atmospheric_pressure"] <= 110000):
|
|
|
|
| 226 |
return False
|
| 227 |
if not (0 <= record["global_horizontal_radiation"] <= 1200):
|
|
|
|
| 228 |
return False
|
| 229 |
if not (0 <= record["wind_speed"] <= 20):
|
|
|
|
| 230 |
return False
|
| 231 |
if not (0 <= record["wind_direction"] <= 360):
|
|
|
|
| 232 |
return False
|
| 233 |
|
| 234 |
-
# Validate typical/extreme periods
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
for
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
return False
|
| 242 |
|
| 243 |
-
# Validate
|
| 244 |
-
if
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
|
| 250 |
return True
|
| 251 |
|
|
@@ -294,6 +331,59 @@ class ClimateData:
|
|
| 294 |
longitude = float(header_parts[7])
|
| 295 |
elevation = float(header_parts[8])
|
| 296 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
# Parse TYPICAL/EXTREME PERIODS
|
| 298 |
typical_extreme_periods = {}
|
| 299 |
for line in epw_lines:
|
|
@@ -313,7 +403,9 @@ class ClimateData:
|
|
| 313 |
]:
|
| 314 |
key = f"{'summer' if 'Summer' in period_name else 'winter'}_{'extreme' if 'Max' in period_name else 'typical' if 'Average' in period_name else ''}"
|
| 315 |
start_month, start_day = map(int, start_date.split('/'))
|
| 316 |
-
|
|
|
|
|
|
|
| 317 |
typical_extreme_periods[key] = {
|
| 318 |
"start": {"month": start_month, "day": start_day},
|
| 319 |
"end": {"month": end_month, "day": end_day}
|
|
@@ -351,6 +443,7 @@ class ClimateData:
|
|
| 351 |
epw_file=epw_data,
|
| 352 |
typical_extreme_periods=typical_extreme_periods,
|
| 353 |
ground_temperatures=ground_temperatures,
|
|
|
|
| 354 |
id=f"{country[:1].upper()}{city[:3].upper()}",
|
| 355 |
country=country,
|
| 356 |
state_province=state_province,
|
|
@@ -392,6 +485,7 @@ class ClimateData:
|
|
| 392 |
epw_file=epw_data,
|
| 393 |
typical_extreme_periods=climate_data_dict["typical_extreme_periods"],
|
| 394 |
ground_temperatures=climate_data_dict["ground_temperatures"],
|
|
|
|
| 395 |
id=climate_data_dict["id"],
|
| 396 |
country=climate_data_dict["country"],
|
| 397 |
state_province=climate_data_dict["state_province"],
|
|
@@ -437,7 +531,8 @@ class ClimateData:
|
|
| 437 |
"""Display design conditions for HVAC calculations using Markdown."""
|
| 438 |
st.subheader("Design Conditions")
|
| 439 |
|
| 440 |
-
|
|
|
|
| 441 |
**Location Details:**
|
| 442 |
- **Country**: {location.country}
|
| 443 |
- **City**: {location.city}
|
|
@@ -445,8 +540,11 @@ class ClimateData:
|
|
| 445 |
- **Latitude**: {location.latitude}°
|
| 446 |
- **Longitude**: {location.longitude}°
|
| 447 |
- **Elevation**: {location.elevation} m
|
| 448 |
-
|
| 449 |
-
|
|
|
|
|
|
|
|
|
|
| 450 |
- **Climate Zone**: {location.climate_zone}
|
| 451 |
- **Heating Degree Days (base 18°C)**: {location.heating_degree_days} HDD
|
| 452 |
- **Cooling Degree Days (base 18°C)**: {location.cooling_degree_days} CDD
|
|
@@ -456,7 +554,42 @@ class ClimateData:
|
|
| 456 |
- **Summer Daily Temperature Range**: {location.summer_daily_range} °C
|
| 457 |
- **Mean Wind Speed**: {location.wind_speed} m/s
|
| 458 |
- **Mean Atmospheric Pressure**: {location.pressure} Pa
|
| 459 |
-
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
|
| 461 |
@staticmethod
|
| 462 |
def assign_climate_zone(hdd: float, cdd: float, avg_humidity: float) -> str:
|
|
@@ -773,6 +906,7 @@ class ClimateData:
|
|
| 773 |
epw_file=epw_data,
|
| 774 |
typical_extreme_periods=loc_dict["typical_extreme_periods"],
|
| 775 |
ground_temperatures=loc_dict["ground_temperatures"],
|
|
|
|
| 776 |
id=loc_dict["id"],
|
| 777 |
country=loc_dict["country"],
|
| 778 |
state_province=loc_dict["state_province"],
|
|
|
|
| 18 |
from io import StringIO
|
| 19 |
import pvlib
|
| 20 |
from datetime import datetime, timedelta
|
| 21 |
+
import re
|
| 22 |
|
| 23 |
# Define paths
|
| 24 |
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
| 46 |
hourly_data: List[Dict] # Hourly data for integration with main.py
|
| 47 |
typical_extreme_periods: Dict[str, Dict] # Typical/extreme periods (summer/winter)
|
| 48 |
ground_temperatures: Dict[str, List[float]] # Monthly ground temperatures by depth
|
| 49 |
+
design_conditions: Dict[str, Any] # Design conditions from EPW header
|
| 50 |
|
| 51 |
+
def __init__(self, epw_file: pd.DataFrame, typical_extreme_periods: Dict, ground_temperatures: Dict, design_conditions: Dict, **kwargs):
|
| 52 |
"""Initialize ClimateLocation with EPW file data and header information."""
|
| 53 |
self.id = kwargs.get("id")
|
| 54 |
self.country = kwargs.get("country")
|
|
|
|
| 59 |
self.elevation = kwargs.get("elevation")
|
| 60 |
self.typical_extreme_periods = typical_extreme_periods
|
| 61 |
self.ground_temperatures = ground_temperatures
|
| 62 |
+
self.design_conditions = design_conditions
|
| 63 |
|
| 64 |
# Extract columns from EPW data
|
| 65 |
months = pd.to_numeric(epw_file[1], errors='coerce').values
|
|
|
|
| 98 |
self.climate_zone = ClimateData.assign_climate_zone(self.heating_degree_days, self.cooling_degree_days, np.nanmean(humidity))
|
| 99 |
|
| 100 |
# Store hourly data with enhanced fields
|
| 101 |
+
self.hourly_data = []
|
| 102 |
+
for i in range(len(months)):
|
| 103 |
+
if np.isnan(months[i]) or np.isnan(days[i]) or np.isnan(hours[i]) or np.isnan(dry_bulb[i]):
|
| 104 |
+
continue # Skip records with missing critical fields
|
| 105 |
+
record = {
|
| 106 |
"month": int(months[i]),
|
| 107 |
"day": int(days[i]),
|
| 108 |
"hour": int(hours[i]),
|
| 109 |
"dry_bulb": float(dry_bulb[i]),
|
| 110 |
+
"relative_humidity": float(humidity[i]) if not np.isnan(humidity[i]) else 0.0,
|
| 111 |
+
"atmospheric_pressure": float(pressure[i]) if not np.isnan(pressure[i]) else self.pressure,
|
| 112 |
+
"global_horizontal_radiation": float(global_radiation[i]) if not np.isnan(global_radiation[i]) else 0.0,
|
| 113 |
+
"wind_speed": float(wind_speed[i]) if not np.isnan(wind_speed[i]) else 0.0,
|
| 114 |
+
"wind_direction": float(wind_direction[i]) if not np.isnan(wind_direction[i]) else 0.0
|
| 115 |
}
|
| 116 |
+
self.hourly_data.append(record)
|
| 117 |
+
|
| 118 |
+
if len(self.hourly_data) != 8760:
|
| 119 |
+
st.warning(f"Hourly data has {len(self.hourly_data)} records instead of 8760. Some records may have been excluded due to missing data.")
|
|
|
|
|
|
|
| 120 |
|
| 121 |
def to_dict(self) -> Dict[str, Any]:
|
| 122 |
"""Convert the climate location to a dictionary."""
|
|
|
|
| 139 |
"pressure": self.pressure,
|
| 140 |
"hourly_data": self.hourly_data,
|
| 141 |
"typical_extreme_periods": self.typical_extreme_periods,
|
| 142 |
+
"ground_temperatures": self.ground_temperatures,
|
| 143 |
+
"design_conditions": self.design_conditions
|
| 144 |
}
|
| 145 |
|
| 146 |
class ClimateData:
|
|
|
|
| 187 |
"id", "country", "city", "latitude", "longitude", "elevation",
|
| 188 |
"climate_zone", "heating_degree_days", "cooling_degree_days",
|
| 189 |
"winter_design_temp", "summer_design_temp_db", "summer_design_temp_wb",
|
| 190 |
+
"summer_daily_range", "wind_speed", "pressure", "hourly_data"
|
|
|
|
| 191 |
]
|
| 192 |
|
| 193 |
for field in required_fields:
|
| 194 |
if field not in data:
|
| 195 |
+
st.error(f"Validation failed: Missing required field '{field}'")
|
| 196 |
return False
|
| 197 |
|
| 198 |
if not (-90 <= data["latitude"] <= 90 and -180 <= data["longitude"] <= 180):
|
| 199 |
+
st.error("Validation failed: Invalid latitude or longitude")
|
| 200 |
return False
|
| 201 |
if data["elevation"] < 0:
|
| 202 |
+
st.error("Validation failed: Negative elevation")
|
| 203 |
return False
|
| 204 |
if data["climate_zone"] not in ["0A", "0B", "1A", "1B", "2A", "2B", "3A", "3B", "3C", "4A", "4B", "4C", "5A", "5B", "5C", "6A", "6B", "7", "8"]:
|
| 205 |
+
st.error(f"Validation failed: Invalid climate zone '{data['climate_zone']}'")
|
| 206 |
return False
|
| 207 |
if not (data["heating_degree_days"] >= 0 and data["cooling_degree_days"] >= 0):
|
| 208 |
+
st.error("Validation failed: Negative degree days")
|
| 209 |
return False
|
| 210 |
if not (-50 <= data["winter_design_temp"] <= 20):
|
| 211 |
+
st.error(f"Validation failed: Winter design temp {data['winter_design_temp']} outside range")
|
| 212 |
return False
|
| 213 |
if not (0 <= data["summer_design_temp_db"] <= 50 and 0 <= data["summer_design_temp_wb"] <= 40):
|
| 214 |
+
st.error("Validation failed: Invalid summer design temperatures")
|
| 215 |
return False
|
| 216 |
if data["summer_daily_range"] < 0:
|
| 217 |
+
st.error("Validation failed: Negative summer daily range")
|
| 218 |
return False
|
| 219 |
if not (0 <= data["wind_speed"] <= 20):
|
| 220 |
+
st.error(f"Validation failed: Wind speed {data['wind_speed']} outside range")
|
| 221 |
return False
|
| 222 |
if not (80000 <= data["pressure"] <= 110000):
|
| 223 |
+
st.error(f"Validation failed: Pressure {data['pressure']} outside range")
|
| 224 |
return False
|
| 225 |
|
| 226 |
+
if not data["hourly_data"] or len(data["hourly_data"]) < 8700: # Allow slight data loss
|
| 227 |
+
st.error(f"Validation failed: Hourly data has {len(data['hourly_data'])} records, expected ~8760")
|
| 228 |
return False
|
| 229 |
for record in data["hourly_data"]:
|
| 230 |
if not (1 <= record["month"] <= 12):
|
| 231 |
+
st.error(f"Validation failed: Invalid month {record['month']}")
|
| 232 |
return False
|
| 233 |
if not (1 <= record["day"] <= 31):
|
| 234 |
+
st.error(f"Validation failed: Invalid day {record['day']}")
|
| 235 |
return False
|
| 236 |
if not (1 <= record["hour"] <= 24):
|
| 237 |
+
st.error(f"Validation failed: Invalid hour {record['hour']}")
|
| 238 |
return False
|
| 239 |
if not (-50 <= record["dry_bulb"] <= 50):
|
| 240 |
+
st.error(f"Validation failed: Dry bulb {record['dry_bulb']} outside range")
|
| 241 |
return False
|
| 242 |
if not (0 <= record["relative_humidity"] <= 100):
|
| 243 |
+
st.error(f"Validation failed: Relative humidity {record['relative_humidity']} outside range")
|
| 244 |
return False
|
| 245 |
if not (80000 <= record["atmospheric_pressure"] <= 110000):
|
| 246 |
+
st.error(f"Validation failed: Atmospheric pressure {record['atmospheric_pressure']} outside range")
|
| 247 |
return False
|
| 248 |
if not (0 <= record["global_horizontal_radiation"] <= 1200):
|
| 249 |
+
st.error(f"Validation failed: Global radiation {record['global_horizontal_radiation']} outside range")
|
| 250 |
return False
|
| 251 |
if not (0 <= record["wind_speed"] <= 20):
|
| 252 |
+
st.error(f"Validation failed: Wind speed {record['wind_speed']} outside range")
|
| 253 |
return False
|
| 254 |
if not (0 <= record["wind_direction"] <= 360):
|
| 255 |
+
st.error(f"Validation failed: Wind direction {record['wind_direction']} outside range")
|
| 256 |
return False
|
| 257 |
|
| 258 |
+
# Validate typical/extreme periods (optional)
|
| 259 |
+
if "typical_extreme_periods" in data and data["typical_extreme_periods"]:
|
| 260 |
+
expected_periods = ["summer_extreme", "summer_typical", "winter_extreme", "winter_typical"]
|
| 261 |
+
if not all(key in data["typical_extreme_periods"] for key in expected_periods):
|
| 262 |
+
st.warning("Validation warning: Missing some typical/extreme periods")
|
| 263 |
+
for period in data["typical_extreme_periods"].values():
|
| 264 |
+
for date in ["start", "end"]:
|
| 265 |
+
if not (1 <= period[date]["month"] <= 12 and 1 <= period[date]["day"] <= 31):
|
| 266 |
+
st.error(f"Validation failed: Invalid date in typical/extreme periods: {period[date]}")
|
| 267 |
+
return False
|
| 268 |
+
|
| 269 |
+
# Validate ground temperatures (optional)
|
| 270 |
+
if "ground_temperatures" in data and data["ground_temperatures"]:
|
| 271 |
+
for depth, temps in data["ground_temperatures"].items():
|
| 272 |
+
if len(temps) != 12 or not all(0 <= t <= 50 for t in temps):
|
| 273 |
+
st.error(f"Validation failed: Invalid ground temperatures for depth {depth}")
|
| 274 |
return False
|
| 275 |
|
| 276 |
+
# Validate design conditions (optional)
|
| 277 |
+
if "design_conditions" in data and data["design_conditions"]:
|
| 278 |
+
if not all(key in data["design_conditions"] for key in ["heating", "cooling", "extremes"]):
|
| 279 |
+
st.warning("Validation warning: Incomplete design conditions")
|
| 280 |
+
for section in ["heating", "cooling", "extremes"]:
|
| 281 |
+
if section in data["design_conditions"]:
|
| 282 |
+
for key, value in data["design_conditions"][section].items():
|
| 283 |
+
if isinstance(value, (int, float)) and not (-50 <= value <= 50):
|
| 284 |
+
st.error(f"Validation failed: Invalid {section} design condition {key}: {value}")
|
| 285 |
+
return False
|
| 286 |
|
| 287 |
return True
|
| 288 |
|
|
|
|
| 331 |
longitude = float(header_parts[7])
|
| 332 |
elevation = float(header_parts[8])
|
| 333 |
|
| 334 |
+
# Parse DESIGN CONDITIONS
|
| 335 |
+
design_conditions = {}
|
| 336 |
+
for line in epw_lines:
|
| 337 |
+
if line.startswith("DESIGN CONDITIONS"):
|
| 338 |
+
parts = line.strip().split(',')
|
| 339 |
+
design_conditions = {
|
| 340 |
+
"heating": {
|
| 341 |
+
"coldest_month": int(parts[3]),
|
| 342 |
+
"dry_bulb": float(parts[4]),
|
| 343 |
+
"mean_coincident_db": float(parts[5]),
|
| 344 |
+
"humidification_db": float(parts[6]),
|
| 345 |
+
"mean_coincident_wb": float(parts[7]),
|
| 346 |
+
"wind_speed_1": float(parts[8]),
|
| 347 |
+
"wind_direction_1": float(parts[9]),
|
| 348 |
+
"wind_speed_2": float(parts[10]),
|
| 349 |
+
"wind_direction_2": float(parts[11])
|
| 350 |
+
},
|
| 351 |
+
"cooling": {
|
| 352 |
+
"hottest_month": int(parts[12]),
|
| 353 |
+
"dry_bulb_0_4": float(parts[13]),
|
| 354 |
+
"mean_coincident_wb_0_4": float(parts[14]),
|
| 355 |
+
"wet_bulb_0_4": float(parts[15]),
|
| 356 |
+
"mean_coincident_db_0_4": float(parts[16]),
|
| 357 |
+
"dry_bulb_1_0": float(parts[17]),
|
| 358 |
+
"mean_coincident_wb_1_0": float(parts[18]),
|
| 359 |
+
"wet_bulb_1_0": float(parts[19]),
|
| 360 |
+
"mean_coincident_db_1_0": float(parts[20]),
|
| 361 |
+
"dry_bulb_2_0": float(parts[21]),
|
| 362 |
+
"mean_coincident_wb_2_0": float(parts[22]),
|
| 363 |
+
"wet_bulb_2_0": float(parts[23]),
|
| 364 |
+
"mean_coincident_db_2_0": float(parts[24]),
|
| 365 |
+
"evaporation_wb_0_4": float(parts[25]),
|
| 366 |
+
"mean_coincident_db_wb_0_4": float(parts[26])
|
| 367 |
+
},
|
| 368 |
+
"extremes": {
|
| 369 |
+
"annual_max_db": float(parts[27]),
|
| 370 |
+
"mean_db": float(parts[28]),
|
| 371 |
+
"std_dev_db": float(parts[29]),
|
| 372 |
+
"min_db": float(parts[30]),
|
| 373 |
+
"max_db_1": float(parts[31]),
|
| 374 |
+
"min_db_1": float(parts[32]),
|
| 375 |
+
"max_db_2": float(parts[33]),
|
| 376 |
+
"min_db_2": float(parts[34]),
|
| 377 |
+
"max_db_3": float(parts[35]),
|
| 378 |
+
"min_db_3": float(parts[36]),
|
| 379 |
+
"max_db_4": float(parts[37]),
|
| 380 |
+
"min_db_4": float(parts[38]),
|
| 381 |
+
"max_db_5": float(parts[39]),
|
| 382 |
+
"min_db_5": float(parts[40])
|
| 383 |
+
}
|
| 384 |
+
}
|
| 385 |
+
break
|
| 386 |
+
|
| 387 |
# Parse TYPICAL/EXTREME PERIODS
|
| 388 |
typical_extreme_periods = {}
|
| 389 |
for line in epw_lines:
|
|
|
|
| 403 |
]:
|
| 404 |
key = f"{'summer' if 'Summer' in period_name else 'winter'}_{'extreme' if 'Max' in period_name else 'typical' if 'Average' in period_name else ''}"
|
| 405 |
start_month, start_day = map(int, start_date.split('/'))
|
| 406 |
+
# Robustly handle spaces in end_date (e.g., "1/ 5")
|
| 407 |
+
end_date_clean = re.sub(r'\s+', '', end_date)
|
| 408 |
+
end_month, end_day = map(int, end_date_clean.split('/'))
|
| 409 |
typical_extreme_periods[key] = {
|
| 410 |
"start": {"month": start_month, "day": start_day},
|
| 411 |
"end": {"month": end_month, "day": end_day}
|
|
|
|
| 443 |
epw_file=epw_data,
|
| 444 |
typical_extreme_periods=typical_extreme_periods,
|
| 445 |
ground_temperatures=ground_temperatures,
|
| 446 |
+
design_conditions=design_conditions,
|
| 447 |
id=f"{country[:1].upper()}{city[:3].upper()}",
|
| 448 |
country=country,
|
| 449 |
state_province=state_province,
|
|
|
|
| 485 |
epw_file=epw_data,
|
| 486 |
typical_extreme_periods=climate_data_dict["typical_extreme_periods"],
|
| 487 |
ground_temperatures=climate_data_dict["ground_temperatures"],
|
| 488 |
+
design_conditions=climate_data_dict["design_conditions"],
|
| 489 |
id=climate_data_dict["id"],
|
| 490 |
country=climate_data_dict["country"],
|
| 491 |
state_province=climate_data_dict["state_province"],
|
|
|
|
| 531 |
"""Display design conditions for HVAC calculations using Markdown."""
|
| 532 |
st.subheader("Design Conditions")
|
| 533 |
|
| 534 |
+
# Location Details
|
| 535 |
+
st.markdown("""
|
| 536 |
**Location Details:**
|
| 537 |
- **Country**: {location.country}
|
| 538 |
- **City**: {location.city}
|
|
|
|
| 540 |
- **Latitude**: {location.latitude}°
|
| 541 |
- **Longitude**: {location.longitude}°
|
| 542 |
- **Elevation**: {location.elevation} m
|
| 543 |
+
""".format(location=location))
|
| 544 |
+
|
| 545 |
+
# Calculated Climate Parameters
|
| 546 |
+
st.markdown("""
|
| 547 |
+
**Calculated Climate Parameters:**
|
| 548 |
- **Climate Zone**: {location.climate_zone}
|
| 549 |
- **Heating Degree Days (base 18°C)**: {location.heating_degree_days} HDD
|
| 550 |
- **Cooling Degree Days (base 18°C)**: {location.cooling_degree_days} CDD
|
|
|
|
| 554 |
- **Summer Daily Temperature Range**: {location.summer_daily_range} °C
|
| 555 |
- **Mean Wind Speed**: {location.wind_speed} m/s
|
| 556 |
- **Mean Atmospheric Pressure**: {location.pressure} Pa
|
| 557 |
+
""".format(location=location))
|
| 558 |
+
|
| 559 |
+
# EPW Header Design Conditions
|
| 560 |
+
if location.design_conditions:
|
| 561 |
+
st.markdown("**Design Conditions (EPW Header):**")
|
| 562 |
+
st.markdown("""
|
| 563 |
+
- **Heating:**
|
| 564 |
+
- Coldest Month: {location.design_conditions['heating']['coldest_month']}
|
| 565 |
+
- Dry-Bulb Temp: {location.design_conditions['heating']['dry_bulb']} °C
|
| 566 |
+
- Mean Coincident Dry-Bulb: {location.design_conditions['heating']['mean_coincident_db']} °C
|
| 567 |
+
- Humidification Dry-Bulb: {location.design_conditions['heating']['humidification_db']} °C
|
| 568 |
+
- Mean Coincident Wet-Bulb: {location.design_conditions['heating']['mean_coincident_wb']} °C
|
| 569 |
+
- **Cooling:**
|
| 570 |
+
- Hottest Month: {location.design_conditions['cooling']['hottest_month']}
|
| 571 |
+
- Dry-Bulb Temp (0.4%): {location.design_conditions['cooling']['dry_bulb_0_4']} °C
|
| 572 |
+
- Wet-Bulb Temp (0.4%): {location.design_conditions['cooling']['wet_bulb_0_4']} °C
|
| 573 |
+
- Mean Coincident Wet-Bulb (0.4%): {location.design_conditions['cooling']['mean_coincident_wb_0_4']} °C
|
| 574 |
+
- **Extremes:**
|
| 575 |
+
- Annual Max Dry-Bulb: {location.design_conditions['extremes']['annual_max_db']} °C
|
| 576 |
+
- Annual Min Dry-Bulb: {location.design_conditions['extremes']['min_db']} °C
|
| 577 |
+
""".format(location=location))
|
| 578 |
+
|
| 579 |
+
# Typical/Extreme Periods
|
| 580 |
+
if location.typical_extreme_periods:
|
| 581 |
+
st.markdown("**Typical/Extreme Periods:**")
|
| 582 |
+
for key, period in location.typical_extreme_periods.items():
|
| 583 |
+
period_name = key.replace('_', ' ').title()
|
| 584 |
+
st.markdown(f"- **{period_name}**: {period['start']['month']}/{period['start']['day']} to {period['end']['month']}/{period['end']['day']}")
|
| 585 |
+
|
| 586 |
+
# Ground Temperatures
|
| 587 |
+
if location.ground_temperatures:
|
| 588 |
+
st.markdown("**Ground Temperatures:**")
|
| 589 |
+
month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
| 590 |
+
for depth, temps in location.ground_temperatures.items():
|
| 591 |
+
temp_str = ", ".join(f"{month}: {temp:.2f}°C" for month, temp in zip(month_names, temps))
|
| 592 |
+
st.markdown(f"- **Depth {depth}m**: {temp_str}")
|
| 593 |
|
| 594 |
@staticmethod
|
| 595 |
def assign_climate_zone(hdd: float, cdd: float, avg_humidity: float) -> str:
|
|
|
|
| 906 |
epw_file=epw_data,
|
| 907 |
typical_extreme_periods=loc_dict["typical_extreme_periods"],
|
| 908 |
ground_temperatures=loc_dict["ground_temperatures"],
|
| 909 |
+
design_conditions=loc_dict["design_conditions"],
|
| 910 |
id=loc_dict["id"],
|
| 911 |
country=loc_dict["country"],
|
| 912 |
state_province=loc_dict["state_province"],
|