mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 99e: PV pitch enum-not-degrees + cert 9501 Layer 2 chain test
`EpcPropertyData.PhotovoltaicArray.pitch` is the RdSAP 10 §11.1 integer code (1=0°, 2=30°, 3=45°, 4=60°, 5=90°) — NOT degrees. The cascade's `cert_to_inputs._PV_PITCH_DEG_BY_CODE` reads the code, not the value. Slice 99d's mapper passed the raw degrees (45) directly, which fell through to the default 30° lookup (Appendix U3.3 S(SW, 30°) ≈ 1029 kWh/m²/yr vs S(SW, 45°) ≈ 1004 — 2.5% over-credit on the PV generation, manifesting as -£6.27 over-credit on total cost → +0.23 SAP delta). Added `_elmhurst_pv_pitch_code` helper that maps the lodged degrees to the nearest tabulated code (snap-to-nearest fallback for non- tabulated tilts; defaults to code 2 / 30° per the cascade's own `_PV_PITCH_DEG_DEFAULT`). Effect on cert 9501 Summary path: - pv_export_credit £256.30 → £250.02 (= worksheet 250.02 exact) - total_fuel_cost £842.94 → £849.21 (= worksheet 849.21 exact) - sap_continuous 68.7577 → **68.5252** (= worksheet 68.5252 exact; Δ -0.0000 at 1e-4) `test_summary_9501_full_chain_sap_matches_worksheet_pdf_exactly` added — the second flat-shaped cert pinned to worksheet SAP at 1e-4 after the cert 0330 / 001479 boiler-house chain tests. Third boiler validation cert closed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
4264e0ad4b
commit
0735c7e81c
2 changed files with 52 additions and 2 deletions
|
|
@ -403,10 +403,38 @@ def test_summary_9501_pv_array_surfaced_from_elmhurst_section_19() -> None:
|
|||
assert len(arrays) == 1
|
||||
assert abs(arrays[0].peak_power - 2.36) <= 1e-4
|
||||
assert arrays[0].orientation == 6 # SAP octant: South-West
|
||||
assert arrays[0].pitch == 45
|
||||
assert arrays[0].pitch == 3 # RdSAP §11.1 pitch enum: code 3 = 45°
|
||||
assert arrays[0].overshading == 1 # RdSAP code: None or very little
|
||||
|
||||
|
||||
def test_summary_9501_full_chain_sap_matches_worksheet_pdf_exactly() -> None:
|
||||
# Arrange — cert 9501-3059-8202-7356-0204 (Summary_000784.pdf /
|
||||
# dr87-0001-000784.pdf) is the third boiler validation cert and
|
||||
# the first FLAT in the per-cert mapper validation cohort.
|
||||
# Mains-gas Vaillant PCDB idx 19007, mid-terrace top-floor flat
|
||||
# with Room-in-Roof + measured PV (2.36 kWp SW @ 45°). TFA 113.08
|
||||
# m². Worksheet PDF "SAP value" line lodges unrounded SAP
|
||||
# **68.5252**.
|
||||
#
|
||||
# Slices 99a-99e jointly closed the Summary path from Δ -5.25 to
|
||||
# 1e-4: 99a extractor attachment fix (built_form=''), 99b dwelling
|
||||
# _type identifies top-floor flat (cascade exposure routing), 99c
|
||||
# RR gables external for flats + SO Solid Brick wall code, 99d
|
||||
# surface PV array from §19.0, 99e PV pitch enum-not-degrees.
|
||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000784_PDF)
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
||||
|
||||
# Act
|
||||
result = calculate_sap_from_inputs(
|
||||
cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
|
||||
)
|
||||
|
||||
# Assert — 1e-4 pin (project memory `feedback_zero_error_strict`).
|
||||
worksheet_unrounded_sap = 68.5252
|
||||
assert abs(result.sap_score_continuous - worksheet_unrounded_sap) < 1e-4
|
||||
|
||||
|
||||
def test_summary_001479_full_chain_sap_matches_worksheet_pdf_exactly() -> None:
|
||||
# Arrange — cert 001479 (Summary_001479.pdf / P960-0001-001479.pdf)
|
||||
# is the first cohort cert with a real GOV.UK EPB API counterpart
|
||||
|
|
|
|||
|
|
@ -2938,13 +2938,35 @@ def _elmhurst_pv_arrays(
|
|||
return [
|
||||
PhotovoltaicArray(
|
||||
peak_power=renewables.pv_peak_power_kw,
|
||||
pitch=renewables.pv_elevation_deg,
|
||||
pitch=_elmhurst_pv_pitch_code(renewables.pv_elevation_deg),
|
||||
orientation=_elmhurst_orientation_int(renewables.pv_orientation),
|
||||
overshading=_elmhurst_pv_overshading_int(renewables.pv_overshading),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
# RdSAP 10 §11.1 PV pitch enum (degrees → integer code consumed by
|
||||
# `cert_to_inputs._PV_PITCH_DEG_BY_CODE` in the Appendix U3.3 surface-
|
||||
# flux cascade). Elmhurst lodges the actual degrees value
|
||||
# ("Elevation: 45°"); the cascade reads the integer code.
|
||||
_ELMHURST_PV_PITCH_DEG_TO_CODE: Dict[int, int] = {
|
||||
0: 1, 30: 2, 45: 3, 60: 4, 90: 5,
|
||||
}
|
||||
|
||||
|
||||
def _elmhurst_pv_pitch_code(elevation_deg: int) -> int:
|
||||
"""Map elevation in degrees → RdSAP 10 §11.1 pitch code (1..5).
|
||||
Snaps to the nearest tabulated tilt; missing/unknown → code 2 (30°)
|
||||
to mirror `cert_to_inputs._PV_PITCH_DEG_DEFAULT`."""
|
||||
if elevation_deg in _ELMHURST_PV_PITCH_DEG_TO_CODE:
|
||||
return _ELMHURST_PV_PITCH_DEG_TO_CODE[elevation_deg]
|
||||
nearest = min(
|
||||
_ELMHURST_PV_PITCH_DEG_TO_CODE.keys(),
|
||||
key=lambda d: abs(d - elevation_deg),
|
||||
)
|
||||
return _ELMHURST_PV_PITCH_DEG_TO_CODE[nearest]
|
||||
|
||||
|
||||
def _elmhurst_pv_overshading_int(description: Optional[str]) -> int:
|
||||
"""Map an Elmhurst PV-overshading description to the RdSAP integer
|
||||
code. Falls back to 1 (None or very little, ZPV=1.0) when missing
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue