Spaces:
Running
Running
""" | |
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" | |
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") | |
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")) | |