Slice S0380.33: round synthesized PV kWp to 2 d.p. per RdSAP10 §15 — closes cert 6835 +0.015 SAP residual

RdSAP10 §15 p.66 (Rounding of data):
    "kWp for photovoltaics, etc.: 2 d.p."

Cert 6835 lodges Photovoltaic Supply as "Proportion of roof
area = 40" (no explicit kWp). Per RdSAP10 §11.1 b) p.60 the
cascade synthesizes kWp = 0.12 × PV area where PV area is
roof_area / cos(35°). For cert 6835:
    PV area = 36.9 × 0.40 / cos(35°) = 18.0186 m^2
    kWp unrounded = 0.12 x 18.0186  = 2.16224
    kWp at 2 d.p. = 2.16             (matches worksheet
                                       "Cells Peak = 2.16")

SAP 10.2 §M1 EPV = 0.8 x kWp x S x ZPV. With the 0.0022 kWp
delta the cascade was overstating PV generation by 1.5448 kWh/yr,
adding -0.20 GBP to (252) total PV credit, dropping (255) total
energy cost by 0.20, lowering ECF and raising SAP by +0.015.

Cohort-2 distribution after S0380.31..S0380.33:
    35 exact + 3 <=0.07 (was 34 + 4 at S0380.32 HEAD).
Cert 6835: +0.014534 -> -4.3e-5.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-28 15:18:16 +00:00
parent 396907f46a
commit 2c3eb17b96
2 changed files with 20 additions and 11 deletions

View file

@ -883,7 +883,13 @@ def _synthesize_pv_arrays_from_percent_roof_area(
if is_pitched:
bp_pv_area /= cos_factor
pv_area_m2 += bp_pv_area
kwp = _PV_PEAK_POWER_KWP_PER_M2 * pv_area_m2
# RdSAP10 §15 p.66: "kWp for photovoltaics, etc.: 2 d.p." — round
# before the EPV cascade so it matches the worksheet's "Cells Peak"
# column (cert 6835: cascade 0.12 × 36.9 × 0.40 / cos(35°) = 2.16224
# → 2.16, matching worksheet "Cells Peak = 2.16"). The 0.0022 kWp
# delta otherwise feeds straight into (233) PV generation as a
# +1.5 kWh/yr over-credit and a +0.015 SAP residual.
kwp = _round_half_up(_PV_PEAK_POWER_KWP_PER_M2 * pv_area_m2, 2)
if kwp <= 0:
return None
return [

View file

@ -481,10 +481,14 @@ def test_pv_generation_synthesizes_array_from_percent_roof_area_per_rdsap_11_1()
Cohort-2 cert 6835 (Semi-Detached bungalow, single storey, TFA 36.9
, pitched roof) lodges only "Proportion of roof area = 40" the
cascade must synthesize a 2.16 kWp array (= 36.9 × 0.40 / cos(35°)
× 0.12) and route the generation through the Appendix M cost
cascade. Verified against the worksheet's "Cells Peak = 2.16,
Orientation = South, Elevation = 30°, Overshading = Modest" line.
cascade must synthesize the array and route the generation through
the Appendix M cost cascade. Worksheet (cert 6835 dr87-0001-000624)
lodges "Cells Peak = 2.16, Orientation = South, Elevation = 30°,
Overshading = Modest" and worksheet (233) PV generation = 1492.3348
kWh/yr. Per RdSAP10 §15 p.66 "kWp for photovoltaics, etc.: 2 d.p."
the cascade rounds kWp before the SAP 10.2 §M1 EPV calculation
without the round, cascade's 2.16224 kWp drives a +1.5 kWh/yr
over-credit and a +0.015 SAP residual vs the worksheet.
"""
from datatypes.epc.domain.epc_property_data import (
PhotovoltaicSupply, PhotovoltaicSupplyNoneOrNoDetails,
@ -513,12 +517,11 @@ def test_pv_generation_synthesizes_array_from_percent_roof_area_per_rdsap_11_1()
# Act
gen_kwh = cert_to_inputs(epc).pv_generation_kwh_per_yr
# Assert — kWp = 0.12 × 36.9 × 0.40 / cos(35°) = 2.1622. With S =
# ~862 kWh/m²/yr at South 30° UK-avg (Appendix U3.3) and ZPV = 0.8
# (Modest), annual EPV = 0.8 × 2.1622 × 862 × 0.8 ≈ 1490 kWh/yr —
# within 1% of the worksheet's 1492.33 kWh/yr.
assert 1450.0 < gen_kwh < 1530.0, (
f"expected ~1490 kWh/yr (per RdSAP §11.1 b synthesis), got {gen_kwh:.2f}"
# Assert — EPV = 0.8 × 2.16 × S(South,30°,UK-avg) × 0.8(Modest);
# the cascade rounds kWp to 2 d.p. so the result lands on the
# worksheet's 1492.3348 kWh/yr at 1e-4.
assert abs(gen_kwh - 1492.3348) <= 1e-4, (
f"expected 1492.3348 kWh/yr (worksheet cert 6835), got {gen_kwh:.4f}"
)