Model/domain/sap10_calculator/docs/HANDOVER_PV_BETA_SPLIT.md
Khalim Conn-Kowlessar 33edff136a docs: PV β-split phase COMPLETE handover (6/6 slices)
Finalises the handover doc after S0380.49 ships the effective-monthly
Table 12e PE factor for the PV split. Full cohort residual trajectory
table across all four milestones (pre-44 / post-45 / post-48 /
post-49), final cross-cascade architecture diagram, and the punch-list
of open work (β fine-tuning, HP electricity demand, monthly E_PV
distribution) — none in the β-split phase scope, each a candidate
follow-up slice.

Cluster PE residual closed by ~50% magnitude over the phase:
-7..-14 → -2.8..-3.7 kWh/m². CO2 all <0.11 t/yr; SAP all exact.
2026-05-28 19:28:34 +00:00

7 KiB
Raw Blame History

Handover — PV β-factor split (6/6 COMPLETE)

Branch feature/per-cert-mapper-validation. This phase shipped 6 slices (S0380.44 → S0380.49) that implemented and wired the full SAP 10.2 Appendix M1 β-factor across PE, CO2, and Cost cascades, surfaced the real-API battery capacity, and wired effective-monthly Table 12e PE factors for the PV split.

HEAD at handover end: e75198ce (Slice S0380.49). Test suite: 763 pass + 0 fail.

Slices shipped this phase

Slice Commit What Spec
S0380.44 5344bc89 New module worksheet/photovoltaic.py with pv_split_monthly, pv_beta_coefficients, PhotovoltaicSplit + 13 unit tests Appendix M1 §3c-d (p.94), §4 (p.94)
S0380.45 49de18e8 Wired β-split into PE cascade Appendix M1 §3a (p.93), §8 (p.94)
S0380.46 5b269f23 Wired β-split into CO2 cascade Appendix M1 §7 (p.94), Table 12d code 60
S0380.47 42ed38f7 Wired β-split into cost cascade (zero cohort impact — Table 32 collapses code 30 = code 60 = 13.19 p/kWh) Appendix M1 §6 (p.94), Table 32 code 30/60
S0380.48 bf99b1c7 E_PV "magnitude bug" audit revealed the real bug: schema gap on pv_batteries[].battery_capacity flat shape. Schema fix + mapper fall-back surfaced the 5-kWh batteries. Cohort PE +2.7..+8.1 → -3.5..-4.5 Appendix M1 §3c (p.94)
S0380.49 e75198ce Wired effective-monthly Table 12e PE factors (pv_dwelling_primary_factor + pv_exported_primary_factor) for the PV split. Cluster closed -3.5..-4.5 → -2.8..-3.7 Appendix M1 §8 (p.94), Table 12e code 30/60

Residual progress — full PE cohort trajectory

Cert Pre-S0380.44 Post-S0380.45 Post-S0380.48 Post-S0380.49
0330 (no PV) +0.44 +0.44 +0.44 +0.44
0350 (PV+5kWh) 7.78 +2.73 3.58 2.96
0380 (PV+5kWh) 14.60 +8.09 4.01 3.06
2130 (PV gas) 38.63 9.70 9.70 8.22
2225 (PV+5kWh) 11.77 +4.48 4.50 3.73
2636 (PV+5kWh) 9.65 +3.42 4.14 3.44
3800 (PV+5kWh) 9.61 +3.58 4.01 3.25
9285 (PV+5kWh) 7.96 +3.20 3.46 2.81
9418 (PV+5kWh) 7.30 +4.67 3.76 3.01
9501 (PV no battery) 8.28 +0.25 +0.25 +0.65

CO2 residuals all <0.11 t/yr; SAP scores all exact (except 2130 at +1 — pre-existing, a gas-combi/secondary-heating gap unrelated to PV). 9501 drifted slightly because its β=0.498 already matched worksheet exactly, so the factor correction surfaced a small previously-hidden gap. Cluster shows clean closure trajectory.

Remaining work (open front)

The 7-cert ASHP+battery cluster now sits at -2.8..-3.7 kWh/m² PE. The differential breakdown:

  1. β fine-tuning (~1-2 kWh/m²): cascade β = 0.751-0.812 vs worksheet β = 0.7426 for cert 0380. This is a monthly D_PV distribution detail. The _pv_eligible_demand_monthly_kwh helper sums lighting/appliances/cooking/electric-shower/pumps-fans/main-1 /hot-water tuples; their relative monthly weighting affects β.

  2. Heat pump electricity demand (~1 kWh/m²): the ASHP cohort has main-heating-fuel = electricity. The cascade's D_PV inclusion list may not perfectly match the worksheet's footnote 32 restrictions ("excludes electricity used for off-peak space and water heating"). Verify against worksheet line refs for cert 0380.

  3. Possible small E_PV or pv_split monthly distribution gaps (~0.5 kWh/m²): per-month E_PV in the cascade vs worksheet may differ marginally if _pv_array_monthly_generation_kwh uses slightly different solar-flux interpolation than the worksheet.

Each of these is a candidate for a follow-up slice but not part of the β-split phase scope.

Architecture: cross-cascade β-split shape (final)

All three cascades use the uniform shape:

Cascade Dwelling factor (IMPORT) Exported factor (EXPORT)
PE pv_dwelling_primary_factor → fall back to other_primary_factor (1.501) pv_exported_primary_factor → fall back to pv_export_primary_factor (0.501)
CO2 pv_dwelling_co2_factor_kg_per_kwh → fall back to no credit pv_exported_co2_factor_kg_per_kwh → fall back to no credit
Cost pv_dwelling_import_price_gbp_per_kwh → Table 32 code 30 (13.19 p/kWh) pv_export_credit_gbp_per_kwh → Table 32 code 60 (13.19 p/kWh)

Shared cross-cascade state:

  • pv_dwelling_kwh_per_yr + pv_exported_kwh_per_yr on CalculatorInputs carry the β-split per-year totals.
  • All factor fields are Optional[float] = None (defaults preserve the legacy synthetic-construction behaviour in unit tests).
  • cert_to_inputs populates them via _effective_monthly_co2_factor / _effective_monthly_pe_factor over pv_split.epv_*_monthly_kwh, keyed on Table 12 code 30 for dwelling and code 60 for exported.

Test baseline at HEAD

PYTHONPATH=/workspaces/model python -m pytest \
    backend/documents_parser/tests/test_summary_pdf_mapper_chain.py \
    backend/documents_parser/tests/test_elmhurst_extractor.py \
    backend/documents_parser/tests/test_elmhurst_end_to_end.py \
    domain/sap10_calculator/worksheet/tests/test_e2e_elmhurst_sap_score.py \
    domain/sap10_calculator/worksheet/tests/test_water_heating.py \
    domain/sap10_calculator/worksheet/tests/test_mean_internal_temperature.py \
    domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py \
    domain/sap10_calculator/rdsap/tests/test_golden_fixtures.py \
    domain/sap10_calculator/tests/test_pcdb_table_362_lookup.py \
    domain/sap10_ml/tests/test_rdsap_uvalues.py \
    datatypes/epc/schema/tests/test_schema_loading.py \
    domain/sap10_calculator/worksheet/tests/test_photovoltaic.py \
    --no-cov -q

Expected: 763 pass + 0 fail.

Conventions preserved