mabuseif commited on
Commit
8ce23ea
·
verified ·
1 Parent(s): 562b976

Update data/climate_data.py

Browse files
Files changed (1) hide show
  1. 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
- for i in range(len(months))
111
- if not any(np.isnan([
112
- months[i], days[i], hours[i], dry_bulb[i], humidity[i],
113
- pressure[i], global_radiation[i], wind_speed[i], wind_direction[i]
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"]) != 8760:
 
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
- expected_periods = ["summer_extreme", "summer_typical", "winter_extreme", "winter_typical"]
236
- if not all(key in data["typical_extreme_periods"] for key in expected_periods):
237
- return False
238
- for period in data["typical_extreme_periods"].values():
239
- for date in ["start", "end"]:
240
- if not (1 <= period[date]["month"] <= 12 and 1 <= period[date]["day"] <= 31):
 
 
 
 
 
 
 
 
 
241
  return False
242
 
243
- # Validate ground temperatures
244
- if not data["ground_temperatures"]:
245
- return False
246
- for depth, temps in data["ground_temperatures"].items():
247
- if len(temps) != 12 or not all(0 <= t <= 50 for t in temps):
248
- return False
 
 
 
 
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
- end_month, end_day = map(int, end_date.replace(' ', '').split('/'))
 
 
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
- st.markdown(f"""
 
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
- **Climate Parameters:**
 
 
 
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"],