Model/docs/sap-spec/HANDOVER_NEXT.md
Khalim Conn-Kowlessar d44af109a9 Docs: SAP calculator module README + API integration test handover
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>
2026-05-24 10:04:34 +00:00

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.mdread 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:

  1. Spins up the API (FastAPI or whatever the http surface is).
  2. Sends a request with a representative EpcPropertyData payload (use one of the 6 Elmhurst fixtures' build_epc() outputs as the reference, or send the upstream JSON shape if that's the boundary).
  3. 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).
  4. 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-4 against DEMAND_LINE_272_TOTAL_CO2)
    • current_pe_kwh (abs=1e-4 against DEMAND_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 type
  • LINE_* — 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 rating
  • LINE_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:

  1. Where does cert data enter the system? Find the FastAPI / Django / whatever endpoint that accepts cert input. Look under backend/ for routers.
  2. What's the request payload shape? Is it EpcPropertyData JSON directly, or a different upstream representation that gets mapped? Check datatypes/epc/domain/mapper.py — the mapper from various schema versions (SAP-Schema-18/19, RdSAP-Schema-18) to EpcPropertyData lives there.
  3. Is SAP scoring already wired to the API? Search the backend for imports of domain.sap.rdsap.cert_to_inputs or domain.sap.calculator. If it's not yet wired, the integration test is a forcing function for wiring it.
  4. 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 / # Assert headers 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.