Spaces:
Sleeping
Sleeping
Update data/climate_data.py
Browse files- data/climate_data.py +60 -43
data/climate_data.py
CHANGED
|
@@ -92,6 +92,7 @@ class ClimateLocation:
|
|
| 92 |
self.longitude = kwargs.get("longitude")
|
| 93 |
self.elevation = kwargs.get("elevation")
|
| 94 |
self.time_zone = kwargs.get("time_zone", 0.0) # Default to 0.0 if not provided
|
|
|
|
| 95 |
self.typical_extreme_periods = typical_extreme_periods
|
| 96 |
self.ground_temperatures = ground_temperatures
|
| 97 |
|
|
@@ -136,9 +137,6 @@ class ClimateLocation:
|
|
| 136 |
# Log wind speed diagnostics
|
| 137 |
logger.info(f"Wind speed stats: min={wind_speed.min():.1f}, max={wind_speed.max():.1f}, mean={self.wind_speed:.1f}")
|
| 138 |
|
| 139 |
-
# Assign climate zone
|
| 140 |
-
self.climate_zone = ClimateData.assign_climate_zone(self.heating_degree_days, self.cooling_degree_days, np.nanmean(humidity))
|
| 141 |
-
|
| 142 |
# Store hourly data with enhanced fields
|
| 143 |
self.hourly_data = []
|
| 144 |
for i in range(len(months)):
|
|
@@ -359,6 +357,7 @@ class ClimateData:
|
|
| 359 |
# Clear invalid session_state["climate_data"] to prevent validation errors
|
| 360 |
if "climate_data" in session_state and not all(key in session_state["climate_data"] for key in ["id", "country", "city"]):
|
| 361 |
st.warning("Clearing invalid climate data from session state.")
|
|
|
|
| 362 |
del session_state["climate_data"]
|
| 363 |
|
| 364 |
uploaded_file = st.file_uploader("Upload EPW File", type=["epw"])
|
|
@@ -376,16 +375,18 @@ class ClimateData:
|
|
| 376 |
# Parse header
|
| 377 |
header = next(line for line in epw_lines if line.startswith("LOCATION"))
|
| 378 |
header_parts = header.split(",")
|
|
|
|
|
|
|
| 379 |
city = header_parts[1].strip() or "Unknown"
|
| 380 |
# Clean city name by removing suffixes like '.Racecourse'
|
| 381 |
city = re.sub(r'\..*', '', city)
|
| 382 |
state_province = header_parts[2].strip() or "Unknown"
|
| 383 |
country = header_parts[3].strip() or "Unknown"
|
| 384 |
-
|
| 385 |
-
latitude = float(header_parts[6])
|
| 386 |
-
longitude = float(header_parts[7])
|
| 387 |
-
elevation = float(header_parts[8])
|
| 388 |
time_zone = float(header_parts[5]) if header_parts[5].strip() and ClimateData.is_numeric(header_parts[5]) else 0.0
|
|
|
|
| 389 |
|
| 390 |
# Parse TYPICAL/EXTREME PERIODS
|
| 391 |
typical_extreme_periods = {}
|
|
@@ -485,7 +486,8 @@ class ClimateData:
|
|
| 485 |
latitude=latitude,
|
| 486 |
longitude=longitude,
|
| 487 |
elevation=elevation,
|
| 488 |
-
time_zone=time_zone
|
|
|
|
| 489 |
)
|
| 490 |
self.add_location(location)
|
| 491 |
climate_data_dict = location.to_dict()
|
|
@@ -493,50 +495,64 @@ class ClimateData:
|
|
| 493 |
raise ValueError("Invalid climate data extracted from EPW file.")
|
| 494 |
session_state["climate_data"] = climate_data_dict
|
| 495 |
st.success("Climate data extracted from EPW file!")
|
|
|
|
| 496 |
|
| 497 |
except Exception as e:
|
| 498 |
st.error(f"Error processing EPW file: {str(e)}. Ensure it has 8760 hourly records and correct format.")
|
|
|
|
| 499 |
|
| 500 |
-
elif "climate_data" in session_state
|
| 501 |
# Reconstruct from session_state
|
| 502 |
climate_data_dict = session_state["climate_data"]
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 537 |
|
| 538 |
# Display tabs if location and epw_data are available
|
| 539 |
if location and epw_data is not None:
|
|
|
|
| 540 |
tab1, tab2, tab3, tab4, tab5 = st.tabs([
|
| 541 |
"General Information",
|
| 542 |
"Psychrometric Chart",
|
|
@@ -562,6 +578,7 @@ class ClimateData:
|
|
| 562 |
|
| 563 |
else:
|
| 564 |
st.info("No climate data available. Please upload an EPW file to proceed.")
|
|
|
|
| 565 |
|
| 566 |
def display_design_conditions(self, location: ClimateLocation):
|
| 567 |
"""Display design conditions for HVAC calculations using styled HTML."""
|
|
|
|
| 92 |
self.longitude = kwargs.get("longitude")
|
| 93 |
self.elevation = kwargs.get("elevation")
|
| 94 |
self.time_zone = kwargs.get("time_zone", 0.0) # Default to 0.0 if not provided
|
| 95 |
+
self.climate_zone = kwargs.get("climate_zone", "Unknown") # Use provided climate_zone
|
| 96 |
self.typical_extreme_periods = typical_extreme_periods
|
| 97 |
self.ground_temperatures = ground_temperatures
|
| 98 |
|
|
|
|
| 137 |
# Log wind speed diagnostics
|
| 138 |
logger.info(f"Wind speed stats: min={wind_speed.min():.1f}, max={wind_speed.max():.1f}, mean={self.wind_speed:.1f}")
|
| 139 |
|
|
|
|
|
|
|
|
|
|
| 140 |
# Store hourly data with enhanced fields
|
| 141 |
self.hourly_data = []
|
| 142 |
for i in range(len(months)):
|
|
|
|
| 357 |
# Clear invalid session_state["climate_data"] to prevent validation errors
|
| 358 |
if "climate_data" in session_state and not all(key in session_state["climate_data"] for key in ["id", "country", "city"]):
|
| 359 |
st.warning("Clearing invalid climate data from session state.")
|
| 360 |
+
logger.warning("Invalid climate_data in session_state, clearing.")
|
| 361 |
del session_state["climate_data"]
|
| 362 |
|
| 363 |
uploaded_file = st.file_uploader("Upload EPW File", type=["epw"])
|
|
|
|
| 375 |
# Parse header
|
| 376 |
header = next(line for line in epw_lines if line.startswith("LOCATION"))
|
| 377 |
header_parts = header.split(",")
|
| 378 |
+
if len(header_parts) < 10:
|
| 379 |
+
raise ValueError("Invalid LOCATION header: too few fields.")
|
| 380 |
city = header_parts[1].strip() or "Unknown"
|
| 381 |
# Clean city name by removing suffixes like '.Racecourse'
|
| 382 |
city = re.sub(r'\..*', '', city)
|
| 383 |
state_province = header_parts[2].strip() or "Unknown"
|
| 384 |
country = header_parts[3].strip() or "Unknown"
|
| 385 |
+
climate_zone = header_parts[8].strip() or "Unknown"
|
| 386 |
+
latitude = float(header_parts[6]) if header_parts[6].strip() and ClimateData.is_numeric(header_parts[6]) else 0.0
|
| 387 |
+
longitude = float(header_parts[7]) if header_parts[7].strip() and ClimateData.is_numeric(header_parts[7]) else 0.0
|
|
|
|
| 388 |
time_zone = float(header_parts[5]) if header_parts[5].strip() and ClimateData.is_numeric(header_parts[5]) else 0.0
|
| 389 |
+
elevation = float(header_parts[9]) if header_parts[9].strip() and ClimateData.is_numeric(header_parts[9]) else 0.0
|
| 390 |
|
| 391 |
# Parse TYPICAL/EXTREME PERIODS
|
| 392 |
typical_extreme_periods = {}
|
|
|
|
| 486 |
latitude=latitude,
|
| 487 |
longitude=longitude,
|
| 488 |
elevation=elevation,
|
| 489 |
+
time_zone=time_zone,
|
| 490 |
+
climate_zone=climate_zone
|
| 491 |
)
|
| 492 |
self.add_location(location)
|
| 493 |
climate_data_dict = location.to_dict()
|
|
|
|
| 495 |
raise ValueError("Invalid climate data extracted from EPW file.")
|
| 496 |
session_state["climate_data"] = climate_data_dict
|
| 497 |
st.success("Climate data extracted from EPW file!")
|
| 498 |
+
logger.info("Successfully processed EPW file and stored in session_state.")
|
| 499 |
|
| 500 |
except Exception as e:
|
| 501 |
st.error(f"Error processing EPW file: {str(e)}. Ensure it has 8760 hourly records and correct format.")
|
| 502 |
+
logger.error(f"EPW processing error: {str(e)}")
|
| 503 |
|
| 504 |
+
elif "climate_data" in session_state:
|
| 505 |
# Reconstruct from session_state
|
| 506 |
climate_data_dict = session_state["climate_data"]
|
| 507 |
+
if not self.validate_climate_data(climate_data_dict):
|
| 508 |
+
st.error("Stored climate data is invalid. Please upload a new EPW file.")
|
| 509 |
+
logger.error("Validation failed for session_state.climate_data: %s", climate_data_dict.get("id", "Unknown"))
|
| 510 |
+
session_state["climate_data"] = {} # Clear invalid data
|
| 511 |
+
else:
|
| 512 |
+
try:
|
| 513 |
+
# Rebuild epw_data from hourly_data with full EPW column structure
|
| 514 |
+
hourly_data = climate_data_dict["hourly_data"]
|
| 515 |
+
# Create a DataFrame with 35 columns (0 to 34), initialized with NaN
|
| 516 |
+
epw_data = pd.DataFrame(np.nan, index=range(len(hourly_data)), columns=range(35))
|
| 517 |
+
# Populate relevant columns with hourly_data
|
| 518 |
+
epw_data[1] = [d["month"] for d in hourly_data] # Month
|
| 519 |
+
epw_data[2] = [d["day"] for d in hourly_data] # Day
|
| 520 |
+
epw_data[3] = [d["hour"] for d in hourly_data] # Hour
|
| 521 |
+
epw_data[6] = [d["dry_bulb"] for d in hourly_data] # Dry-bulb temperature
|
| 522 |
+
epw_data[8] = [d["relative_humidity"] for d in hourly_data] # Relative humidity
|
| 523 |
+
epw_data[9] = [d["atmospheric_pressure"] for d in hourly_data] # Pressure
|
| 524 |
+
epw_data[13] = [d["global_horizontal_radiation"] for d in hourly_data] # Global horizontal radiation
|
| 525 |
+
epw_data[20] = [d["wind_direction"] for d in hourly_data] # Wind direction
|
| 526 |
+
epw_data[21] = [d["wind_speed"] for d in hourly_data] # Wind speed
|
| 527 |
+
|
| 528 |
+
# Create ClimateLocation with reconstructed epw_data
|
| 529 |
+
location = ClimateLocation(
|
| 530 |
+
epw_file=epw_data,
|
| 531 |
+
typical_extreme_periods=climate_data_dict["typical_extreme_periods"],
|
| 532 |
+
ground_temperatures=climate_data_dict["ground_temperatures"],
|
| 533 |
+
id=climate_data_dict["id"],
|
| 534 |
+
country=climate_data_dict["country"],
|
| 535 |
+
state_province=climate_data_dict["state_province"],
|
| 536 |
+
city=climate_data_dict["city"],
|
| 537 |
+
latitude=climate_data_dict["latitude"],
|
| 538 |
+
longitude=climate_data_dict["longitude"],
|
| 539 |
+
elevation=climate_data_dict["elevation"],
|
| 540 |
+
time_zone=climate_data_dict["time_zone"],
|
| 541 |
+
climate_zone=climate_data_dict["climate_zone"]
|
| 542 |
+
)
|
| 543 |
+
# Override hourly_data to ensure consistency
|
| 544 |
+
location.hourly_data = climate_data_dict["hourly_data"]
|
| 545 |
+
self.add_location(location)
|
| 546 |
+
st.info("Displaying previously extracted climate data.")
|
| 547 |
+
logger.info("Successfully reconstructed climate data from session_state for ID: %s", climate_data_dict["id"])
|
| 548 |
+
except Exception as e:
|
| 549 |
+
st.error(f"Error reconstructing climate data: {str(e)}. Please upload a new EPW file.")
|
| 550 |
+
logger.error(f"Reconstruction error: {str(e)}")
|
| 551 |
+
session_state["climate_data"] = {} # Clear invalid data
|
| 552 |
|
| 553 |
# Display tabs if location and epw_data are available
|
| 554 |
if location and epw_data is not None:
|
| 555 |
+
logger.info("Displaying climate data tabs for location: %s", location.id)
|
| 556 |
tab1, tab2, tab3, tab4, tab5 = st.tabs([
|
| 557 |
"General Information",
|
| 558 |
"Psychrometric Chart",
|
|
|
|
| 578 |
|
| 579 |
else:
|
| 580 |
st.info("No climate data available. Please upload an EPW file to proceed.")
|
| 581 |
+
logger.info("No climate data to display; prompting for EPW upload.")
|
| 582 |
|
| 583 |
def display_design_conditions(self, location: ClimateLocation):
|
| 584 |
"""Display design conditions for HVAC calculations using styled HTML."""
|