Model/backend/documents_parser
Khalim Conn-Kowlessar 8443c77069 Slice 98: API path shower-counts + window-rounding → cert 0330 1e-4
Closes the cert 0330 API path Layer 4 gate (Δ -0.000011 vs worksheet
SAP 61.5993) by surfacing two previously-broken inputs to the HW
cascade plus aligning the wall-net-deduction with the worksheet's
2-d.p.-per-window rounding convention.

(a) RdSAP schema 21.0.x `shower_outlets` shape mismatch:
    real-API certs lodge `[{"shower_outlet_type": N, "shower_wwhrs":
    M}, ...]` (a list of bare ShowerOutlet dicts), but the schema
    modelled it as `[ShowerOutlets]` with nested
    `{"shower_outlet": {...}}` wrappers. `from_dict` silently dropped
    every bare element's payload (left `shower_outlet=None`),
    blanking the cascade's mixer/electric counts on cert 0330 (and 4
    other golden fixtures). Normalisation in `from_api_response`
    rewrites the bare list shape to the wrapped form before
    `from_dict` parses, so the schema's `ShowerOutlets` dataclass
    sees the data it expects — no schema-class breakage downstream.

    New helper `_count_shower_outlets_by_type` walks the normalised
    list and counts outlets by integer code:
    - code 1 → mixer (drives `mixer_shower_count`)
    - code 2 → electric (drives `electric_shower_count`)
    Empirically derived from the golden cohort + Summary mapper
    cross-check (cert 0330 lodges code 2 + Summary surfaces "Electric
    shower"; cert 0240 lodges multiple code-1 outlets on a
    conventional oil-boiler + cylinder dwelling). No spec page
    reference found.

    Wired into both `from_rdsap_schema_21_0_0` and
    `from_rdsap_schema_21_0_1`. Effect on cert 0330 API path:
    `mixer_shower_count` 1 (cascade default) → 0; `electric_shower_
    count` None (= 0) → 1; HW kWh 3172.65 → 2111.93. SAP Δ +2.1155
    → -0.0012.

(b) Per-window 2-d.p. area rounding in wall-net deduction:
    RdSAP 10 §15 rounds per-window area at 2 d.p. before any sum.
    The cascade's `windows_w_per_k_total` branch already rounds
    per-window for the curtain transform; the wall-net deduction
    branch (computing `gross_wall - windows - door` for the (29a)
    line) was rounding the SUM once, which for cert 0330's 9 Main
    windows yields 12.22 m² vs the worksheet's per-window-rounded
    12.23 m² — Δ +0.01 m² × U=1.5 = +0.015 W/K on (29a). Aligned
    both branches to round per-window, matching worksheet line (27).
    SAP Δ -0.0012 → -0.000011.

Layer 4 chain test added:
- `test_api_0330_full_chain_sap_matches_worksheet_pdf_exactly` pins
  cert 0330 API path SAP at 1e-4 vs worksheet 61.5993. This is the
  second boiler validation cert with a Layer 4 1e-4 gate (cert
  001479 is the first).

Re-pinned golden cert residuals (shifted by changes (a) and (b)):
- 0300: PE +7.52 → +8.44, CO2 -0.27 → -0.23 (Slice 98a — electric
  shower count surfaced; cert has 1 electric + 1 mixer outlets)
- 2130: PE -38.17 → -38.18, CO2 +0.305 → +0.304 (Slice 98b —
  window rounding edge)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 18:51:44 +00:00
..
handler address JTK review comments 2026-04-20 15:11:17 +00:00
tests Slice 98: API path shower-counts + window-rounding → cert 0330 1e-4 2026-05-26 18:51:44 +00:00
__init__.py Map to RdSapSiteNotes from site notes JSON 🟥 2026-04-16 13:54:03 +00:00
db_writer.py include updating epc_property_data to pashub to ara workflow 2026-04-29 09:55:14 +00:00
elmhurst_extractor.py Slice 53: Summary_000487 chain pins SAP at 1e-4 — last cohort cert closed 2026-05-24 21:42:42 +00:00
extractor.py Handle wall thickness "Unmeasurable" 🟩 2026-04-30 16:41:16 +00:00
local_runner.py update local runner to work for elmhurst 2026-04-24 14:01:36 +00:00
parser.py load ecmk site notes to db 2026-04-29 11:20:47 +00:00
pdf.py update local runner to work for elmhurst 2026-04-24 14:01:36 +00:00