""" ASHRAE 169 climate data module for HVAC Load Calculator. This module provides access to climate data for various locations based on ASHRAE 169 standard. Author: Dr Majed Abuseif Date: March 2025 Version: 1.0.0 """ from typing import Dict, List, Any, Optional import pandas as pd import numpy as np import os import json from dataclasses import dataclass import streamlit as st import plotly.graph_objects as go from io import StringIO # 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 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_temps: Dict[str, float] # Average monthly temperatures (°C) monthly_humidity: Dict[str, float] # Average monthly relative humidity (%) wind_speed: float # Mean wind speed (m/s) pressure: float # Atmospheric pressure (Pa) def __init__(self, epw_file=None, manual_data=None, **kwargs): """Initialize ClimateLocation with EPW file or manual data.""" if epw_file is not None and isinstance(epw_file, pd.DataFrame): # Extract from EPW (epw_data[6] for dry-bulb temperature) temps = np.array(epw_file[6], dtype=float) self.winter_design_temp = np.percentile(temps[~np.isnan(temps)], 0.4) # 99.6% percentile self.wind_speed = round(np.nanmean(epw_file[21]), 1) # Wind speed (m/s, index 21) self.pressure = round(np.nanmean(epw_file[9]), 1) # Atmospheric pressure (Pa, index 9) # Populate other fields from EPW processing self.id = kwargs.get("id") self.country = kwargs.get("country") self.state_province = kwargs.get("state_province") self.city = kwargs.get("city") self.latitude = kwargs.get("latitude") self.longitude = kwargs.get("longitude") self.elevation = kwargs.get("elevation") self.climate_zone = kwargs.get("climate_zone") self.heating_degree_days = kwargs.get("heating_degree_days") self.cooling_degree_days = kwargs.get("cooling_degree_days") self.summer_design_temp_db = kwargs.get("summer_design_temp_db") self.summer_design_temp_wb = kwargs.get("summer_design_temp_wb") self.summer_daily_range = kwargs.get("summer_daily_range") self.monthly_temps = kwargs.get("monthly_temps") self.monthly_humidity = kwargs.get("monthly_humidity") elif manual_data: self.winter_design_temp = manual_data.get("winter_temp", -10.0) self.wind_speed = manual_data.get("wind_speed", 5.0) self.pressure = manual_data.get("pressure", 101325.0) # Use provided pressure # Populate other fields from manual data for key, value in kwargs.items(): setattr(self, key, value) else: # Default initialization with kwargs for key, value in kwargs.items(): setattr(self, key, value) self.winter_design_temp = kwargs.get("winter_design_temp", -10.0) self.wind_speed = kwargs.get("wind_speed", 5.0) self.pressure = self.adjust_pressure_for_altitude(kwargs.get("elevation", 0.0)) def adjust_pressure_for_altitude(self, elevation: float) -> float: """Calculate atmospheric pressure based on elevation.""" if elevation is None: return 101325.0 # Default sea-level pressure if elevation is None return 101325 * (1 - 2.25577e-5 * elevation)**5.25588 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, "wind_speed": self.wind_speed, "pressure": self.pressure } class ClimateData: """Class for managing ASHRAE 169 climate data.""" def __init__(self): """Initialize climate data.""" self.locations = {} self.countries = [] self.country_states = {} def _group_locations_by_country_state(self) -> Dict[str, Dict[str, List[str]]]: """Group locations by country and state/province.""" 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) for country in result: for state in result[country]: result[country][state] = sorted(result[country][state]) return result def add_location(self, location: ClimateLocation): """Add a new location to the dictionary.""" self.locations[location.id] = location self.countries = sorted(list(set(loc.country for loc in self.locations.values()))) self.country_states = self._group_locations_by_country_state() def get_location_by_id(self, location_id: str, session_state: Dict[str, Any]) -> Optional[Dict[str, Any]]: """Retrieve climate data by ID from session state or locations.""" if "climate_data" in session_state and session_state["climate_data"].get("id") == location_id: return session_state["climate_data"] if location_id in self.locations: return self.locations[location_id].to_dict() return None @staticmethod def validate_climate_data(data: Dict[str, Any]) -> bool: """Validate climate data for required fields and ranges.""" required_fields = [ "id", "country", "city", "latitude", "longitude", "elevation", "climate_zone", "heating_degree_days", "cooling_degree_days", "winter_design_temp", "summer_design_temp_db", "summer_design_temp_wb", "summer_daily_range", "monthly_temps", "monthly_humidity", "wind_speed", "pressure" ] month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] for field in required_fields: if field not in data: return False if not (-90 <= data["latitude"] <= 90 and -180 <= data["longitude"] <= 180): return False if data["elevation"] < 0: return False 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"]: return False if not (data["heating_degree_days"] >= 0 and data["cooling_degree_days"] >= 0): return False if not (-50 <= data["winter_design_temp"] <= 20): return False if not (0 <= data["summer_design_temp_db"] <= 50 and 0 <= data["summer_design_temp_wb"] <= 40): return False if data["summer_daily_range"] < 0: return False if not (0 <= data["wind_speed"] <= 20): return False if not (50000 <= data["pressure"] <= 120000): return False for month in month_names: if month not in data["monthly_temps"] or month not in data["monthly_humidity"]: return False if not (-50 <= data["monthly_temps"][month] <= 50): return False if not (0 <= data["monthly_humidity"][month] <= 100): return False return True @staticmethod def calculate_wet_bulb(dry_bulb: np.ndarray, relative_humidity: np.ndarray) -> np.ndarray: """Calculate Wet Bulb Temperature using Stull (2011) approximation.""" db = np.array(dry_bulb, dtype=float) rh = np.array(relative_humidity, dtype=float) term1 = db * np.arctan(0.151977 * (rh + 8.313659)**0.5) term2 = np.arctan(db + rh) term3 = np.arctan(rh - 1.676331) term4 = 0.00391838 * rh**1.5 * np.arctan(0.023101 * rh) term5 = -4.686035 wet_bulb = term1 + term2 - term3 + term4 + term5 invalid_mask = (rh < 5) | (rh > 99) | (db < -20) | (db > 50) | np.isnan(db) | np.isnan(rh) wet_bulb[invalid_mask] = np.nan return wet_bulb def display_climate_input(self, session_state: Dict[str, Any]): """Display form for EPW upload or manual input in Streamlit.""" st.title("Climate Data") if not session_state.building_info.get("country") or not session_state.building_info.get("city"): st.warning("Please enter country and city in Building Information first.") st.button("Go to Building Information", on_click=lambda: setattr(session_state, "page", "Building Information")) return st.subheader(f"Location: {session_state.building_info['country']}, {session_state.building_info['city']}") tab1, tab2 = st.tabs(["Upload EPW File", "Manual Input"]) # EPW Upload Tab with tab1: uploaded_file = st.file_uploader("Upload EPW File", type=["epw"]) if uploaded_file: try: epw_content = uploaded_file.read().decode("utf-8") epw_lines = epw_content.splitlines() header = next(line for line in epw_lines if line.startswith("LOCATION")) header_parts = header.split(",") latitude = float(header_parts[6]) longitude = float(header_parts[7]) elevation = float(header_parts[8]) data_start_idx = next(i for i, line in enumerate(epw_lines) if line.startswith("DATA PERIODS")) + 1 epw_data = pd.read_csv(StringIO("\n".join(epw_lines[data_start_idx:])), header=None, dtype=str) # Validate row and column counts if len(epw_data) != 8760: raise ValueError(f"EPW file has {len(epw_data)} records, expected 8760.") if len(epw_data.columns) != 35: raise ValueError(f"EPW file has {len(epw_data.columns)} columns, expected 35.") # Convert relevant columns to numeric for col in [1, 6, 8, 9, 21]: epw_data[col] = pd.to_numeric(epw_data[col], errors='coerce') if epw_data[col].isna().all(): raise ValueError(f"Column {col} (e.g., {'wind speed' if col == 21 else 'pressure' if col == 9 else 'other'}) contains only non-numeric or missing data.") months = epw_data[1].values # Month dry_bulb = epw_data[6].values # Dry-bulb temperature (°C) humidity = epw_data[8].values # Relative humidity (%) pressure = epw_data[9].values # Atmospheric pressure (Pa) wind_speed = epw_data[21].values # Wind speed (m/s) wet_bulb = self.calculate_wet_bulb(dry_bulb, humidity) if np.all(np.isnan(dry_bulb)) or np.all(np.isnan(humidity)) or np.all(np.isnan(wet_bulb)): raise ValueError("Dry bulb, humidity, or calculated wet bulb data is entirely NaN.") daily_temps = np.nanmean(dry_bulb.reshape(-1, 24), axis=1) hdd = round(np.nansum(np.maximum(18 - daily_temps, 0))) cdd = round(np.nansum(np.maximum(daily_temps - 18, 0))) winter_design_temp = round(np.nanpercentile(dry_bulb, 0.4), 1) summer_design_temp_db = round(np.nanpercentile(dry_bulb, 99.6), 1) summer_design_temp_wb = round(np.nanpercentile(wet_bulb, 99.6), 1) summer_mask = (months >= 6) & (months <= 8) summer_temps = dry_bulb[summer_mask].reshape(-1, 24) summer_daily_range = round(np.nanmean(np.nanmax(summer_temps, axis=1) - np.nanmin(summer_temps, axis=1)), 1) monthly_temps = {} monthly_humidity = {} month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] for i in range(1, 13): month_mask = (months == i) monthly_temps[month_names[i-1]] = round(np.nanmean(dry_bulb[month_mask]), 1) monthly_humidity[month_names[i-1]] = round(np.nanmean(humidity[month_mask]), 1) avg_humidity = np.nanmean(humidity) climate_zone = self.assign_climate_zone(hdd, cdd, avg_humidity) location = ClimateLocation( epw_file=epw_data, id=f"{session_state.building_info['country'][:2].upper()}-{session_state.building_info['city'][:3].upper()}", country=session_state.building_info["country"], state_province="N/A", city=session_state.building_info["city"], latitude=latitude, longitude=longitude, elevation=elevation, climate_zone=climate_zone, heating_degree_days=hdd, cooling_degree_days=cdd, summer_design_temp_db=summer_design_temp_db, summer_design_temp_wb=summer_design_temp_wb, summer_daily_range=summer_daily_range, monthly_temps=monthly_temps, monthly_humidity=monthly_humidity ) self.add_location(location) climate_data_dict = location.to_dict() if not self.validate_climate_data(climate_data_dict): raise ValueError("Invalid climate data extracted from EPW file.") session_state["climate_data"] = climate_data_dict # Save to session state st.success("Climate data extracted from EPW file with calculated Wet Bulb Temperature!") st.write(f"Debug: Saved climate data for {location.city} (ID: {location.id}): {climate_data_dict}") # Debug self.display_design_conditions(location) self.visualize_data(location, epw_data=epw_data) except Exception as e: st.error(f"Error processing EPW file: {str(e)}. Ensure it has 8760 hourly records and correct format.") # Manual Input Tab with tab2: with st.form("manual_climate_form"): col1, col2 = st.columns(2) with col1: latitude = st.number_input( "Latitude", min_value=-90.0, max_value=90.0, value=0.0, step=0.1, help="Enter the latitude of the location in degrees (e.g., 64.1 for Reykjavik)" ) longitude = st.number_input( "Longitude", min_value=-180.0, max_value=180.0, value=0.0, step=0.1, help="Enter the longitude of the location in degrees (e.g., -21.9 for Reykjavik)" ) elevation = st.number_input( "Elevation (m)", min_value=0.0, value=0.0, step=10.0, help="Enter the elevation of the location above sea level in meters" ) climate_zone = st.selectbox( "Climate Zone", ["0A", "0B", "1A", "1B", "2A", "2B", "3A", "3B", "3C", "4A", "4B", "4C", "5A", "5B", "5C", "6A", "6B", "7", "8"], help="Select the ASHRAE climate zone for the location (e.g., 6A for cold, humid climates)" ) with col2: hdd = st.number_input( "Heating Degree Days (base 18°C)", min_value=0.0, value=0.0, step=100.0, help="Enter the annual heating degree days using an 18°C base temperature" ) cdd = st.number_input( "Cooling Degree Days (base 18°C)", min_value=0.0, value=0.0, step=100.0, help="Enter the annual cooling degree days using an 18°C base temperature" ) winter_design_temp = st.number_input( "Winter Design Temp (99.6%) (°C)", min_value=-50.0, max_value=10.0, value=0.0, step=0.5, help="Enter the winter design temperature in °C" ) summer_design_temp_db = st.number_input( "Summer Design Temp DB (0.4%) (°C)", min_value=0.0, max_value=50.0, value=35.0, step=0.5, help="Enter the 0.4% summer design dry-bulb temperature in °C (extreme hot condition)" ) summer_design_temp_wb = st.number_input( "Summer Design Temp WB (0.4%) (°C)", min_value=0.0, max_value=40.0, value=25.0, step=0.5, help="Enter the 0.4% summer design wet-bulb temperature in °C (for humidity consideration)" ) summer_daily_range = st.number_input( "Summer Daily Range (°C)", min_value=0.0, value=5.0, step=0.5, help="Enter the average daily temperature range in summer in °C" ) wind_speed = st.number_input( "Wind Speed (m/s)", min_value=0.0, max_value=20.0, value=5.0, step=0.1, help="Enter the average wind speed in meters per second" ) pressure = st.number_input( "Atmospheric Pressure (Pa)", min_value=50000.0, max_value=120000.0, value=101325.0, step=100.0, help="Enter the average atmospheric pressure in Pascals (e.g., 101325 Pa for sea level)" ) # Monthly Data with clear titles (no help added here) monthly_temps = {} monthly_humidity = {} month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] st.subheader("Monthly Temperatures") col1, col2 = st.columns(2) with col1: for month in month_names[:6]: monthly_temps[month] = st.number_input(f"{month} Temp (°C)", min_value=-50.0, max_value=50.0, value=20.0, step=0.5, key=f"temp_{month}") with col2: for month in month_names[6:]: monthly_temps[month] = st.number_input(f"{month} Temp (°C)", min_value=-50.0, max_value=50.0, value=20.0, step=0.5, key=f"temp_{month}") st.subheader("Monthly Humidity") col1, col2 = st.columns(2) with col1: for month in month_names[:6]: monthly_humidity[month] = st.number_input(f"{month} Humidity (%)", min_value=0.0, max_value=100.0, value=50.0, step=5.0, key=f"hum_{month}") with col2: for month in month_names[6:]: monthly_humidity[month] = st.number_input(f"{month} Humidity (%)", min_value=0.0, max_value=100.0, value=50.0, step=5.0, key=f"hum_{month}") if st.form_submit_button("Save Climate Data"): try: # Generate ID internally using country and city from session_state generated_id = f"{session_state.building_info['country'][:2].upper()}-{session_state.building_info['city'][:3].upper()}" manual_data = { "winter_temp": winter_design_temp, "wind_speed": wind_speed, "pressure": pressure # Use user-provided pressure } location = ClimateLocation( manual_data=manual_data, id=generated_id, country=session_state.building_info["country"], state_province="N/A", city=session_state.building_info["city"], latitude=latitude, longitude=longitude, elevation=elevation, climate_zone=climate_zone, heating_degree_days=hdd, cooling_degree_days=cdd, summer_design_temp_db=summer_design_temp_db, summer_design_temp_wb=summer_design_temp_wb, summer_daily_range=summer_daily_range, monthly_temps=monthly_temps, monthly_humidity=monthly_humidity ) self.add_location(location) climate_data_dict = location.to_dict() if not self.validate_climate_data(climate_data_dict): raise ValueError("Invalid climate data. Please check all inputs.") session_state["climate_data"] = climate_data_dict # Save to session state st.success("Climate data saved manually!") st.write(f"Debug: Saved climate data for {location.city} (ID: {location.id}): {climate_data_dict}") # Debug self.display_design_conditions(location) self.visualize_data(location, epw_data=None) except Exception as e: st.error(f"Error saving climate data: {str(e)}. Please check inputs and try again.") col1, col2 = st.columns(2) with col1: st.button("Back to Building Information", on_click=lambda: setattr(session_state, "page", "Building Information")) with col2: if self.locations: st.button("Continue to Building Components", on_click=lambda: setattr(session_state, "page", "Building Components")) else: st.button("Continue to Building Components", disabled=True) # Display saved session state data (if any) if "climate_data" in session_state and session_state["climate_data"]: st.subheader("Saved Climate Data") st.json(session_state["climate_data"]) # Display as JSON for clarity def display_design_conditions(self, location: ClimateLocation): """Display a table of design conditions including additional parameters for HVAC calculations.""" st.subheader("Design Conditions for HVAC Calculations") design_data = pd.DataFrame({ "Parameter": [ "Latitude", "Longitude", "Elevation (m)", "Climate Zone", "Heating Degree Days (base 18°C)", "Cooling Degree Days (base 18°C)", "Winter Design Temperature (99.6%)", "Summer Design Dry-Bulb Temp (0.4%)", "Summer Design Wet-Bulb Temp (0.4%)", "Summer Daily Temperature Range", "Wind Speed (m/s)", "Atmospheric Pressure (Pa)" ], "Value": [ f"{location.latitude}°", f"{location.longitude}°", f"{location.elevation} m", location.climate_zone, f"{location.heating_degree_days} HDD", f"{location.cooling_degree_days} CDD", f"{location.winter_design_temp} °C", f"{location.summer_design_temp_db} °C", f"{location.summer_design_temp_wb} °C", f"{location.summer_daily_range} °C", f"{location.wind_speed} m/s", f"{location.pressure} Pa" ] }) month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] monthly_temp_data = pd.DataFrame({ "Parameter": [f"{month} Avg Temp" for month in month_names], "Value": [f"{location.monthly_temps[month]} °C" for month in month_names] }) monthly_humidity_data = pd.DataFrame({ "Parameter": [f"{month} Avg Humidity" for month in month_names], "Value": [f"{location.monthly_humidity[month]} %" for month in month_names] }) full_design_data = pd.concat([design_data, monthly_temp_data, monthly_humidity_data], ignore_index=True) st.table(full_design_data) @staticmethod def assign_climate_zone(hdd: float, cdd: float, avg_humidity: float) -> str: """Assign ASHRAE 169 climate zone based on HDD, CDD, and humidity.""" if cdd > 10000: return "0A" if avg_humidity > 60 else "0B" elif cdd > 5000: return "1A" if avg_humidity > 60 else "1B" elif cdd > 2500: return "2A" if avg_humidity > 60 else "2B" elif hdd < 2000 and cdd > 1000: return "3A" if avg_humidity > 60 else "3B" if avg_humidity < 40 else "3C" elif hdd < 3000: return "4A" if avg_humidity > 60 else "4B" if avg_humidity < 40 else "4C" elif hdd < 4000: return "5A" if avg_humidity > 60 else "5B" if avg_humidity < 40 else "5C" elif hdd < 5000: return "6A" if avg_humidity > 60 else "6B" elif hdd < 7000: return "7" else: return "8" @staticmethod def visualize_data(location: ClimateLocation, epw_data: Optional[pd.DataFrame] = None): """Visualize monthly temperature and humidity data.""" st.subheader("Monthly Climate Data Visualization") months = list(range(1, 13)) month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] temps_avg = [location.monthly_temps[m] for m in month_names] humidity_avg = [location.monthly_humidity[m] for m in month_names] fig_temp = go.Figure() fig_temp.add_trace(go.Scatter( x=months, y=temps_avg, mode='lines+markers', name='Avg Temperature (°C)', line=dict(color='red'), marker=dict(size=8) )) if epw_data is not None: dry_bulb = epw_data[6].values month_col = epw_data[1].values temps_min = [] temps_max = [] for i in range(1, 13): month_mask = (month_col == i) temps_min.append(round(np.nanmin(dry_bulb[month_mask]), 1)) temps_max.append(round(np.nanmax(dry_bulb[month_mask]), 1)) fig_temp.add_trace(go.Scatter( x=months, y=temps_max, mode='lines', name='Max Temperature (°C)', line=dict(color='red', dash='dash'), opacity=0.5 )) fig_temp.add_trace(go.Scatter( x=months, y=temps_min, mode='lines', name='Min Temperature (°C)', line=dict(color='red', dash='dash'), opacity=0.5, fill='tonexty', fillcolor='rgba(255, 0, 0, 0.1)' )) fig_temp.update_layout( title='Monthly Temperatures', xaxis_title='Month', yaxis_title='Temperature (°C)', xaxis=dict(tickmode='array', tickvals=months, ticktext=month_names), legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01) ) st.plotly_chart(fig_temp, use_container_width=True) fig_hum = go.Figure() fig_hum.add_trace(go.Scatter( x=months, y=humidity_avg, mode='lines+markers', name='Avg Humidity (%)', line=dict(color='blue'), marker=dict(size=8) )) if epw_data is not None: humidity = epw_data[8].values month_col = epw_data[1].values humidity_min = [] humidity_max = [] for i in range(1, 13): month_mask = (month_col == i) humidity_min.append(round(np.nanmin(humidity[month_mask]), 1)) humidity_max.append(round(np.nanmax(humidity[month_mask]), 1)) fig_hum.add_trace(go.Scatter( x=months, y=humidity_max, mode='lines', name='Max Humidity (%)', line=dict(color='blue', dash='dash'), opacity=0.5 )) fig_hum.add_trace(go.Scatter( x=months, y=humidity_min, mode='lines', name='Min Humidity (%)', line=dict(color='blue', dash='dash'), opacity=0.5, fill='tonexty', fillcolor='rgba(0, 0, 255, 0.1)' )) fig_hum.update_layout( title='Monthly Relative Humidity', xaxis_title='Month', yaxis_title='Relative Humidity (%)', xaxis=dict(tickmode='array', tickvals=months, ticktext=month_names), legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01) ) st.plotly_chart(fig_hum, use_container_width=True) def export_to_json(self, file_path: str) -> None: """Export all climate data to a 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': """Load climate data from a JSON file.""" with open(file_path, 'r') as f: data = json.load(f) climate_data = cls() for loc_id, loc_dict in data.items(): location = ClimateLocation(**loc_dict) climate_data.add_location(location) return climate_data if __name__ == "__main__": climate_data = ClimateData() session_state = {"building_info": {"country": "Iceland", "city": "Reykjavik"}, "page": "Climate Data"} climate_data.display_climate_input(session_state)