Spaces:
Sleeping
Sleeping
""" | |
Area calculation system module for HVAC Load Calculator. | |
This module implements net wall area calculation and area validation functions. | |
""" | |
from typing import Dict, List, Any, Optional, Tuple | |
import pandas as pd | |
import numpy as np | |
import os | |
import json | |
from dataclasses import dataclass, field | |
# Import data models | |
from data.building_components import Wall, Window, Door, Orientation, ComponentType | |
# Define paths | |
DATA_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
class AreaCalculationSystem: | |
"""Class for managing area calculations and validations.""" | |
def __init__(self): | |
"""Initialize area calculation system.""" | |
self.walls = {} | |
self.windows = {} | |
self.doors = {} | |
def add_wall(self, wall: Wall) -> None: | |
""" | |
Add a wall to the area calculation system. | |
Args: | |
wall: Wall object | |
""" | |
self.walls[wall.id] = wall | |
def add_window(self, window: Window) -> None: | |
""" | |
Add a window to the area calculation system. | |
Args: | |
window: Window object | |
""" | |
self.windows[window.id] = window | |
def add_door(self, door: Door) -> None: | |
""" | |
Add a door to the area calculation system. | |
Args: | |
door: Door object | |
""" | |
self.doors[door.id] = door | |
def remove_wall(self, wall_id: str) -> bool: | |
""" | |
Remove a wall from the area calculation system. | |
Args: | |
wall_id: Wall identifier | |
Returns: | |
True if the wall was removed, False otherwise | |
""" | |
if wall_id not in self.walls: | |
return False | |
# Remove all windows and doors associated with this wall | |
for window_id, window in list(self.windows.items()): | |
if window.wall_id == wall_id: | |
del self.windows[window_id] | |
for door_id, door in list(self.doors.items()): | |
if door.wall_id == wall_id: | |
del self.doors[door_id] | |
del self.walls[wall_id] | |
return True | |
def remove_window(self, window_id: str) -> bool: | |
""" | |
Remove a window from the area calculation system. | |
Args: | |
window_id: Window identifier | |
Returns: | |
True if the window was removed, False otherwise | |
""" | |
if window_id not in self.windows: | |
return False | |
# Remove window from associated wall | |
window = self.windows[window_id] | |
if window.wall_id and window.wall_id in self.walls: | |
wall = self.walls[window.wall_id] | |
if window_id in wall.windows: | |
wall.windows.remove(window_id) | |
self._update_wall_net_area(wall.id) | |
del self.windows[window_id] | |
return True | |
def remove_door(self, door_id: str) -> bool: | |
""" | |
Remove a door from the area calculation system. | |
Args: | |
door_id: Door identifier | |
Returns: | |
True if the door was removed, False otherwise | |
""" | |
if door_id not in self.doors: | |
return False | |
# Remove door from associated wall | |
door = self.doors[door_id] | |
if door.wall_id and door.wall_id in self.walls: | |
wall = self.walls[door.wall_id] | |
if door_id in wall.doors: | |
wall.doors.remove(door_id) | |
self._update_wall_net_area(wall.id) | |
del self.doors[door_id] | |
return True | |
def assign_window_to_wall(self, window_id: str, wall_id: str) -> bool: | |
""" | |
Assign a window to a wall. | |
Args: | |
window_id: Window identifier | |
wall_id: Wall identifier | |
Returns: | |
True if the window was assigned, False otherwise | |
""" | |
if window_id not in self.windows or wall_id not in self.walls: | |
return False | |
window = self.windows[window_id] | |
wall = self.walls[wall_id] | |
# Remove window from previous wall if assigned | |
if window.wall_id and window.wall_id in self.walls and window.wall_id != wall_id: | |
prev_wall = self.walls[window.wall_id] | |
if window_id in prev_wall.windows: | |
prev_wall.windows.remove(window_id) | |
self._update_wall_net_area(prev_wall.id) | |
# Assign window to new wall | |
window.wall_id = wall_id | |
window.orientation = wall.orientation | |
# Add window to wall's window list if not already there | |
if window_id not in wall.windows: | |
wall.windows.append(window_id) | |
# Update wall net area | |
self._update_wall_net_area(wall_id) | |
return True | |
def assign_door_to_wall(self, door_id: str, wall_id: str) -> bool: | |
""" | |
Assign a door to a wall. | |
Args: | |
door_id: Door identifier | |
wall_id: Wall identifier | |
Returns: | |
True if the door was assigned, False otherwise | |
""" | |
if door_id not in self.doors or wall_id not in self.walls: | |
return False | |
door = self.doors[door_id] | |
wall = self.walls[wall_id] | |
# Remove door from previous wall if assigned | |
if door.wall_id and door.wall_id in self.walls and door.wall_id != wall_id: | |
prev_wall = self.walls[door.wall_id] | |
if door_id in prev_wall.doors: | |
prev_wall.doors.remove(door_id) | |
self._update_wall_net_area(prev_wall.id) | |
# Assign door to new wall | |
door.wall_id = wall_id | |
door.orientation = wall.orientation | |
# Add door to wall's door list if not already there | |
if door_id not in wall.doors: | |
wall.doors.append(door_id) | |
# Update wall net area | |
self._update_wall_net_area(wall_id) | |
return True | |
def _update_wall_net_area(self, wall_id: str) -> None: | |
""" | |
Update the net area of a wall by subtracting windows and doors. | |
Args: | |
wall_id: Wall identifier | |
""" | |
if wall_id not in self.walls: | |
return | |
wall = self.walls[wall_id] | |
# Calculate total window area | |
total_window_area = sum(self.windows[window_id].area | |
for window_id in wall.windows | |
if window_id in self.windows) | |
# Calculate total door area | |
total_door_area = sum(self.doors[door_id].area | |
for door_id in wall.doors | |
if door_id in self.doors) | |
# Update wall net area | |
if wall.gross_area is None: | |
wall.gross_area = wall.area | |
wall.net_area = wall.gross_area - total_window_area - total_door_area | |
wall.area = wall.net_area # Update the main area property | |
def update_all_net_areas(self) -> None: | |
"""Update the net areas of all walls.""" | |
for wall_id in self.walls: | |
self._update_wall_net_area(wall_id) | |
def validate_areas(self) -> List[Dict[str, Any]]: | |
""" | |
Validate all areas and return a list of validation issues. | |
Returns: | |
List of validation issues | |
""" | |
issues = [] | |
# Check for negative or zero net wall areas | |
for wall_id, wall in self.walls.items(): | |
if wall.net_area <= 0: | |
issues.append({ | |
"type": "error", | |
"component_id": wall_id, | |
"component_type": "Wall", | |
"message": f"Wall '{wall.name}' has a negative or zero net area. " | |
f"Gross area: {wall.gross_area} m², " | |
f"Window area: {sum(self.windows[window_id].area for window_id in wall.windows if window_id in self.windows)} m², " | |
f"Door area: {sum(self.doors[door_id].area for door_id in wall.doors if door_id in self.doors)} m², " | |
f"Net area: {wall.net_area} m²." | |
}) | |
elif wall.net_area < 0.5: | |
issues.append({ | |
"type": "warning", | |
"component_id": wall_id, | |
"component_type": "Wall", | |
"message": f"Wall '{wall.name}' has a very small net area ({wall.net_area} m²). " | |
f"Consider adjusting window and door sizes." | |
}) | |
# Check for windows without walls | |
for window_id, window in self.windows.items(): | |
if not window.wall_id: | |
issues.append({ | |
"type": "warning", | |
"component_id": window_id, | |
"component_type": "Window", | |
"message": f"Window '{window.name}' is not assigned to any wall." | |
}) | |
elif window.wall_id not in self.walls: | |
issues.append({ | |
"type": "error", | |
"component_id": window_id, | |
"component_type": "Window", | |
"message": f"Window '{window.name}' is assigned to a non-existent wall (ID: {window.wall_id})." | |
}) | |
# Check for doors without walls | |
for door_id, door in self.doors.items(): | |
if not door.wall_id: | |
issues.append({ | |
"type": "warning", | |
"component_id": door_id, | |
"component_type": "Door", | |
"message": f"Door '{door.name}' is not assigned to any wall." | |
}) | |
elif door.wall_id not in self.walls: | |
issues.append({ | |
"type": "error", | |
"component_id": door_id, | |
"component_type": "Door", | |
"message": f"Door '{door.name}' is assigned to a non-existent wall (ID: {door.wall_id})." | |
}) | |
# Check for windows and doors with zero area | |
for window_id, window in self.windows.items(): | |
if window.area <= 0: | |
issues.append({ | |
"type": "error", | |
"component_id": window_id, | |
"component_type": "Window", | |
"message": f"Window '{window.name}' has a zero or negative area ({window.area} m²)." | |
}) | |
for door_id, door in self.doors.items(): | |
if door.area <= 0: | |
issues.append({ | |
"type": "error", | |
"component_id": door_id, | |
"component_type": "Door", | |
"message": f"Door '{door.name}' has a zero or negative area ({door.area} m²)." | |
}) | |
return issues | |
def get_wall_components(self, wall_id: str) -> Dict[str, List[str]]: | |
""" | |
Get all components (windows and doors) associated with a wall. | |
Args: | |
wall_id: Wall identifier | |
Returns: | |
Dictionary with lists of window and door IDs | |
""" | |
if wall_id not in self.walls: | |
return {"windows": [], "doors": []} | |
wall = self.walls[wall_id] | |
return { | |
"windows": [window_id for window_id in wall.windows if window_id in self.windows], | |
"doors": [door_id for door_id in wall.doors if door_id in self.doors] | |
} | |
def get_wall_area_breakdown(self, wall_id: str) -> Dict[str, float]: | |
""" | |
Get a breakdown of wall areas (gross, net, windows, doors). | |
Args: | |
wall_id: Wall identifier | |
Returns: | |
Dictionary with area breakdown | |
""" | |
if wall_id not in self.walls: | |
return {} | |
wall = self.walls[wall_id] | |
# Calculate total window area | |
window_area = sum(self.windows[window_id].area | |
for window_id in wall.windows | |
if window_id in self.windows) | |
# Calculate total door area | |
door_area = sum(self.doors[door_id].area | |
for door_id in wall.doors | |
if door_id in self.doors) | |
return { | |
"gross_area": wall.gross_area, | |
"net_area": wall.net_area, | |
"window_area": window_area, | |
"door_area": door_area | |
} | |
def get_total_areas(self) -> Dict[str, float]: | |
""" | |
Get total areas for all component types. | |
Returns: | |
Dictionary with total areas | |
""" | |
# Calculate total wall areas | |
total_wall_gross_area = sum(wall.gross_area for wall in self.walls.values() if wall.gross_area is not None) | |
total_wall_net_area = sum(wall.net_area for wall in self.walls.values() if wall.net_area is not None) | |
# Calculate total window area | |
total_window_area = sum(window.area for window in self.windows.values()) | |
# Calculate total door area | |
total_door_area = sum(door.area for door in self.doors.values()) | |
return { | |
"total_wall_gross_area": total_wall_gross_area, | |
"total_wall_net_area": total_wall_net_area, | |
"total_window_area": total_window_area, | |
"total_door_area": total_door_area | |
} | |
def get_areas_by_orientation(self) -> Dict[str, Dict[str, float]]: | |
""" | |
Get areas for all component types grouped by orientation. | |
Returns: | |
Dictionary with areas by orientation | |
""" | |
# Initialize result dictionary | |
result = {} | |
# Process walls | |
for wall in self.walls.values(): | |
orientation = wall.orientation.value | |
if orientation not in result: | |
result[orientation] = { | |
"wall_gross_area": 0, | |
"wall_net_area": 0, | |
"window_area": 0, | |
"door_area": 0 | |
} | |
result[orientation]["wall_gross_area"] += wall.gross_area if wall.gross_area is not None else 0 | |
result[orientation]["wall_net_area"] += wall.net_area if wall.net_area is not None else 0 | |
# Process windows | |
for window in self.windows.values(): | |
orientation = window.orientation.value | |
if orientation not in result: | |
result[orientation] = { | |
"wall_gross_area": 0, | |
"wall_net_area": 0, | |
"window_area": 0, | |
"door_area": 0 | |
} | |
result[orientation]["window_area"] += window.area | |
# Process doors | |
for door in self.doors.values(): | |
orientation = door.orientation.value | |
if orientation not in result: | |
result[orientation] = { | |
"wall_gross_area": 0, | |
"wall_net_area": 0, | |
"window_area": 0, | |
"door_area": 0 | |
} | |
result[orientation]["door_area"] += door.area | |
return result | |
def export_to_json(self, file_path: str) -> None: | |
""" | |
Export all components to a JSON file. | |
Args: | |
file_path: Path to the output JSON file | |
""" | |
data = { | |
"walls": {wall_id: wall.to_dict() for wall_id, wall in self.walls.items()}, | |
"windows": {window_id: window.to_dict() for window_id, window in self.windows.items()}, | |
"doors": {door_id: door.to_dict() for door_id, door in self.doors.items()} | |
} | |
with open(file_path, 'w') as f: | |
json.dump(data, f, indent=4) | |
def import_from_json(self, file_path: str) -> Tuple[int, int, int]: | |
""" | |
Import components from a JSON file. | |
Args: | |
file_path: Path to the input JSON file | |
Returns: | |
Tuple with counts of walls, windows, and doors imported | |
""" | |
from data.building_components import BuildingComponentFactory | |
with open(file_path, 'r') as f: | |
data = json.load(f) | |
wall_count = 0 | |
window_count = 0 | |
door_count = 0 | |
# Import walls | |
for wall_id, wall_data in data.get("walls", {}).items(): | |
try: | |
wall = BuildingComponentFactory.create_component(wall_data) | |
self.walls[wall_id] = wall | |
wall_count += 1 | |
except Exception as e: | |
print(f"Error importing wall {wall_id}: {e}") | |
# Import windows | |
for window_id, window_data in data.get("windows", {}).items(): | |
try: | |
window = BuildingComponentFactory.create_component(window_data) | |
self.windows[window_id] = window | |
window_count += 1 | |
except Exception as e: | |
print(f"Error importing window {window_id}: {e}") | |
# Import doors | |
for door_id, door_data in data.get("doors", {}).items(): | |
try: | |
door = BuildingComponentFactory.create_component(door_data) | |
self.doors[door_id] = door | |
door_count += 1 | |
except Exception as e: | |
print(f"Error importing door {door_id}: {e}") | |
# Update all net areas | |
self.update_all_net_areas() | |
return (wall_count, window_count, door_count) | |
# Create a singleton instance | |
area_calculation_system = AreaCalculationSystem() | |
# Export area calculation system to JSON if needed | |
if __name__ == "__main__": | |
area_calculation_system.export_to_json(os.path.join(DATA_DIR, "data", "area_calculation_system.json")) | |