From 06989d6b0f360b7d4e946a04aee2f8ee4fbffd3a Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 11 Jun 2026 07:54:06 +0000 Subject: [PATCH] fix(elmhurst-extractor): allocate single-glazed alt-wall windows to the alternative wall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The §11 layout parser keys a window's wall Location on the glazing-prefix / orientation tokens around its data row. An alt-wall window lodges its "Alternative wall 1" Location wrapped across the lines bracketing the W×H×A row. For a DOUBLE-glazed alt window the prefix line also carries the glazing phrase ("Double between 2002 Alternative wall"), so the partition breaks there and the location survives into the window's pre-data slice. For a SINGLE-glazed alt window the "Alternative wall" line stands alone with no glazing-type word, so _partition_after_manuf scanned past it and swallowed it into the PREVIOUS window's suffix — the window then defaulted to "External wall" and its opening deducted from the wrong wall. Fix: treat a standalone wall-location line ("Alternative wall" / "External wall" / "Party wall") as a window boundary in _partition_after_manuf, so it attaches to the following window's prefix. Surfaced by simulated case 34 (cert 001431 electric-storage flat): 2 of 4 single-glazed alt-wall windows were mis-allocated, splitting 2.75/10.78 m² instead of the worksheet's 4.63/8.90 corridor/external opening areas. Elmhurst-extractor only; API gauge unaffected. Regression gate green (3 pre-existing fails unrelated); worksheet harness 47/47 unchanged. Case 34's alt-wall opening area now matches the worksheet; the corridor wall net area is correct (the cert's residual is now isolated to the unheated-corridor door, a separate slice). Co-Authored-By: Claude Opus 4.8 --- .../documents_parser/elmhurst_extractor.py | 14 +++++++- .../fixtures/Summary_case34_storage_flat.pdf | Bin 0 -> 81121 bytes .../tests/test_summary_pdf_mapper_chain.py | 33 ++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 backend/documents_parser/tests/fixtures/Summary_case34_storage_flat.pdf diff --git a/backend/documents_parser/elmhurst_extractor.py b/backend/documents_parser/elmhurst_extractor.py index fa5dadc0..55dd04d6 100644 --- a/backend/documents_parser/elmhurst_extractor.py +++ b/backend/documents_parser/elmhurst_extractor.py @@ -1007,7 +1007,17 @@ class ElmhurstSiteNotesExtractor: joined to the data line (no separate prefix line exists), so the only signal of window-transition is the orientation tokens rotating: orient_suffix(k) → orient_prefix(k+1). Falls through - to `next_data_idx` when neither marker is present.""" + to `next_data_idx` when neither marker is present. + + (c) A standalone wall-location line ("Alternative wall", "External + wall", "Party wall") in the gap belongs to the NEXT window's + prefix — it is that window's §11 Location cell, wrapped above its + W×H×A data row. When the next window is single-glazed its prefix + line carries no glazing-type word (branch a never fires), so + without this the "Alternative wall" line is swallowed into the + current window's suffix and the next window defaults to "External + wall" (simulated case 34: 2 of 4 single-glazed alt-wall windows + mis-allocated → wrong corridor-wall net area).""" scan_start = manuf_idx + 4 seen_orient = False for j in range(scan_start, next_data_idx): @@ -1015,6 +1025,8 @@ class ElmhurstSiteNotesExtractor: first_word = stripped.split(" ", 1)[0] if first_word in self._GLAZING_TYPE_PREFIX_WORDS: return j + if "wall" in stripped.lower(): + return j if stripped in self._ORIENTATION_TOKENS: if seen_orient: return j diff --git a/backend/documents_parser/tests/fixtures/Summary_case34_storage_flat.pdf b/backend/documents_parser/tests/fixtures/Summary_case34_storage_flat.pdf new file mode 100644 index 0000000000000000000000000000000000000000..33cf586d1c18f9a1857ebbc52fd9217bc1a41676 GIT binary patch literal 81121 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 literal 0 HcmV?d00001 diff --git a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py index fbc1c6f9..a0963de3 100644 --- a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py +++ b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py @@ -85,6 +85,11 @@ _SUMMARY_000904_PDF = _FIXTURES / "Summary_000904.pdf" # cert 9285 _SUMMARY_000900_PDF = _FIXTURES / "Summary_000900.pdf" # cert 2225 _SUMMARY_000898_PDF = _FIXTURES / "Summary_000898.pdf" # cert 2636 _SUMMARY_000902_PDF = _FIXTURES / "Summary_000902.pdf" # cert 9418 +# simulated case 34 (cert 001431 reconfigured as a slimline electric-storage +# flat with an unheated corridor / sheltered alternative wall + 4 alt-wall +# windows). Regression net for the flat-roof, sheltered-wall, and §11 +# alt-wall-window-allocation fixes. +_SUMMARY_CASE34_PDF = _FIXTURES / "Summary_case34_storage_flat.pdf" _SUMMARY_000889_PDF = _FIXTURES / "Summary_000889.pdf" # cert 2536 (Normal cylinder) _SUMMARY_000884_PDF = _FIXTURES / "Summary_000884.pdf" # cert 9421 (Normal cylinder) _SUMMARY_000910_PDF = _FIXTURES / "Summary_000910.pdf" # cert 0036 (Flat, party wall U=0) @@ -1547,6 +1552,34 @@ def test_summary_mapper_raises_on_unmapped_cylinder_insulation_label() -> None: assert excinfo.value.value == "Polyester wool" +def test_case34_alt_wall_windows_all_allocated_to_alternative_wall() -> None: + # Arrange — simulated case 34 lodges 4 windows on "Alternative wall 1" + # (0.70 + 1.75 + 1.18 + 1.00 = 4.63 m²) and 6 on the external wall. The + # §11 layout interleaves the wrapped "Alternative wall / 1" Location cell + # around each window's data row; for single-glazed alt windows the + # location line carries no glazing-type word, so the partition swallowed + # it into the previous window's suffix and the window defaulted to + # "External wall" — mis-deducting its opening from the wrong wall. + from domain.sap10_calculator.worksheet.heat_transmission import ( + _window_on_alt_wall, # pyright: ignore[reportPrivateUsage] + ) + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_CASE34_PDF) + site_notes = ElmhurstSiteNotesExtractor(pages).extract() + epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes) + + # Act + alt_area = sum( + round(float(w.window_width) * float(w.window_height), 2) + for w in (epc.sap_windows or []) + if _window_on_alt_wall(w) + ) + alt_count = sum(1 for w in (epc.sap_windows or []) if _window_on_alt_wall(w)) + + # Assert — all 4 alt-wall windows recovered (worksheet alt openings 4.63). + assert alt_count == 4 + assert abs(alt_area - 4.63) <= 0.01 + + def test_map_elmhurst_alternative_wall_carries_sheltered_flag() -> None: # Arrange — Elmhurst Summary §7 lodges "Alternative Wall N Sheltered # Wall: Yes" for a sub-area adjacent to an unheated buffer (e.g. a flat's