The SAP 10.2 / RdSAP 10 calculator is closed at 930/930 pin tests green.
Tidying the docs for hand-off to the API-integration agent.
New: docs/sap-spec/SAP_CALCULATOR.md
Canonical module overview — public API surface, two-cascade
architecture (Rating UK-avg, Demand postcode), simulator-use-case
example, file map, validation contract + hard rules, fixture cohort
notes, spec page references. Replaces the scattered "what's the
shape" knowledge that was previously only in commit messages.
Rewritten: docs/sap-spec/HANDOVER_NEXT.md
Old handover (work queue for slices 26-36) is obsolete. Replaced
with the next agent's brief: build an API → SAP scoring integration
test using the 6 Elmhurst fixtures. Includes a copy-paste reference
scoring path, expected outputs per fixture, list of files to read
on day 1, and scope guardrails.
Refreshed module docstrings:
- cert_to_inputs.py: now describes both cascades, the deferred-edge-
case list reflects current state (RR/secondary/§15 living-area
rounding all DONE; thermal-mass and control-temp adjustment still
deferred).
- calculator.py: per-end-use CO2/PE factor machinery documented;
stale "single-fuel approximation" claim removed (closed in slice 32).
- sap/README.md: validation paragraph now says "930/930 green" and
points to SAP_CALCULATOR.md instead of the obsolete HANDOVER_NEXT.
Verified the API examples in both docs produce the expected per-fixture
outputs (SAP=62, EI=60, Carbon=3104.1222, PE=16931.7227 for 000474).
Wider regression: 1585/1585 PASS, zero failures.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
8.4 KiB
Handover — API → SAP integration test
The SAP 10.2 / RdSAP 10 calculator is closed: 930/930 pin tests
green against the 6 Elmhurst U985 worksheet PDFs (Rating cascade for
SAP rating + EI rating; Demand cascade for EPC Current Carbon +
Current Primary Energy). Architecture + public API live in
SAP_CALCULATOR.md — read that first.
Your job: build an integration test that runs API request → cert → SAP scoring end-to-end against this calculator, using the 6 Elmhurst fixtures as the strongest test case in the repo.
What "done" looks like
A test (probably under backend/ somewhere, exact location TBD by
the codebase shape) that:
- Spins up the API (FastAPI or whatever the http surface is).
- Sends a request with a representative
EpcPropertyDatapayload (use one of the 6 Elmhurst fixtures'build_epc()outputs as the reference, or send the upstream JSON shape if that's the boundary). - Receives the 4 EPC-facing outputs back through whatever endpoint the API exposes them on (or invokes the SAP scoring code path the API would use internally).
- Asserts the 4 outputs match the fixture's lodged values at the
stated tolerance:
sap_score(integer, exact match)ei_rating(integer, exact match)current_carbon_kg(abs=1e-4againstDEMAND_LINE_272_TOTAL_CO2)current_pe_kwh(abs=1e-4againstDEMAND_LINE_286_TOTAL_PE)
Parametrise the test over all 6 fixtures so any regression in the plumbing fails loudly.
What's in the box
Public API (the only thing you need from the SAP module)
from domain.sap.rdsap.cert_to_inputs import (
cert_to_inputs, # Rating cascade
cert_to_demand_inputs, # Demand cascade
local_climate_for_cert,
environmental_section_from_cert,
primary_energy_section_from_cert,
)
from domain.sap.calculator import calculate_sap_from_inputs, SapResult
See SAP_CALCULATOR.md §2 for the recommended dwelling_outputs(epc)
function shape — copy-paste it as your reference scoring path.
Fixture cohort (the most comprehensive test case in the repo)
6 real-world certs with full PDF ground-truth:
| Fixture | TFA | Notable cert-shape features |
|---|---|---|
_elmhurst_worksheet_000474 |
56.79 | Main + 2 ext, gas combi, no secondary |
_elmhurst_worksheet_000477 |
77.58 | RR main-only, electric secondary |
_elmhurst_worksheet_000480 |
84.41 | Main + ext + RR, electric secondary |
_elmhurst_worksheet_000487 |
81.57 | RR + ext + alt-wall, electric shower |
_elmhurst_worksheet_000490 |
66.06 | Main + ext |
_elmhurst_worksheet_000516 |
90.54 | Main only |
Each fixture exposes:
build_epc() -> EpcPropertyData— encode the cert as our domain typeLINE_*— rating-cascade worksheet expected values (Block 1)DEMAND_LINE_*— demand-cascade worksheet expected values (Block 2)SAP_VALUE_CONTINUOUS/LINE_258_SAP_RATING_INTEGER— SAP ratingLINE_274_EI_RATING_INTEGER— EI rating
Expected EPC outputs per fixture:
| sap_score | ei_rating | current_carbon_kg | current_pe_kwh | |
|---|---|---|---|---|
| 000474 | 62 | 60 | 3104.1222 | 16931.7227 |
| 000477 | 65 | 69 | 2879.7824 | 16545.4543 |
| 000480 | 61 | 65 | 3479.1552 | 19953.4189 |
| 000487 | 62 | 69 | 3005.2667 | 17755.3174 |
| 000490 | 57 | 61 | 3250.1703 | 18583.7962 |
| 000516 | 63 | 66 | 3501.4376 | 20087.8232 |
What you'll need to investigate
The SAP calculator side is a pure-Python function chain — easy. The API side is what you need to map out:
- Where does cert data enter the system? Find the FastAPI / Django
/ whatever endpoint that accepts cert input. Look under
backend/for routers. - What's the request payload shape? Is it
EpcPropertyDataJSON directly, or a different upstream representation that gets mapped? Checkdatatypes/epc/domain/mapper.py— the mapper from various schema versions (SAP-Schema-18/19, RdSAP-Schema-18) toEpcPropertyDatalives there. - Is SAP scoring already wired to the API? Search the backend for
imports of
domain.sap.rdsap.cert_to_inputsordomain.sap.calculator. If it's not yet wired, the integration test is a forcing function for wiring it. - What's the response shape? The 4 outputs above are what the EPC publishes; the API may already expose them, or may expose a wider surface (per-section breakdown for retrofit modelling, etc.).
If the API doesn't yet expose SAP scoring, the integration test scope might include adding the endpoint. Confirm scope with the user before expanding.
Workflow conventions (from the SAP cleanup work)
- AAA tests —
# Arrange / # Act / # Assertheaders on every new test. - One slice = one commit with Co-Authored-By trailer.
pytest.approx(..., abs=1e-4)for the EPC outputs — same bar as the SAP cascade tests. The 4 expected values above are at 4 d.p. so abs=1e-4 is the floor.- Don't widen tolerances. If a pin fails, it's a real bug (probably in the API plumbing, since the calculator is closed).
Files to read on day 1
| File | Why |
|---|---|
docs/sap-spec/SAP_CALCULATOR.md |
Module API + architecture (you're heading there) |
packages/domain/src/domain/sap/calculator.py |
SapResult fields you'll assert against |
packages/domain/src/domain/sap/rdsap/cert_to_inputs.py |
The 3 public entry points + the section helpers |
packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000474.py |
A reference fixture — build_epc() shows the EpcPropertyData shape |
packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py |
The current e2e test pattern — model your integration test on this |
backend/ (explore) |
API entry points |
datatypes/epc/domain/mapper.py |
Schema → EpcPropertyData mappers |
Quick orient
# Confirm SAP calculator is still 930/930 green
python -m pytest \
packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py \
packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py \
--no-cov --no-header --tb=no -q
# Show the 4 EPC outputs for fixture 000474
cd packages/domain/src && python -c "
from domain.sap.rdsap.cert_to_inputs import (
cert_to_inputs, local_climate_for_cert,
environmental_section_from_cert, primary_energy_section_from_cert,
)
from domain.sap.calculator import calculate_sap_from_inputs
from domain.sap.worksheet.tests import _elmhurst_worksheet_000474 as w
epc = w.build_epc()
pc = local_climate_for_cert(epc)
rating = calculate_sap_from_inputs(cert_to_inputs(epc))
env_rating = environmental_section_from_cert(epc)
env_demand = environmental_section_from_cert(epc, postcode_climate=pc)
pe_demand = primary_energy_section_from_cert(epc, postcode_climate=pc)
print(f'SAP: {rating.sap_score}') # 62 (UK-avg)
print(f'EI: {env_rating.ei_rating_integer}') # 60 (UK-avg)
print(f'Carbon: {env_demand.total_co2_kg_per_yr:.4f} kg/yr') # 3104.1222 (postcode)
print(f'PE: {pe_demand.total_pe_kwh_per_yr:.4f} kWh/yr') # 16931.7227 (postcode)
"
Important: SAP rating and EI rating use UK-average climate; Current
Carbon and Current Primary Energy use postcode climate. Don't read EI
from the demand-cascade environmental_section_from_cert — that's a
postcode-conditions EI value, not what the EPC publishes.
What's NOT in scope
- Extending the SAP calculator. It's closed at the EPC-output layer.
If you find an additional cert-shape variation that breaks the
calculator, capture it as a new conformance fixture (see
packages/domain/src/domain/sap/README.md) — don't paper over it in the integration test. - BEDF fuel pricing. The Fuel Bill on the EPC uses postcode-specific BEDF prices (PCDB Table 200), which are deferred. The 4 outputs above cover SAP + EI + Carbon + PE; Fuel Bill is a follow-up.
- The Demand-SAP "improved dwelling" cascade. That's Block 3 of the U985 worksheet (retrofit-applied SAP rating). Out of scope.
Good luck. The SAP side is solid; this is purely a plumbing exercise.