Spaces:
Sleeping
Sleeping
Update app/climate_data.py
Browse files- app/climate_data.py +90 -5
app/climate_data.py
CHANGED
@@ -32,6 +32,7 @@ logger = logging.getLogger(__name__)
|
|
32 |
MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
33 |
CLIMATE_ZONES = ["0A", "0B", "1A", "1B", "2A", "2B", "3A", "3B", "3C", "4A", "4B", "4C", "5A", "5B", "5C", "6A", "6B", "7", "8"]
|
34 |
AU_CCH_DIR = "au_cch" # Relative path to au_cch folder
|
|
|
35 |
|
36 |
# Location mapping for Australian climate projections
|
37 |
LOCATION_MAPPING = {
|
@@ -357,9 +358,75 @@ class ClimateDataManager:
|
|
357 |
"pressure": 101325.0
|
358 |
}
|
359 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
360 |
def _process_hourly_data(self, df: pd.DataFrame) -> List[Dict[str, Any]]:
|
361 |
"""
|
362 |
-
Process hourly data from EPW DataFrame.
|
363 |
|
364 |
Args:
|
365 |
df: DataFrame containing EPW data
|
@@ -372,7 +439,7 @@ class ClimateDataManager:
|
|
372 |
try:
|
373 |
# Ensure numeric columns
|
374 |
numeric_columns = [
|
375 |
-
"dry_bulb_temp", "relative_humidity", "atmospheric_pressure",
|
376 |
"global_horizontal_radiation", "direct_normal_radiation",
|
377 |
"diffuse_horizontal_radiation", "wind_speed", "wind_direction"
|
378 |
]
|
@@ -391,18 +458,32 @@ class ClimateDataManager:
|
|
391 |
if pd.isna(row["month"]) or pd.isna(row["day"]) or pd.isna(row["hour"]) or pd.isna(row["dry_bulb_temp"]):
|
392 |
continue # Skip rows with missing critical data
|
393 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
394 |
record = {
|
395 |
"month": int(row["month"]),
|
396 |
"day": int(row["day"]),
|
397 |
"hour": int(row["hour"]),
|
398 |
"dry_bulb": float(row["dry_bulb_temp"]) if not pd.isna(row["dry_bulb_temp"]) else 20.0,
|
|
|
399 |
"relative_humidity": float(row["relative_humidity"]) if not pd.isna(row["relative_humidity"]) else 50.0,
|
400 |
"atmospheric_pressure": float(row["atmospheric_pressure"]) if not pd.isna(row["atmospheric_pressure"]) else 101325.0,
|
401 |
"global_horizontal_radiation": float(row["global_horizontal_radiation"]) if not pd.isna(row["global_horizontal_radiation"]) else 0.0,
|
402 |
"direct_normal_radiation": float(row["direct_normal_radiation"]) if not pd.isna(row["direct_normal_radiation"]) else 0.0,
|
403 |
"diffuse_horizontal_radiation": float(row["diffuse_horizontal_radiation"]) if not pd.isna(row["diffuse_horizontal_radiation"]) else 0.0,
|
404 |
"wind_speed": float(row["wind_speed"]) if not pd.isna(row["wind_speed"]) else 0.0,
|
405 |
-
"wind_direction": float(row["wind_direction"]) if not pd.isna(row["wind_direction"]) else 0.0
|
|
|
406 |
}
|
407 |
hourly_data.append(record)
|
408 |
|
@@ -769,13 +850,15 @@ def display_climate_summary(climate_data: Dict[str, Any]):
|
|
769 |
"Day": record["day"],
|
770 |
"Hour": record["hour"],
|
771 |
"Dry Bulb Temp (°C)": f"{record['dry_bulb']:.1f}",
|
|
|
772 |
"Relative Humidity (%)": f"{record['relative_humidity']:.1f}",
|
773 |
"Atmospheric Pressure (Pa)": f"{record['atmospheric_pressure']:.1f}",
|
774 |
"Global Horizontal Radiation (W/m²)": f"{record['global_horizontal_radiation']:.1f}",
|
775 |
"Direct Normal Radiation (W/m²)": f"{record['direct_normal_radiation']:.1f}",
|
776 |
"Diffuse Horizontal Radiation (W/m²)": f"{record['diffuse_horizontal_radiation']:.1f}",
|
777 |
"Wind Speed (m/s)": f"{record['wind_speed']:.1f}",
|
778 |
-
"Wind Direction (°)": f"{record['wind_direction']:.1f}"
|
|
|
779 |
}
|
780 |
for record in climate_data["hourly_data"]
|
781 |
]
|
@@ -812,10 +895,12 @@ def display_climate_help():
|
|
812 |
EPW (EnergyPlus Weather) files contain hourly weather data for a specific location, including:
|
813 |
|
814 |
* Dry-bulb temperature
|
|
|
815 |
* Relative humidity
|
816 |
* Solar radiation (direct and diffuse)
|
817 |
* Wind speed and direction
|
818 |
* Atmospheric pressure
|
|
|
819 |
|
820 |
**Where to Find EPW Files:**
|
821 |
|
@@ -838,7 +923,7 @@ def display_climate_help():
|
|
838 |
* Typical/Extreme periods
|
839 |
* Ground temperatures by depth
|
840 |
* Monthly average temperatures and solar radiation
|
841 |
-
* Hourly data statistics with downloadable tables
|
842 |
|
843 |
This information will be used throughout the calculation process.
|
844 |
""")
|
|
|
32 |
MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
33 |
CLIMATE_ZONES = ["0A", "0B", "1A", "1B", "2A", "2B", "3A", "3B", "3C", "4A", "4B", "4C", "5A", "5B", "5C", "6A", "6B", "7", "8"]
|
34 |
AU_CCH_DIR = "au_cch" # Relative path to au_cch folder
|
35 |
+
SKY_CLEARNESS_CONSTANT = 5.535e-6 # Constant k for Perez model Sky Clearness Index
|
36 |
|
37 |
# Location mapping for Australian climate projections
|
38 |
LOCATION_MAPPING = {
|
|
|
358 |
"pressure": 101325.0
|
359 |
}
|
360 |
|
361 |
+
def _calculate_dew_point(self, dry_bulb: float, relative_humidity: float, atmospheric_pressure: float) -> float:
|
362 |
+
"""
|
363 |
+
Calculate dew point temperature using August-Roche-Magnus formula.
|
364 |
+
|
365 |
+
Args:
|
366 |
+
dry_bulb: Dry bulb temperature in °C
|
367 |
+
relative_humidity: Relative humidity in %
|
368 |
+
atmospheric_pressure: Atmospheric pressure in Pa (not used in simplified formula)
|
369 |
+
|
370 |
+
Returns:
|
371 |
+
Dew point temperature in °C
|
372 |
+
"""
|
373 |
+
try:
|
374 |
+
# Step 1: Calculate saturation vapor pressure (hPa)
|
375 |
+
es = 6.1078 * 10 ** ((7.5 * dry_bulb) / (237.3 + dry_bulb))
|
376 |
+
es = es * 100 # Convert to Pa
|
377 |
+
|
378 |
+
# Step 2: Calculate actual vapor pressure
|
379 |
+
e = (relative_humidity * es) / 100
|
380 |
+
|
381 |
+
# Step 3: Calculate dew point temperature
|
382 |
+
if e <= 0:
|
383 |
+
logger.warning(f"Invalid vapor pressure {e} Pa, returning dry bulb temperature {dry_bulb}°C as dew point")
|
384 |
+
return dry_bulb
|
385 |
+
ln_term = math.log(e / 610.78)
|
386 |
+
dew_point = (237.3 * ln_term) / (7.5 - ln_term)
|
387 |
+
|
388 |
+
# Ensure dew point does not exceed dry bulb temperature
|
389 |
+
dew_point = min(dew_point, dry_bulb)
|
390 |
+
|
391 |
+
return round(dew_point, 1)
|
392 |
+
except (ValueError, ZeroDivisionError) as e:
|
393 |
+
logger.warning(f"Error calculating dew point: {str(e)}, returning dry bulb temperature {dry_bulb}°C")
|
394 |
+
return dry_bulb
|
395 |
+
|
396 |
+
def _calculate_sky_clearness_index(self, diffuse_horizontal: float, global_horizontal: float, direct_normal: float) -> Optional[float]:
|
397 |
+
"""
|
398 |
+
Calculate Sky Clearness Index using the Perez model.
|
399 |
+
|
400 |
+
Args:
|
401 |
+
diffuse_horizontal: Diffuse horizontal irradiance in W/m²
|
402 |
+
global_horizontal: Global horizontal irradiance in W/m²
|
403 |
+
direct_normal: Direct normal irradiance in W/m²
|
404 |
+
|
405 |
+
Returns:
|
406 |
+
Sky Clearness Index (dimensionless) or None if undefined (e.g., nighttime)
|
407 |
+
"""
|
408 |
+
try:
|
409 |
+
# Handle nighttime or invalid data
|
410 |
+
if global_horizontal <= 0 or diffuse_horizontal < 0 or direct_normal < 0:
|
411 |
+
return None
|
412 |
+
|
413 |
+
# Cap diffuse_horizontal to global_horizontal to handle potential measurement errors
|
414 |
+
diffuse_horizontal = min(diffuse_horizontal, global_horizontal)
|
415 |
+
|
416 |
+
# Calculate Sky Clearness Index using Perez model
|
417 |
+
k = SKY_CLEARNESS_CONSTANT
|
418 |
+
if global_horizontal == 0:
|
419 |
+
return None # Avoid division by zero
|
420 |
+
epsilon = ((diffuse_horizontal / global_horizontal) + (k * direct_normal)) / (1 + k)
|
421 |
+
|
422 |
+
return round(epsilon, 3)
|
423 |
+
except Exception as e:
|
424 |
+
logger.warning(f"Error calculating Sky Clearness Index: {str(e)}, returning None")
|
425 |
+
return None
|
426 |
+
|
427 |
def _process_hourly_data(self, df: pd.DataFrame) -> List[Dict[str, Any]]:
|
428 |
"""
|
429 |
+
Process hourly data from EPW DataFrame, including dew point and Sky Clearness Index.
|
430 |
|
431 |
Args:
|
432 |
df: DataFrame containing EPW data
|
|
|
439 |
try:
|
440 |
# Ensure numeric columns
|
441 |
numeric_columns = [
|
442 |
+
"dry_bulb_temp", "dew_point_temp", "relative_humidity", "atmospheric_pressure",
|
443 |
"global_horizontal_radiation", "direct_normal_radiation",
|
444 |
"diffuse_horizontal_radiation", "wind_speed", "wind_direction"
|
445 |
]
|
|
|
458 |
if pd.isna(row["month"]) or pd.isna(row["day"]) or pd.isna(row["hour"]) or pd.isna(row["dry_bulb_temp"]):
|
459 |
continue # Skip rows with missing critical data
|
460 |
|
461 |
+
# Calculate dew point temperature
|
462 |
+
dry_bulb = float(row["dry_bulb_temp"]) if not pd.isna(row["dry_bulb_temp"]) else 20.0
|
463 |
+
relative_humidity = float(row["relative_humidity"]) if not pd.isna(row["relative_humidity"]) else 50.0
|
464 |
+
atmospheric_pressure = float(row["atmospheric_pressure"]) if not pd.isna(row["atmospheric_pressure"]) else 101325.0
|
465 |
+
dew_point = self._calculate_dew_point(dry_bulb, relative_humidity, atmospheric_pressure)
|
466 |
+
|
467 |
+
# Calculate Sky Clearness Index
|
468 |
+
diffuse_horizontal = float(row["diffuse_horizontal_radiation"]) if not pd.isna(row["diffuse_horizontal_radiation"]) else 0.0
|
469 |
+
global_horizontal = float(row["global_horizontal_radiation"]) if not pd.isna(row["global_horizontal_radiation"]) else 0.0
|
470 |
+
direct_normal = float(row["direct_normal_radiation"]) if not pd.isna(row["direct_normal_radiation"]) else 0.0
|
471 |
+
sky_clearness_index = self._calculate_sky_clearness_index(diffuse_horizontal, global_horizontal, direct_normal)
|
472 |
+
|
473 |
record = {
|
474 |
"month": int(row["month"]),
|
475 |
"day": int(row["day"]),
|
476 |
"hour": int(row["hour"]),
|
477 |
"dry_bulb": float(row["dry_bulb_temp"]) if not pd.isna(row["dry_bulb_temp"]) else 20.0,
|
478 |
+
"dew_point": dew_point,
|
479 |
"relative_humidity": float(row["relative_humidity"]) if not pd.isna(row["relative_humidity"]) else 50.0,
|
480 |
"atmospheric_pressure": float(row["atmospheric_pressure"]) if not pd.isna(row["atmospheric_pressure"]) else 101325.0,
|
481 |
"global_horizontal_radiation": float(row["global_horizontal_radiation"]) if not pd.isna(row["global_horizontal_radiation"]) else 0.0,
|
482 |
"direct_normal_radiation": float(row["direct_normal_radiation"]) if not pd.isna(row["direct_normal_radiation"]) else 0.0,
|
483 |
"diffuse_horizontal_radiation": float(row["diffuse_horizontal_radiation"]) if not pd.isna(row["diffuse_horizontal_radiation"]) else 0.0,
|
484 |
"wind_speed": float(row["wind_speed"]) if not pd.isna(row["wind_speed"]) else 0.0,
|
485 |
+
"wind_direction": float(row["wind_direction"]) if not pd.isna(row["wind_direction"]) else 0.0,
|
486 |
+
"sky_clearness_index": sky_clearness_index if sky_clearness_index is not None else None
|
487 |
}
|
488 |
hourly_data.append(record)
|
489 |
|
|
|
850 |
"Day": record["day"],
|
851 |
"Hour": record["hour"],
|
852 |
"Dry Bulb Temp (°C)": f"{record['dry_bulb']:.1f}",
|
853 |
+
"Dew Point Temp (°C)": f"{record['dew_point']:.1f}" if record['dew_point'] is not None else "N/A",
|
854 |
"Relative Humidity (%)": f"{record['relative_humidity']:.1f}",
|
855 |
"Atmospheric Pressure (Pa)": f"{record['atmospheric_pressure']:.1f}",
|
856 |
"Global Horizontal Radiation (W/m²)": f"{record['global_horizontal_radiation']:.1f}",
|
857 |
"Direct Normal Radiation (W/m²)": f"{record['direct_normal_radiation']:.1f}",
|
858 |
"Diffuse Horizontal Radiation (W/m²)": f"{record['diffuse_horizontal_radiation']:.1f}",
|
859 |
"Wind Speed (m/s)": f"{record['wind_speed']:.1f}",
|
860 |
+
"Wind Direction (°)": f"{record['wind_direction']:.1f}",
|
861 |
+
"Sky Clearness Index": f"{record['sky_clearness_index']:.3f}" if record['sky_clearness_index'] is not None else "N/A"
|
862 |
}
|
863 |
for record in climate_data["hourly_data"]
|
864 |
]
|
|
|
895 |
EPW (EnergyPlus Weather) files contain hourly weather data for a specific location, including:
|
896 |
|
897 |
* Dry-bulb temperature
|
898 |
+
* Dew point temperature
|
899 |
* Relative humidity
|
900 |
* Solar radiation (direct and diffuse)
|
901 |
* Wind speed and direction
|
902 |
* Atmospheric pressure
|
903 |
+
* Sky Clearness Index (calculated)
|
904 |
|
905 |
**Where to Find EPW Files:**
|
906 |
|
|
|
923 |
* Typical/Extreme periods
|
924 |
* Ground temperatures by depth
|
925 |
* Monthly average temperatures and solar radiation
|
926 |
+
* Hourly data statistics with downloadable tables (including dew point and Sky Clearness Index)
|
927 |
|
928 |
This information will be used throughout the calculation process.
|
929 |
""")
|