test-29 / data /climate_data.py
mabuseif's picture
Upload 31 files
9c46f14 verified
"""
ASHRAE 169 climate data module for HVAC Load Calculator.
This module provides access to climate data for various locations based on ASHRAE 169 standard.
"""
from typing import Dict, List, Any, Optional, Tuple
import pandas as pd
import numpy as np
import os
import json
from dataclasses import dataclass
# Define paths
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
@dataclass
class ClimateLocation:
"""Class representing a climate location with ASHRAE 169 data."""
id: str
country: str
state_province: str
city: str
latitude: float
longitude: float
elevation: float # meters
climate_zone: str
heating_degree_days: float # base 18°C
cooling_degree_days: float # base 18°C
# Design conditions
winter_design_temp: float # 99.6% heating design temperature (°C)
summer_design_temp_db: float # 0.4% cooling design dry-bulb temperature (°C)
summer_design_temp_wb: float # 0.4% cooling design wet-bulb temperature (°C)
summer_daily_range: float # Mean daily temperature range in summer (°C)
# Monthly data
monthly_temps: Dict[str, float] # Average monthly temperatures (°C)
monthly_humidity: Dict[str, float] # Average monthly relative humidity (%)
def to_dict(self) -> Dict[str, Any]:
"""Convert the climate location to a dictionary."""
return {
"id": self.id,
"country": self.country,
"state_province": self.state_province,
"city": self.city,
"latitude": self.latitude,
"longitude": self.longitude,
"elevation": self.elevation,
"climate_zone": self.climate_zone,
"heating_degree_days": self.heating_degree_days,
"cooling_degree_days": self.cooling_degree_days,
"winter_design_temp": self.winter_design_temp,
"summer_design_temp_db": self.summer_design_temp_db,
"summer_design_temp_wb": self.summer_design_temp_wb,
"summer_daily_range": self.summer_daily_range,
"monthly_temps": self.monthly_temps,
"monthly_humidity": self.monthly_humidity
}
class ClimateData:
"""Class for managing ASHRAE 169 climate data."""
def __init__(self):
"""Initialize climate data."""
self.locations = self._load_climate_locations()
self.countries = sorted(list(set(loc.country for loc in self.locations.values())))
self.country_states = self._group_locations_by_country_state()
@staticmethod
def get_design_conditions(climate_zone: str) -> Dict[str, Dict[str, float]]:
"""
Get design conditions for a specific climate zone.
Args:
climate_zone: ASHRAE climate zone (e.g., '1A', '3B', '5A')
Returns:
Dictionary with summer and winter design conditions
"""
# Default design conditions by climate zone
design_conditions_by_zone = {
# Hot-Humid
"1A": {
"summer": {"db": 35.0, "wb": 28.0, "dp": 25.5},
"winter": {"db": 12.0, "rh": 80.0}
},
# Hot-Dry
"1B": {
"summer": {"db": 42.0, "wb": 24.0, "dp": 18.0},
"winter": {"db": 10.0, "rh": 40.0}
},
# Hot-Humid
"2A": {
"summer": {"db": 34.0, "wb": 26.5, "dp": 24.0},
"winter": {"db": 8.0, "rh": 75.0}
},
# Hot-Dry
"2B": {
"summer": {"db": 40.0, "wb": 23.0, "dp": 16.5},
"winter": {"db": 6.0, "rh": 45.0}
},
# Warm-Humid
"3A": {
"summer": {"db": 33.0, "wb": 25.0, "dp": 22.5},
"winter": {"db": 2.0, "rh": 70.0}
},
# Warm-Dry
"3B": {
"summer": {"db": 38.0, "wb": 22.0, "dp": 15.0},
"winter": {"db": 4.0, "rh": 40.0}
},
# Warm-Marine
"3C": {
"summer": {"db": 28.0, "wb": 20.0, "dp": 17.0},
"winter": {"db": 5.0, "rh": 80.0}
},
# Mixed-Humid
"4A": {
"summer": {"db": 32.0, "wb": 24.0, "dp": 21.0},
"winter": {"db": -5.0, "rh": 70.0}
},
# Mixed-Dry
"4B": {
"summer": {"db": 35.0, "wb": 20.0, "dp": 13.0},
"winter": {"db": -3.0, "rh": 45.0}
},
# Mixed-Marine
"4C": {
"summer": {"db": 27.0, "wb": 19.0, "dp": 16.0},
"winter": {"db": -2.0, "rh": 80.0}
},
# Cool-Humid
"5A": {
"summer": {"db": 31.0, "wb": 23.0, "dp": 20.0},
"winter": {"db": -15.0, "rh": 70.0}
},
# Cool-Dry
"5B": {
"summer": {"db": 33.0, "wb": 18.0, "dp": 11.0},
"winter": {"db": -10.0, "rh": 45.0}
},
# Cool-Marine
"5C": {
"summer": {"db": 25.0, "wb": 18.0, "dp": 15.0},
"winter": {"db": -5.0, "rh": 80.0}
},
# Cold-Humid
"6A": {
"summer": {"db": 30.0, "wb": 22.0, "dp": 19.0},
"winter": {"db": -20.0, "rh": 70.0}
},
# Cold-Dry
"6B": {
"summer": {"db": 31.0, "wb": 17.0, "dp": 10.0},
"winter": {"db": -15.0, "rh": 45.0}
},
# Very Cold
"7": {
"summer": {"db": 28.0, "wb": 20.0, "dp": 17.0},
"winter": {"db": -25.0, "rh": 70.0}
},
# Subarctic/Arctic
"8": {
"summer": {"db": 25.0, "wb": 18.0, "dp": 15.0},
"winter": {"db": -30.0, "rh": 70.0}
}
}
# Return design conditions for the specified climate zone
# If climate zone not found, return default values
if climate_zone in design_conditions_by_zone:
return design_conditions_by_zone[climate_zone]
else:
# Default to 4A if climate zone not found
return design_conditions_by_zone["4A"]
@staticmethod
def get_monthly_temperatures(climate_zone: str) -> Dict[int, Dict[str, float]]:
"""
Get monthly average temperatures for a specific climate zone.
Args:
climate_zone: ASHRAE climate zone (e.g., '1A', '3B', '5A')
Returns:
Dictionary with monthly average temperatures indexed by month number (1-12)
Each month contains 'avg_db', 'max_db', and 'min_db' values
"""
# Helper function to convert month name format to numeric format with min/max values
def convert_month_format(month_data):
month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
result = {}
for i, month in enumerate(month_names, 1):
avg_temp = month_data[month]
# Generate reasonable min/max values based on average
min_temp = avg_temp - 5.0
max_temp = avg_temp + 5.0
result[i] = {
'avg_db': avg_temp,
'min_db': min_temp,
'max_db': max_temp
}
return result
# Default monthly temperatures by climate zone (in month name format)
monthly_temps_raw = {
# Hot-Humid (like Miami)
"1A": {
"Jan": 20.0, "Feb": 20.5, "Mar": 22.0, "Apr": 24.0, "May": 26.0,
"Jun": 28.0, "Jul": 29.0, "Aug": 29.0, "Sep": 28.0, "Oct": 26.0,
"Nov": 23.0, "Dec": 21.0
},
# Hot-Dry (like Riyadh)
"1B": {
"Jan": 15.0, "Feb": 17.0, "Mar": 22.0, "Apr": 27.0, "May": 32.0,
"Jun": 35.0, "Jul": 37.0, "Aug": 36.0, "Sep": 33.0, "Oct": 28.0,
"Nov": 22.0, "Dec": 17.0
},
# Hot-Humid (like Houston)
"2A": {
"Jan": 12.0, "Feb": 13.5, "Mar": 17.0, "Apr": 21.0, "May": 25.0,
"Jun": 28.0, "Jul": 29.0, "Aug": 29.0, "Sep": 27.0, "Oct": 22.0,
"Nov": 17.0, "Dec": 13.0
},
# Hot-Dry (like Phoenix)
"2B": {
"Jan": 13.0, "Feb": 15.0, "Mar": 18.0, "Apr": 23.0, "May": 28.0,
"Jun": 33.0, "Jul": 35.0, "Aug": 34.0, "Sep": 31.0, "Oct": 25.0,
"Nov": 18.0, "Dec": 13.0
},
# Warm-Humid (like Atlanta)
"3A": {
"Jan": 6.0, "Feb": 8.0, "Mar": 12.0, "Apr": 17.0, "May": 21.0,
"Jun": 25.0, "Jul": 27.0, "Aug": 26.0, "Sep": 23.0, "Oct": 18.0,
"Nov": 12.0, "Dec": 7.0
},
# Warm-Dry (like Los Angeles)
"3B": {
"Jan": 14.6, "Feb": 15.1, "Mar": 15.8, "Apr": 17.1, "May": 18.3,
"Jun": 20.1, "Jul": 22.3, "Aug": 22.9, "Sep": 22.1, "Oct": 20.3,
"Nov": 17.2, "Dec": 14.9
},
# Warm-Marine (like San Francisco)
"3C": {
"Jan": 10.0, "Feb": 11.0, "Mar": 12.0, "Apr": 13.0, "May": 14.0,
"Jun": 16.0, "Jul": 17.0, "Aug": 17.0, "Sep": 18.0, "Oct": 16.0,
"Nov": 13.0, "Dec": 10.0
},
# Mixed-Humid (like New York)
"4A": {
"Jan": 0.5, "Feb": 2.1, "Mar": 6.3, "Apr": 12.5, "May": 18.2,
"Jun": 23.1, "Jul": 25.8, "Aug": 24.9, "Sep": 20.7, "Oct": 14.3,
"Nov": 8.2, "Dec": 2.4
},
# Mixed-Dry (like Albuquerque)
"4B": {
"Jan": 3.0, "Feb": 5.0, "Mar": 9.0, "Apr": 14.0, "May": 19.0,
"Jun": 24.0, "Jul": 26.0, "Aug": 25.0, "Sep": 21.0, "Oct": 15.0,
"Nov": 8.0, "Dec": 3.0
},
# Mixed-Marine (like Seattle)
"4C": {
"Jan": 5.0, "Feb": 6.0, "Mar": 8.0, "Apr": 10.0, "May": 13.0,
"Jun": 16.0, "Jul": 18.0, "Aug": 18.0, "Sep": 16.0, "Oct": 12.0,
"Nov": 8.0, "Dec": 5.0
},
# Cool-Humid (like Chicago)
"5A": {
"Jan": -3.5, "Feb": -1.2, "Mar": 4.1, "Apr": 10.3, "May": 16.5,
"Jun": 22.1, "Jul": 24.8, "Aug": 23.9, "Sep": 19.7, "Oct": 12.8,
"Nov": 5.2, "Dec": -1.4
},
# Cool-Dry (like Denver)
"5B": {
"Jan": 0.0, "Feb": 2.0, "Mar": 6.0, "Apr": 10.0, "May": 15.0,
"Jun": 20.0, "Jul": 23.0, "Aug": 22.0, "Sep": 18.0, "Oct": 12.0,
"Nov": 5.0, "Dec": 0.0
},
# Cool-Marine (like Vancouver)
"5C": {
"Jan": 3.0, "Feb": 4.0, "Mar": 6.0, "Apr": 9.0, "May": 12.0,
"Jun": 15.0, "Jul": 17.0, "Aug": 17.0, "Sep": 14.0, "Oct": 10.0,
"Nov": 6.0, "Dec": 3.0
},
# Cold-Humid (like Minneapolis)
"6A": {
"Jan": -9.0, "Feb": -6.0, "Mar": 0.0, "Apr": 8.0, "May": 15.0,
"Jun": 20.0, "Jul": 23.0, "Aug": 22.0, "Sep": 17.0, "Oct": 10.0,
"Nov": 1.0, "Dec": -6.0
},
# Cold-Dry (like Helena)
"6B": {
"Jan": -5.0, "Feb": -2.0, "Mar": 2.0, "Apr": 7.0, "May": 12.0,
"Jun": 17.0, "Jul": 21.0, "Aug": 20.0, "Sep": 15.0, "Oct": 9.0,
"Nov": 1.0, "Dec": -4.0
},
# Very Cold (like Duluth)
"7": {
"Jan": -12.0, "Feb": -9.0, "Mar": -3.0, "Apr": 5.0, "May": 12.0,
"Jun": 17.0, "Jul": 20.0, "Aug": 19.0, "Sep": 14.0, "Oct": 7.0,
"Nov": -1.0, "Dec": -9.0
},
# Subarctic/Arctic (like Fairbanks)
"8": {
"Jan": -20.0, "Feb": -16.0, "Mar": -10.0, "Apr": 0.0, "May": 10.0,
"Jun": 16.0, "Jul": 18.0, "Aug": 15.0, "Sep": 8.0, "Oct": -2.0,
"Nov": -12.0, "Dec": -18.0
}
}
# Return monthly temperatures for the specified climate zone
# If climate zone not found, return default values
if climate_zone in monthly_temps_raw:
return convert_month_format(monthly_temps_raw[climate_zone])
else:
# Default to 4A if climate zone not found
return convert_month_format(monthly_temps_raw["4A"])
def _load_climate_locations(self) -> Dict[str, ClimateLocation]:
"""
Load climate location data.
Returns:
Dictionary of climate locations indexed by ID
"""
# This would typically load from a JSON or CSV file with ASHRAE 169 data
# For now, we'll define some sample locations inline
# Sample monthly data (for all locations in this example)
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
# New York monthly temperatures (°C)
ny_temps = {
"Jan": 0.5, "Feb": 2.1, "Mar": 6.3, "Apr": 12.5, "May": 18.2,
"Jun": 23.1, "Jul": 25.8, "Aug": 24.9, "Sep": 20.7, "Oct": 14.3,
"Nov": 8.2, "Dec": 2.4
}
# New York monthly humidity (%)
ny_humidity = {
"Jan": 65, "Feb": 62, "Mar": 58, "Apr": 55, "May": 60,
"Jun": 65, "Jul": 68, "Aug": 70, "Sep": 68, "Oct": 63,
"Nov": 67, "Dec": 68
}
# Los Angeles monthly temperatures (°C)
la_temps = {
"Jan": 14.6, "Feb": 15.1, "Mar": 15.8, "Apr": 17.1, "May": 18.3,
"Jun": 20.1, "Jul": 22.3, "Aug": 22.9, "Sep": 22.1, "Oct": 20.3,
"Nov": 17.2, "Dec": 14.9
}
# Los Angeles monthly humidity (%)
la_humidity = {
"Jan": 63, "Feb": 67, "Mar": 70, "Apr": 71, "May": 74,
"Jun": 75, "Jul": 76, "Aug": 76, "Sep": 74, "Oct": 70,
"Nov": 65, "Dec": 63
}
# Chicago monthly temperatures (°C)
chi_temps = {
"Jan": -3.5, "Feb": -1.2, "Mar": 4.1, "Apr": 10.3, "May": 16.5,
"Jun": 22.1, "Jul": 24.8, "Aug": 23.9, "Sep": 19.7, "Oct": 12.8,
"Nov": 5.2, "Dec": -1.4
}
# Chicago monthly humidity (%)
chi_humidity = {
"Jan": 72, "Feb": 70, "Mar": 65, "Apr": 60, "May": 64,
"Jun": 67, "Jul": 70, "Aug": 73, "Sep": 71, "Oct": 68,
"Nov": 72, "Dec": 75
}
# London monthly temperatures (°C)
lon_temps = {
"Jan": 5.2, "Feb": 5.5, "Mar": 7.4, "Apr": 9.9, "May": 13.3,
"Jun": 16.7, "Jul": 18.7, "Aug": 18.3, "Sep": 15.9, "Oct": 12.2,
"Nov": 8.3, "Dec": 5.9
}
# London monthly humidity (%)
lon_humidity = {
"Jan": 84, "Feb": 80, "Mar": 76, "Apr": 72, "May": 70,
"Jun": 70, "Jul": 71, "Aug": 72, "Sep": 75, "Oct": 80,
"Nov": 84, "Dec": 86
}
# Sydney monthly temperatures (°C)
syd_temps = {
"Jan": 23.5, "Feb": 23.4, "Mar": 22.1, "Apr": 19.5, "May": 16.5,
"Jun": 14.1, "Jul": 13.4, "Aug": 14.5, "Sep": 16.6, "Oct": 18.8,
"Nov": 20.6, "Dec": 22.6
}
# Sydney monthly humidity (%)
syd_humidity = {
"Jan": 65, "Feb": 68, "Mar": 68, "Apr": 67, "May": 70,
"Jun": 70, "Jul": 68, "Aug": 63, "Sep": 60, "Oct": 60,
"Nov": 62, "Dec": 63
}
# Create sample locations
locations = {
"US-NY-NYC": ClimateLocation(
id="US-NY-NYC",
country="United States",
state_province="New York",
city="New York",
latitude=40.7128,
longitude=-74.0060,
elevation=10.0,
climate_zone="4A",
heating_degree_days=2600,
cooling_degree_days=1200,
winter_design_temp=-8.3,
summer_design_temp_db=32.8,
summer_design_temp_wb=25.6,
summer_daily_range=8.3,
monthly_temps=ny_temps,
monthly_humidity=ny_humidity
),
"US-CA-LAX": ClimateLocation(
id="US-CA-LAX",
country="United States",
state_province="California",
city="Los Angeles",
latitude=34.0522,
longitude=-118.2437,
elevation=93.0,
climate_zone="3B",
heating_degree_days=800,
cooling_degree_days=1200,
winter_design_temp=8.3,
summer_design_temp_db=32.2,
summer_design_temp_wb=23.3,
summer_daily_range=6.7,
monthly_temps=la_temps,
monthly_humidity=la_humidity
),
"US-IL-CHI": ClimateLocation(
id="US-IL-CHI",
country="United States",
state_province="Illinois",
city="Chicago",
latitude=41.8781,
longitude=-87.6298,
elevation=179.0,
climate_zone="5A",
heating_degree_days=3500,
cooling_degree_days=1000,
winter_design_temp=-16.7,
summer_design_temp_db=33.3,
summer_design_temp_wb=25.6,
summer_daily_range=8.9,
monthly_temps=chi_temps,
monthly_humidity=chi_humidity
),
"UK-LDN": ClimateLocation(
id="UK-LDN",
country="United Kingdom",
state_province="England",
city="London",
latitude=51.5074,
longitude=-0.1278,
elevation=35.0,
climate_zone="4A",
heating_degree_days=2500,
cooling_degree_days=200,
winter_design_temp=-3.9,
summer_design_temp_db=28.3,
summer_design_temp_wb=20.0,
summer_daily_range=10.0,
monthly_temps=lon_temps,
monthly_humidity=lon_humidity
),
"AU-NSW-SYD": ClimateLocation(
id="AU-NSW-SYD",
country="Australia",
state_province="New South Wales",
city="Sydney",
latitude=-33.8688,
longitude=151.2093,
elevation=3.0,
climate_zone="3C",
heating_degree_days=600,
cooling_degree_days=900,
winter_design_temp=7.2,
summer_design_temp_db=31.1,
summer_design_temp_wb=24.4,
summer_daily_range=7.8,
monthly_temps=syd_temps,
monthly_humidity=syd_humidity
)
}
return locations
def _group_locations_by_country_state(self) -> Dict[str, Dict[str, List[str]]]:
"""
Group locations by country and state/province.
Returns:
Nested dictionary of countries, states, and cities
"""
result = {}
for loc in self.locations.values():
if loc.country not in result:
result[loc.country] = {}
if loc.state_province not in result[loc.country]:
result[loc.country][loc.state_province] = []
result[loc.country][loc.state_province].append(loc.city)
# Sort states and cities
for country in result:
for state in result[country]:
result[country][state] = sorted(result[country][state])
return result
def get_location(self, location_id: str) -> Optional[ClimateLocation]:
"""
Get climate location by ID.
Args:
location_id: Location identifier
Returns:
ClimateLocation object or None if not found
"""
return self.locations.get(location_id)
def find_location(self, country: str, state_province: str = None, city: str = None) -> Optional[ClimateLocation]:
"""
Find a climate location by country, state/province, and city.
Args:
country: Country name
state_province: State or province name (optional)
city: City name (optional)
Returns:
ClimateLocation object or None if not found
"""
for loc in self.locations.values():
if loc.country == country:
if state_province is None or loc.state_province == state_province:
if city is None or loc.city == city:
return loc
return None
def find_locations_by_climate_zone(self, climate_zone: str) -> List[ClimateLocation]:
"""
Find climate locations by climate zone.
Args:
climate_zone: ASHRAE climate zone
Returns:
List of ClimateLocation objects
"""
return [loc for loc in self.locations.values() if loc.climate_zone == climate_zone]
def get_states_for_country(self, country: str) -> List[str]:
"""
Get states/provinces for a country.
Args:
country: Country name
Returns:
List of state/province names
"""
if country in self.country_states:
return sorted(self.country_states[country].keys())
return []
def get_cities_for_state(self, country: str, state_province: str) -> List[str]:
"""
Get cities for a state/province.
Args:
country: Country name
state_province: State or province name
Returns:
List of city names
"""
if country in self.country_states and state_province in self.country_states[country]:
return self.country_states[country][state_province]
return []
def get_location_id(self, country: str, state_province: str, city: str) -> Optional[str]:
"""
Get location ID for a city.
Args:
country: Country name
state_province: State or province name
city: City name
Returns:
Location ID or None if not found
"""
for loc_id, loc in self.locations.items():
if (loc.country == country and
loc.state_province == state_province and
loc.city == city):
return loc_id
return None
def export_to_json(self, file_path: str) -> None:
"""
Export all climate data to a JSON file.
Args:
file_path: Path to the output JSON file
"""
data = {loc_id: loc.to_dict() for loc_id, loc in self.locations.items()}
with open(file_path, 'w') as f:
json.dump(data, f, indent=4)
@classmethod
def from_json(cls, file_path: str) -> 'ClimateData':
"""
Create a ClimateData instance from a JSON file.
Args:
file_path: Path to the input JSON file
Returns:
A new ClimateData instance
"""
with open(file_path, 'r') as f:
data = json.load(f)
climate_data = cls()
climate_data.locations = {}
for loc_id, loc_dict in data.items():
climate_data.locations[loc_id] = ClimateLocation(
id=loc_dict["id"],
country=loc_dict["country"],
state_province=loc_dict["state_province"],
city=loc_dict["city"],
latitude=loc_dict["latitude"],
longitude=loc_dict["longitude"],
elevation=loc_dict["elevation"],
climate_zone=loc_dict["climate_zone"],
heating_degree_days=loc_dict["heating_degree_days"],
cooling_degree_days=loc_dict["cooling_degree_days"],
winter_design_temp=loc_dict["winter_design_temp"],
summer_design_temp_db=loc_dict["summer_design_temp_db"],
summer_design_temp_wb=loc_dict["summer_design_temp_wb"],
summer_daily_range=loc_dict["summer_daily_range"],
monthly_temps=loc_dict["monthly_temps"],
monthly_humidity=loc_dict["monthly_humidity"]
)
climate_data.countries = sorted(list(set(loc.country for loc in climate_data.locations.values())))
climate_data.country_states = climate_data._group_locations_by_country_state()
return climate_data
# Create a singleton instance
climate_data = ClimateData()
# Export climate data to JSON if needed
if __name__ == "__main__":
climate_data.export_to_json(os.path.join(DATA_DIR, "climate_data.json"))