""" Shading system module for HVAC Load Calculator. This module implements shading type selection and coverage percentage interface. """ from typing import Dict, List, Any, Optional, Tuple import pandas as pd import numpy as np import os import json from enum import Enum from dataclasses import dataclass # Define paths DATA_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) class ShadingType(Enum): """Enumeration for shading types.""" NONE = "None" INTERNAL = "Internal" EXTERNAL = "External" BETWEEN_GLASS = "Between-glass" @dataclass class ShadingDevice: """Class representing a shading device.""" id: str name: str shading_type: ShadingType shading_coefficient: float # 0-1 (1 = no shading) coverage_percentage: float = 100.0 # 0-100% description: str = "" def __post_init__(self): """Validate shading device data after initialization.""" if self.shading_coefficient < 0 or self.shading_coefficient > 1: raise ValueError("Shading coefficient must be between 0 and 1") if self.coverage_percentage < 0 or self.coverage_percentage > 100: raise ValueError("Coverage percentage must be between 0 and 100") @property def effective_shading_coefficient(self) -> float: """Calculate the effective shading coefficient considering coverage percentage.""" # If coverage is less than 100%, the effective coefficient is a weighted average # between the device coefficient and 1.0 (no shading) coverage_factor = self.coverage_percentage / 100.0 return self.shading_coefficient * coverage_factor + 1.0 * (1 - coverage_factor) def to_dict(self) -> Dict[str, Any]: """Convert the shading device to a dictionary.""" return { "id": self.id, "name": self.name, "shading_type": self.shading_type.value, "shading_coefficient": self.shading_coefficient, "coverage_percentage": self.coverage_percentage, "description": self.description, "effective_shading_coefficient": self.effective_shading_coefficient } class ShadingSystem: """Class for managing shading devices and calculations.""" def __init__(self): """Initialize shading system.""" self.shading_devices = {} self.load_preset_devices() def load_preset_devices(self) -> None: """Load preset shading devices.""" # Internal shading devices self.shading_devices["preset_venetian_blinds"] = ShadingDevice( id="preset_venetian_blinds", name="Venetian Blinds", shading_type=ShadingType.INTERNAL, shading_coefficient=0.6, description="Standard internal venetian blinds" ) self.shading_devices["preset_roller_shade"] = ShadingDevice( id="preset_roller_shade", name="Roller Shade", shading_type=ShadingType.INTERNAL, shading_coefficient=0.7, description="Standard internal roller shade" ) self.shading_devices["preset_drapes_light"] = ShadingDevice( id="preset_drapes_light", name="Light Drapes", shading_type=ShadingType.INTERNAL, shading_coefficient=0.8, description="Light-colored internal drapes" ) self.shading_devices["preset_drapes_dark"] = ShadingDevice( id="preset_drapes_dark", name="Dark Drapes", shading_type=ShadingType.INTERNAL, shading_coefficient=0.5, description="Dark-colored internal drapes" ) # External shading devices self.shading_devices["preset_overhang"] = ShadingDevice( id="preset_overhang", name="Overhang", shading_type=ShadingType.EXTERNAL, shading_coefficient=0.4, description="External overhang" ) self.shading_devices["preset_louvers"] = ShadingDevice( id="preset_louvers", name="Louvers", shading_type=ShadingType.EXTERNAL, shading_coefficient=0.3, description="External louvers" ) self.shading_devices["preset_exterior_screen"] = ShadingDevice( id="preset_exterior_screen", name="Exterior Screen", shading_type=ShadingType.EXTERNAL, shading_coefficient=0.5, description="External screen" ) # Between-glass shading devices self.shading_devices["preset_between_glass_blinds"] = ShadingDevice( id="preset_between_glass_blinds", name="Between-glass Blinds", shading_type=ShadingType.BETWEEN_GLASS, shading_coefficient=0.5, description="Blinds between glass panes" ) def get_device(self, device_id: str) -> Optional[ShadingDevice]: """ Get a shading device by ID. Args: device_id: Device identifier Returns: ShadingDevice object or None if not found """ return self.shading_devices.get(device_id) def get_devices_by_type(self, shading_type: ShadingType) -> List[ShadingDevice]: """ Get all shading devices of a specific type. Args: shading_type: Shading type Returns: List of ShadingDevice objects """ return [device for device in self.shading_devices.values() if device.shading_type == shading_type] def get_preset_devices(self) -> List[ShadingDevice]: """ Get all preset shading devices. Returns: List of ShadingDevice objects """ return [device for device_id, device in self.shading_devices.items() if device_id.startswith("preset_")] def get_custom_devices(self) -> List[ShadingDevice]: """ Get all custom shading devices. Returns: List of ShadingDevice objects """ return [device for device_id, device in self.shading_devices.items() if device_id.startswith("custom_")] def add_device(self, name: str, shading_type: ShadingType, shading_coefficient: float, coverage_percentage: float = 100.0, description: str = "") -> str: """ Add a custom shading device. Args: name: Device name shading_type: Shading type shading_coefficient: Shading coefficient (0-1) coverage_percentage: Coverage percentage (0-100) description: Device description Returns: Device ID """ import uuid device_id = f"custom_shading_{str(uuid.uuid4())[:8]}" device = ShadingDevice( id=device_id, name=name, shading_type=shading_type, shading_coefficient=shading_coefficient, coverage_percentage=coverage_percentage, description=description ) self.shading_devices[device_id] = device return device_id def update_device(self, device_id: str, name: str = None, shading_coefficient: float = None, coverage_percentage: float = None, description: str = None) -> bool: """ Update a shading device. Args: device_id: Device identifier name: New device name (optional) shading_coefficient: New shading coefficient (optional) coverage_percentage: New coverage percentage (optional) description: New device description (optional) Returns: True if the device was updated, False otherwise """ if device_id not in self.shading_devices: return False # Don't allow updating preset devices if device_id.startswith("preset_"): return False device = self.shading_devices[device_id] if name is not None: device.name = name if shading_coefficient is not None: if shading_coefficient < 0 or shading_coefficient > 1: return False device.shading_coefficient = shading_coefficient if coverage_percentage is not None: if coverage_percentage < 0 or coverage_percentage > 100: return False device.coverage_percentage = coverage_percentage if description is not None: device.description = description return True def remove_device(self, device_id: str) -> bool: """ Remove a shading device. Args: device_id: Device identifier Returns: True if the device was removed, False otherwise """ if device_id not in self.shading_devices: return False # Don't allow removing preset devices if device_id.startswith("preset_"): return False del self.shading_devices[device_id] return True def calculate_effective_shgc(self, base_shgc: float, device_id: str) -> float: """ Calculate the effective SHGC (Solar Heat Gain Coefficient) with shading. Args: base_shgc: Base SHGC of the window device_id: Shading device identifier Returns: Effective SHGC with shading """ if device_id not in self.shading_devices: return base_shgc device = self.shading_devices[device_id] return base_shgc * device.effective_shading_coefficient def export_to_json(self, file_path: str) -> None: """ Export all shading devices to a JSON file. Args: file_path: Path to the output JSON file """ data = {device_id: device.to_dict() for device_id, device in self.shading_devices.items()} with open(file_path, 'w') as f: json.dump(data, f, indent=4) def import_from_json(self, file_path: str) -> int: """ Import shading devices from a JSON file. Args: file_path: Path to the input JSON file Returns: Number of devices imported """ with open(file_path, 'r') as f: data = json.load(f) count = 0 for device_id, device_data in data.items(): try: shading_type = ShadingType(device_data["shading_type"]) device = ShadingDevice( id=device_id, name=device_data["name"], shading_type=shading_type, shading_coefficient=device_data["shading_coefficient"], coverage_percentage=device_data.get("coverage_percentage", 100.0), description=device_data.get("description", "") ) self.shading_devices[device_id] = device count += 1 except Exception as e: print(f"Error importing shading device {device_id}: {e}") return count # Create a singleton instance shading_system = ShadingSystem() # Export shading system to JSON if needed if __name__ == "__main__": shading_system.export_to_json(os.path.join(DATA_DIR, "data", "shading_system.json"))