from __future__ import annotations from pathlib import Path import openpyxl import pytest from domain.magicplan.models import ( Door, DoorVentilation, Floor, Plan, Room, Window, WindowVentilation, ) from domain.magicplan.ventilation_audit import populate_sheet def _make_window(with_ventilation: bool = True) -> Window: vent = ( WindowVentilation( opening_type="Hinged", num_openings=1, pct_openable=50, trickle_vent_area_mm2=1000, num_trickle_vents=2, ) if with_ventilation else None ) return Window(width_m=1.0, height_m=1.2, area_m2=1.2, ventilation=vent) def _make_door(with_ventilation: bool = True) -> Door: vent = DoorVentilation(undercut_mm=10.0) if with_ventilation else None return Door(width_mm=800.0, height_mm=2000.0, ventilation=vent) def _make_plan( num_rooms: int = 1, num_windows_per_room: int = 1, num_doors_per_room: int = 1, ) -> Plan: rooms = [ Room( name=f"Room {i}", width_m=3.0, length_m=4.0, area_m2=12.0, windows=[_make_window() for _ in range(num_windows_per_room)], doors=[_make_door() for _ in range(num_doors_per_room)], ) for i in range(num_rooms) ] return Plan( uid="test-uid", name="Test Plan", address="1 Test St", postcode="TE1 1ST", floors=[Floor(level=0, name="Ground", rooms=rooms)], ) def _blank_sheet() -> object: return openpyxl.Workbook().active def test_raises_when_rooms_exceed_50() -> None: # Arrange plan = _make_plan(num_rooms=51, num_windows_per_room=0, num_doors_per_room=0) sheet = _blank_sheet() # Act / Assert with pytest.raises(ValueError, match="50"): populate_sheet(sheet, plan) def test_raises_when_windows_exceed_50() -> None: # Arrange plan = _make_plan(num_rooms=1, num_windows_per_room=51, num_doors_per_room=0) sheet = _blank_sheet() # Act / Assert with pytest.raises(ValueError, match="50"): populate_sheet(sheet, plan) def test_raises_when_doors_exceed_50() -> None: # Arrange plan = _make_plan(num_rooms=1, num_windows_per_room=0, num_doors_per_room=51) sheet = _blank_sheet() # Act / Assert with pytest.raises(ValueError, match="50"): populate_sheet(sheet, plan) def test_writes_window_room_name_to_column_F() -> None: # Arrange plan = _make_plan(num_rooms=1, num_windows_per_room=1, num_doors_per_room=0) sheet = _blank_sheet() # Act populate_sheet(sheet, plan) # Assert assert sheet["F6"].value == "Room 0" def test_writes_window_dimensions_to_columns_G_and_H() -> None: # Arrange plan = _make_plan(num_rooms=1, num_windows_per_room=1, num_doors_per_room=0) sheet = _blank_sheet() # Act populate_sheet(sheet, plan) # Assert assert sheet["G6"].value == 1.0 assert sheet["H6"].value == 1.2 def test_writes_window_opening_data_to_columns_J_K_L() -> None: # Arrange plan = _make_plan(num_rooms=1, num_windows_per_room=1, num_doors_per_room=0) sheet = _blank_sheet() # Act populate_sheet(sheet, plan) # Assert assert sheet["J6"].value == "Hinged" assert sheet["K6"].value == 1 assert sheet["L6"].value == pytest.approx(0.5) def test_writes_trickle_vent_data_to_columns_N_and_O() -> None: # Arrange plan = _make_plan(num_rooms=1, num_windows_per_room=1, num_doors_per_room=0) sheet = _blank_sheet() # Act populate_sheet(sheet, plan) # Assert assert sheet["N6"].value == 1000 assert sheet["O6"].value == 2 def test_writes_door_room_name_to_column_R() -> None: # Arrange plan = _make_plan(num_rooms=1, num_windows_per_room=0, num_doors_per_room=1) sheet = _blank_sheet() # Act populate_sheet(sheet, plan) # Assert assert sheet["R6"].value == "Room 0" def test_writes_door_dimensions_to_columns_S_and_T() -> None: # Arrange plan = _make_plan(num_rooms=1, num_windows_per_room=0, num_doors_per_room=1) sheet = _blank_sheet() # Act populate_sheet(sheet, plan) # Assert assert sheet["S6"].value == 800.0 assert sheet["T6"].value == 10.0 def test_writes_zeros_when_window_has_no_ventilation() -> None: # Arrange rooms = [Room(name="Room 0", width_m=3.0, length_m=4.0, area_m2=12.0, windows=[_make_window(with_ventilation=False)], doors=[])] plan = Plan(uid="u", name="n", address="a", postcode="p", floors=[Floor(level=0, name="G", rooms=rooms)]) sheet = _blank_sheet() # Act populate_sheet(sheet, plan) # Assert assert sheet["J6"].value == 0 assert sheet["K6"].value == 0 assert sheet["L6"].value == 0 assert sheet["N6"].value == 0 assert sheet["O6"].value == 0 def test_writes_zeros_when_door_has_no_ventilation() -> None: # Arrange rooms = [Room(name="Room 0", width_m=3.0, length_m=4.0, area_m2=12.0, windows=[], doors=[_make_door(with_ventilation=False)])] plan = Plan(uid="u", name="n", address="a", postcode="p", floors=[Floor(level=0, name="G", rooms=rooms)]) sheet = _blank_sheet() # Act populate_sheet(sheet, plan) # Assert assert sheet["T6"].value == 0 _TEMPLATE_PATH = ( Path(__file__).parent.parent.parent.parent / "applications" / "audit_generator" / "Master Sero Template - Data Extraction.xlsx" ) def test_real_template_survives_18_doors() -> None: # Row 23 = DATA_START_ROW(6) + 17 — previously a merged separator that caused # AttributeError: 'MergedCell' object attribute 'value' is read-only wb = openpyxl.load_workbook(_TEMPLATE_PATH) sheet = wb["D1 Ventilation"] plan = _make_plan(num_rooms=18, num_windows_per_room=0, num_doors_per_room=1) populate_sheet(sheet, plan) assert sheet["R23"].value == "Room 17"