From 0edeeaefa69f3e73c374bdd50db6b6480c10326c Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 10 Jun 2026 13:21:50 +0000 Subject: [PATCH] =?UTF-8?q?populate=5Fsheet=20writes=20to=20new=20Sero=20t?= =?UTF-8?q?emplate=20column=20layout=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- domain/magicplan/ventilation_audit.py | 42 +++---- orchestration/audit_generator_orchestrator.py | 2 +- .../magicplan/test_ventilation_audit.py | 107 ++++++++++++++++++ 3 files changed, 129 insertions(+), 22 deletions(-) diff --git a/domain/magicplan/ventilation_audit.py b/domain/magicplan/ventilation_audit.py index 36214412..74586909 100644 --- a/domain/magicplan/ventilation_audit.py +++ b/domain/magicplan/ventilation_audit.py @@ -11,9 +11,10 @@ from domain.magicplan.models import Door, Plan, Room, Window _DATA_START_ROW = 6 _MAX_ROWS = 50 -_Y_CF_RANGE = f"Y{_DATA_START_ROW}:Y{_DATA_START_ROW + _MAX_ROWS - 1}" +_DOOR_AREA_COL = "U" +_DOOR_AREA_CF_RANGE = f"{_DOOR_AREA_COL}{_DATA_START_ROW}:{_DOOR_AREA_COL}{_DATA_START_ROW + _MAX_ROWS - 1}" _Y_THRESHOLD = 7600 -_Y_HEADER = CellRichText( +_DOOR_AREA_HEADER = CellRichText( TextBlock(InlineFont(b=True, sz=11, rFont="Aptos Narrow"), "Area (mm2)\n"), TextBlock(InlineFont(b=True, sz=11, color=Color(rgb="FF0000"), rFont="Aptos Narrow"), "<"), TextBlock(InlineFont(b=True, sz=11, rFont="Aptos Narrow"), " 7600 "), @@ -23,14 +24,14 @@ _Y_HEADER = CellRichText( def _apply_column_y_formatting(sheet: Any) -> None: sheet.conditional_formatting.add( - _Y_CF_RANGE, + _DOOR_AREA_CF_RANGE, CellIsRule(operator="lessThan", formula=[str(_Y_THRESHOLD)], font=Font(color=Color(rgb="FF0000"))), ) sheet.conditional_formatting.add( - _Y_CF_RANGE, + _DOOR_AREA_CF_RANGE, CellIsRule(operator="greaterThan", formula=[str(_Y_THRESHOLD)], font=Font(color=Color(rgb="196B24"))), ) - sheet["Y3"] = _Y_HEADER + sheet[f"{_DOOR_AREA_COL}3"] = _DOOR_AREA_HEADER def _write_cell(sheet: Any, row: int, col: str, value: Any) -> None: @@ -61,26 +62,25 @@ def populate_sheet(sheet: Any, plan: Plan) -> None: for i, (room_name, window) in enumerate(windows): row = _DATA_START_ROW + i vent = window.ventilation - _write_cell(sheet, row, "G", room_name) - _write_cell(sheet, row, "H", window.width_m) - _write_cell(sheet, row, "I", window.height_m) - # J = formula =H*I — do not write - _write_cell(sheet, row, "K", vent.opening_type if vent else 0) - _write_cell(sheet, row, "L", vent.num_openings if vent else 0) + _write_cell(sheet, row, "F", room_name) + _write_cell(sheet, row, "G", window.width_m) + _write_cell(sheet, row, "H", window.height_m) + # I = formula =G*H — do not write + _write_cell(sheet, row, "J", vent.opening_type if vent else 0) + _write_cell(sheet, row, "K", vent.num_openings if vent else 0) pct = vent.pct_openable if vent else None - _write_cell(sheet, row, "M", (pct / 100) if pct is not None else 0) - # N = formula =J*M — do not write - # O, P = blank (visual check by auditor) - _write_cell(sheet, row, "Q", vent.trickle_vent_area_mm2 if vent else 0) - _write_cell(sheet, row, "R", vent.num_trickle_vents if vent else 0) - # S = formula =Q*R — do not write + _write_cell(sheet, row, "L", (pct / 100) if pct is not None else 0) + # M = formula =I*L — do not write + _write_cell(sheet, row, "N", vent.trickle_vent_area_mm2 if vent else 0) + _write_cell(sheet, row, "O", vent.num_trickle_vents if vent else 0) + # P = formula =N*O — do not write for i, (room_name, door) in enumerate(doors): row = _DATA_START_ROW + i vent = door.ventilation - _write_cell(sheet, row, "V", room_name) - _write_cell(sheet, row, "W", door.width_mm) - _write_cell(sheet, row, "X", vent.undercut_mm if vent else 0) - # Y = formula =W*X — do not write + _write_cell(sheet, row, "R", room_name) + _write_cell(sheet, row, "S", door.width_mm) + _write_cell(sheet, row, "T", vent.undercut_mm if vent else 0) + # U = formula =S*T — do not write _apply_column_y_formatting(sheet) diff --git a/orchestration/audit_generator_orchestrator.py b/orchestration/audit_generator_orchestrator.py index 3e436348..c87ef70c 100644 --- a/orchestration/audit_generator_orchestrator.py +++ b/orchestration/audit_generator_orchestrator.py @@ -19,7 +19,7 @@ from infrastructure.s3.s3_client import S3Client if TYPE_CHECKING: from orchestration.audit_generator_unit_of_work import AuditGeneratorUnitOfWork -_TEMPLATE_PATH = Path(__file__).parent.parent / "applications" / "audit_generator" / "d1_ventilation_template.xlsx" +_TEMPLATE_PATH = Path(__file__).parent.parent / "applications" / "audit_generator" / "Master Sero Template - Data Extraction.xlsx" _SHEET_NAME = "D1 Ventilation" diff --git a/tests/domain/magicplan/test_ventilation_audit.py b/tests/domain/magicplan/test_ventilation_audit.py index eb7ba7cc..8d2542d3 100644 --- a/tests/domain/magicplan/test_ventilation_audit.py +++ b/tests/domain/magicplan/test_ventilation_audit.py @@ -92,3 +92,110 @@ def test_raises_when_doors_exceed_50() -> None: # 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