From d743f6fb3bfdd967c33a92f0d0b6bf301fe22741 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 14 Jul 2026 23:11:09 +0000 Subject: [PATCH] =?UTF-8?q?Capture=20the=20per-surface=20RIR=20insulation-?= =?UTF-8?q?known=20answer=20from=20the=20survey=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .../documents_parser/tests/test_extractor.py | 120 ++++++++++++++++++ .../epc/surveys/pashub_rdsap_site_notes.py | 4 + 2 files changed, 124 insertions(+) diff --git a/backend/documents_parser/tests/test_extractor.py b/backend/documents_parser/tests/test_extractor.py index 9e356acd9..444742b48 100644 --- a/backend/documents_parser/tests/test_extractor.py +++ b/backend/documents_parser/tests/test_extractor.py @@ -842,6 +842,126 @@ class TestRoofSpaceRoomInRoof: RoomInRoofSurfaceDetail(length_m=4.94, height_m=5.17), ] + # The same survey's RIR block in full, INCLUDING the per-surface + # "{surface}: Are you able to provide details of the insulation …?" + # questions (the question text wraps onto a second PDF line; the Yes/No + # sits two tokens after the "{prefix}: Are you able …" line). Verbatim + # from accuracy fixture deal 499617935574 tokens — the dwelling whose + # loft hatch was screwed shut, so every roof-going surface answers "No". + _RIR_BLOCK_WITH_INSULATION_QUESTIONS = [ + "Are there rooms in the roof?", + "Yes", + "Roof room age range:", + "B: 1900 - 1929", + "Floor area of room in roof:", + "32.35 m²", + "Are details of the room in roof known?", + "Yes", + "Are details of the gable walls known?", + "Gable wall 1 & 2", + "Gable wall 1 length:", + "6.64 m", + "Gable wall 1 height:", + "2.74 m", + "Page 7", + "", + "Gable wall 1 U-Value:", + "Unknown", + "Gable wall 1: Are you able to provide details of the", + "insulation type?", + "Yes", + "Gable wall 1 type:", + "Exposed", + "Gable wall 2 length:", + "6.64 m", + "Gable wall 2 height:", + "2.74 m", + "Gable wall 2 U-Value:", + "Unknown", + "Gable wall 2: Are you able to provide details of the", + "insulation type?", + "Yes", + "Gable wall 2 type:", + "Party", + "Are details of the stud walls known?", + "None", + "Are details of the slope walls known?", + "Slope 1 & 2", + "Slope 1 length:", + "4.94 m", + "Slope 1 height:", + "1.73 m", + "Slope 1 U-Value:", + "Unknown", + "Slope 1: Are you able to provide details of the", + "insulation type and thickness?", + "No", + "Slope 2 length:", + "5 m", + "Slope 2 height:", + "2.95 m", + "Slope 2 U-Value:", + "Unknown", + "Slope 2: Are you able to provide details of the", + "insulation type and thickness?", + "No", + "Are details of the common walls known?", + "Common wall 1 & 2", + "Common wall 1 length:", + "4.94 m", + "Common wall 1 height:", + "1.45 m", + "Common wall 1 U-Value:", + "Unknown", + "Common wall 2 length:", + "4.94 m", + "Common wall 2 height:", + "1.3 m", + "Common wall 2 U-Value:", + "Unknown", + "Are details of the flat ceiling known?", + "Flat ceiling 1", + "Flat ceiling 1 length:", + "4.94 m", + "Flat ceiling 1 height:", + "5.17 m", + "Flat ceiling 1 U-Value:", + "Unknown", + "Flat ceiling 1: Are you able to provide details of the", + "insulation thickness, type or location?", + "No", + ] + + def test_per_surface_insulation_known_answers_are_captured(self) -> None: + # Arrange — fixture 1's roof space with the question-bearing RIR + # block spliced in place of its "rooms in the roof? No" lodging. + lines = load_text_fixture() + idx = lines.index("Are there rooms in the roof?") + spliced = ( + lines[:idx] + + self._RIR_BLOCK_WITH_INSULATION_QUESTIONS + + lines[idx + 2 :] + ) + + # Act + rir = ( + PasHubRdSapSiteNotesExtractor(spliced) + .extract_roof_space() + .main_building.room_in_roof + ) + + # Assert — gables answered Yes, slopes/flat ceiling answered No; + # common walls are never asked, so stay None (unknown). + assert rir is not None + assert rir.gables is not None + assert [g.insulation_known for g in rir.gables] == [True, True] + assert rir.slopes is not None + assert [s.insulation_known for s in rir.slopes] == [False, False] + assert rir.common_walls is not None + assert [c.insulation_known for c in rir.common_walls] == [None, None] + assert rir.flat_ceilings is not None + assert [f.insulation_known for f in rir.flat_ceilings] == [False] + def test_no_rooms_in_roof_leaves_detail_absent(self) -> None: # Arrange / Act — fixture 1 as lodged ("No"). detail = PasHubRdSapSiteNotesExtractor( diff --git a/datatypes/epc/surveys/pashub_rdsap_site_notes.py b/datatypes/epc/surveys/pashub_rdsap_site_notes.py index 564f5a425..aa278425e 100644 --- a/datatypes/epc/surveys/pashub_rdsap_site_notes.py +++ b/datatypes/epc/surveys/pashub_rdsap_site_notes.py @@ -116,6 +116,10 @@ class RoomInRoofSurfaceDetail: height_m: Optional[float] = None u_value: Optional[float] = None gable_type: Optional[str] = None + # The surveyor's per-surface "{surface}: Are you able to provide details + # of the insulation …?" Yes/No; None when the form didn't ask (legacy + # forms, and common walls — never asked on any form). + insulation_known: Optional[bool] = None @dataclass