mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
319 lines
10 KiB
Python
319 lines
10 KiB
Python
import openpyxl
|
||
from datatypes.magicplan.domain.models import Door, Floor, Plan, Room, Window
|
||
from backend.magic_plan.audit_script import AuditRow, _apply_section_borders, _build_rows, _write_data_rows, _write_headers
|
||
|
||
|
||
def test_build_rows_opening_and_trickle_vent_fields_are_none() -> None:
|
||
# Arrange — plan with one window so opening/trickle_vent fields are exercised
|
||
plan = Plan(
|
||
uid="x",
|
||
name=None,
|
||
floors=[
|
||
Floor(level=0, name=None, rooms=[
|
||
Room(
|
||
name="Living Room",
|
||
width_m=3.0,
|
||
length_m=4.0,
|
||
area_m2=12.0,
|
||
windows=[Window(width_m=1.0, height_m=1.0, area_m2=1.0, opening_type="side_hung")],
|
||
),
|
||
]),
|
||
],
|
||
)
|
||
|
||
# Act
|
||
rows = _build_rows(plan)
|
||
|
||
# Assert — all new forward-compatible fields default to None
|
||
data_row = next(r for r in rows if r.window_label is not None)
|
||
assert data_row.opening_count is None
|
||
assert data_row.opening_width_m is None
|
||
assert data_row.opening_height_m is None
|
||
assert data_row.opening_total_area_m2 is None
|
||
assert data_row.trickle_vent_blocked is None
|
||
assert data_row.trickle_vent_pictured is None
|
||
assert data_row.trickle_vent_effective_area is None
|
||
assert data_row.trickle_vent_count is None
|
||
assert data_row.trickle_vent_total_effective_area is None
|
||
|
||
|
||
def test_build_rows_empty_plan_returns_empty_list() -> None:
|
||
# Arrange
|
||
plan = Plan(uid="x", name=None, floors=[])
|
||
|
||
# Act
|
||
rows: list[AuditRow] = _build_rows(plan)
|
||
|
||
# Assert
|
||
assert rows == []
|
||
|
||
|
||
def test_build_rows_single_room_no_windows_or_doors() -> None:
|
||
# Arrange
|
||
plan = Plan(
|
||
uid="x",
|
||
name=None,
|
||
floors=[
|
||
Floor(level=0, name=None, rooms=[
|
||
Room(name="Hall", width_m=2.0, length_m=3.0, area_m2=6.0),
|
||
]),
|
||
],
|
||
)
|
||
|
||
# Act
|
||
rows: list[AuditRow] = _build_rows(plan)
|
||
|
||
# Assert — separator + one data row
|
||
assert len(rows) == 2
|
||
separator, data = rows
|
||
assert separator.floor_level == 0
|
||
assert data.room_name == "Hall"
|
||
assert data.room_width_m == 2.0
|
||
assert data.room_length_m == 3.0
|
||
assert data.room_area_m2 == 6.0
|
||
assert data.window_label is None
|
||
assert data.door_location is None
|
||
|
||
|
||
def test_build_rows_room_with_more_windows_than_doors() -> None:
|
||
# Arrange — 2 windows, 1 door → 2 data rows; room columns blank on row 2
|
||
plan = Plan(
|
||
uid="x",
|
||
name=None,
|
||
floors=[
|
||
Floor(level=0, name=None, rooms=[
|
||
Room(
|
||
name="Lounge",
|
||
width_m=4.0,
|
||
length_m=5.0,
|
||
area_m2=20.0,
|
||
windows=[
|
||
Window(width_m=1.0, height_m=1.0, area_m2=1.0, opening_type="side_hung"),
|
||
Window(width_m=0.8, height_m=1.0, area_m2=0.8, opening_type="top_hung"),
|
||
],
|
||
doors=[Door(width_mm=762.0)],
|
||
),
|
||
]),
|
||
],
|
||
)
|
||
|
||
# Act
|
||
rows: list[AuditRow] = _build_rows(plan)
|
||
|
||
# Assert
|
||
data_rows = [r for r in rows if r.floor_level is None]
|
||
assert len(data_rows) == 2
|
||
# row 0 carries room columns
|
||
assert data_rows[0].room_name == "Lounge"
|
||
assert data_rows[0].window_label == "W1"
|
||
assert data_rows[0].door_location == "Lounge"
|
||
# row 1 room columns are blank
|
||
assert data_rows[1].room_name is None
|
||
assert data_rows[1].window_label == "W2"
|
||
assert data_rows[1].door_location is None
|
||
|
||
|
||
def test_build_rows_room_with_more_doors_than_windows() -> None:
|
||
# Arrange — 1 window, 2 doors → 2 data rows
|
||
plan = Plan(
|
||
uid="x",
|
||
name=None,
|
||
floors=[
|
||
Floor(level=0, name=None, rooms=[
|
||
Room(
|
||
name="Bedroom",
|
||
width_m=3.5,
|
||
length_m=4.0,
|
||
area_m2=14.0,
|
||
windows=[Window(width_m=1.2, height_m=1.0, area_m2=1.2, opening_type="side_hung")],
|
||
doors=[Door(width_mm=762.0), Door(width_mm=686.0)],
|
||
),
|
||
]),
|
||
],
|
||
)
|
||
|
||
# Act
|
||
rows: list[AuditRow] = _build_rows(plan)
|
||
|
||
# Assert
|
||
data_rows = [r for r in rows if r.floor_level is None]
|
||
assert len(data_rows) == 2
|
||
assert data_rows[0].window_label == "W1"
|
||
assert data_rows[0].door_width_mm == 762.0
|
||
assert data_rows[1].window_label is None
|
||
assert data_rows[1].door_width_mm == 686.0
|
||
|
||
|
||
def test_build_rows_floor_separator_precedes_rooms() -> None:
|
||
# Arrange
|
||
plan = Plan(
|
||
uid="x",
|
||
name=None,
|
||
floors=[
|
||
Floor(level=0, name=None, rooms=[
|
||
Room(name="Room A", width_m=1.0, length_m=1.0, area_m2=1.0),
|
||
]),
|
||
Floor(level=1, name=None, rooms=[
|
||
Room(name="Room B", width_m=1.0, length_m=1.0, area_m2=1.0),
|
||
]),
|
||
],
|
||
)
|
||
|
||
# Act
|
||
rows: list[AuditRow] = _build_rows(plan)
|
||
|
||
# Assert — rows in order: sep(0), data, sep(1), data
|
||
assert rows[0].floor_level == 0
|
||
assert rows[1].room_name == "Room A"
|
||
assert rows[2].floor_level == 1
|
||
assert rows[3].room_name == "Room B"
|
||
|
||
|
||
def test_build_rows_window_labels_sequential_across_floors() -> None:
|
||
# Arrange — two floors, one window each; labels must be W1, W2 (no reset)
|
||
plan = Plan(
|
||
uid="x",
|
||
name=None,
|
||
floors=[
|
||
Floor(level=0, name=None, rooms=[
|
||
Room(
|
||
name="Ground Room",
|
||
width_m=1.0,
|
||
length_m=1.0,
|
||
area_m2=1.0,
|
||
windows=[Window(width_m=1.0, height_m=1.0, area_m2=1.0, opening_type="side_hung")],
|
||
),
|
||
]),
|
||
Floor(level=1, name=None, rooms=[
|
||
Room(
|
||
name="First Room",
|
||
width_m=1.0,
|
||
length_m=1.0,
|
||
area_m2=1.0,
|
||
windows=[Window(width_m=1.0, height_m=1.0, area_m2=1.0, opening_type="top_hung")],
|
||
),
|
||
]),
|
||
],
|
||
)
|
||
|
||
# Act
|
||
rows: list[AuditRow] = _build_rows(plan)
|
||
|
||
# Assert
|
||
labels = [r.window_label for r in rows if r.window_label is not None]
|
||
assert labels == ["W1", "W2"]
|
||
|
||
|
||
def test_build_rows_room_name_in_location_on_every_row() -> None:
|
||
# Arrange — room with 2 windows and 2 doors
|
||
plan = Plan(
|
||
uid="x",
|
||
name=None,
|
||
floors=[
|
||
Floor(level=0, name=None, rooms=[
|
||
Room(
|
||
name="Kitchen",
|
||
width_m=3.0,
|
||
length_m=3.0,
|
||
area_m2=9.0,
|
||
windows=[
|
||
Window(width_m=1.0, height_m=1.0, area_m2=1.0, opening_type="side_hung"),
|
||
Window(width_m=0.8, height_m=1.0, area_m2=0.8, opening_type="top_hung"),
|
||
],
|
||
doors=[Door(width_mm=762.0), Door(width_mm=686.0)],
|
||
),
|
||
]),
|
||
],
|
||
)
|
||
|
||
# Act
|
||
rows: list[AuditRow] = _build_rows(plan)
|
||
|
||
# Assert — every data row carries "Kitchen" in location fields
|
||
data_rows = [r for r in rows if r.floor_level is None]
|
||
for row in data_rows:
|
||
assert row.window_location == "Kitchen"
|
||
assert row.door_location == "Kitchen"
|
||
|
||
|
||
def test_write_headers_two_rows_correct_labels_and_column_positions() -> None:
|
||
# Arrange
|
||
wb = openpyxl.Workbook()
|
||
ws = wb.active # type: ignore[assignment]
|
||
|
||
# Act
|
||
_write_headers(ws)
|
||
|
||
# Assert — group labels in row 1 at the new column positions
|
||
assert ws.cell(row=1, column=1).value == "Room"
|
||
assert ws.cell(row=1, column=6).value == "Window Area"
|
||
assert ws.cell(row=1, column=12).value == "Openings"
|
||
assert ws.cell(row=1, column=16).value == "Trickle Vents"
|
||
assert ws.cell(row=1, column=22).value == "Doors"
|
||
# Assert — "Door Undercuts" label no longer used
|
||
assert ws.cell(row=1, column=28).value is None
|
||
# Assert — only two header rows; row 3 is empty
|
||
assert ws.cell(row=3, column=1).value is None
|
||
assert ws.cell(row=3, column=13).value is None
|
||
# Assert — opening columns use self-describing labels (no "Opening 1" sub-header)
|
||
assert ws.cell(row=2, column=12).value == "No. of Openings"
|
||
assert ws.cell(row=2, column=13).value == "Opening Width m"
|
||
assert ws.cell(row=2, column=14).value == "Opening Height m"
|
||
assert ws.cell(row=2, column=15).value == "Total Area m²"
|
||
|
||
|
||
def test_apply_section_borders_sets_medium_right_border_on_boundary_columns() -> None:
|
||
# Arrange
|
||
wb = openpyxl.Workbook()
|
||
ws = wb.active # type: ignore[assignment]
|
||
|
||
# Act
|
||
_apply_section_borders(ws, 1)
|
||
|
||
# Assert — medium right-border on last col of each subtable
|
||
for col in (4, 11, 15, 20):
|
||
assert ws.cell(row=1, column=col).border.right.style == "medium", f"col {col}"
|
||
# Assert — non-boundary columns are untouched
|
||
for col in (1, 6, 12, 22):
|
||
assert ws.cell(row=1, column=col).border.right.style is None, f"col {col}"
|
||
|
||
|
||
def test_write_data_rows_places_door_data_at_cols_22_to_25() -> None:
|
||
# Arrange
|
||
wb = openpyxl.Workbook()
|
||
ws = wb.active # type: ignore[assignment]
|
||
rows = [
|
||
AuditRow(
|
||
room_name="Hall",
|
||
room_width_m=2.0,
|
||
room_length_m=3.0,
|
||
room_area_m2=6.0,
|
||
door_location="Hall",
|
||
door_width_mm=762.0,
|
||
),
|
||
]
|
||
|
||
# Act
|
||
_write_data_rows(ws, rows)
|
||
|
||
# Assert — door columns at V–Y (22–25), not old positions 28–31
|
||
# data starts at row 3 (after 2 header rows)
|
||
assert ws.cell(row=3, column=22).value == "Hall"
|
||
assert ws.cell(row=3, column=23).value == 762.0
|
||
assert ws.cell(row=3, column=28).value is None
|
||
assert ws.cell(row=3, column=29).value is None
|
||
|
||
|
||
def test_write_headers_blank_separator_columns_have_no_fill() -> None:
|
||
# Arrange
|
||
wb = openpyxl.Workbook()
|
||
ws = wb.active # type: ignore[assignment]
|
||
|
||
# Act
|
||
_write_headers(ws)
|
||
|
||
# Assert — cols 5 (E) and 21 (U) have no fill in either header row
|
||
for col in (5, 21):
|
||
for row in (1, 2):
|
||
fill = ws.cell(row=row, column=col).fill
|
||
assert fill.patternType is None, f"col {col} row {row} should have no fill"
|