Model/backend/documents_parser/tests
Khalim Conn-Kowlessar 482ce88b55 Slice S0380.161: SAP 10.2 Table 5a warm-air fan gain (SFP × 0.04 × V)
SAP 10.2 Table 5a (PDF p.177) row "Warm air heating system fans
a) c)" computes the gain as SFP × 0.04 × V (W). Footnote c) sets
the default SFP to 1.5 W/(l/s) when no PCDB warm-air-unit record
is lodged; footnote a) applies the heating-season-only mask
(zero in summer months). Footnote c) further omits the gain when
the dwelling has balanced whole-house mechanical ventilation
(MVHR / MV) — same omission as the Table 4f kWh-side footnote e).

Pre-slice the cascade's `internal_gains_from_cert` only wired the
central-heating-pump row of Table 5a; the warm-air-fan gain helper
(`warm_air_heating_fan_w`) existed but was unwired. The kWh-side
parallel (Table 4f, 136.35 kWh/yr) was wired in S0380.158 — this
slice closes the symmetry on the gain side.

Per-line walk on electric 2 (SAP code 524 = Cat 5 ASHP with
warm-air distribution, V = 227.25 m³, no balanced MV):

  worksheet (70)[Jan] = 13.6350 W
  cascade (70)[Jan]   = 0.0000 W      delta = -13.635 W
  worksheet (98c)[Jan] = 1600.43 kWh
  cascade (98c)[Jan]  = 1608.12 kWh   delta = +7.69 kWh

13.635 W = 1.5 × 0.04 × 227.25 exactly. The -13.6 W winter gain
shortfall propagates through the §7 utilisation cascade and over-
states cascade SH demand by ~57 kWh/yr (cascade 9483 vs worksheet
9426), under-charging cost by ~£2.50 with opposite sign to the
S0380.156-.158 closures.

Fix: new `_any_main_system_has_warm_air_distribution(epc)` +
`_has_balanced_mechanical_ventilation(epc)` predicates in
`internal_gains.py`, mirroring `cert_to_inputs._TABLE_4A_WARM_AIR_SAP_CODES`
+ `_BALANCED_MV_KIND_NAMES` (kept here as siblings so the worksheet
layer stays free of rdsap deps). Orchestrator wires
`warm_air_heating_fan_w(sfp=1.5, dwelling_volume_m3)` into the
heating-season term of `pumps_fans_monthly_w` when warm-air
distribution is present and balanced MV is not.

Closures electric 2:
  ΔSAP_c -0.1087 → -0.0000 EXACT
  Δcost  +£2.50 → -£0.00 EXACT
  ΔCO2   +16.54 → +11.95 (joins lighting-PE deferred cohort)
  ΔPE    +97.69 → +48.66 (joins lighting-PE deferred cohort)

Electric 2 joins the 15-variant lighting-PE deferred cohort
(electric 1 + electric 3/5/6/7/8/9 + solid fuel 5/6/7/8 + solid
fuel 4/9/10/11 + electric 2) where SAP/cost are EXACT but PE/CO2
carry an Elmhurst-vs-spec MONTHLY-factor offset (cohort uses
Table 12 annual factors on the off-peak HW immersion line; spec
mandates Table 12d/12e monthly per the header).

Verbatim spec quote (SAP 10.2 Table 5a row "Warm air heating
system fans a) c)", PDF p.177):
  "Warm air heating system fans a) c)  SFP × 0.04 × V"
  Footnote c): "SFP is the specific fan power from the database
    record for the warm air unit if applicable; otherwise
    1.5 W/(l/s). These values of SFP include an in-use factor.
    If the heating system is a warm air unit and there is balanced
    whole house mechanical ventilation, the gains for the warm air
    system should not be included."
  Footnote a): "... Set to zero in summer months. ..."

Σ |ΔSAP_c| across 25-variant cohort: 0.18 → 0.07 (~60% reduction).
No regressions on the other 24 variants or any golden fixture —
gate keyed on Table 4a warm-air SAP code frozenset (only electric
2 in the corpus has a code in that set).

Tests: 905 pass (+1), 0 fail. Pyright net-zero (35 → 35).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 23:16:57 +00:00
..
fixtures Slice S0380.52: cert 000565 Elmhurst-only mapper-driven cascade pin + glazing-label coverage 2026-05-28 22:03:52 +00:00
__init__.py Map to RdSapSiteNotes from site notes JSON 🟥 2026-04-16 13:54:03 +00:00
test_elmhurst_end_to_end.py Slice S0380.17: map Elmhurst §11 glazing-type labels to SAP10 codes 2026-05-27 23:05:52 +00:00
test_elmhurst_extractor.py extract window frame details from elmhurst site notes 🟥 2026-04-27 15:50:25 +00:00
test_end_to_end.py P6.1 follow-on: unbox BuildingPartIdentifier at backend boundaries 2026-05-20 09:58:23 +00:00
test_extractor.py Handle wall thickness "Unmeasurable" 🟩 2026-04-30 16:41:16 +00:00
test_heating_systems_corpus.py Slice S0380.161: SAP 10.2 Table 5a warm-air fan gain (SFP × 0.04 × V) 2026-06-01 23:16:57 +00:00
test_pdf.py rename example site notes to PasHub_ and add Elmhurst example 2026-04-24 13:01:51 +00:00
test_summary_pdf_mapper_chain.py Slice S0380.143: RdSAP 10 §10.11 Table 29 — derive cylinder insulation defaults from construction age band when §15.1 lodges "No Access" 2026-05-31 21:03:10 +00:00