|
|
""" |
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
coverage_percentage: float = 100.0 |
|
|
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.""" |
|
|
|
|
|
|
|
|
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.""" |
|
|
|
|
|
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" |
|
|
) |
|
|
|
|
|
|
|
|
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" |
|
|
) |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
shading_system = ShadingSystem() |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
shading_system.export_to_json(os.path.join(DATA_DIR, "data", "shading_system.json")) |
|
|
|