mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
inviestigation with hyde values
This commit is contained in:
parent
5923f8d072
commit
0079752eab
9 changed files with 2774 additions and 1 deletions
93
.claude/skills/epc-to-elmhurst-rdsap-inputs/SKILL.md
Normal file
93
.claude/skills/epc-to-elmhurst-rdsap-inputs/SKILL.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
---
|
||||
name: epc-to-elmhurst-rdsap-inputs
|
||||
description: Convert an EPC certificate (by UPRN, certificate number, or local epc.json) into a markdown sheet of Elmhurst Energy RdSAP entry-tool inputs, page by page, so a human can key them in and compare Elmhurst's SAP score against this repo's engine. Use when verifying SAP-calculator accuracy against Elmhurst, reproducing a lodged cert in Elmhurst, or when the user mentions Elmhurst, RdSAP inputs, or checking the SAP score for a UPRN/certificate.
|
||||
---
|
||||
|
||||
# EPC → Elmhurst RdSAP inputs
|
||||
|
||||
Produces a markdown crib sheet for re-keying a real EPC certificate into
|
||||
Elmhurst Energy's RdSAP entry tool, so the operator can read off Elmhurst's
|
||||
SAP score and compare it to this engine's. The accuracy comparison is the
|
||||
whole point — the markdown leads with **our engine's SAP score** as the
|
||||
number to beat, and flags known divergences.
|
||||
|
||||
This is prompt-driven: you read the cert's real values, look up each Elmhurst
|
||||
field in [reference/mapping.md](reference/mapping.md), and format the result.
|
||||
**Ground every number in the loaded `EpcPropertyData` and the engine's
|
||||
computed values — never guess a code or area.** Codes you can't find in the
|
||||
mapping reference must be looked up in the cited source file, not invented.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Resolve the cert to an `EpcPropertyData`** (one of):
|
||||
- **UPRN** — `scripts/fetch_real_life_epc_sample.py <uprn>` (fetches, saves to
|
||||
`backend/epc_api/json_samples/real_life_examples/<schema>/uprn_<uprn>/epc.json`,
|
||||
prints schema + lodged rating + engine output), or
|
||||
`EpcClientService(auth_token=...).get_by_uprn(<uprn>)`.
|
||||
- **Certificate number** — `EpcClientService.get_by_certificate_number(<cert>)`.
|
||||
- **Local json** — `EpcPropertyDataMapper.from_api_response(json.load(...))`.
|
||||
|
||||
Token is in `backend/.env` (`OPEN_EPC_API_TOKEN`, else `EPC_AUTH_TOKEN`).
|
||||
For a saved json, mock `httpx.get` to return `{"data": <json>}` (see the
|
||||
fetch script), or call the mapper directly.
|
||||
|
||||
2. **Compute the engine's view** so the sheet shows real numbers, not guesses:
|
||||
```python
|
||||
from domain.sap10_calculator.calculator import Sap10Calculator
|
||||
from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_inputs
|
||||
inputs = cert_to_inputs(epc) # window areas, U-values, fuel costs, cylinder
|
||||
result = Sap10Calculator().calculate(epc) # our SAP score + per-end-use kWh
|
||||
```
|
||||
Pull window area from `inputs.heat_transmission.windows_w_per_k` + the
|
||||
synthesised `epc.sap_windows`; U-values from `inputs.heat_transmission`;
|
||||
fuel £/kWh from `inputs.*_fuel_cost_gbp_per_kwh`.
|
||||
|
||||
3. **Write the markdown**, one section per Elmhurst page, in this order:
|
||||
Property Description · Dimensions · Conservatory · Walls (incl. Party wall) ·
|
||||
Roofs · Floors · Openings (Windows, Doors) · Ventilation & Lighting
|
||||
(Ventilation, Mechanical Ventilation, Air Pressure Test, Lighting) ·
|
||||
Space Heating (Main Heating 1, Main Heating 2, Community Heating, Meters) ·
|
||||
Water Heating (Water Heating + cylinder, Community Hot Water, Solar Water
|
||||
Heating, WWHRS, FGHRS) · New Technologies (PV, Wind, Hydro, Special Features).
|
||||
For each field give the **Elmhurst label**, the **value to enter**, and where
|
||||
useful the **EES dropdown path** and **SAP code**. Use the lookup tables and
|
||||
gotchas in [reference/mapping.md](reference/mapping.md).
|
||||
|
||||
4. **Save** the file next to the cert json as `elmhurst_inputs.md`
|
||||
(e.g. `.../real_life_examples/<schema>/uprn_<uprn>/elmhurst_inputs.md`).
|
||||
|
||||
5. **Tell the operator**: key it into Elmhurst, then report the SAP score (and
|
||||
heating cost £ if shown). If it differs from our engine's score, that's a
|
||||
calculator finding — capture it.
|
||||
|
||||
## Output shape
|
||||
|
||||
Start the file with a header block:
|
||||
```
|
||||
# Elmhurst RdSAP inputs — UPRN <uprn> (cert <cert>, <schema>)
|
||||
**Lodged SAP:** <energy_rating_current> **Our engine:** <result.sap_score> ← compare Elmhurst against this
|
||||
**Known divergences:** <e.g. off-peak fuel-cost bug — see Meters>
|
||||
```
|
||||
Then the page sections as tables: `| Elmhurst field | Value | Notes (SAP code / EES path) |`.
|
||||
|
||||
## Critical gotchas (full detail in reference)
|
||||
|
||||
- **Economy-7 / off-peak electricity** (`main_fuel_type`/`water_heating_fuel` 29):
|
||||
Elmhurst meter type **must be Dual-rate / Economy 7 (7-hour)**, not Single.
|
||||
Our engine has a **known over-rating bug** here — it prices 100% of off-peak
|
||||
space heating + hot water at the 5.50p low rate instead of the SAP Table 12a
|
||||
high/low split. Always flag this in the output for all-electric off-peak certs.
|
||||
- **WWHRS**: `sap_heating.instantaneous_wwhrs` is **bath/shower ROOM counts**
|
||||
(ADR-0028), NOT a heat-recovery device → WWHRS = **No** unless a real unit is lodged.
|
||||
- **Party wall** code 1 = Solid (U=0, Elmhurst "Solid"), not "Unable to determine".
|
||||
- **Cylinder insulation** type 1 = Foam, 2 = Jacket.
|
||||
- **Water heating** 903 = Electric immersion off-peak → Elmhurst "Water Heater"
|
||||
category, not "Boiler Circulator" (901 = from main system).
|
||||
- **Windows** are synthesised from `glazed_area` band × TFA — not real geometry.
|
||||
|
||||
## Canonical example
|
||||
|
||||
UPRN **10002468137** (cert `0215-2818-7357-9703-2145`, RdSAP-Schema-17.1):
|
||||
all-electric high-heat-retention storage heaters on Economy 7, solid-brick
|
||||
uninsulated end-terrace. **Lodged SAP 55 vs engine 62** — the over-rating that
|
||||
motivated this skill. Use it to sanity-check output.
|
||||
215
.claude/skills/epc-to-elmhurst-rdsap-inputs/reference/mapping.md
Normal file
215
.claude/skills/epc-to-elmhurst-rdsap-inputs/reference/mapping.md
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
# EpcPropertyData → Elmhurst RdSAP field mapping
|
||||
|
||||
Complete code→value reference for the `epc-to-elmhurst-rdsap-inputs` skill.
|
||||
Every mapping cites its source in this repo. When a lodged code isn't listed
|
||||
here, look it up in the cited source file — do **not** guess.
|
||||
|
||||
Field names below are the GOV.UK API / `EpcPropertyData` cert fields. Elmhurst
|
||||
labels are the entry-tool's on-screen labels.
|
||||
|
||||
---
|
||||
|
||||
## Property Description
|
||||
|
||||
| Cert field | Code → value |
|
||||
|---|---|
|
||||
| `property_type` | 0=House, 1=Bungalow, 2=Flat, 3=Maisonette, 4=Park home |
|
||||
| `built_form` | 1=Detached, 2=Semi-detached, 3=End-terrace, 4=Mid-terrace, 5=Enclosed end-terrace, 6=Enclosed mid-terrace |
|
||||
| `construction_age_band` (England/Wales) | A=before 1900, B=1900–1929, C=1930–1949, D=1950–1966, E=1967–1975, F=1976–1982, G=1983–1990, H=1991–1995, I=1996–2002, J=2003–2006, K=2007 onwards |
|
||||
| Storeys | count of distinct floors in `sap_building_parts[].sap_floor_dimensions` |
|
||||
| Habitable / Heated rooms | `habitable_room_count` / `heated_room_count` |
|
||||
| Extensions / Rooms in roof | `extensions_count`; rooms-in-roof only if a room-in-roof building part is lodged |
|
||||
|
||||
## Dimensions
|
||||
|
||||
From `sap_building_parts[].sap_floor_dimensions[]`. Type = `measurement_type`
|
||||
(1 = Internal). One row per floor (`floor` 0 = ground/Lowest, 1 = 1st, …):
|
||||
- Floor Area = `total_floor_area`
|
||||
- Room Height = `room_height`
|
||||
- Heat Loss Perimeter = `heat_loss_perimeter`
|
||||
- Party Wall Length = `party_wall_length`
|
||||
- Heated Basement only if a basement floor is lodged.
|
||||
|
||||
## Conservatory
|
||||
|
||||
`conservatory_type` 1 = none → unchecked. `has_heated_separate_conservatory`.
|
||||
|
||||
## Walls (Main + Party)
|
||||
|
||||
**External / Main wall:**
|
||||
| Cert field | Mapping |
|
||||
|---|---|
|
||||
| `wall_construction` | 3 = Solid brick (look up others in source if not 3) |
|
||||
| Insulation | `wall_insulation_thickness` "NI" or `wall_insulation_type` NONE → **As Built**; genuine retrofit (External/Internal/Filled) → that type |
|
||||
| `wall_dry_lined` | Y/N → Dry-lining Yes/No |
|
||||
| `wall_thickness` (mm) | Wall Thickness; `wall_thickness_measured` Y → "Wall Thickness Unknown" unchecked |
|
||||
| `sap_alternative_wall` | enter as Alternative Wall 1 (`wall_area`, same code mapping) |
|
||||
|
||||
**Party wall** — `party_wall_construction` → SAP10 wall code → U-value
|
||||
(source: `datatypes/epc/domain/mapper.py` `_API_PARTY_WALL_CONSTRUCTION_TO_SAP10`):
|
||||
| Code | Meaning | U | Elmhurst "Type" |
|
||||
|---|---|---|---|
|
||||
| 0 / None | no lodging | — | (cascade default) |
|
||||
| 1 | Solid masonry / timber / system | **0.0** | **Solid** |
|
||||
| 2 | Cavity masonry, unfilled | 0.5 | Cavity (unfilled) |
|
||||
| 3 | Cavity masonry, filled | 0.2 | Cavity (filled) |
|
||||
| 4 (house) / 5 (flat) | Unable to determine | 0.25 | Unable to determine |
|
||||
|
||||
⚠️ Do **not** leave a code-1 party wall as "Unable to determine" — that wrongly
|
||||
adds ~0.25 × area of heat loss and depresses the Elmhurst score.
|
||||
|
||||
## Roofs
|
||||
|
||||
| Cert field | Mapping |
|
||||
|---|---|
|
||||
| Type | loft access → "PA Pitched (slates/tiles), access to loft"; flat / room-in-roof per description |
|
||||
| `roof_insulation_location` | 2 = loft (at joists) → Insulation "Joists" |
|
||||
| `roof_insulation_thickness` | string e.g. "200mm" → Insulation Thickness 200 mm (drives the default U) |
|
||||
|
||||
## Floors
|
||||
|
||||
| Cert field | Mapping |
|
||||
|---|---|
|
||||
| `floor_construction` | 1 = Solid; 4 = Solid (no-insulation variant); a "Suspended"-prefixed type only if genuinely suspended (timber vs not-timber matters for infiltration) |
|
||||
| Insulation | "no insulation (assumed)" / `floor_insulation` absent → **As built** |
|
||||
|
||||
Source: `domain/sap10_calculator/worksheet/heat_transmission.py` floor logic.
|
||||
|
||||
## Openings — Windows (RdSAP reduced-data, synthesised)
|
||||
|
||||
RdSAP certs carry **no real window geometry**. The engine synthesises it
|
||||
(`datatypes/epc/domain/mapper.py` `_synthesise_reduced_field_windows`):
|
||||
```
|
||||
total_glazing_area = 0.148 × total_floor_area × band_multiplier # _RDSAP20_GLAZING_RATIO = 0.148
|
||||
split 4-way across orientations (1,3,5,7) = N, E, S, W # _RDSAP20_SYNTH_ORIENTATIONS
|
||||
each window: width = area/4, height = 1.0 (height=1 so width carries the area)
|
||||
```
|
||||
`glazed_area` band multiplier (`_RDSAP20_GLAZED_AREA_BAND_MULTIPLIER`):
|
||||
| Code | Band | × |
|
||||
|---|---|---|
|
||||
| 1 | Normal | 1.00 |
|
||||
| 2 | More than typical | 1.25 |
|
||||
| 3 | Less than typical | 0.81 |
|
||||
| 4 | Much more than typical | 1.51 |
|
||||
| 5 | Much less than typical | 0.62 |
|
||||
|
||||
Per-window fields:
|
||||
- Glazing Type: from `multiple_glazing_type`; when no explicit install date is
|
||||
lodged use **"Double with unknown install date"** (don't assert a date band).
|
||||
⚠️ RdSAP-17.x/18/19 inherit RdSAP-20.0.0 glazing coefficients — the code→date-band
|
||||
translation across the 17.1 ↔ RdSAP-10 boundary is a known fidelity risk; report the
|
||||
U-value Elmhurst assigns vs the engine's effective `windows_w_per_k ÷ total area`.
|
||||
- Frame Type: `pvc_window_frames` true → PVC
|
||||
- Glazing Gap: `glazing_gap` mm
|
||||
- Orientation: not lodged — spread evenly N/E/S/W (matches the engine)
|
||||
- Location: **External wall**
|
||||
- Draught Proofed: `percent_draughtproofed` (100 → checked)
|
||||
- U-value / g-value: leave Elmhurst defaults; note them for comparison.
|
||||
|
||||
## Openings — Doors
|
||||
|
||||
`door_count` (Total), `insulated_door_count` (Insulated), draughtproofed =
|
||||
`door_count` when `percent_draughtproofed` 100. Engine default uninsulated door
|
||||
U = 3.0 W/m²K, area 1.85 m² → `doors_w_per_k` ≈ count × 1.85 × 3.0.
|
||||
|
||||
## Ventilation & Lighting
|
||||
|
||||
- **Ventilation**: open chimneys = `open_fireplaces_count`; flues/passive
|
||||
vents/flueless gas fires/extract fans = lodged counts (0 if not lodged);
|
||||
Fixed space cooling = `has_fixed_air_conditioning`. Draught Lobby not in RdSAP
|
||||
house reduced-data → leave default.
|
||||
- **Mechanical Ventilation**: `mechanical_ventilation` 0 = natural → unchecked.
|
||||
- **Air Pressure Test**: RdSAP certs → "Not available" (uses % draughtproofing).
|
||||
- **Lighting**: SAP-2012 certs lodge **outlets**, not bulbs —
|
||||
Total bulbs = `fixed_lighting_outlets_count`,
|
||||
low energy = `low_energy_fixed_lighting_outlets_count`. RdSAP-10's bulb
|
||||
methodology differs slightly, but lighting is a minor energy term.
|
||||
|
||||
## Space Heating
|
||||
|
||||
**Main Heating** — `sap_heating.main_heating_details[]`:
|
||||
- `sap_main_heating_code` is the SAP code. e.g. **409 = High heat retention
|
||||
storage heaters** (EES path: Electric → Electric → Storage → High heat retention).
|
||||
- `main_heating_control` is the controls SAP code. e.g. **2404 = Controls for
|
||||
high heat retention storage heaters** (EES: Storage Radiator Systems → CSD).
|
||||
- `main_heating_fraction` → Percentage of Heat (1 = 100%).
|
||||
- `storage_heaters[]`: count + `high_heat_retention` flag → the heater list.
|
||||
- Only one main system → leave Main Heating 2 empty. PCDF refs 0 unless a PCDF
|
||||
boiler/control is lodged. Heat Emitter / Flue / Pump Age are wet-system fields —
|
||||
N/A once a storage-heater code is chosen.
|
||||
|
||||
**Secondary** — e.g. "Portable electric heaters (assumed)" → Electric →
|
||||
Electric → Room Heaters → Panel/convector/radiant (SAP 691), standard-tariff
|
||||
electricity (fuel 30). `secondary_heating_fraction` default 0.1.
|
||||
|
||||
**Community Heating**: None unless community-heating lodged.
|
||||
|
||||
**Meters** — `sap_energy_source`:
|
||||
- `mains_gas` Y/N → "Mains gas supply available" checkbox.
|
||||
- Electricity meter type from the heating/HW **fuel code** (see below).
|
||||
- `meter_type`; smart-meter flags if lodged.
|
||||
|
||||
### Fuel codes & Economy-7 (CRITICAL — known engine bug)
|
||||
|
||||
Table 32 unit costs, p/kWh (`domain/sap10_calculator/tables/table_32.py`):
|
||||
| Code | Fuel | p/kWh |
|
||||
|---|---|---|
|
||||
| 30 | Electricity, standard tariff | 13.19 |
|
||||
| 31 | 7-hour tariff **low / off-peak** | 5.50 |
|
||||
| 32 | 7-hour tariff **high** | 15.29 |
|
||||
| 33 | 10-hour low | 7.50 |
|
||||
| 34 | 10-hour high | 14.68 |
|
||||
| 35 | 24-hour heating tariff | 6.61 |
|
||||
| 38 / 40 | 18-hour high / low | 13.67 / 7.41 |
|
||||
|
||||
**`main_fuel_type` / `water_heating_fuel` 29 = off-peak (7-hour) electricity** →
|
||||
Elmhurst Electricity meter type = **Dual-rate / Economy 7 (7-hour)**, NOT Single.
|
||||
|
||||
⚠️ **Known over-rating bug:** the engine prices **100% of off-peak space heating
|
||||
AND hot water at the 5.50p low rate** (`inputs.space_heating_fuel_cost_gbp_per_kwh`
|
||||
= 0.055), instead of the SAP **Table 12a high/low split** (a portion at the 15.29p
|
||||
high rate). This under-costs all-electric Economy-7 dwellings and inflates the SAP
|
||||
score. Always surface this in the output's "Known divergences". Canonical case:
|
||||
UPRN 10002468137 — lodged 55, engine 62.
|
||||
|
||||
## Water Heating
|
||||
|
||||
| Cert field | Mapping |
|
||||
|---|---|
|
||||
| `water_heating_code` | 901 = From main heating system (Elmhurst "Boiler Circulator"); **903 = Electric immersion, off-peak → Elmhurst "Water Heater" category** (NOT Boiler Circulator) |
|
||||
| `water_heating_fuel` | as Fuel codes above (29 = off-peak) |
|
||||
| `has_hot_water_cylinder` | → "Hot Water Cylinder Present" |
|
||||
| `cylinder_size` | band: 1=Small, 2=Medium, 3=Large |
|
||||
| `cylinder_insulation_type` | **1 = factory Foam, 2 = loose Jacket** (source: `cert_to_inputs.py` `_CYLINDER_INSULATION_TYPE_LOOSE_JACKET = 2`) |
|
||||
| `cylinder_insulation_thickness` | mm (38 mm ≈ factory foam; jackets 80 mm+) |
|
||||
| `immersion_heating_type` | 1 = single |
|
||||
|
||||
- **Community Hot Water**: 0 unless lodged.
|
||||
- **Solar Water Heating**: `solar_water_heating` Y/N.
|
||||
- **WWHRS**: ⚠️ `sap_heating.instantaneous_wwhrs` holds **bath/shower ROOM
|
||||
counts** (ADR-0028: `rooms_with_bath_and_or_shower`, `rooms_with_mixer_shower_no_bath`,
|
||||
`rooms_with_bath_and_mixer_shower`) — it is **NOT** a heat-recovery device.
|
||||
Set WWHRS = **No / not present** unless a genuine WWHRS unit is lodged. A
|
||||
phantom WWHRS recovers heat and wrongly raises the Elmhurst score.
|
||||
- **FGHRS**: `main_heating_details[].has_fghrs` Y/N (per main heating system).
|
||||
|
||||
## New Technologies
|
||||
|
||||
- **PV**: `sap_energy_source.photovoltaic_supply` — `none_or_no_details` → None.
|
||||
- **Wind**: `wind_turbines_count` 0 → not present (terrain type irrelevant then).
|
||||
- **Hydro**: 0 unless lodged.
|
||||
- **Special Features (Appendix Q)**: none unless lodged.
|
||||
- "Export capable meter" has no effect with no generation.
|
||||
|
||||
---
|
||||
|
||||
## Source files
|
||||
|
||||
| Concern | File |
|
||||
|---|---|
|
||||
| API → EpcPropertyData mapper, party-wall & window synthesis | `datatypes/epc/domain/mapper.py` |
|
||||
| cert → calculator inputs, cylinder insulation, fuel costs | `domain/sap10_calculator/rdsap/cert_to_inputs.py` |
|
||||
| heat transmission (U-values, floors, party walls) | `domain/sap10_calculator/worksheet/heat_transmission.py` |
|
||||
| fuel unit costs | `domain/sap10_calculator/tables/table_32.py` |
|
||||
| EPC fetch by UPRN | `scripts/fetch_real_life_epc_sample.py` |
|
||||
| EPC client | `infrastructure/epc_client/epc_client_service.py` |
|
||||
1000
backend/epc_api/json_samples/SAP-Schema-17.1/corpus.jsonl
Normal file
1000
backend/epc_api/json_samples/SAP-Schema-17.1/corpus.jsonl
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,189 @@
|
|||
# Elmhurst RdSAP inputs — UPRN 10002468137 (cert 0215-2818-7357-9703-2145, RdSAP-Schema-17.1)
|
||||
|
||||
**Lodged SAP:** 55 **Our engine:** 62 (continuous 62.47) ← compare Elmhurst against **62**
|
||||
**Known divergences:**
|
||||
- **Economy-7 fuel-cost bug (see Meters / Water Heating):** the engine prices 100% of off-peak space heating + hot water at the 5.50p low rate (£0.055/kWh) instead of the SAP Table 12a high/low split. Engine total fuel cost **£595.68** vs lodged heating £619 + HW £140 + lighting £39 ≈ **£798**. This under-costing is the lead suspect for the +7 over-rating.
|
||||
- **Glazing date band (see Windows):** RdSAP-17.1 glazing codes inherit RdSAP-20.0.0 coefficients — record the U-value Elmhurst assigns vs the engine's effective 2.27 W/m²K.
|
||||
|
||||
Property: End-terrace house, 1 Foundry Cottages, Heyshott, Midhurst GU29 0DB. All-electric, high-heat-retention storage heaters on Economy 7, solid-brick uninsulated.
|
||||
|
||||
---
|
||||
|
||||
## Property Description
|
||||
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Property type | House | `property_type` 0 |
|
||||
| Built form | End-Terrace | `built_form` 3 |
|
||||
| Storeys | 2 | ground + first floor |
|
||||
| Habitable Rooms | 3 | `habitable_room_count` |
|
||||
| Heated Habitable Rooms | 3 | `heated_room_count` |
|
||||
| Date built — Main | **A — before 1900** | `construction_age_band` A (E&W) |
|
||||
| Extensions / Rooms in Roof | none | `extensions_count` 0; loft, not room-in-roof |
|
||||
|
||||
## Dimensions (Type: Internal)
|
||||
|
||||
| Floor | Floor Area [m²] | Room Height [m] | Heat Loss Perimeter [m] | Party Wall Length [m] |
|
||||
|---|---|---|---|---|
|
||||
| Lowest Floor (ground) | 24.00 | 2.40 | 12.16 | 7.06 |
|
||||
| 1st Floor | 24.00 | 2.50 | 13.86 | 7.06 |
|
||||
|
||||
Total floor area 48 m². Heated Basement: unchecked.
|
||||
|
||||
## Conservatory
|
||||
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Is there a conservatory? | No (unchecked) | `conservatory_type` 1 = none |
|
||||
|
||||
## Walls
|
||||
|
||||
**External / Main wall**
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Type | Solid Brick | `wall_construction` 3 |
|
||||
| Insulation | As Built | `wall_insulation_thickness` "NI" (none) |
|
||||
| Dry-lining | No | `wall_dry_lined` N |
|
||||
| Wall Thickness | 300 mm | `wall_thickness` 300, measured (Unknown unchecked) |
|
||||
| U-value Known | unchecked | none lodged |
|
||||
|
||||
**Alternative Wall 1** — area 3.40 m², Solid Brick, As Built, No dry-lining, 300 mm (`sap_alternative_wall`). Alternative Wall 2: 0.00.
|
||||
|
||||
**Party wall**
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Type | **Solid** (U-value 0.00) | `party_wall_construction` 1 → solid masonry, U=0. **NOT "Unable to determine"** (that would wrongly add ~0.25 × area). Engine `party_walls_w_per_k` = 0.0 ✓ |
|
||||
|
||||
## Roofs
|
||||
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Type | PA Pitched (slates/tiles), access to loft | loft insulation |
|
||||
| Insulation | Joists | `roof_insulation_location` 2 |
|
||||
| Insulation Thickness | **200 mm** | `roof_insulation_thickness` "200mm" (default U ≈ 0.21; engine roof 5.04 W/K) |
|
||||
| U-value Known | unchecked | |
|
||||
|
||||
## Floors
|
||||
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Location | Ground floor | |
|
||||
| Type | **Solid** | `floor_construction` 1 (NOT suspended) |
|
||||
| Insulation | As built | "no insulation (assumed)" |
|
||||
| U-value Known | unchecked | engine floor 16.8 W/K |
|
||||
|
||||
## Openings — Windows
|
||||
|
||||
RdSAP reduced-data: **no real geometry** — synthesised as 4 windows, one per cardinal direction. Total glazing **5.75 m²** (0.148 × 48 × 0.81; `glazed_area` 3 = "less than typical", ×0.81).
|
||||
|
||||
Add **4 identical windows**, orientations **North / East / South / West**:
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Width [m] | 1.44 | area/4 |
|
||||
| Height [m] | 1.00 | height=1 so width carries area |
|
||||
| Glazing Type | **Double with unknown install date** | `multiple_glazing_type` 3, no explicit date; engine eff U **2.27 W/m²K** — compare to Elmhurst's default (≈2.70) |
|
||||
| Frame Type | PVC | `pvc_window_frames` true |
|
||||
| Glazing Gap | 12 mm | `glazing_gap` 12 |
|
||||
| Location | **External wall** | not Alternative wall |
|
||||
| Orientation | N / E / S / W (one each) | not lodged; engine spreads evenly |
|
||||
| Draught Proofed | ✓ (100%) | `percent_draughtproofed` 100 |
|
||||
| U-value / g-value | leave Elmhurst default | note for comparison |
|
||||
|
||||
## Openings — Doors
|
||||
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Total Number of Doors | 2 | `door_count` |
|
||||
| Number of Insulated Doors | 0 | `insulated_door_count` |
|
||||
| Number of Draught Proofed Doors | 2 | 100% draughtproofed |
|
||||
|
||||
(Engine doors 11.1 W/K = 2 × 1.85 m² × 3.0 default U ✓)
|
||||
|
||||
## Ventilation & Lighting
|
||||
|
||||
**Ventilation** — all 0 / unchecked: open chimneys 0 (`open_fireplaces_count`), no flues/passive vents/flueless gas fires; Fixed space cooling unchecked (`has_fixed_air_conditioning` false). Draught Lobby: leave default (not in RdSAP house data).
|
||||
|
||||
**Mechanical Ventilation** — unchecked (`mechanical_ventilation` 0 = natural).
|
||||
|
||||
**Air Pressure Test** — Not available (RdSAP uses % draughtproofing).
|
||||
|
||||
**Lighting**
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Total number of bulbs | 6 | `fixed_lighting_outlets_count` (SAP-2012 lodges outlets, not bulbs) |
|
||||
| Low energy | 6 (100%) | `low_energy_fixed_lighting_outlets_count`; minor energy term (engine 133 kWh) |
|
||||
| Incandescents | 0 | |
|
||||
|
||||
## Space Heating
|
||||
|
||||
**Main Heating 1**
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Main Heating EES Code | **SEK → SAP 409, High heat retention storage heaters** | EES: Electric → Electric → Storage → High heat retention. `sap_main_heating_code` 409 |
|
||||
| Main Heating Controls EES | **CSD → SAP 2404, Controls for high heat retention storage heaters** | EES: Storage Radiator Systems → CSD. `main_heating_control` 2404 |
|
||||
| Percentage of Heat | 100 | `main_heating_fraction` 1 |
|
||||
| Storage heater list | 6 heaters, all high heat retention | `storage_heaters` 2+2+2 |
|
||||
| PCDF refs / Compensator | 0 | none lodged |
|
||||
| Heat Emitter / Flue / Pump Age | N/A | wet-system fields — disabled for storage heaters |
|
||||
|
||||
**Main Heating 2** — none (clear "COM"; % heat 0). Only one main system.
|
||||
|
||||
**Community Heating** — None.
|
||||
|
||||
**Meters** ⚠️
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Electricity meter type | **Dual-rate / Economy 7 (7-hour)** | NOT Single. `main_fuel_type`/`water_heating_fuel` 29 = off-peak 7-hour. **Engine bug: prices 100% at 5.50p low rate (£0.055/kWh) — should apply SAP Table 12a high/low split (high rate 15.29p)** |
|
||||
| Mains gas | **unchecked** | `mains_gas` N |
|
||||
| Electricity / Gas Smart Meter | unchecked | |
|
||||
|
||||
**Secondary heating**
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Is there secondary heating? | Yes | engine 10% fraction |
|
||||
| Secondary Heating EES Code | REA → SAP 691, Electric Panel/convector/radiant heaters | "Portable electric heaters (assumed)"; EES Electric → Electric → Room Heaters → Panel/convector/radiant. **Standard** tariff (fuel 30, 15.29p) — engine got this rate right |
|
||||
|
||||
## Water Heating
|
||||
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Water Heating EES Code | **SAP 903, Electric immersion (off-peak)** | Elmhurst "**Water Heater**" category — NOT "Boiler Circulator" (901 = from main system). `water_heating_code` 903 |
|
||||
| Hot Water Cylinder Present | ✓ checked | `has_hot_water_cylinder` true |
|
||||
| Cylinder Size | Medium | `cylinder_size` 2 |
|
||||
| Insulated | **Foam** | `cylinder_insulation_type` 1 = factory foam |
|
||||
| Insulation Thickness | 38 mm | `cylinder_insulation_thickness` |
|
||||
| Immersion Heater | Single | `immersion_heating_type` 1 |
|
||||
|
||||
(HW also priced at the buggy £0.055/kWh — engine HW 2134 kWh.)
|
||||
|
||||
- **Community Hot Water** — PCDF ref 0 (none).
|
||||
- **Solar Water Heating** — unchecked (`solar_water_heating` N).
|
||||
- **WWHRS** ⚠️ — **No / not present**. `sap_heating.instantaneous_wwhrs` here = bath/shower ROOM counts (1 bath/shower room + 1 bath-with-mixer), NOT a heat-recovery device (ADR-0028). Do **not** add a Showersave unit — it would wrongly raise the score.
|
||||
- **FGHRS** — both unchecked (`has_fghrs` N).
|
||||
|
||||
## New Technologies
|
||||
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Photovoltaic panel | None | no PV lodged |
|
||||
| Wind turbine present? | No | `wind_turbines_count` 0 |
|
||||
| Small-Scale Hydro | 0.00 | |
|
||||
| Special Features (Appendix Q) | none | |
|
||||
|
||||
(Export capable meter has no effect with no generation.)
|
||||
|
||||
---
|
||||
|
||||
## Engine reference values (for cross-check)
|
||||
|
||||
| Quantity | Engine value |
|
||||
|---|---|
|
||||
| SAP score | **62** (continuous 62.47) |
|
||||
| Total fuel cost | £595.68 |
|
||||
| Space heating | 6717.47 kWh/yr (main fuel 6045.73, secondary 671.75) |
|
||||
| Hot water | 2134.0 kWh/yr |
|
||||
| Lighting | 133.35 kWh/yr |
|
||||
| Heat loss (total) | 144.28 W/K — walls 80.98, roof 5.04, floor 16.80, windows 13.07 (eff U 2.27), doors 11.10, party wall 0.0, thermal bridging 17.30 |
|
||||
| Space heating / HW fuel | £0.055/kWh (off-peak low rate — **see bug note**) |
|
||||
|
||||
**Next step:** key these into Elmhurst, then read off Elmhurst's SAP score. If it lands near **55** (lodged) rather than our **62**, the Economy-7 fuel-cost split is confirmed as the over-rating cause.
|
||||
|
|
@ -0,0 +1,349 @@
|
|||
{
|
||||
"uprn": 10002468137,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, 200 mm loft insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Solid brick, as built, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 1
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Solid, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 2,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Low energy lighting in all fixed outlets",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "GU29 0DB",
|
||||
"hot_water": {
|
||||
"description": "Electric immersion, off-peak",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
"post_town": "MIDHURST",
|
||||
"built_form": 3,
|
||||
"created_at": "2017-05-29 21:33:44.000000",
|
||||
"door_count": 2,
|
||||
"glazed_area": 3,
|
||||
"glazing_gap": 12,
|
||||
"region_code": 14,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"cylinder_size": 2,
|
||||
"water_heating_code": 903,
|
||||
"water_heating_fuel": 29,
|
||||
"instantaneous_wwhrs": {
|
||||
"rooms_with_bath_and_or_shower": 1,
|
||||
"rooms_with_mixer_shower_no_bath": 0,
|
||||
"rooms_with_bath_and_mixer_shower": 1
|
||||
},
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 29,
|
||||
"storage_heaters": [
|
||||
{
|
||||
"index_number": 230013,
|
||||
"number_of_heaters": 2,
|
||||
"high_heat_retention": "true"
|
||||
},
|
||||
{
|
||||
"index_number": 230001,
|
||||
"number_of_heaters": 2,
|
||||
"high_heat_retention": "true"
|
||||
},
|
||||
{
|
||||
"index_number": 230002,
|
||||
"number_of_heaters": 2,
|
||||
"high_heat_retention": "true"
|
||||
}
|
||||
],
|
||||
"heat_emitter_type": 0,
|
||||
"emitter_temperature": "NA",
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2404,
|
||||
"main_heating_category": 7,
|
||||
"main_heating_fraction": 1,
|
||||
"sap_main_heating_code": 409,
|
||||
"main_heating_data_source": 2
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": 1,
|
||||
"cylinder_insulation_type": 1,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"cylinder_insulation_thickness": 38
|
||||
},
|
||||
"sap_version": 9.92,
|
||||
"schema_type": "RdSAP-Schema-17.1",
|
||||
"uprn_source": "Energy Assessor",
|
||||
"country_code": "EAW",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": "Electric storage heaters",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 2
|
||||
}
|
||||
],
|
||||
"dwelling_type": "End-terrace house",
|
||||
"language_code": 1,
|
||||
"property_type": 0,
|
||||
"address_line_1": "1 Foundry Cottages",
|
||||
"address_line_2": "Heyshott",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2017-05-29",
|
||||
"inspection_date": "2017-05-08",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"total_floor_area": 48,
|
||||
"transaction_type": 5,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 3,
|
||||
"pvc_window_frames": "true",
|
||||
"registration_date": "2017-05-29",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "N",
|
||||
"meter_type": 1,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"pv_connection": 0,
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"wind_turbines_terrain_type": 2
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "Portable electric heaters (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 300,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 3,
|
||||
"building_part_number": 1,
|
||||
"sap_alternative_wall": {
|
||||
"wall_area": 3.4,
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 300,
|
||||
"wall_construction": 3,
|
||||
"wall_insulation_type": 4,
|
||||
"wall_thickness_measured": "Y",
|
||||
"wall_insulation_thickness": "NI"
|
||||
},
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.4,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 24,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 7.06,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_construction": 1,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 12.16,
|
||||
"quantity": "metres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": {
|
||||
"value": 2.5,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 24,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 7.06,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 13.86,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "A",
|
||||
"party_wall_construction": 1,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "200mm",
|
||||
"wall_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"low_energy_lighting": 100,
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 3,
|
||||
"heating_cost_current": {
|
||||
"value": 619,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 4.9,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 55,
|
||||
"lighting_cost_current": {
|
||||
"value": 39,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Controls for high heat retention storage heaters",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"multiple_glazing_type": 3,
|
||||
"open_fireplaces_count": 0,
|
||||
"has_hot_water_cylinder": "true",
|
||||
"heating_cost_potential": {
|
||||
"value": 253,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 140,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 309,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "14,000",
|
||||
"improvement_type": "Q",
|
||||
"improvement_details": {
|
||||
"improvement_number": 7
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 72,
|
||||
"environmental_impact_rating": 54
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": {
|
||||
"value": 39,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "6,000",
|
||||
"improvement_type": "W2",
|
||||
"improvement_details": {
|
||||
"improvement_number": 58
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 74,
|
||||
"environmental_impact_rating": 58
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": {
|
||||
"value": 63,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "6,000",
|
||||
"improvement_type": "N",
|
||||
"improvement_details": {
|
||||
"improvement_number": 19
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 77,
|
||||
"environmental_impact_rating": 64
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"typical_saving": {
|
||||
"value": 20,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "1,000",
|
||||
"improvement_type": "X",
|
||||
"improvement_details": {
|
||||
"improvement_number": 48
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 78,
|
||||
"environmental_impact_rating": 66
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"typical_saving": {
|
||||
"value": 318,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "8,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 93,
|
||||
"environmental_impact_rating": 78
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.1,
|
||||
"energy_rating_potential": 93,
|
||||
"lighting_cost_potential": {
|
||||
"value": 39,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "LIG-17.1",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 74,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 1657,
|
||||
"impact_of_solid_wall_insulation": -3811,
|
||||
"space_heating_existing_dwelling": 7524
|
||||
},
|
||||
"energy_consumption_current": 602,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "2.11r11",
|
||||
"energy_consumption_potential": 136,
|
||||
"environmental_impact_current": 33,
|
||||
"fixed_lighting_outlets_count": 6,
|
||||
"current_energy_efficiency_band": "D",
|
||||
"environmental_impact_potential": 78,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "A",
|
||||
"co2_emissions_current_per_floor_area": 102,
|
||||
"low_energy_fixed_lighting_outlets_count": 6
|
||||
}
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
{
|
||||
"uprn": 100020450179,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, 200 mm loft insulation",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, filled cavity",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Solid, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 2,
|
||||
"window": {
|
||||
"description": "Fully double glazed",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Low energy lighting in 78% of fixed outlets",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "BR5 2TD",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "ORPINGTON",
|
||||
"built_form": 2,
|
||||
"created_at": "2018-10-08 14:02:11.000000",
|
||||
"door_count": 1,
|
||||
"glazed_area": 1,
|
||||
"glazing_gap": "16+",
|
||||
"region_code": 14,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"cylinder_size": 1,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"instantaneous_wwhrs": {
|
||||
"rooms_with_bath_and_or_shower": 1,
|
||||
"rooms_with_mixer_shower_no_bath": 0,
|
||||
"rooms_with_bath_and_mixer_shower": 1
|
||||
},
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": 0,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2106,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"central_heating_pump_age": 0,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 17505
|
||||
}
|
||||
],
|
||||
"immersion_heating_type": "NA",
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 9.93,
|
||||
"schema_type": "RdSAP-Schema-18.0",
|
||||
"uprn_source": "Energy Assessor",
|
||||
"country_code": "EAW",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": "Boiler and radiators, mains gas",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"dwelling_type": "Semi-detached house",
|
||||
"language_code": 1,
|
||||
"property_type": 0,
|
||||
"address_line_1": "20, Brenchley Road",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2018-10-08",
|
||||
"inspection_date": "2018-10-05",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"total_floor_area": 73,
|
||||
"transaction_type": 8,
|
||||
"conservatory_type": 2,
|
||||
"heated_room_count": 5,
|
||||
"pvc_window_frames": "true",
|
||||
"registration_date": "2018-10-08",
|
||||
"sap_energy_source": {
|
||||
"mains_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"photovoltaic_supply": {
|
||||
"none_or_no_details": {
|
||||
"pv_connection": 0,
|
||||
"percent_roof_area": 0
|
||||
}
|
||||
},
|
||||
"wind_turbines_count": 0,
|
||||
"wind_turbines_terrain_type": 2
|
||||
},
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"identifier": "Main Dwelling",
|
||||
"wall_dry_lined": "N",
|
||||
"wall_thickness": 300,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": {
|
||||
"value": 2.5,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": {
|
||||
"value": 36.55,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 6.48,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"floor_construction": 1,
|
||||
"heat_loss_perimeter": {
|
||||
"value": 17.76,
|
||||
"quantity": "metres"
|
||||
}
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": {
|
||||
"value": 2.43,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"total_floor_area": {
|
||||
"value": 36.55,
|
||||
"quantity": "square metres"
|
||||
},
|
||||
"party_wall_length": {
|
||||
"value": 6.48,
|
||||
"quantity": "metres"
|
||||
},
|
||||
"heat_loss_perimeter": {
|
||||
"value": 17.76,
|
||||
"quantity": "metres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 2,
|
||||
"construction_age_band": "D",
|
||||
"party_wall_construction": 1,
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "200mm",
|
||||
"wall_insulation_thickness": "NI",
|
||||
"floor_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"low_energy_lighting": 78,
|
||||
"solar_water_heating": "N",
|
||||
"habitable_room_count": 5,
|
||||
"heating_cost_current": {
|
||||
"value": 396,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 2.3,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 73,
|
||||
"lighting_cost_current": {
|
||||
"value": 65,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, room thermostat and TRVs",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"multiple_glazing_type": 3,
|
||||
"open_fireplaces_count": 0,
|
||||
"has_hot_water_cylinder": "false",
|
||||
"heating_cost_potential": {
|
||||
"value": 367,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 83,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 29,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "6,000",
|
||||
"improvement_type": "W2",
|
||||
"improvement_details": {
|
||||
"improvement_number": 58
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 74,
|
||||
"environmental_impact_rating": 73
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": {
|
||||
"value": 29,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "6,000",
|
||||
"improvement_type": "N",
|
||||
"improvement_details": {
|
||||
"improvement_number": 19
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 75,
|
||||
"environmental_impact_rating": 75
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": {
|
||||
"value": 305,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "8,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 87,
|
||||
"environmental_impact_rating": 86
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.0,
|
||||
"energy_rating_potential": 87,
|
||||
"lighting_cost_potential": {
|
||||
"value": 65,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "LIG-18.0",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 54,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 1866,
|
||||
"space_heating_existing_dwelling": 6466
|
||||
},
|
||||
"energy_consumption_current": 178,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "3.08r07",
|
||||
"energy_consumption_potential": 75,
|
||||
"environmental_impact_current": 71,
|
||||
"fixed_lighting_outlets_count": 9,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 86,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 31,
|
||||
"low_energy_fixed_lighting_outlets_count": 7
|
||||
}
|
||||
|
|
@ -0,0 +1,433 @@
|
|||
{
|
||||
"uprn": 10092973954,
|
||||
"roofs": [
|
||||
{
|
||||
"description": {
|
||||
"value": "(other premises above)",
|
||||
"language": "1"
|
||||
},
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": {
|
||||
"value": "Average thermal transmittance 0.17 W/m\u00b2K",
|
||||
"language": "1"
|
||||
},
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": {
|
||||
"value": "Average thermal transmittance 0.13 W/m\u00b2K",
|
||||
"language": "1"
|
||||
},
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": "ND",
|
||||
"windows": {
|
||||
"description": {
|
||||
"value": "High performance glazing",
|
||||
"language": "1"
|
||||
},
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"lighting": {
|
||||
"description": {
|
||||
"value": "Low energy lighting in all fixed outlets",
|
||||
"language": "1"
|
||||
},
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "ME1 3WR",
|
||||
"data_type": 2,
|
||||
"hot_water": {
|
||||
"description": {
|
||||
"value": "From main system",
|
||||
"language": "1"
|
||||
},
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "ROCHESTER",
|
||||
"built_form": 1,
|
||||
"created_at": "2020-03-12 08:41:35",
|
||||
"living_area": 23.45,
|
||||
"orientation": 6,
|
||||
"region_code": 14,
|
||||
"report_type": 3,
|
||||
"sap_heating": {
|
||||
"water_fuel_type": 1,
|
||||
"water_heating_code": 901,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"main_fuel_type": 1,
|
||||
"heat_emitter_type": 1,
|
||||
"emitter_temperature": 0,
|
||||
"is_flue_fan_present": "true",
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2110,
|
||||
"is_interlocked_system": "true",
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"main_heating_flue_type": 2,
|
||||
"central_heating_pump_age": 2,
|
||||
"main_heating_data_source": 1,
|
||||
"main_heating_index_number": 17929,
|
||||
"has_separate_delayed_start": "false",
|
||||
"load_or_weather_compensation": 4,
|
||||
"compensating_controller_index_number": 200004,
|
||||
"is_central_heating_pump_in_heated_space": "true"
|
||||
}
|
||||
],
|
||||
"has_hot_water_cylinder": "false",
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"secondary_heating_category": 1,
|
||||
"sap_heating_design_water_use": 1
|
||||
},
|
||||
"sap_version": 9.92,
|
||||
"schema_type": "SAP-Schema-17.1",
|
||||
"uprn_source": "Address Matched",
|
||||
"country_code": "ENG",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": {
|
||||
"value": "Boiler and radiators, mains gas",
|
||||
"language": "1"
|
||||
},
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"air_tightness": {
|
||||
"description": {
|
||||
"value": "Air permeability 2.6 m\u00b3/h.m\u00b2 (as tested)",
|
||||
"language": "1"
|
||||
},
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"dwelling_type": {
|
||||
"value": "Ground-floor flat",
|
||||
"language": "1"
|
||||
},
|
||||
"language_code": 1,
|
||||
"property_type": 2,
|
||||
"address_line_1": "Flat 1 William House",
|
||||
"address_line_2": "22, Keepers Cottage Lane",
|
||||
"address_line_3": "Wouldham",
|
||||
"assessment_date": "2020-03-12",
|
||||
"assessment_type": "SAP",
|
||||
"completion_date": "2020-03-12",
|
||||
"inspection_date": "2020-03-12",
|
||||
"sap_ventilation": {
|
||||
"psv_count": 0,
|
||||
"pressure_test": 1,
|
||||
"wet_rooms_count": 2,
|
||||
"air_permeability": 2.6,
|
||||
"open_flues_count": 0,
|
||||
"ventilation_type": 6,
|
||||
"extract_fans_count": 0,
|
||||
"open_fireplaces_count": 0,
|
||||
"sheltered_sides_count": 1,
|
||||
"kitchen_duct_fans_count": 0,
|
||||
"kitchen_room_fans_count": 0,
|
||||
"kitchen_wall_fans_count": 1,
|
||||
"flueless_gas_fires_count": 0,
|
||||
"mechanical_vent_duct_type": 2,
|
||||
"non_kitchen_duct_fans_count": 0,
|
||||
"non_kitchen_room_fans_count": 0,
|
||||
"non_kitchen_wall_fans_count": 1,
|
||||
"mechanical_ventilation_data_source": 1,
|
||||
"mechanical_vent_system_index_number": 500229,
|
||||
"is_mechanical_vent_approved_installer_scheme": "true"
|
||||
},
|
||||
"design_water_use": 1,
|
||||
"sap_data_version": 9.92,
|
||||
"sap_flat_details": {
|
||||
"level": 1
|
||||
},
|
||||
"total_floor_area": 68,
|
||||
"transaction_type": 6,
|
||||
"conservatory_type": 1,
|
||||
"registration_date": "2020-03-12",
|
||||
"sap_energy_source": {
|
||||
"electricity_tariff": 1,
|
||||
"wind_turbines_count": 0,
|
||||
"wind_turbine_terrain_type": 2,
|
||||
"fixed_lighting_outlets_count": 1,
|
||||
"low_energy_fixed_lighting_outlets_count": 1,
|
||||
"low_energy_fixed_lighting_outlets_percentage": 100
|
||||
},
|
||||
"sap_opening_types": [
|
||||
{
|
||||
"name": "Door (1)",
|
||||
"type": 1,
|
||||
"u_value": 1.4,
|
||||
"data_source": 2,
|
||||
"description": "Data from Manufacturer",
|
||||
"glazing_type": 1
|
||||
},
|
||||
{
|
||||
"name": "Windows (1)",
|
||||
"type": 4,
|
||||
"u_value": 1.4,
|
||||
"frame_type": 2,
|
||||
"data_source": 2,
|
||||
"description": "Data from Manufacturer",
|
||||
"frame_factor": 0.7,
|
||||
"glazing_type": 7,
|
||||
"solar_transmittance": 0.63
|
||||
}
|
||||
],
|
||||
"secondary_heating": {
|
||||
"description": {
|
||||
"value": "None",
|
||||
"language": "1"
|
||||
},
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"sap_roofs": [
|
||||
{
|
||||
"name": "Exposed Roof",
|
||||
"u_value": 0,
|
||||
"roof_type": 2,
|
||||
"kappa_value": 0,
|
||||
"total_roof_area": 0
|
||||
},
|
||||
{
|
||||
"name": "Ceiling",
|
||||
"u_value": 0,
|
||||
"roof_type": 4,
|
||||
"kappa_value": 20,
|
||||
"total_roof_area": 67.84
|
||||
}
|
||||
],
|
||||
"sap_walls": [
|
||||
{
|
||||
"name": "Brickwork",
|
||||
"u_value": 0.18,
|
||||
"wall_type": 2,
|
||||
"kappa_value": 18,
|
||||
"total_wall_area": 13.1,
|
||||
"is_curtain_walling": "false"
|
||||
},
|
||||
{
|
||||
"name": "Weatherboarding",
|
||||
"u_value": 0.18,
|
||||
"wall_type": 2,
|
||||
"kappa_value": 18,
|
||||
"total_wall_area": 46.95,
|
||||
"is_curtain_walling": "false"
|
||||
},
|
||||
{
|
||||
"name": "Sole Plate Detail",
|
||||
"u_value": 0.18,
|
||||
"wall_type": 2,
|
||||
"kappa_value": 18,
|
||||
"total_wall_area": 3.1,
|
||||
"is_curtain_walling": "false"
|
||||
},
|
||||
{
|
||||
"name": "Stair Wall",
|
||||
"u_value": 0.19,
|
||||
"wall_type": 2,
|
||||
"kappa_value": 18,
|
||||
"total_wall_area": 28.39,
|
||||
"is_curtain_walling": "false"
|
||||
},
|
||||
{
|
||||
"name": "Stud Walls",
|
||||
"u_value": 0,
|
||||
"wall_type": 5,
|
||||
"kappa_value": 9,
|
||||
"total_wall_area": 125.952
|
||||
}
|
||||
],
|
||||
"identifier": "Main Dwelling",
|
||||
"overshading": 2,
|
||||
"sap_openings": [
|
||||
{
|
||||
"name": 1,
|
||||
"type": "Door (1)",
|
||||
"width": 1,
|
||||
"height": 2,
|
||||
"location": "Stair Wall",
|
||||
"orientation": 6
|
||||
},
|
||||
{
|
||||
"name": 2,
|
||||
"type": "Windows (1)",
|
||||
"width": 1,
|
||||
"height": 5.92,
|
||||
"location": "Brickwork",
|
||||
"orientation": 8
|
||||
},
|
||||
{
|
||||
"name": 3,
|
||||
"type": "Windows (1)",
|
||||
"width": 1,
|
||||
"height": 1.45,
|
||||
"location": "Brickwork",
|
||||
"orientation": 2
|
||||
},
|
||||
{
|
||||
"name": 4,
|
||||
"type": "Windows (1)",
|
||||
"width": 1,
|
||||
"height": 1.45,
|
||||
"location": "Brickwork",
|
||||
"orientation": 6
|
||||
},
|
||||
{
|
||||
"name": 5,
|
||||
"type": "Windows (1)",
|
||||
"width": 1,
|
||||
"height": 5.28,
|
||||
"location": "Weatherboarding",
|
||||
"orientation": 2
|
||||
},
|
||||
{
|
||||
"name": 6,
|
||||
"type": "Windows (1)",
|
||||
"width": 1,
|
||||
"height": 1.5,
|
||||
"location": "Weatherboarding",
|
||||
"orientation": 4
|
||||
}
|
||||
],
|
||||
"construction_year": 2020,
|
||||
"sap_thermal_bridges": {
|
||||
"thermal_bridges": [
|
||||
{
|
||||
"length": 9.79,
|
||||
"psi_value": 0.3,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E2"
|
||||
},
|
||||
{
|
||||
"length": 3.18,
|
||||
"psi_value": 0.04,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E3"
|
||||
},
|
||||
{
|
||||
"length": 23.7,
|
||||
"psi_value": 0.05,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E4"
|
||||
},
|
||||
{
|
||||
"length": 38.14,
|
||||
"psi_value": 0.16,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E5"
|
||||
},
|
||||
{
|
||||
"length": 38.14,
|
||||
"psi_value": 0.07,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E7"
|
||||
},
|
||||
{
|
||||
"length": 21.6,
|
||||
"psi_value": 0.09,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E16"
|
||||
},
|
||||
{
|
||||
"length": 12,
|
||||
"psi_value": -0.09,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E17"
|
||||
}
|
||||
],
|
||||
"thermal_bridge_code": 5
|
||||
},
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"storey": 0,
|
||||
"u_value": 0.13,
|
||||
"floor_type": 2,
|
||||
"kappa_value": 75,
|
||||
"storey_height": 2.4,
|
||||
"heat_loss_area": 67.84,
|
||||
"total_floor_area": 67.84
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"heating_cost_current": {
|
||||
"value": 201,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"co2_emissions_current": 1.1,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 83,
|
||||
"lighting_cost_current": {
|
||||
"value": 55,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": {
|
||||
"value": "Time and temperature zone control",
|
||||
"language": "1"
|
||||
},
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"has_hot_water_cylinder": "false",
|
||||
"heating_cost_potential": {
|
||||
"value": 201,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 68,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"co2_emissions_potential": 1.1,
|
||||
"energy_rating_potential": 83,
|
||||
"lighting_cost_potential": {
|
||||
"value": 55,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "LIG-17.0",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 68,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"is_in_smoke_control_area": "unknown",
|
||||
"renewable_heat_incentive": {
|
||||
"rhi_new_dwelling": {
|
||||
"space_heating": 2140,
|
||||
"water_heating": 1522
|
||||
}
|
||||
},
|
||||
"seller_commission_report": "Y",
|
||||
"energy_consumption_current": 91,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_percentage": 100,
|
||||
"calculation_software_version": "Version: 1.0.4.25",
|
||||
"energy_consumption_potential": 91,
|
||||
"environmental_impact_current": 86,
|
||||
"current_energy_efficiency_band": "B",
|
||||
"environmental_impact_potential": 86,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 16
|
||||
}
|
||||
|
|
@ -10,9 +10,11 @@ raises only on a missing *required* field.
|
|||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
|
||||
|
||||
@dataclass
|
||||
class SapSchema17_1:
|
||||
# Stub — slice 1 will grow this to parse the real cert's identity fields.
|
||||
uprn: int
|
||||
schema_type: str
|
||||
total_floor_area: Union[int, float]
|
||||
|
|
|
|||
199
tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py
Normal file
199
tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
"""Real-cert accuracy pins for the SAP 10 calculator.
|
||||
|
||||
Each case is a real certificate captured one-off from the GOV.UK EPB
|
||||
register and frozen as a JSON sample under
|
||||
`backend/epc_api/json_samples/real_life_examples/<name>/epc.json`. The test feeds that
|
||||
sample through the *real* call path — `EpcClientService` (with `httpx`
|
||||
mocked, so no network and so the JSON can be hand-tweaked to probe
|
||||
cases) → `EpcPropertyDataMapper` → `EpcPropertyData` →
|
||||
`Sap10Calculator` — and pins `SapResult` fields against expected
|
||||
values confirmed by a business domain expert.
|
||||
|
||||
This complements the curated Elmhurst cohort (`worksheet/
|
||||
test_e2e_elmhurst_sap_score.py`): that pins against U985 worksheet PDFs
|
||||
via in-code `build_epc()` fixtures; this exercises the raw-API front
|
||||
end (the mapper) on real lodged certs.
|
||||
|
||||
Per `[[feedback-e2e-validation-philosophy]]`: pins are exact (ints) or
|
||||
`abs=1e-4` (floats). A failing pin is a calculator bug to fix, not a
|
||||
tolerance to relax. Start each case with the expert-confirmed
|
||||
`sap_score`; add per-end-use kWh pins as each is validated against a
|
||||
worksheet.
|
||||
"""
|
||||
|
||||
import json
|
||||
import pathlib
|
||||
from dataclasses import dataclass
|
||||
from typing import Final, Optional
|
||||
from unittest.mock import patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from domain.sap10_calculator.calculator import Sap10Calculator
|
||||
from infrastructure.epc_client.epc_client_service import EpcClientService
|
||||
|
||||
_SAMPLES_DIR: Final[pathlib.Path] = pathlib.Path(
|
||||
"backend/epc_api/json_samples/real_life_examples"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RealCertExpectation:
|
||||
"""Expert-confirmed expected `SapResult` values for one real cert.
|
||||
|
||||
Samples are bucketed by `schema` so the tree stays legible as cases
|
||||
grow: `_SAMPLES_DIR / schema / sample / epc.json`. `cert_num` is the
|
||||
certificate number the cert is fetched by (cosmetic here — the mocked
|
||||
client ignores it — but kept for traceability). Float fields are
|
||||
`None` until validated against a worksheet, so a case can land with
|
||||
just the `sap_score` and grow over time.
|
||||
|
||||
`unsupported_schema=True` marks a cert whose schema the mapper can't
|
||||
yet consume (full SAP vs RdSAP). Those cases are expected to FAIL at
|
||||
the mapper until support lands — see `test_real_cert_sap_score`.
|
||||
"""
|
||||
|
||||
schema: str
|
||||
sample: str
|
||||
cert_num: str
|
||||
sap_score: int
|
||||
space_heating_kwh_per_yr: Optional[float] = None
|
||||
main_heating_fuel_kwh_per_yr: Optional[float] = None
|
||||
hot_water_kwh_per_yr: Optional[float] = None
|
||||
co2_kg_per_yr: Optional[float] = None
|
||||
unsupported_schema: bool = False
|
||||
|
||||
|
||||
# Absolute tolerance for float pins — matches the Elmhurst cohort.
|
||||
_FLOAT_PIN_ABS: Final[float] = 1e-4
|
||||
|
||||
|
||||
_EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = (
|
||||
# UPRN 100020450179 → cert 9543-2865-6207-9208-6301. RdSAP-Schema-18.0,
|
||||
# band C, lodged 2018-10-08. Lodged `energy_rating_current` = 73; our
|
||||
# calculator reproduces 73 exactly. kWh pins to be added once confirmed
|
||||
# against a worksheet with the domain expert.
|
||||
RealCertExpectation(
|
||||
schema="RdSAP-Schema-18.0",
|
||||
sample="uprn_100020450179",
|
||||
cert_num="9543-2865-6207-9208-6301",
|
||||
sap_score=73,
|
||||
),
|
||||
# UPRN 10092973954 → cert 0862-3892-7875-2690-2325. SAP-Schema-17.1 —
|
||||
# a FULL-SAP cert (new-build/on-construction), NOT RdSAP. The mapper
|
||||
# only supports RdSAP schemas, so the chain raises `Unsupported EPC
|
||||
# schema` today. Kept as a strict-xfail boundary case: lodged rating
|
||||
# is 83, and when full-SAP mapper support lands this xfail flips to a
|
||||
# failure prompting us to fill in the real expected score.
|
||||
RealCertExpectation(
|
||||
schema="SAP-Schema-17.1",
|
||||
sample="uprn_10092973954",
|
||||
cert_num="0862-3892-7875-2690-2325",
|
||||
sap_score=83,
|
||||
unsupported_schema=True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _as_param(exp: RealCertExpectation) -> object:
|
||||
"""Wrap a case as a pytest param, marking unsupported-schema certs as
|
||||
strict xfails (they raise `ValueError` at the mapper until full-SAP
|
||||
support exists; strict so the marker can't silently outlive the gap)."""
|
||||
marks = (
|
||||
[
|
||||
pytest.mark.xfail(
|
||||
reason="full-SAP (non-RdSAP) schema not yet supported by the mapper",
|
||||
raises=ValueError,
|
||||
strict=True,
|
||||
)
|
||||
]
|
||||
if exp.unsupported_schema
|
||||
else []
|
||||
)
|
||||
return pytest.param(exp, id=exp.sample, marks=marks)
|
||||
|
||||
|
||||
# Cases that can actually be mapped + calculated — used by the kWh-pin
|
||||
# test, which has nothing to assert for an unmappable cert.
|
||||
_MAPPABLE: Final[tuple[RealCertExpectation, ...]] = tuple(
|
||||
e for e in _EXPECTATIONS if not e.unsupported_schema
|
||||
)
|
||||
|
||||
|
||||
def _mock_certificate_response(cert_data: dict[str, object]) -> httpx.Response:
|
||||
"""A 200 from `/api/certificate` wrapping `cert_data` as the register
|
||||
does (payload under the `data` key)."""
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={"data": cert_data},
|
||||
request=httpx.Request("GET", "https://example.test/api/certificate"),
|
||||
)
|
||||
|
||||
|
||||
def _load_cert(exp: RealCertExpectation) -> dict[str, object]:
|
||||
return json.loads(
|
||||
(_SAMPLES_DIR / exp.schema / exp.sample / "epc.json").read_text()
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.parametrize("exp", [_as_param(e) for e in _EXPECTATIONS])
|
||||
def test_real_cert_sap_score(exp: RealCertExpectation) -> None:
|
||||
"""SAP score for a real lodged cert matches the expert-confirmed value.
|
||||
|
||||
Full real chain: `get_by_certificate_number` → mapper →
|
||||
`EpcPropertyData` → `Sap10Calculator.calculate`, with `httpx` mocked
|
||||
so the frozen sample stands in for the live register. Unsupported-
|
||||
schema cases are strict xfails — they raise at the mapper.
|
||||
"""
|
||||
# Arrange — frozen real-API sample, served through the mocked client
|
||||
cert_data = _load_cert(exp)
|
||||
service = EpcClientService(auth_token="test-token")
|
||||
|
||||
# Act
|
||||
with patch("httpx.get", return_value=_mock_certificate_response(cert_data)):
|
||||
epc = service.get_by_certificate_number(exp.cert_num)
|
||||
result = Sap10Calculator().calculate(epc)
|
||||
|
||||
# Assert — exact pin; no tolerance widening
|
||||
assert result.sap_score == exp.sap_score, (
|
||||
f"{exp.sample}: sap_score actual={result.sap_score}, "
|
||||
f"expected={exp.sap_score}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.parametrize("exp", [_as_param(e) for e in _MAPPABLE])
|
||||
def test_real_cert_end_use_kwh_pins(exp: RealCertExpectation) -> None:
|
||||
"""Per-end-use kWh pins for fields the expert has confirmed.
|
||||
|
||||
Skips fields still set to `None` (not yet worksheet-validated), so a
|
||||
case contributes pins incrementally as it matures. Once every float
|
||||
field on a case is populated this stops skipping for that case.
|
||||
"""
|
||||
# Arrange
|
||||
cert_data = _load_cert(exp)
|
||||
service = EpcClientService(auth_token="test-token")
|
||||
float_fields = (
|
||||
"space_heating_kwh_per_yr",
|
||||
"main_heating_fuel_kwh_per_yr",
|
||||
"hot_water_kwh_per_yr",
|
||||
"co2_kg_per_yr",
|
||||
)
|
||||
confirmed = {f: getattr(exp, f) for f in float_fields if getattr(exp, f) is not None}
|
||||
if not confirmed:
|
||||
pytest.skip(f"{exp.sample}: no worksheet-confirmed kWh pins yet")
|
||||
|
||||
# Act
|
||||
with patch("httpx.get", return_value=_mock_certificate_response(cert_data)):
|
||||
epc = service.get_by_certificate_number(exp.cert_num)
|
||||
result = Sap10Calculator().calculate(epc)
|
||||
|
||||
# Assert
|
||||
for field_name, expected in confirmed.items():
|
||||
actual = getattr(result, field_name)
|
||||
assert actual == pytest.approx(expected, abs=_FLOAT_PIN_ABS), (
|
||||
f"{exp.sample}.{field_name}: actual={actual}, expected={expected}, "
|
||||
f"diff={abs(actual - expected):.4f}"
|
||||
)
|
||||
Loading…
Add table
Reference in a new issue