Spaces:
Sleeping
Sleeping
| """ | |
| Enhanced Drapery module for HVAC Load Calculator with comprehensive CLTD implementation and SCL integration. | |
| This module provides classes and functions for handling drapery properties | |
| and calculating their effects on window heat transfer using detailed ASHRAE CLTD/SCL methods. | |
| Includes comprehensive CLTD tables for windows (SingleClear, DoubleTinted, LowE, Reflective) | |
| at multiple latitudes (24°N, 40°N, 48°N) and all orientations, as well as detailed | |
| climatic corrections and door CLTD calculations. | |
| Enhanced to map UI shading coefficients to drapery properties (openness, color, fullness) | |
| and apply conduction reduction (5-15%) based on openness per ASHRAE guidelines. | |
| """ | |
| from typing import Dict, Any, Optional, Tuple, List, Union | |
| from enum import Enum | |
| import math | |
| import pandas as pd | |
| from data.ashrae_tables import ASHRAETables | |
| class DraperyOpenness(Enum): | |
| """Enum for drapery openness classification.""" | |
| OPEN = "Open (>25%)" | |
| SEMI_OPEN = "Semi-open (7-25%)" | |
| CLOSED = "Closed (0-7%)" | |
| class DraperyColor(Enum): | |
| """Enum for drapery color/reflectance classification.""" | |
| DARK = "Dark (0-25%)" | |
| MEDIUM = "Medium (25-50%)" | |
| LIGHT = "Light (>50%)" | |
| class GlazingType(Enum): | |
| """Enum for glazing types.""" | |
| SINGLE_CLEAR = "Single Clear" | |
| SINGLE_TINTED = "Single Tinted" | |
| DOUBLE_CLEAR = "Double Clear" | |
| DOUBLE_TINTED = "Double Tinted" | |
| LOW_E = "Low-E" | |
| REFLECTIVE = "Reflective" | |
| class FrameType(Enum): | |
| """Enum for window frame types.""" | |
| ALUMINUM = "Aluminum without Thermal Break" | |
| ALUMINUM_THERMAL_BREAK = "Aluminum with Thermal Break" | |
| VINYL = "Vinyl/Fiberglass" | |
| WOOD = "Wood/Vinyl-Clad Wood" | |
| INSULATED = "Insulated" | |
| class SurfaceColor(Enum): | |
| """Enum for surface color classification.""" | |
| DARK = "Dark" | |
| MEDIUM = "Medium" | |
| LIGHT = "Light" | |
| class Latitude(Enum): | |
| """Enum for latitude ranges.""" | |
| LAT_24N = "24N" | |
| LAT_40N = "40N" | |
| LAT_48N = "48N" | |
| # U-Factors for various fenestration products (Table 9-1) in SI units (W/m²K) | |
| # Format: {(glazing_type, frame_type): u_factor} | |
| WINDOW_U_FACTORS = { | |
| # Single Clear Glass | |
| (GlazingType.SINGLE_CLEAR, FrameType.ALUMINUM): 7.22, | |
| (GlazingType.SINGLE_CLEAR, FrameType.ALUMINUM_THERMAL_BREAK): 6.14, | |
| (GlazingType.SINGLE_CLEAR, FrameType.VINYL): 5.11, | |
| (GlazingType.SINGLE_CLEAR, FrameType.WOOD): 5.06, | |
| (GlazingType.SINGLE_CLEAR, FrameType.INSULATED): 4.60, | |
| # Single Tinted Glass | |
| (GlazingType.SINGLE_TINTED, FrameType.ALUMINUM): 7.22, | |
| (GlazingType.SINGLE_TINTED, FrameType.ALUMINUM_THERMAL_BREAK): 6.14, | |
| (GlazingType.SINGLE_TINTED, FrameType.VINYL): 5.11, | |
| (GlazingType.SINGLE_TINTED, FrameType.WOOD): 5.06, | |
| (GlazingType.SINGLE_TINTED, FrameType.INSULATED): 4.60, | |
| # Double Clear Glass | |
| (GlazingType.DOUBLE_CLEAR, FrameType.ALUMINUM): 4.60, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.ALUMINUM_THERMAL_BREAK): 3.41, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.VINYL): 3.01, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.WOOD): 2.90, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.INSULATED): 2.50, | |
| # Double Tinted Glass | |
| (GlazingType.DOUBLE_TINTED, FrameType.ALUMINUM): 4.60, | |
| (GlazingType.DOUBLE_TINTED, FrameType.ALUMINUM_THERMAL_BREAK): 3.41, | |
| (GlazingType.DOUBLE_TINTED, FrameType.VINYL): 3.01, | |
| (GlazingType.DOUBLE_TINTED, FrameType.WOOD): 2.90, | |
| (GlazingType.DOUBLE_TINTED, FrameType.INSULATED): 2.50, | |
| # Low-E Glass | |
| (GlazingType.LOW_E, FrameType.ALUMINUM): 3.41, | |
| (GlazingType.LOW_E, FrameType.ALUMINUM_THERMAL_BREAK): 2.67, | |
| (GlazingType.LOW_E, FrameType.VINYL): 2.33, | |
| (GlazingType.LOW_E, FrameType.WOOD): 2.22, | |
| (GlazingType.LOW_E, FrameType.INSULATED): 1.87, | |
| # Reflective Glass | |
| (GlazingType.REFLECTIVE, FrameType.ALUMINUM): 3.41, | |
| (GlazingType.REFLECTIVE, FrameType.ALUMINUM_THERMAL_BREAK): 2.67, | |
| (GlazingType.REFLECTIVE, FrameType.VINYL): 2.33, | |
| (GlazingType.REFLECTIVE, FrameType.WOOD): 2.22, | |
| (GlazingType.REFLECTIVE, FrameType.INSULATED): 1.87, | |
| } | |
| # SHGC values for various glazing types (Table 9-3) | |
| # Format: {(glazing_type, frame_type): shgc} | |
| WINDOW_SHGC = { | |
| # Single Clear Glass | |
| (GlazingType.SINGLE_CLEAR, FrameType.ALUMINUM): 0.78, | |
| (GlazingType.SINGLE_CLEAR, FrameType.ALUMINUM_THERMAL_BREAK): 0.75, | |
| (GlazingType.SINGLE_CLEAR, FrameType.VINYL): 0.67, | |
| (GlazingType.SINGLE_CLEAR, FrameType.WOOD): 0.65, | |
| (GlazingType.SINGLE_CLEAR, FrameType.INSULATED): 0.63, | |
| # Single Tinted Glass | |
| (GlazingType.SINGLE_TINTED, FrameType.ALUMINUM): 0.65, | |
| (GlazingType.SINGLE_TINTED, FrameType.ALUMINUM_THERMAL_BREAK): 0.62, | |
| (GlazingType.SINGLE_TINTED, FrameType.VINYL): 0.55, | |
| (GlazingType.SINGLE_TINTED, FrameType.WOOD): 0.53, | |
| (GlazingType.SINGLE_TINTED, FrameType.INSULATED): 0.52, | |
| # Double Clear Glass | |
| (GlazingType.DOUBLE_CLEAR, FrameType.ALUMINUM): 0.65, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.ALUMINUM_THERMAL_BREAK): 0.61, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.VINYL): 0.53, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.WOOD): 0.51, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.INSULATED): 0.49, | |
| # Double Tinted Glass | |
| (GlazingType.DOUBLE_TINTED, FrameType.ALUMINUM): 0.53, | |
| (GlazingType.DOUBLE_TINTED, FrameType.ALUMINUM_THERMAL_BREAK): 0.50, | |
| (GlazingType.DOUBLE_TINTED, FrameType.VINYL): 0.42, | |
| (GlazingType.DOUBLE_TINTED, FrameType.WOOD): 0.40, | |
| (GlazingType.DOUBLE_TINTED, FrameType.INSULATED): 0.38, | |
| # Low-E Glass | |
| (GlazingType.LOW_E, FrameType.ALUMINUM): 0.46, | |
| (GlazingType.LOW_E, FrameType.ALUMINUM_THERMAL_BREAK): 0.44, | |
| (GlazingType.LOW_E, FrameType.VINYL): 0.38, | |
| (GlazingType.LOW_E, FrameType.WOOD): 0.36, | |
| (GlazingType.LOW_E, FrameType.INSULATED): 0.34, | |
| # Reflective Glass | |
| (GlazingType.REFLECTIVE, FrameType.ALUMINUM): 0.33, | |
| (GlazingType.REFLECTIVE, FrameType.ALUMINUM_THERMAL_BREAK): 0.31, | |
| (GlazingType.REFLECTIVE, FrameType.VINYL): 0.27, | |
| (GlazingType.REFLECTIVE, FrameType.WOOD): 0.25, | |
| (GlazingType.REFLECTIVE, FrameType.INSULATED): 0.24, | |
| } | |
| # Door U-Factors in SI units (W/m²K) | |
| # Format: {door_type: u_factor} | |
| DOOR_U_FACTORS = { | |
| "WoodSolid": 3.35, # Approximated from Group D walls | |
| "MetalInsulated": 2.61, # Approximated from Group F walls | |
| "GlassDoor": 7.22, # Same as single clear glass with aluminum frame | |
| "InsulatedMetal": 2.15, # Insulated metal door | |
| "InsulatedWood": 1.93, # Insulated wood door | |
| "Custom": 3.00, # Default for custom doors | |
| } | |
| # Skylight U-Factors in SI units (W/m²K) | |
| # Format: {(glazing_type, frame_type): u_factor} | |
| SKYLIGHT_U_FACTORS = { | |
| # Single Clear Glass | |
| (GlazingType.SINGLE_CLEAR, FrameType.ALUMINUM): 7.79, | |
| (GlazingType.SINGLE_CLEAR, FrameType.ALUMINUM_THERMAL_BREAK): 6.71, | |
| (GlazingType.SINGLE_CLEAR, FrameType.VINYL): 5.68, | |
| (GlazingType.SINGLE_CLEAR, FrameType.WOOD): 5.63, | |
| (GlazingType.SINGLE_CLEAR, FrameType.INSULATED): 5.17, | |
| # Single Tinted Glass | |
| (GlazingType.SINGLE_TINTED, FrameType.ALUMINUM): 7.79, | |
| (GlazingType.SINGLE_TINTED, FrameType.ALUMINUM_THERMAL_BREAK): 6.71, | |
| (GlazingType.SINGLE_TINTED, FrameType.VINYL): 5.68, | |
| (GlazingType.SINGLE_TINTED, FrameType.WOOD): 5.63, | |
| (GlazingType.SINGLE_TINTED, FrameType.INSULATED): 5.17, | |
| # Double Clear Glass | |
| (GlazingType.DOUBLE_CLEAR, FrameType.ALUMINUM): 5.17, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.ALUMINUM_THERMAL_BREAK): 3.98, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.VINYL): 3.58, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.WOOD): 3.47, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.INSULATED): 3.07, | |
| # Double Tinted Glass | |
| (GlazingType.DOUBLE_TINTED, FrameType.ALUMINUM): 5.17, | |
| (GlazingType.DOUBLE_TINTED, FrameType.ALUMINUM_THERMAL_BREAK): 3.98, | |
| (GlazingType.DOUBLE_TINTED, FrameType.VINYL): 3.58, | |
| (GlazingType.DOUBLE_TINTED, FrameType.WOOD): 3.47, | |
| (GlazingType.DOUBLE_TINTED, FrameType.INSULATED): 3.07, | |
| # Low-E Glass | |
| (GlazingType.LOW_E, FrameType.ALUMINUM): 3.98, | |
| (GlazingType.LOW_E, FrameType.ALUMINUM_THERMAL_BREAK): 3.24, | |
| (GlazingType.LOW_E, FrameType.VINYL): 2.90, | |
| (GlazingType.LOW_E, FrameType.WOOD): 2.78, | |
| (GlazingType.LOW_E, FrameType.INSULATED): 2.44, | |
| # Reflective Glass | |
| (GlazingType.REFLECTIVE, FrameType.ALUMINUM): 3.98, | |
| (GlazingType.REFLECTIVE, FrameType.ALUMINUM_THERMAL_BREAK): 3.24, | |
| (GlazingType.REFLECTIVE, FrameType.VINYL): 2.90, | |
| (GlazingType.REFLECTIVE, FrameType.WOOD): 2.78, | |
| (GlazingType.REFLECTIVE, FrameType.INSULATED): 2.44, | |
| } | |
| # Skylight SHGC values | |
| # Format: {(glazing_type, frame_type): shgc} | |
| SKYLIGHT_SHGC = { | |
| # Single Clear Glass | |
| (GlazingType.SINGLE_CLEAR, FrameType.ALUMINUM): 0.83, | |
| (GlazingType.SINGLE_CLEAR, FrameType.ALUMINUM_THERMAL_BREAK): 0.80, | |
| (GlazingType.SINGLE_CLEAR, FrameType.VINYL): 0.72, | |
| (GlazingType.SINGLE_CLEAR, FrameType.WOOD): 0.70, | |
| (GlazingType.SINGLE_CLEAR, FrameType.INSULATED): 0.68, | |
| # Single Tinted Glass | |
| (GlazingType.SINGLE_TINTED, FrameType.ALUMINUM): 0.70, | |
| (GlazingType.SINGLE_TINTED, FrameType.ALUMINUM_THERMAL_BREAK): 0.67, | |
| (GlazingType.SINGLE_TINTED, FrameType.VINYL): 0.60, | |
| (GlazingType.SINGLE_TINTED, FrameType.WOOD): 0.58, | |
| (GlazingType.SINGLE_TINTED, FrameType.INSULATED): 0.57, | |
| # Double Clear Glass | |
| (GlazingType.DOUBLE_CLEAR, FrameType.ALUMINUM): 0.70, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.ALUMINUM_THERMAL_BREAK): 0.66, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.VINYL): 0.58, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.WOOD): 0.56, | |
| (GlazingType.DOUBLE_CLEAR, FrameType.INSULATED): 0.54, | |
| # Double Tinted Glass | |
| (GlazingType.DOUBLE_TINTED, FrameType.ALUMINUM): 0.58, | |
| (GlazingType.DOUBLE_TINTED, FrameType.ALUMINUM_THERMAL_BREAK): 0.55, | |
| (GlazingType.DOUBLE_TINTED, FrameType.VINYL): 0.47, | |
| (GlazingType.DOUBLE_TINTED, FrameType.WOOD): 0.45, | |
| (GlazingType.DOUBLE_TINTED, FrameType.INSULATED): 0.43, | |
| # Low-E Glass | |
| (GlazingType.LOW_E, FrameType.ALUMINUM): 0.51, | |
| (GlazingType.LOW_E, FrameType.ALUMINUM_THERMAL_BREAK): 0.49, | |
| (GlazingType.LOW_E, FrameType.VINYL): 0.43, | |
| (GlazingType.LOW_E, FrameType.WOOD): 0.41, | |
| (GlazingType.LOW_E, FrameType.INSULATED): 0.39, | |
| # Reflective Glass | |
| (GlazingType.REFLECTIVE, FrameType.ALUMINUM): 0.38, | |
| (GlazingType.REFLECTIVE, FrameType.ALUMINUM_THERMAL_BREAK): 0.36, | |
| (GlazingType.REFLECTIVE, FrameType.VINYL): 0.32, | |
| (GlazingType.REFLECTIVE, FrameType.WOOD): 0.30, | |
| (GlazingType.REFLECTIVE, FrameType.INSULATED): 0.29, | |
| } | |
| class Drapery: | |
| """Class for handling drapery properties and effects on window heat transfer.""" | |
| def __init__(self, openness: str = "Semi-Open", color: str = "Medium", | |
| fullness: float = 1.5, enabled: bool = True, shading_device: str = "Drapes"): | |
| """ | |
| Initialize drapery properties with UI-compatible inputs. | |
| Args: | |
| openness: Drapery openness category ("Closed", "Semi-Open", "Open") | |
| color: Drapery color category ("Light", "Medium", "Dark") | |
| fullness: Fullness factor (1.0 for flat, 1.0-2.0 for pleated) | |
| enabled: Whether drapery is enabled | |
| shading_device: Type of shading device ("Venetian Blinds", "Drapes", etc.) | |
| """ | |
| self.openness = openness | |
| self.color = color | |
| self.fullness = fullness | |
| self.enabled = enabled | |
| self.shading_device = shading_device | |
| def get_openness_category(self) -> str: | |
| """Get openness category as string.""" | |
| return self.openness | |
| def get_color_category(self) -> str: | |
| """Get color category as string.""" | |
| return self.color | |
| def get_shading_coefficient(self, shgc: float = 0.5) -> float: | |
| """ | |
| Calculate shading coefficient for drapery based on UI inputs. | |
| Args: | |
| shgc: Solar Heat Gain Coefficient of window (default 0.5) | |
| Returns: | |
| Shading coefficient (0.0-1.0) | |
| """ | |
| if not self.enabled: | |
| return 1.0 | |
| # Mapping of UI shading devices to properties | |
| mapping = { | |
| ("Venetian Blinds", "Light"): {"openness": "Semi-Open", "color": "Light", "fullness": 1.0, "sc": 0.6}, | |
| ("Venetian Blinds", "Medium"): {"openness": "Semi-Open", "color": "Medium", "fullness": 1.0, "sc": 0.65}, | |
| ("Venetian Blinds", "Dark"): {"openness": "Semi-Open", "color": "Dark", "fullness": 1.0, "sc": 0.7}, | |
| ("Drapes", "Light"): {"openness": "Closed", "color": "Light", "fullness": 1.5, "sc": 0.59}, | |
| ("Drapes", "Medium"): {"openness": "Closed", "color": "Medium", "fullness": 1.5, "sc": 0.74}, | |
| ("Drapes", "Dark"): {"openness": "Closed", "color": "Dark", "fullness": 1.5, "sc": 0.87}, | |
| ("Roller Shades", "Light"): {"openness": "Open", "color": "Light", "fullness": 1.0, "sc": 0.8}, | |
| ("Roller Shades", "Medium"): {"openness": "Open", "color": "Medium", "fullness": 1.0, "sc": 0.88}, | |
| ("Roller Shades", "Dark"): {"openness": "Open", "color": "Dark", "fullness": 1.0, "sc": 0.94}, | |
| } | |
| # Get shading coefficient from mapping or default to table-based value | |
| properties = mapping.get((self.shading_device, self.color), { | |
| "openness": self.openness, | |
| "color": self.color, | |
| "fullness": self.fullness, | |
| "sc": 0.85 | |
| }) | |
| base_sc = properties["sc"] | |
| # Adjust for fullness if different from mapped value | |
| if self.fullness != properties["fullness"]: | |
| fullness_factor = 1.0 - 0.05 * (self.fullness - 1.0) | |
| base_sc *= fullness_factor | |
| return base_sc | |
| def get_conduction_reduction(self) -> float: | |
| """ | |
| Get conduction reduction factor based on openness. | |
| Returns: | |
| Reduction factor (0.05-0.15) | |
| """ | |
| reductions = { | |
| "Closed": 0.15, # 15% reduction | |
| "Semi-Open": 0.10, # 10% reduction | |
| "Open": 0.05 # 5% reduction | |
| } | |
| return reductions.get(self.openness, 0.10) | |
| class CLTDCalculator: | |
| """Class for calculating Cooling Load Temperature Difference (CLTD) values.""" | |
| def __init__(self, indoor_temp: float = 25.6, outdoor_max_temp: float = 35.0, | |
| outdoor_daily_range: float = 11.7, latitude: Latitude = Latitude.LAT_40N, | |
| month: int = 7): | |
| """ | |
| Initialize CLTD calculator. | |
| Args: | |
| indoor_temp: Indoor design temperature (°C) | |
| outdoor_max_temp: Outdoor maximum temperature (°C) | |
| outdoor_daily_range: Daily temperature range (°C) | |
| latitude: Latitude category (24°N, 40°N, 48°N) | |
| month: Month (1-12) | |
| """ | |
| self.indoor_temp = indoor_temp # °C | |
| self.outdoor_max_temp = outdoor_max_temp # °C | |
| self.outdoor_daily_range = outdoor_daily_range # °C | |
| self.latitude = latitude | |
| self.month = month | |
| self.outdoor_avg_temp = outdoor_max_temp - outdoor_daily_range / 2 | |
| # Initialize ASHRAE tables for SCL data | |
| self.ashrae_tables = ASHRAETables() | |
| # Load CLTD tables | |
| self.cltd_window_tables = self._load_cltd_window_table() | |
| self.cltd_door_tables = self._load_cltd_door_table() | |
| self.cltd_skylight_tables = self._load_cltd_skylight_table() | |
| # Load correction factors | |
| self.latitude_corrections = self._load_latitude_correction() | |
| self.month_corrections = self._load_month_correction() | |
| def _load_cltd_window_table(self) -> Dict[str, Dict[str, pd.DataFrame]]: | |
| """ | |
| Load CLTD tables for windows at multiple latitudes (July). | |
| Returns: | |
| Dictionary of DataFrames with CLTD values indexed by hour (0-23) | |
| and columns for orientations (N, NE, E, SE, S, SW, W, NW) | |
| """ | |
| hours = list(range(24)) | |
| # Comprehensive window CLTD data for different latitudes, glazing types, and orientations | |
| window_cltd_data = { | |
| "24N": { | |
| "SingleClear": { | |
| "N": [3, 2, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3], | |
| "NE": [3, 2, 1, 1, 1, 3, 6, 9, 11, 10, 9, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3], | |
| "E": [3, 2, 1, 1, 1, 3, 7, 11, 13, 13, 11, 9, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3], | |
| "SE": [3, 2, 1, 1, 1, 2, 4, 6, 8, 10, 11, 11, 10, 9, 7, 5, 4, 3, 3, 3, 3, 3, 3, 3], | |
| "S": [3, 2, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3], | |
| "SW": [3, 2, 1, 1, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 11, 10, 8, 6, 5, 4, 3, 3, 3, 3], | |
| "W": [3, 2, 1, 1, 1, 2, 3, 4, 6, 8, 10, 11, 11, 11, 10, 9, 8, 7, 6, 5, 4, 3, 3, 3], | |
| "NW": [3, 2, 1, 1, 1, 2, 3, 5, 7, 9, 10, 10, 9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3] | |
| }, | |
| "DoubleTinted": { | |
| "N": [2, 1, 0, 0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2, 2], | |
| "NE": [2, 1, 0, 0, 0, 2, 5, 7, 9, 8, 7, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], | |
| "E": [2, 1, 0, 0, 0, 2, 5, 9, 10, 10, 9, 7, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2], | |
| "SE": [2, 1, 0, 0, 0, 1, 3, 5, 6, 8, 9, 9, 8, 7, 5, 3, 2, 2, 2, 2, 2, 2, 2, 2], | |
| "S": [2, 1, 0, 0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2], | |
| "SW": [2, 1, 0, 0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 9, 8, 6, 4, 3, 2, 2, 2, 2, 2], | |
| "W": [2, 1, 0, 0, 0, 1, 2, 3, 5, 6, 8, 9, 9, 9, 8, 7, 6, 5, 4, 3, 2, 2, 2, 2], | |
| "NW": [2, 1, 0, 0, 0, 1, 2, 4, 5, 7, 8, 8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] | |
| }, | |
| "LowE": { | |
| "N": [1, 0, 0, 0, 0, 0, 1, 2, 3, 4, 4, 5, 5, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1], | |
| "NE": [1, 0, 0, 0, 0, 1, 4, 6, 8, 7, 6, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "E": [1, 0, 0, 0, 0, 1, 4, 8, 9, 9, 8, 6, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "SE": [1, 0, 0, 0, 0, 0, 2, 4, 5, 7, 8, 8, 7, 6, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1], | |
| "S": [1, 0, 0, 0, 0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 5, 4, 3, 2, 2, 1, 1, 1, 1, 1], | |
| "SW": [1, 0, 0, 0, 0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 8, 7, 5, 3, 2, 2, 1, 1, 1, 1], | |
| "W": [1, 0, 0, 0, 0, 0, 1, 2, 4, 5, 7, 8, 8, 8, 7, 6, 5, 4, 3, 2, 2, 1, 1, 1], | |
| "NW": [1, 0, 0, 0, 0, 0, 1, 3, 4, 6, 7, 7, 6, 5, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1] | |
| }, | |
| "Reflective": { | |
| "N": [0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "NE": [0, 0, 0, 0, 0, 1, 3, 5, 6, 5, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "E": [0, 0, 0, 0, 0, 1, 3, 6, 7, 7, 6, 5, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "SE": [0, 0, 0, 0, 0, 0, 1, 3, 4, 5, 6, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "S": [0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1], | |
| "SW": [0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 5, 5, 6, 6, 5, 4, 2, 2, 1, 1, 1, 1, 1], | |
| "W": [0, 0, 0, 0, 0, 0, 1, 1, 3, 4, 5, 6, 6, 6, 5, 4, 4, 3, 2, 2, 1, 1, 1, 1], | |
| "NW": [0, 0, 0, 0, 0, 0, 1, 2, 3, 5, 5, 5, 4, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1] | |
| } | |
| }, | |
| "40N": { | |
| "SingleClear": { | |
| "N": [2, 1, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2], | |
| "NE": [2, 1, 0, 0, 0, 2, 5, 8, 10, 9, 8, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2], | |
| "E": [2, 1, 0, 0, 0, 2, 6, 10, 12, 12, 10, 8, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2, 2], | |
| "SE": [2, 1, 0, 0, 0, 1, 3, 5, 7, 9, 10, 10, 9, 8, 6, 4, 3, 2, 2, 2, 2, 2, 2, 2], | |
| "S": [2, 1, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2], | |
| "SW": [2, 1, 0, 0, 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 10, 9, 7, 5, 4, 3, 2, 2, 2, 2], | |
| "W": [2, 1, 0, 0, 0, 1, 2, 3, 5, 7, 9, 10, 10, 10, 9, 8, 7, 6, 5, 4, 3, 2, 2, 2], | |
| "NW": [2, 1, 0, 0, 0, 1, 2, 4, 6, 8, 9, 9, 8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2] | |
| }, | |
| "DoubleTinted": { | |
| "N": [1, 0, 0, 0, 0, 0, 1, 2, 3, 4, 4, 5, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "NE": [1, 0, 0, 0, 0, 1, 4, 6, 8, 7, 6, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "E": [1, 0, 0, 0, 0, 1, 4, 8, 9, 9, 8, 6, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "SE": [1, 0, 0, 0, 0, 0, 2, 4, 5, 7, 8, 8, 7, 6, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "S": [1, 0, 0, 0, 0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1], | |
| "SW": [1, 0, 0, 0, 0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 8, 7, 5, 3, 2, 1, 1, 1, 1, 1], | |
| "W": [1, 0, 0, 0, 0, 0, 1, 2, 4, 5, 7, 8, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1], | |
| "NW": [1, 0, 0, 0, 0, 0, 1, 3, 4, 6, 7, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1] | |
| }, | |
| "LowE": { | |
| "N": [0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0], | |
| "NE": [0, 0, 0, 0, 0, 1, 3, 5, 7, 6, 5, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "E": [0, 0, 0, 0, 0, 1, 3, 7, 8, 8, 7, 5, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "SE": [0, 0, 0, 0, 0, 0, 1, 3, 4, 6, 7, 7, 6, 5, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0], | |
| "S": [0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 4, 5, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0], | |
| "SW": [0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 6, 4, 2, 1, 1, 0, 0, 0, 0], | |
| "W": [0, 0, 0, 0, 0, 0, 0, 1, 3, 4, 6, 7, 7, 7, 6, 5, 4, 3, 2, 1, 1, 0, 0, 0], | |
| "NW": [0, 0, 0, 0, 0, 0, 0, 2, 3, 5, 6, 6, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0] | |
| }, | |
| "Reflective": { | |
| "N": [0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "NE": [0, 0, 0, 0, 0, 0, 2, 4, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "E": [0, 0, 0, 0, 0, 0, 2, 5, 6, 6, 5, 4, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "SE": [0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 5, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "S": [0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 4, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0], | |
| "SW": [0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 4, 4, 5, 5, 4, 3, 1, 1, 0, 0, 0, 0, 0], | |
| "W": [0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 5, 5, 5, 4, 3, 3, 2, 1, 1, 0, 0, 0, 0], | |
| "NW": [0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 4, 4, 3, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0] | |
| } | |
| }, | |
| "48N": { | |
| "SingleClear": { | |
| "N": [1, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1], | |
| "NE": [1, 0, 0, 0, 0, 1, 4, 7, 9, 8, 7, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "E": [1, 0, 0, 0, 0, 1, 5, 9, 11, 11, 9, 7, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1], | |
| "SE": [1, 0, 0, 0, 0, 0, 2, 4, 6, 8, 9, 9, 8, 7, 5, 3, 2, 1, 1, 1, 1, 1, 1, 1], | |
| "S": [1, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1], | |
| "SW": [1, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 7, 8, 9, 9, 8, 6, 4, 3, 2, 1, 1, 1, 1], | |
| "W": [1, 0, 0, 0, 0, 0, 1, 2, 4, 6, 8, 9, 9, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1], | |
| "NW": [1, 0, 0, 0, 0, 0, 1, 3, 5, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1] | |
| }, | |
| "DoubleTinted": { | |
| "N": [0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 4, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "NE": [0, 0, 0, 0, 0, 0, 3, 5, 7, 6, 5, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "E": [0, 0, 0, 0, 0, 0, 3, 7, 8, 8, 7, 5, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "SE": [0, 0, 0, 0, 0, 0, 1, 3, 4, 6, 7, 7, 6, 5, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "S": [0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 4, 5, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0], | |
| "SW": [0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 6, 4, 2, 1, 0, 0, 0, 0, 0], | |
| "W": [0, 0, 0, 0, 0, 0, 0, 1, 3, 4, 6, 7, 7, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0], | |
| "NW": [0, 0, 0, 0, 0, 0, 0, 2, 3, 5, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0] | |
| }, | |
| "LowE": { | |
| "N": [0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "NE": [0, 0, 0, 0, 0, 0, 2, 4, 6, 5, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "E": [0, 0, 0, 0, 0, 0, 2, 6, 7, 7, 6, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "SE": [0, 0, 0, 0, 0, 0, 0, 2, 3, 5, 6, 6, 5, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "S": [0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 4, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0], | |
| "SW": [0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 4, 5, 6, 6, 5, 3, 1, 0, 0, 0, 0, 0, 0], | |
| "W": [0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 5, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0], | |
| "NW": [0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 5, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0] | |
| }, | |
| "Reflective": { | |
| "N": [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "NE": [0, 0, 0, 0, 0, 0, 1, 3, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "E": [0, 0, 0, 0, 0, 0, 1, 4, 5, 5, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "SE": [0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "S": [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0], | |
| "SW": [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 3, 4, 4, 3, 2, 0, 0, 0, 0, 0, 0, 0], | |
| "W": [0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 4, 4, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0], | |
| "NW": [0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] | |
| } | |
| } | |
| } | |
| # Convert to DataFrames | |
| window_cltd_tables = {} | |
| for latitude, glazing_data in window_cltd_data.items(): | |
| window_cltd_tables[latitude] = {} | |
| for glazing_type, orientation_data in glazing_data.items(): | |
| window_cltd_tables[latitude][glazing_type] = pd.DataFrame(orientation_data, index=hours) | |
| return window_cltd_tables | |
| def _load_cltd_door_table(self) -> Dict[str, pd.DataFrame]: | |
| """ | |
| Load CLTD tables for doors. | |
| Returns: | |
| Dictionary of DataFrames with CLTD values indexed by hour (0-23) | |
| """ | |
| hours = list(range(24)) | |
| # Door CLTD data approximated from wall groups | |
| door_cltd_data = { | |
| "WoodSolid": { # Approximated from Group D walls | |
| 'N': [4, 3, 2, 1, 0, 1, 8, 16, 20, 21, 22, 25, 29, 31, 33, 35, 37, 37, 30, 20, 14, 10, 8, 6], | |
| 'NE': [4, 3, 2, 1, 0, 3, 20, 42, 54, 56, 51, 42, 35, 33, 33, 33, 33, 31, 27, 21, 16, 13, 10, 8], | |
| 'E': [4, 3, 2, 1, 0, 3, 21, 47, 62, 66, 62, 51, 39, 35, 34, 33, 35, 35, 32, 27, 22, 16, 13, 10], | |
| 'SE': [4, 3, 2, 1, 0, 1, 11, 28, 41, 47, 48, 45, 38, 35, 34, 33, 35, 35, 30, 27, 21, 16, 13, 10], | |
| 'S': [4, 3, 2, 1, 0, 0, 2, 6, 11, 15, 21, 27, 32, 34, 34, 33, 35, 35, 30, 26, 21, 16, 12, 10], | |
| 'SW': [4, 3, 4, 5, 6, 6, 4, 6, 11, 16, 20, 25, 30, 45, 62, 76, 33, 35, 30, 26, 21, 23, 15, 11], | |
| 'W': [5, 3, 5, 5, 6, 4, 6, 11, 16, 20, 25, 30, 45, 62, 76, 33, 35, 30, 26, 21, 23, 15, 11, 8], | |
| 'NW': [5, 3, 4, 5, 5, 6, 4, 6, 11, 16, 20, 25, 30, 45, 62, 76, 33, 35, 30, 26, 21, 23, 15, 11] | |
| }, | |
| "MetalInsulated": { # Approximated from Group F walls | |
| 'N': [10, 8, 6, 4, 2, 1, 1, 2, 4, 6, 9, 11, 13, 15, 18, 20, 22, 24, 26, 26, 24, 21, 19, 15], | |
| 'NE': [10, 8, 6, 4, 2, 2, 2, 5, 11, 19, 25, 30, 32, 32, 31, 31, 31, 32, 30, 28, 26, 23, 20, 17], | |
| 'E': [11, 8, 6, 4, 2, 3, 2, 5, 12, 21, 30, 35, 38, 38, 38, 38, 38, 30, 30, 28, 25, 21, 18, 17], | |
| 'SE': [10, 7, 5, 3, 2, 2, 1, 3, 7, 13, 19, 24, 27, 29, 29, 29, 29, 29, 27, 25, 23, 20, 17, 15], | |
| 'S': [8, 6, 4, 3, 1, 2, 1, 0, 0, 2, 4, 6, 10, 13, 15, 19, 21, 22, 22, 22, 19, 17, 15, 13], | |
| 'SW': [15, 12, 9, 6, 4, 3, 2, 2, 2, 3, 4, 7, 10, 13, 15, 19, 25, 31, 32, 30, 40, 39, 35, 30], | |
| 'W': [20, 16, 12, 9, 6, 4, 3, 3, 3, 3, 5, 7, 10, 13, 15, 19, 27, 36, 34, 30, 50, 40, 40, 40], | |
| 'NW': [18, 14, 11, 8, 5, 4, 3, 2, 2, 3, 5, 7, 10, 13, 15, 19, 27, 36, 34, 30, 40, 40, 40, 40] | |
| }, | |
| "GlassDoor": { # Same as single clear glass | |
| 'N': [3, 2, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3], | |
| 'NE': [3, 2, 1, 1, 1, 3, 6, 9, 11, 10, 9, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3], | |
| 'E': [3, 2, 1, 1, 1, 3, 7, 11, 13, 13, 11, 9, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3], | |
| 'SE': [3, 2, 1, 1, 1, 2, 4, 6, 8, 10, 11, 11, 10, 9, 7, 5, 4, 3, 3, 3, 3, 3, 3, 3], | |
| 'S': [3, 2, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3], | |
| 'SW': [3, 2, 1, 1, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 11, 10, 8, 6, 5, 4, 3, 3, 3, 3], | |
| 'W': [3, 2, 1, 1, 1, 2, 3, 4, 6, 8, 10, 11, 11, 11, 10, 9, 8, 7, 6, 5, 4, 3, 3, 3], | |
| 'NW': [3, 2, 1, 1, 1, 2, 3, 5, 7, 9, 10, 10, 9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3] | |
| }, | |
| "InsulatedMetal": { # Enhanced insulated metal door | |
| 'N': [8, 6, 4, 2, 0, 0, 0, 1, 3, 5, 7, 9, 11, 13, 16, 18, 20, 22, 24, 24, 22, 19, 17, 13], | |
| 'NE': [8, 6, 4, 2, 0, 1, 1, 4, 10, 18, 24, 29, 31, 31, 30, 30, 30, 31, 29, 27, 25, 22, 19, 16], | |
| 'E': [9, 6, 4, 2, 0, 2, 1, 4, 11, 20, 29, 34, 37, 37, 37, 37, 37, 29, 29, 27, 24, 20, 17, 16], | |
| 'SE': [8, 5, 3, 1, 0, 1, 0, 2, 6, 12, 18, 23, 26, 28, 28, 28, 28, 28, 26, 24, 22, 19, 16, 14], | |
| 'S': [6, 4, 2, 1, -1, 1, 0, 0, 0, 1, 3, 5, 9, 12, 14, 18, 20, 21, 21, 21, 18, 16, 14, 12], | |
| 'SW': [13, 10, 7, 4, 2, 2, 1, 1, 1, 2, 3, 6, 9, 12, 14, 18, 24, 30, 31, 29, 39, 38, 34, 29], | |
| 'W': [18, 14, 10, 7, 4, 3, 2, 2, 2, 2, 4, 6, 9, 12, 14, 18, 26, 35, 33, 29, 49, 39, 39, 39], | |
| 'NW': [16, 12, 9, 6, 3, 3, 2, 1, 1, 2, 4, 6, 9, 12, 14, 18, 26, 35, 33, 29, 39, 39, 39, 39] | |
| }, | |
| "InsulatedWood": { # Enhanced insulated wood door | |
| 'N': [3, 2, 1, 0, -1, 0, 7, 15, 19, 20, 21, 24, 28, 30, 32, 34, 36, 36, 29, 19, 13, 9, 7, 5], | |
| 'NE': [3, 2, 1, 0, -1, 2, 19, 41, 53, 55, 50, 41, 34, 32, 32, 32, 32, 30, 26, 20, 15, 12, 9, 7], | |
| 'E': [3, 2, 1, 0, -1, 2, 20, 46, 61, 65, 61, 50, 38, 34, 33, 32, 34, 34, 31, 26, 21, 15, 12, 9], | |
| 'SE': [3, 2, 1, 0, -1, 0, 10, 27, 40, 46, 47, 44, 37, 34, 33, 32, 34, 34, 29, 26, 20, 15, 12, 9], | |
| 'S': [3, 2, 1, 0, -1, -1, 1, 5, 10, 14, 20, 26, 31, 33, 33, 32, 34, 34, 29, 25, 20, 15, 11, 9], | |
| 'SW': [3, 2, 3, 4, 5, 5, 3, 5, 10, 15, 19, 24, 29, 44, 61, 75, 32, 34, 29, 25, 20, 22, 14, 10], | |
| 'W': [4, 2, 4, 4, 5, 3, 5, 10, 15, 19, 24, 29, 44, 61, 75, 32, 34, 29, 25, 20, 22, 14, 10, 7], | |
| 'NW': [4, 2, 3, 4, 4, 5, 3, 5, 10, 15, 19, 24, 29, 44, 61, 75, 32, 34, 29, 25, 20, 22, 14, 10] | |
| }, | |
| "Custom": { # Default for custom doors | |
| 'N': [4, 3, 2, 1, 0, 1, 8, 16, 20, 21, 22, 25, 29, 31, 33, 35, 37, 37, 30, 20, 14, 10, 8, 6], | |
| 'NE': [4, 3, 2, 1, 0, 3, 20, 42, 54, 56, 51, 42, 35, 33, 33, 33, 33, 31, 27, 21, 16, 13, 10, 8], | |
| 'E': [4, 3, 2, 1, 0, 3, 21, 47, 62, 66, 62, 51, 39, 35, 34, 33, 35, 35, 32, 27, 22, 16, 13, 10], | |
| 'SE': [4, 3, 2, 1, 0, 1, 11, 28, 41, 47, 48, 45, 38, 35, 34, 33, 35, 35, 30, 27, 21, 16, 13, 10], | |
| 'S': [4, 3, 2, 1, 0, 0, 2, 6, 11, 15, 21, 27, 32, 34, 34, 33, 35, 35, 30, 26, 21, 16, 12, 10], | |
| 'SW': [4, 3, 4, 5, 6, 6, 4, 6, 11, 16, 20, 25, 30, 45, 62, 76, 33, 35, 30, 26, 21, 23, 15, 11], | |
| 'W': [5, 3, 5, 5, 6, 4, 6, 11, 16, 20, 25, 30, 45, 62, 76, 33, 35, 30, 26, 21, 23, 15, 11, 8], | |
| 'NW': [5, 3, 4, 5, 5, 6, 4, 6, 11, 16, 20, 25, 30, 45, 62, 76, 33, 35, 30, 26, 21, 23, 15, 11] | |
| } | |
| } | |
| # Convert to DataFrames | |
| door_cltd_tables = {} | |
| for door_type, orientation_data in door_cltd_data.items(): | |
| door_cltd_tables[door_type] = pd.DataFrame(orientation_data, index=hours) | |
| return door_cltd_tables | |
| def _load_cltd_skylight_table(self) -> Dict[str, pd.DataFrame]: | |
| """ | |
| Load CLTD tables for skylights (flat, 0° slope). | |
| Returns: | |
| Dictionary of DataFrames with CLTD values indexed by hour (0-23) | |
| """ | |
| hours = list(range(24)) | |
| # Skylight CLTD data for 40°N latitude, July | |
| skylight_cltd_data = { | |
| "SingleClear": { | |
| 'Horizontal': [3, 2, 1, 1, 1, 2, 4, 6, 9, 12, 15, 18, 20, 21, 20, 18, 15, 12, 9, 7, 5, 4, 3, 3] | |
| }, | |
| "DoubleTinted": { | |
| 'Horizontal': [2, 1, 0, 0, 0, 1, 3, 5, 7, 10, 12, 15, 17, 18, 17, 15, 12, 9, 7, 5, 3, 2, 2, 2] | |
| }, | |
| "LowE": { | |
| 'Horizontal': [1, 0, 0, 0, 0, 0, 2, 4, 6, 8, 10, 12, 14, 15, 14, 12, 10, 7, 5, 3, 2, 1, 1, 1] | |
| }, | |
| "Reflective": { | |
| 'Horizontal': [0, 0, 0, 0, 0, 0, 1, 2, 4, 6, 8, 10, 11, 12, 11, 10, 8, 6, 4, 2, 1, 0, 0, 0] | |
| } | |
| } | |
| # Convert to DataFrames | |
| skylight_cltd_tables = {} | |
| for glazing_type, orientation_data in skylight_cltd_data.items(): | |
| skylight_cltd_tables[glazing_type] = pd.DataFrame(orientation_data, index=hours) | |
| return skylight_cltd_tables | |
| def _load_latitude_correction(self) -> Dict[str, float]: | |
| """ | |
| Load latitude correction factors for CLTD. | |
| Returns: | |
| Dictionary of correction factors by latitude | |
| """ | |
| return { | |
| "24N": 0.95, | |
| "40N": 1.00, | |
| "48N": 1.05 | |
| } | |
| def _load_month_correction(self) -> Dict[int, float]: | |
| """ | |
| Load month correction factors for CLTD. | |
| Returns: | |
| Dictionary of correction factors by month | |
| """ | |
| return { | |
| 1: 0.85, 2: 0.90, 3: 0.95, 4: 0.98, 5: 1.00, | |
| 6: 1.02, 7: 1.00, 8: 0.98, 9: 0.95, 10: 0.90, | |
| 11: 0.85, 12: 0.80 | |
| } | |
| def get_cltd_window(self, glazing_type: str, orientation: str, hour: int) -> float: | |
| """ | |
| Get CLTD for a window with corrections. | |
| Args: | |
| glazing_type: Type of glazing ("SingleClear", "DoubleTinted", etc.) | |
| orientation: Orientation ("N", "NE", etc.) | |
| hour: Hour of day (0-23) | |
| Returns: | |
| Corrected CLTD value (°C) | |
| """ | |
| try: | |
| base_cltd = self.cltd_window_tables[self.latitude.value][glazing_type][orientation][hour] | |
| except KeyError: | |
| base_cltd = 0.0 | |
| # Apply corrections | |
| latitude_factor = self.latitude_corrections.get(self.latitude.value, 1.0) | |
| month_factor = self.month_corrections.get(self.month, 1.0) | |
| temp_correction = (self.outdoor_avg_temp - 29.4) + (self.indoor_temp - 24.0) | |
| corrected_cltd = base_cltd * latitude_factor * month_factor + temp_correction | |
| return max(0.0, corrected_cltd) | |
| def get_cltd_door(self, door_type: str, orientation: str, hour: int) -> float: | |
| """ | |
| Get CLTD for a door with corrections. | |
| Args: | |
| door_type: Type of door ("WoodSolid", "MetalInsulated", etc.) | |
| orientation: Orientation ("N", "NE", etc.) | |
| hour: Hour of day (0-23) | |
| Returns: | |
| Corrected CLTD value (°C) | |
| """ | |
| try: | |
| base_cltd = self.cltd_door_tables[door_type][orientation][hour] | |
| except KeyError: | |
| base_cltd = 0.0 | |
| # Apply corrections | |
| latitude_factor = self.latitude_corrections.get(self.latitude.value, 1.0) | |
| month_factor = self.month_corrections.get(self.month, 1.0) | |
| temp_correction = (self.outdoor_avg_temp - 29.4) + (self.indoor_temp - 24.0) | |
| corrected_cltd = base_cltd * latitude_factor * month_factor + temp_correction | |
| return max(0.0, corrected_cltd) | |
| def get_cltd_skylight(self, glazing_type: str, hour: int) -> float: | |
| """ | |
| Get CLTD for a skylight with corrections. | |
| Args: | |
| glazing_type: Type of glazing ("SingleClear", "DoubleTinted", etc.) | |
| hour: Hour of day (0-23) | |
| Returns: | |
| Corrected CLTD value (°C) | |
| """ | |
| try: | |
| base_cltd = self.cltd_skylight_tables[glazing_type]['Horizontal'][hour] | |
| except KeyError: | |
| base_cltd = 0.0 | |
| # Apply corrections | |
| latitude_factor = self.latitude_corrections.get(self.latitude.value, 1.0) | |
| month_factor = self.month_corrections.get(self.month, 1.0) | |
| temp_correction = (self.outdoor_avg_temp - 29.4) + (self.indoor_temp - 24.0) | |
| corrected_cltd = base_cltd * latitude_factor * month_factor + temp_correction | |
| return max(0.0, corrected_cltd) | |
| class WindowHeatGainCalculator: | |
| """Class for calculating window heat gain using CLTD/SCL method.""" | |
| def __init__(self, cltd_calculator: CLTDCalculator): | |
| """ | |
| Initialize window heat gain calculator. | |
| Args: | |
| cltd_calculator: Instance of CLTDCalculator | |
| """ | |
| self.cltd_calculator = cltd_calculator | |
| def calculate_window_heat_gain(self, area: float, glazing_type: GlazingType, | |
| frame_type: FrameType, orientation: str, hour: int, | |
| drapery: Optional[Drapery] = None) -> Tuple[float, float]: | |
| """ | |
| Calculate window heat gain (conduction and solar). | |
| Args: | |
| area: Window area (m²) | |
| glazing_type: Type of glazing | |
| frame_type: Type of frame | |
| orientation: Orientation ("N", "NE", etc.) | |
| hour: Hour of day (0-23) | |
| drapery: Drapery object (optional) | |
| Returns: | |
| Tuple of (conduction_heat_gain, solar_heat_gain) in Watts | |
| """ | |
| # Get U-factor | |
| u_factor = WINDOW_U_FACTORS.get((glazing_type, frame_type), 7.22) | |
| # Get SHGC | |
| shgc = WINDOW_SHGC.get((glazing_type, frame_type), 0.78) | |
| # Get CLTD | |
| cltd = self.cltd_calculator.get_cltd_window(glazing_type.value, orientation, hour) | |
| # Calculate conduction heat gain | |
| conduction_reduction = drapery.get_conduction_reduction() if drapery and drapery.enabled else 0.0 | |
| conduction_heat_gain = area * u_factor * cltd * (1.0 - conduction_reduction) | |
| # Get SCL from ASHRAE tables | |
| scl = self.cltd_calculator.ashrae_tables.get_scl( | |
| latitude=self.cltd_calculator.latitude.value, | |
| orientation=orientation, | |
| hour=hour, | |
| month=self.cltd_calculator.month | |
| ) | |
| # Apply drapery shading coefficient | |
| shading_coefficient = drapery.get_shading_coefficient(shgc) if drapery and drapery.enabled else 1.0 | |
| solar_heat_gain = area * shgc * scl * shading_coefficient | |
| return conduction_heat_gain, solar_heat_gain | |
| def calculate_skylight_heat_gain(self, area: float, glazing_type: GlazingType, | |
| frame_type: FrameType, hour: int, | |
| drapery: Optional[Drapery] = None) -> Tuple[float, float]: | |
| """ | |
| Calculate skylight heat gain (conduction and solar). | |
| Args: | |
| area: Skylight area (m²) | |
| glazing_type: Type of glazing | |
| frame_type: Type of frame | |
| hour: Hour of day (0-23) | |
| drapery: Drapery object (optional) | |
| Returns: | |
| Tuple of (conduction_heat_gain, solar_heat_gain) in Watts | |
| """ | |
| # Get U-factor | |
| u_factor = SKYLIGHT_U_FACTORS.get((glazing_type, frame_type), 7.79) | |
| # Get SHGC | |
| shgc = SKYLIGHT_SHGC.get((glazing_type, frame_type), 0.83) | |
| # Get CLTD | |
| cltd = self.cltd_calculator.get_cltd_skylight(glazing_type.value, hour) | |
| # Calculate conduction heat gain | |
| conduction_reduction = drapery.get_conduction_reduction() if drapery and drapery.enabled else 0.0 | |
| conduction_heat_gain = area * u_factor * cltd * (1.0 - conduction_reduction) | |
| # Get SCL for skylight (horizontal) | |
| scl = self.cltd_calculator.ashrae_tables.get_scl( | |
| latitude=self.cltd_calculator.latitude.value, | |
| orientation='Horizontal', | |
| hour=hour, | |
| month=self.cltd_calculator.month | |
| ) | |
| # Apply drapery shading coefficient | |
| shading_coefficient = drapery.get_shading_coefficient(shgc) if drapery and drapery.enabled else 1.0 | |
| solar_heat_gain = area * shgc * scl * shading_coefficient | |
| return conduction_heat_gain, solar_heat_gain | |
| class DoorHeatGainCalculator: | |
| """Class for calculating door heat gain using CLTD method.""" | |
| def __init__(self, cltd_calculator: CLTDCalculator): | |
| """ | |
| Initialize door heat gain calculator. | |
| Args: | |
| cltd_calculator: Instance of CLTDCalculator | |
| """ | |
| self.cltd_calculator = cltd_calculator | |
| def calculate_door_heat_gain(self, area: float, door_type: str, orientation: str, | |
| hour: int) -> float: | |
| """ | |
| Calculate door heat gain (conduction only). | |
| Args: | |
| area: Door area (m²) | |
| door_type: Type of door ("WoodSolid", "MetalInsulated", etc.) | |
| orientation: Orientation ("N", "NE", etc.) | |
| hour: Hour of day (0-23) | |
| Returns: | |
| Conduction heat gain in Watts | |
| """ | |
| # Get U-factor | |
| u_factor = DOOR_U_FACTORS.get(door_type, 3.00) | |
| # Get CLTD | |
| cltd = self.cltd_calculator.get_cltd_door(door_type, orientation, hour) | |
| # Calculate conduction heat gain | |
| conduction_heat_gain = area * u_factor * cltd | |
| return conduction_heat_gain | |
| def calculate_total_heat_gain(window_area: float, glazing_type: GlazingType, | |
| frame_type: FrameType, orientation: str, hour: int, | |
| drapery: Optional[Drapery] = None, | |
| door_area: float = 0.0, door_type: str = "WoodSolid", | |
| skylight_area: float = 0.0) -> Dict[str, float]: | |
| """ | |
| Calculate total heat gain for a fenestration system. | |
| Args: | |
| window_area: Window area (m²) | |
| glazing_type: Type of glazing | |
| frame_type: Type of frame | |
| orientation: Orientation ("N", "NE", etc.) | |
| hour: Hour of day (0-23) | |
| drapery: Drapery object (optional) | |
| door_area: Door area (m²) | |
| door_type: Type of door | |
| skylight_area: Skylight area (m²) | |
| Returns: | |
| Dictionary with conduction and solar heat gains (Watts) | |
| """ | |
| cltd_calculator = CLTDCalculator() | |
| window_calculator = WindowHeatGainCalculator(cltd_calculator) | |
| door_calculator = DoorHeatGainCalculator(cltd_calculator) | |
| total_conduction = 0.0 | |
| total_solar = 0.0 | |
| # Calculate window heat gain | |
| if window_area > 0: | |
| conduction, solar = window_calculator.calculate_window_heat_gain( | |
| window_area, glazing_type, frame_type, orientation, hour, drapery | |
| ) | |
| total_conduction += conduction | |
| total_solar += solar | |
| # Calculate skylight heat gain | |
| if skylight_area > 0: | |
| conduction, solar = window_calculator.calculate_skylight_heat_gain( | |
| skylight_area, glazing_type, frame_type, hour, drapery | |
| ) | |
| total_conduction += conduction | |
| total_solar += solar | |
| # Calculate door heat gain | |
| if door_area > 0: | |
| conduction = door_calculator.calculate_door_heat_gain( | |
| door_area, door_type, orientation, hour | |
| ) | |
| total_conduction += conduction | |
| return { | |
| "conduction_heat_gain": total_conduction, | |
| "solar_heat_gain": total_solar, | |
| "total_heat_gain": total_conduction + total_solar | |
| } |