""" Weather data module for HVAC Load Calculator. This module provides access to weather data for cooling load calculations. """ from typing import Dict, List, Any, Optional, Tuple import pandas as pd import numpy as np import datetime class WeatherDataProvider: """Class for providing weather data for cooling load calculations.""" def __init__(self): """Initialize weather data provider.""" # Default monthly average temperatures for a temperate climate self.default_monthly_temps = { "Jan": {"avg_temp": 0.0, "daily_range": 8.0, "relative_humidity": 70.0}, "Feb": {"avg_temp": 2.0, "daily_range": 9.0, "relative_humidity": 65.0}, "Mar": {"avg_temp": 7.0, "daily_range": 10.0, "relative_humidity": 60.0}, "Apr": {"avg_temp": 12.0, "daily_range": 11.0, "relative_humidity": 55.0}, "May": {"avg_temp": 17.0, "daily_range": 12.0, "relative_humidity": 50.0}, "Jun": {"avg_temp": 22.0, "daily_range": 13.0, "relative_humidity": 55.0}, "Jul": {"avg_temp": 25.0, "daily_range": 13.0, "relative_humidity": 60.0}, "Aug": {"avg_temp": 24.0, "daily_range": 12.0, "relative_humidity": 65.0}, "Sep": {"avg_temp": 19.0, "daily_range": 11.0, "relative_humidity": 60.0}, "Oct": {"avg_temp": 13.0, "daily_range": 10.0, "relative_humidity": 65.0}, "Nov": {"avg_temp": 7.0, "daily_range": 9.0, "relative_humidity": 70.0}, "Dec": {"avg_temp": 2.0, "daily_range": 8.0, "relative_humidity": 75.0} } # Design conditions for major cities self.city_design_conditions = { "New York": { "latitude": "40N", "summer_db": 32.0, # °C, dry bulb "summer_wb": 24.0, # °C, wet bulb "summer_daily_range": 11.0, # °C "winter_db": -10.0, # °C "winter_humidity": 40.0 # % }, "Los Angeles": { "latitude": "34N", "summer_db": 35.0, "summer_wb": 23.0, "summer_daily_range": 10.0, "winter_db": 5.0, "winter_humidity": 50.0 }, "Chicago": { "latitude": "42N", "summer_db": 33.0, "summer_wb": 25.0, "summer_daily_range": 12.0, "winter_db": -18.0, "winter_humidity": 35.0 }, "Houston": { "latitude": "30N", "summer_db": 36.0, "summer_wb": 26.0, "summer_daily_range": 9.0, "winter_db": 0.0, "winter_humidity": 60.0 }, "Phoenix": { "latitude": "33N", "summer_db": 42.0, "summer_wb": 24.0, "summer_daily_range": 15.0, "winter_db": 2.0, "winter_humidity": 30.0 }, "Miami": { "latitude": "26N", "summer_db": 33.0, "summer_wb": 27.0, "summer_daily_range": 8.0, "winter_db": 10.0, "winter_humidity": 70.0 }, "London": { "latitude": "51N", "summer_db": 28.0, "summer_wb": 20.0, "summer_daily_range": 10.0, "winter_db": -5.0, "winter_humidity": 80.0 }, "Tokyo": { "latitude": "36N", "summer_db": 33.0, "summer_wb": 27.0, "summer_daily_range": 8.0, "winter_db": 0.0, "winter_humidity": 60.0 }, "Sydney": { "latitude": "34S", "summer_db": 31.0, "summer_wb": 22.0, "summer_daily_range": 9.0, "winter_db": 8.0, "winter_humidity": 65.0 }, "Singapore": { "latitude": "1N", "summer_db": 33.0, "summer_wb": 28.0, "summer_daily_range": 7.0, "winter_db": 23.0, "winter_humidity": 85.0 } } def get_monthly_weather_data(self, city: str = None) -> Dict[str, Dict[str, float]]: """ Get monthly weather data for a city. Args: city: City name (optional) Returns: Dictionary of monthly weather data """ # If city is provided and exists in our database, adjust the monthly data if city and city in self.city_design_conditions: city_data = self.city_design_conditions[city] # Create a copy of the default data monthly_data = self.default_monthly_temps.copy() # Adjust the data based on the city's design conditions # This is a simplified approach; in a real implementation, you would use more detailed data # Summer months (Northern Hemisphere: Jun, Jul, Aug; Southern Hemisphere: Dec, Jan, Feb) summer_months = ["Jun", "Jul", "Aug"] if "N" in city_data["latitude"] else ["Dec", "Jan", "Feb"] winter_months = ["Dec", "Jan", "Feb"] if "N" in city_data["latitude"] else ["Jun", "Jul", "Aug"] # Adjust summer months for month in summer_months: monthly_data[month]["avg_temp"] = city_data["summer_db"] - 3.0 # Average is typically lower than design monthly_data[month]["daily_range"] = city_data["summer_daily_range"] monthly_data[month]["relative_humidity"] = 60.0 # Approximate # Adjust winter months for month in winter_months: monthly_data[month]["avg_temp"] = city_data["winter_db"] + 5.0 # Average is typically higher than design monthly_data[month]["daily_range"] = city_data["summer_daily_range"] * 0.7 # Winter range is typically smaller monthly_data[month]["relative_humidity"] = city_data["winter_humidity"] # Adjust transition months (simple linear interpolation) transition_months = [m for m in monthly_data.keys() if m not in summer_months and m not in winter_months] # Sort months chronologically all_months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] transition_months.sort(key=lambda m: all_months.index(m)) # Get average summer and winter values summer_avg_temp = sum(monthly_data[m]["avg_temp"] for m in summer_months) / len(summer_months) winter_avg_temp = sum(monthly_data[m]["avg_temp"] for m in winter_months) / len(winter_months) # Interpolate for transition months num_transition = len(transition_months) for i, month in enumerate(transition_months): factor = (i + 1) / (num_transition + 1) if "N" in city_data["latitude"]: # Northern Hemisphere if all_months.index(month) < all_months.index("Jun"): # Spring monthly_data[month]["avg_temp"] = winter_avg_temp + factor * (summer_avg_temp - winter_avg_temp) else: # Fall monthly_data[month]["avg_temp"] = summer_avg_temp - factor * (summer_avg_temp - winter_avg_temp) else: # Southern Hemisphere if all_months.index(month) < all_months.index("Dec") and all_months.index(month) >= all_months.index("Jun"): # Winter to Summer monthly_data[month]["avg_temp"] = winter_avg_temp + factor * (summer_avg_temp - winter_avg_temp) else: # Summer to Winter monthly_data[month]["avg_temp"] = summer_avg_temp - factor * (summer_avg_temp - winter_avg_temp) return monthly_data # If no city is provided or city is not in our database, return default data return self.default_monthly_temps def get_design_conditions(self, city: str) -> Dict[str, Any]: """ Get design conditions for a city. Args: city: City name Returns: Dictionary of design conditions """ if city in self.city_design_conditions: return self.city_design_conditions[city] else: # Return default design conditions return { "latitude": "40N", "summer_db": 35.0, "summer_wb": 25.0, "summer_daily_range": 11.0, "winter_db": -10.0, "winter_humidity": 50.0 } def get_hourly_temperatures(self, base_temp: float, daily_range: float) -> List[float]: """ Calculate hourly temperatures based on daily range. Args: base_temp: Base temperature (daily average) daily_range: Daily temperature range Returns: List of hourly temperatures """ from utils.ashrae_integration import get_daily_range_percentage hourly_temps = [] for hour in range(1, 25): # Get percentage of daily range for this hour percentage = get_daily_range_percentage(hour) / 100.0 # Calculate temperature temp = base_temp - daily_range / 2.0 + daily_range * percentage hourly_temps.append(temp) return hourly_temps def get_hourly_humidity(self, base_humidity: float, hourly_temps: List[float], base_temp: float) -> List[float]: """ Calculate hourly relative humidity based on temperature variation. Args: base_humidity: Base relative humidity (daily average) hourly_temps: Hourly temperatures base_temp: Base temperature (daily average) Returns: List of hourly relative humidity values """ hourly_humidity = [] for temp in hourly_temps: # This is a simplified approach; in a real implementation, you would use psychrometric formulas # Humidity tends to be inversely related to temperature temp_diff = temp - base_temp humidity_adjustment = -temp_diff * 2.0 # Rough approximation: 2% humidity change per °C humidity = base_humidity + humidity_adjustment humidity = max(10.0, min(100.0, humidity)) # Clamp between 10% and 100% hourly_humidity.append(humidity) return hourly_humidity # Create a singleton instance weather_data_provider = WeatherDataProvider()