mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Add SAP-16.2 schema coverage + single-glazing fix; flat party-wall fix; pin 2 certs
SAP-Schema-16.2 (datatypes/epc/domain/mapper.py): - 16.2 is structurally an RdSAP-17.1 cert under a different name; add _normalize_sap_schema_16_2 (field renames + defaults) and dispatch to the tested from_rdsap_schema_17_1 mapper. uprn_100020933699 maps → SAP 71. - Honour a "Single glazed" windows description when multiple_glazing_type="ND" (was defaulting to double) → RdSAP-21 code 5; eng 72→71 (lodged 70). - 4 regression tests + sap_16_2.json fixture; 0 new pyright errors. Flat party-wall fix (domain/sap10_calculator/worksheet/heat_transmission.py): - Full-SAP flats carry flatness in dwelling_type, not property_type, so the party-wall default fell through to the 0.25 house value instead of the RdSAP Table-15 flat 0.0. Add _is_flat_or_maisonette_dwelling fallback + regression test. uprn_10093116529 80→81 (matches the cert's lodged party u_value 0). Accuracy corpus pins (tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py): - uprn_10093116543 (SAP-17.1 gas-combi semi): engine 81 (Elmhurst 77; documented full-SAP→RdSAP residual — measured wall/floor U + PCDB boiler vs RdSAP defaults). - uprn_10093116529 (SAP-17.1 g/f flat): engine 81 (Elmhurst 78). devcontainer: add poppler-utils (pdfinfo) for the documents-parser PDF fixtures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
928fbbc33a
commit
4d14607e7e
16 changed files with 1460 additions and 6 deletions
|
|
@ -10,12 +10,26 @@ UPRN, tick it and annotate: `— <schema> · eng <X> / elm <Y> · <note>`.
|
|||
2020 new-build flat) — full loop proven: eng 77 / elm 78, engine-on-Elmhurst-
|
||||
inputs 79 (calculator faithful within ~1). Use it to sanity-check the pipeline.
|
||||
|
||||
## E2E testing set
|
||||
|
||||
UPRNs needed for end-to-end testing (also tracked in The 100 below).
|
||||
|
||||
| UPRN | Schema | Status | Engine SAP | Notes |
|
||||
|---|---|---|---|---|
|
||||
| 10093116528 | SAP-17.1 | ✅ pinned | 82 (elm 81) | full-SAP semi; AP50 fix |
|
||||
| 10093116543 | SAP-17.1 | ✅ pinned | 81 (elm 77) | 2017 gas-combi semi |
|
||||
| 10093116529 | SAP-17.1 | ✅ pinned | 81 (elm 78) | 2017 gas-combi g/f flat; party-wall fix |
|
||||
| 100020933699 | SAP-16.2 | 🔧 mapper + glazing fix | 71 (lodged 70) | RdSAP-shaped; single-glazing fix; Elmhurst validation pending |
|
||||
| 44012843 | — | ☐ todo | — | not yet processed |
|
||||
| 10023444324 | — | ☐ todo | — | not yet processed |
|
||||
| 10023444320 | — | ☐ todo | — | not yet processed |
|
||||
|
||||
## The 100
|
||||
|
||||
- [x] 🔧 10093116528 — SAP-17.1 (full-SAP semi) · eng 82 / elm 81 · 🔧 air-perm AP50 fix: q50 Blower-Door routed to (18)=AP50/20 not the AP4/Pulse formula (was eng 78). Residual +1 = lodged-U vs RdSAP age-band-U; FGHRS (60031) omitted both sides. Worked-ref 10092973954 re-pinned 77→80 by same fix.
|
||||
- [ ] 10093116543
|
||||
- [ ] 10093116529
|
||||
- [ ] 100020933699
|
||||
- [x] 10093116543 — SAP-17.1 (2017 gas-combi semi) · eng 81 / elm 77 (lodged 82) · PINNED engine 81. +4 = documented full-SAP→RdSAP residual, NOT a mapper bug: ~1.5 floor-U (cert lodges measured 0.11 vs Elmhurst RdSAP solid default 0.23; U-known override disabled) + ~1 boiler-eff (cert PCDB 17644 88.5% vs Elmhurst generic BGW combi 84%; PCDB search disabled, 89% cascade option is a regular boiler needing a cylinder) + ~0.5 roof band-L/infil. Conservatory leftover from prior cert cleared (worksheet 73→77). No mapper change.
|
||||
- [x] 🔧 10093116529 — SAP-17.1 (2017 gas-combi ground-floor FLAT, TFA 49) · eng 81 / elm 78 (lodged 81) · PINNED engine 81. 🔧 FIXED a real calc bug: full-SAP flats took the 0.25 house party-wall default instead of the RdSAP Table-15 flat 0.0 (flatness is in dwelling_type, not property_type) — heat_transmission._is_flat_or_maisonette_dwelling; +regression test. Cert lodges party u_value 0; Elmhurst worksheet 0.0; fix 80→81. Residual +3 vs Elmhurst = documented full-SAP→RdSAP gap (measured wall 0.184/floor 0.12 + PCDB 88.5% vs generic 84%). Calculator faithful: fed Elmhurst's Us, HTC 93.4 vs ~94. House→Flat Elmhurst switch (storeys→1, roof→another-dwelling-above). No mapper change.
|
||||
- [ ] 🔧 100020933699 — SAP-16.2 SCHEMA COVERAGE ADDED (end-terrace house, band G). 16.2 is structurally RdSAP-17.1 (reduced fields, glazed_area band, construction-code building parts) under a different name; mapped via `_normalize_sap_schema_16_2` (renames windows→window, main_gas→mains_gas, boiler_index_number→main_heating_index_number, wwhrs→instantaneous_wwhrs + defaults) → reuses from_rdsap_schema_17_1. 🔧 Also fixed: "Single glazed" description honoured when multiple_glazing_type="ND" (was defaulting to double; RdSAP-21 code 5) → eng 72→71. +4 regression tests, sap_16_2.json fixture, 0 new pyright errors. eng 71 / lodged 70. ⚠ Known gap: 16.2 lodges no party_wall_length → end-terrace party wall unmodelled (likely the residual +1). ⏳ Elmhurst build (partial: PropDesc/Dims/Walls/Roofs done) + pin still pending.
|
||||
- [ ] 44012843
|
||||
- [ ] 10023444324
|
||||
- [ ] 10092970673
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@ ARG USER_UID=1000
|
|||
ARG USER_GID=1000
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# 1) Toolchain + utilities for building libpostal, plus LazyVim deps
|
||||
# 1) Toolchain + utilities for building libpostal, plus LazyVim deps.
|
||||
# poppler-utils provides `pdfinfo`, required by backend/documents_parser (the
|
||||
# Elmhurst Input-Summary / SAP-worksheet PDF fixtures in the SAP corpus tests
|
||||
# shell out to it).
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
sudo jq vim curl git ca-certificates wget \
|
||||
build-essential pkg-config automake autoconf libtool \
|
||||
ripgrep fd-find make unzip bash-completion \
|
||||
ripgrep fd-find make unzip bash-completion poppler-utils \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 1b) Headed-browser viewer stack for the hyde Elmhurst automation.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
# Elmhurst RdSAP inputs — UPRN 100020933699 (cert 9548-2053-6299-9947-0950, SAP-Schema-16.2)
|
||||
|
||||
**Lodged SAP:** 70 **Our engine:** 72 (continuous 72.29) ← compare Elmhurst against the engine
|
||||
**Property:** End-terrace HOUSE, age band G (1983–1990), mains-gas regular boiler + cylinder, TFA 62 m²
|
||||
|
||||
**Schema note:** SAP-Schema-16.2 is RdSAP-shaped (reduced-field) — mapped via `_normalize_sap_schema_16_2` → RdSAP-17.1 mapper. This is a REDUCED-FIELD cert (descriptions + glazed-area band, no measured U-values), so unlike the 17.1 new-builds the engine and Elmhurst should both use RdSAP age-band/description U-values — expect a TIGHTER agreement.
|
||||
|
||||
**Known divergences / gaps to watch:**
|
||||
- **Party wall:** 16.2 does not lodge `party_wall_length`; the normalizer defaults it to 0, so the engine currently models NO party wall. An end-terrace HAS one party wall — Elmhurst will model it. **This is the likely main divergence; check the worksheet party-wall line.**
|
||||
- Age band G → Elmhurst on-screen band **G 1983-1990**.
|
||||
|
||||
## Property Description
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Property Type | **House** | dwelling_type "End-terrace house" (switch from FLAT — prior cert) |
|
||||
| Built form | **End-Terrace** | built_form 3 |
|
||||
| Age band | **G 1983-1990** | construction_age_band G |
|
||||
| Storeys | 2 | floor 0 + floor 1 |
|
||||
| Habitable rooms | 4 | habitable_room_count 4 |
|
||||
|
||||
## Dimensions
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Ground floor area | 31.06 m² | floor 0 |
|
||||
| First floor area | 31.06 m² | floor 1 |
|
||||
| Room height (ground / first) | 2.40 / 2.65 m | room_height |
|
||||
| Heat-loss perimeter (each) | 15.8 m | heat_loss_perimeter |
|
||||
| Party-wall length (each) | **see note** | 16.2 lodges none; for an end-terrace enter the real party-wall length if Elmhurst derives it from built-form, else note the divergence |
|
||||
|
||||
## Walls
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Construction | Cavity | wall_construction 4 |
|
||||
| Insulation | **Filled** | wall_insulation_type 2 = filled cavity ("Cavity wall, filled cavity") |
|
||||
| Thickness | 280 mm | wall_thickness |
|
||||
| Party wall | Present (End-Terrace) — Unable to determine | built_form 3 |
|
||||
|
||||
## Roofs
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Type | Pitched, access to loft | roof_construction 4 |
|
||||
| Insulation at | Joists | roof_insulation_location 2 |
|
||||
| Thickness | 100 mm | roof_insulation_thickness "100mm" |
|
||||
|
||||
## Floors
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Location / type | Ground floor / Solid | floor_construction 1 |
|
||||
| Insulation | **None** | "Solid, no insulation (assumed)" |
|
||||
|
||||
## Openings
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Windows | **Single glazed**, glazed-area band Normal | glazed_area 1, multiple_glazing_type ND, "Single glazed" |
|
||||
| Doors | **2 doors, uninsulated** | door_count 2, insulated_door_count 0 (high U — solid/older doors) |
|
||||
|
||||
## Ventilation & Lighting
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Ventilation | Natural | no MV; no air-permeability test lodged (ap50/ap4 None) |
|
||||
| Extract fans / flues | 0 / 0 | none lodged; open chimneys 0 |
|
||||
| Sheltered sides | 1 | sheltered_sides 1 |
|
||||
| Air Pressure Test | **None** (do NOT enter Blower Door) | no test lodged — leave method blank |
|
||||
| Lighting | 22% low-energy | low_energy 2 / total 9 outlets |
|
||||
|
||||
## Space Heating
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Main heating | Mains gas **regular boiler + radiators** | fuel 26, emitter 1, control 2106, fan-assisted flue, PCDB 10321. NOT a combi — has a cylinder → use the BGB regular condensing/non-condensing cascade option (per age band G, likely non-condensing) |
|
||||
| Secondary | None | secondary "None" |
|
||||
|
||||
## Water Heating
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Water heating | From main (901), gas | water_heating_fuel 26 |
|
||||
| Hot water cylinder | **Present** — 110 L (Normal), Foam, 25 mm | has_hot_water_cylinder True, cylinder_size 2, insulation type 1 (foam) 25 mm |
|
||||
| Solar / WWHRS / FGHRS | None | — |
|
||||
|
||||
## Fields to clear in Elmhurst (do NOT map)
|
||||
| Elmhurst field | Set to | Why absent |
|
||||
|---|---|---|
|
||||
| Flats page | (gone — House now) | switch from prior FLAT cert |
|
||||
| Conservatory | none / uncheck | not lodged |
|
||||
| Air Pressure Test method | **blank** | no test lodged (prior certs had Blower Door — CLEAR it) |
|
||||
| Main Heating 2 / Secondary | none | single system |
|
||||
| PV / Wind / Hydro | none | none lodged |
|
||||
|
|
@ -0,0 +1,310 @@
|
|||
{
|
||||
"uprn": 100020933699,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, 100 mm loft insulation",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, filled cavity",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Solid, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 2,
|
||||
"windows": [
|
||||
{
|
||||
"description": "Single glazed",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 1
|
||||
}
|
||||
],
|
||||
"lighting": {
|
||||
"description": "Low energy lighting in 22% of fixed outlets",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
"postcode": "SE18 2PE",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "LONDON",
|
||||
"built_form": 3,
|
||||
"created_at": "2013-01-14 15:52:33.000000",
|
||||
"door_count": 2,
|
||||
"glazed_area": 1,
|
||||
"region_code": 17,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"wwhrs": {
|
||||
"rooms_with_bath_and_or_shower": 1,
|
||||
"rooms_with_mixer_shower_no_bath": 0,
|
||||
"rooms_with_bath_and_mixer_shower": 0
|
||||
},
|
||||
"cylinder_size": 2,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"cylinder_thermostat": "Y",
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 1,
|
||||
"boiler_index_number": 10321,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2106,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"main_heating_data_source": 1
|
||||
}
|
||||
],
|
||||
"cylinder_insulation_type": 1,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"cylinder_insulation_thickness": 25
|
||||
},
|
||||
"sap_version": 9.91,
|
||||
"schema_type": "SAP-Schema-16.2",
|
||||
"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": "End-terrace house",
|
||||
"language_code": 1,
|
||||
"property_type": 0,
|
||||
"address_line_1": "1, Acland Close",
|
||||
"schema_version": "LIG-16.1",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2013-01-14",
|
||||
"inspection_date": "2013-01-14",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"total_floor_area": 62,
|
||||
"transaction_type": 8,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 4,
|
||||
"registration_date": "2013-01-14",
|
||||
"restricted_access": 0,
|
||||
"sap_energy_source": {
|
||||
"main_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"photovoltaic_supply": {
|
||||
"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": 280,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": 2.4,
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": 31.06,
|
||||
"floor_construction": 1,
|
||||
"heat_loss_perimeter": 15.8
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": 2.4,
|
||||
"total_floor_area": 31.06,
|
||||
"heat_loss_perimeter": 15.8
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 2,
|
||||
"construction_age_band": "G",
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "100mm"
|
||||
}
|
||||
],
|
||||
"low_energy_lighting": 22,
|
||||
"solar_water_heating": "N",
|
||||
"bedf_revision_number": 333,
|
||||
"habitable_room_count": 4,
|
||||
"heating_cost_current": {
|
||||
"value": 370,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 2.2,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 70,
|
||||
"lighting_cost_current": {
|
||||
"value": 66,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, room thermostat and TRVs",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"multiple_glazing_type": "ND",
|
||||
"open_fireplaces_count": 0,
|
||||
"has_hot_water_cylinder": "true",
|
||||
"heating_cost_potential": {
|
||||
"value": 296,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 106,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 27,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3800 - \u00a31,200",
|
||||
"improvement_type": "W",
|
||||
"improvement_details": {
|
||||
"improvement_number": 47
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 71,
|
||||
"environmental_impact_rating": 73
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": {
|
||||
"value": 26,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a335",
|
||||
"improvement_type": "E",
|
||||
"improvement_details": {
|
||||
"improvement_number": 35
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 72,
|
||||
"environmental_impact_rating": 74
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": {
|
||||
"value": 33,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a34,000 - \u00a36,000",
|
||||
"improvement_type": "N",
|
||||
"improvement_details": {
|
||||
"improvement_number": 19
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 74,
|
||||
"environmental_impact_rating": 76
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"typical_saving": {
|
||||
"value": 52,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a33,300 - \u00a36,500",
|
||||
"improvement_type": "O",
|
||||
"improvement_details": {
|
||||
"improvement_number": 8
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 77,
|
||||
"environmental_impact_rating": 80
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"typical_saving": {
|
||||
"value": 236,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a39,000 - \u00a314,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 89,
|
||||
"environmental_impact_rating": 92
|
||||
},
|
||||
{
|
||||
"sequence": 6,
|
||||
"typical_saving": {
|
||||
"value": 19,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a31,500 - \u00a34,000",
|
||||
"improvement_type": "V",
|
||||
"improvement_details": {
|
||||
"improvement_number": 44
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 90,
|
||||
"environmental_impact_rating": 92
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 0.5,
|
||||
"energy_rating_potential": 90,
|
||||
"lighting_cost_potential": {
|
||||
"value": 37,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_potential": {
|
||||
"value": 72,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2577,
|
||||
"impact_of_loft_insulation": -309,
|
||||
"space_heating_existing_dwelling": 5864
|
||||
},
|
||||
"seller_commission_report": "Y",
|
||||
"energy_consumption_current": 189,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 0,
|
||||
"calculation_software_version": 8.0,
|
||||
"energy_consumption_potential": 36,
|
||||
"environmental_impact_current": 71,
|
||||
"fixed_lighting_outlets_count": 9,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 92,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 36,
|
||||
"low_energy_fixed_lighting_outlets_count": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
# Elmhurst RdSAP inputs — UPRN 10093116529 (cert 8178-7436-5600-9809-0906, SAP-Schema-17.1)
|
||||
|
||||
**Lodged SAP:** 81 **Our engine:** 80 (continuous 80.22) ← compare Elmhurst against the engine
|
||||
**Property:** ground-floor FLAT, 2017 build, mains-gas combi, TFA 49.0 m²
|
||||
|
||||
**Known divergences (flag, don't tune) — same full-SAP→RdSAP pattern as uprn_10093116543:**
|
||||
- **Walls/floor measured U:** cert lodges measured U (wall 0.184, floor 0.12); engine uses them, Elmhurst RdSAP forces band-L defaults (~0.28 / ~0.23). Expected residual.
|
||||
- **Boiler:** cert PCDB 17644 (88.5%); Elmhurst PCDB search disabled → generic BGW combi 84%.
|
||||
- **Age band:** engine 'M', build year 2017 → enter Elmhurst band **L (2012–2022)**.
|
||||
|
||||
## Property Description
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Property Type | **Flat** | dwelling_type "Ground-floor flat" — SWITCH from House (prior cert) |
|
||||
| Built form | (flat — N/A) | — |
|
||||
| Age band | **L (2012–2022)** | construction_year 2017 |
|
||||
| Habitable rooms | 2 | habitable_rooms_count 2 |
|
||||
| Storeys | 1 | single-storey flat |
|
||||
|
||||
## Flats
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Position of flat | Ground Floor | ground-floor flat |
|
||||
| Floor level | 0 | ground = storey 0 |
|
||||
| Corridor | **None** (all-exposed) | the "to corridor" wall is keyed as a semi-exposed wall element instead — see Walls. Avoids the sheltered-alt-wall validation trap. |
|
||||
|
||||
## Dimensions
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Lowest-floor area | 49.03 m² | floor dim[0] |
|
||||
| Room height | 2.60 m | room_height_m |
|
||||
| Heat-loss perimeter | 28.45 m | heat_loss_perimeter_m |
|
||||
| Party-wall length | 4.34 m | party_wall_length_m |
|
||||
|
||||
## Walls
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Main wall | Cavity / As Built | wall_construction 4; cert lodges measured U 0.184 (Elmhurst RdSAP default ~0.28) |
|
||||
| Wall area (external) | 56.6 m² | Wall 1 "External" |
|
||||
| "To corridor" wall | semi-exposed, 17.37 m², U 0.15 | Wall 3 (type 3). Heat loss to unheated corridor — key as an alternative/sheltered wall if Elmhurst allows; else note divergence. |
|
||||
| Party wall | Present, **U Unable to determine** | Wall 2 (type 4) area 11.28; party_walls_w_per_k 2.82 |
|
||||
|
||||
## Roofs
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Roof type | **A Another dwelling above** | ground-floor flat → roof_w_per_k = 0 |
|
||||
|
||||
## Floors
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Location | Ground floor | floor_type 2 |
|
||||
| Type / insulation | Solid / As built | cert lodges measured U 0.12 (Elmhurst RdSAP ~0.23) |
|
||||
|
||||
## Openings
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Windows (combined) | ~24.9 m², East, Double post-2022 | 7 synth windows; U 1.4 / g 0.72 / frame 0.7 |
|
||||
| Doors | 1 insulated, U 1.6 | door_count 1 |
|
||||
|
||||
## Ventilation & Lighting
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Ventilation | Natural + 2 intermittent extract fans | extract_fans_count 2 |
|
||||
| Air Pressure Test | Blower Door, **3.45** (+ cert number) | air_permeability_ap50 3.45 |
|
||||
| Sheltered sides | 2 | sheltered_sides 2 |
|
||||
| Lighting | 100% low-energy | 10 bulbs, all low-energy |
|
||||
|
||||
## Space Heating
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Main heating | Mains gas condensing **combi** (generic BGW 84% substitute) | fuel 1, emitter radiators, control 2110, fan-assisted balanced flue, PCDB 17644 (88.5% — can't enter) |
|
||||
| Secondary | None | — |
|
||||
|
||||
## Water Heating
|
||||
| Elmhurst field | Value | Notes |
|
||||
|---|---|---|
|
||||
| Water heating | From main (combi), gas | code 901; no cylinder |
|
||||
| Solar / WWHRS / FGHRS | None | — |
|
||||
|
||||
## Fields to clear in Elmhurst (do NOT map)
|
||||
| Elmhurst field | Set to | Why absent |
|
||||
|---|---|---|
|
||||
| Property Description · extensions / room-in-roof | blank | none |
|
||||
| Dimensions · 1st-floor row | clear | single-storey flat (was 2-storey house) |
|
||||
| Conservatory | none / uncheck | not lodged (CLEAR — prior-cert leftover risk) |
|
||||
| Space Heating · Main Heating 2 / Secondary | none | single main system |
|
||||
| Water Heating · cylinder | none | combi |
|
||||
| New Tech · PV / Wind / Hydro | none | none lodged |
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,390 @@
|
|||
{
|
||||
"uprn": 10093116529,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "(other premises above)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Average thermal transmittance 0.17 W/m\u00b2K",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Average thermal transmittance 0.12 W/m\u00b2K",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": "ND",
|
||||
"windows": {
|
||||
"description": "High performance glazing",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"lighting": {
|
||||
"description": "Low energy lighting in all fixed outlets",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"postcode": "PO10 8BG",
|
||||
"data_type": 2,
|
||||
"hot_water": {
|
||||
"description": "From main system, flue gas heat recovery",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
},
|
||||
"post_town": "EMSWORTH",
|
||||
"built_form": 2,
|
||||
"created_at": "2018-06-04 10:56:20",
|
||||
"living_area": 21.77,
|
||||
"orientation": 0,
|
||||
"region_code": 16,
|
||||
"report_type": 3,
|
||||
"sap_heating": {
|
||||
"thermal_store": 1,
|
||||
"water_fuel_type": 1,
|
||||
"water_heating_code": 901,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "true",
|
||||
"main_fuel_type": 1,
|
||||
"heat_emitter_type": 1,
|
||||
"fghrs_index_number": 60031,
|
||||
"emitter_temperature": 1,
|
||||
"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": 17644,
|
||||
"has_separate_delayed_start": "true",
|
||||
"load_or_weather_compensation": 0,
|
||||
"is_central_heating_pump_in_heated_space": "true"
|
||||
}
|
||||
],
|
||||
"has_hot_water_cylinder": "false",
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"secondary_heating_category": 1
|
||||
},
|
||||
"sap_version": 9.92,
|
||||
"schema_type": "SAP-Schema-17.1",
|
||||
"uprn_source": "Energy Assessor",
|
||||
"country_code": "ENG",
|
||||
"main_heating": [
|
||||
{
|
||||
"description": "Boiler and radiators, mains gas",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"air_tightness": {
|
||||
"description": "Air permeability 3.5 m\u00b3/h.m\u00b2 (as tested)",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"dwelling_type": "Ground-floor flat",
|
||||
"language_code": 1,
|
||||
"property_type": 2,
|
||||
"address_line_1": "6, Woodfield Park Road",
|
||||
"assessment_date": "2018-06-01",
|
||||
"assessment_type": "SAP",
|
||||
"completion_date": "2018-06-04",
|
||||
"inspection_date": "2018-06-01",
|
||||
"sap_ventilation": {
|
||||
"psv_count": 0,
|
||||
"pressure_test": 1,
|
||||
"air_permeability": 3.45,
|
||||
"open_flues_count": 0,
|
||||
"ventilation_type": 1,
|
||||
"extract_fans_count": 2,
|
||||
"open_fireplaces_count": 0,
|
||||
"sheltered_sides_count": 2,
|
||||
"flueless_gas_fires_count": 0
|
||||
},
|
||||
"design_water_use": 1,
|
||||
"sap_data_version": 9.92,
|
||||
"sap_flat_details": {
|
||||
"level": 1
|
||||
},
|
||||
"total_floor_area": 49,
|
||||
"transaction_type": 6,
|
||||
"conservatory_type": 1,
|
||||
"registration_date": "2018-06-04",
|
||||
"sap_energy_source": {
|
||||
"electricity_tariff": 1,
|
||||
"wind_turbines_count": 0,
|
||||
"wind_turbine_terrain_type": 2,
|
||||
"fixed_lighting_outlets_count": 10,
|
||||
"low_energy_fixed_lighting_outlets_count": 10,
|
||||
"low_energy_fixed_lighting_outlets_percentage": 100
|
||||
},
|
||||
"sap_opening_types": [
|
||||
{
|
||||
"name": 1,
|
||||
"type": 4,
|
||||
"u_value": 1.4,
|
||||
"data_source": 2,
|
||||
"description": "Window",
|
||||
"frame_factor": 0.7,
|
||||
"glazing_type": 4,
|
||||
"solar_transmittance": 0.72
|
||||
},
|
||||
{
|
||||
"name": 8,
|
||||
"type": 1,
|
||||
"u_value": 1.6,
|
||||
"data_source": 2,
|
||||
"description": "Front Door",
|
||||
"glazing_type": 1
|
||||
}
|
||||
],
|
||||
"secondary_heating": {
|
||||
"description": "None",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
},
|
||||
"sap_building_parts": [
|
||||
{
|
||||
"sap_walls": [
|
||||
{
|
||||
"name": "Wall 1",
|
||||
"u_value": 0.184,
|
||||
"wall_type": 2,
|
||||
"description": "External",
|
||||
"total_wall_area": 56.6,
|
||||
"is_curtain_walling": "false"
|
||||
},
|
||||
{
|
||||
"name": "Wall 3",
|
||||
"u_value": 0.15,
|
||||
"wall_type": 3,
|
||||
"description": "To corridor",
|
||||
"total_wall_area": 17.37,
|
||||
"is_curtain_walling": "false"
|
||||
},
|
||||
{
|
||||
"name": "Wall 2",
|
||||
"u_value": 0,
|
||||
"wall_type": 4,
|
||||
"description": "To flat",
|
||||
"total_wall_area": 11.28
|
||||
}
|
||||
],
|
||||
"identifier": "Main Dwelling",
|
||||
"overshading": 2,
|
||||
"sap_openings": [
|
||||
{
|
||||
"name": 1,
|
||||
"type": 1,
|
||||
"width": 0.72,
|
||||
"height": 2.5,
|
||||
"location": "Wall 1",
|
||||
"orientation": 3
|
||||
},
|
||||
{
|
||||
"name": 2,
|
||||
"type": 1,
|
||||
"width": 1.6,
|
||||
"height": 2.5,
|
||||
"location": "Wall 1",
|
||||
"orientation": 3
|
||||
},
|
||||
{
|
||||
"name": 3,
|
||||
"type": 1,
|
||||
"width": 1,
|
||||
"height": 2.5,
|
||||
"location": "Wall 1",
|
||||
"orientation": 7
|
||||
},
|
||||
{
|
||||
"name": 4,
|
||||
"type": 1,
|
||||
"width": 2.5,
|
||||
"height": 2.5,
|
||||
"location": "Wall 1",
|
||||
"orientation": 7
|
||||
},
|
||||
{
|
||||
"name": 5,
|
||||
"type": 1,
|
||||
"width": 1.15,
|
||||
"height": 2.5,
|
||||
"location": "Wall 1",
|
||||
"orientation": 1
|
||||
},
|
||||
{
|
||||
"name": 6,
|
||||
"type": 1,
|
||||
"width": 1.5,
|
||||
"height": 2.5,
|
||||
"location": "Wall 1",
|
||||
"orientation": 1
|
||||
},
|
||||
{
|
||||
"name": 7,
|
||||
"type": 1,
|
||||
"width": 1.5,
|
||||
"height": 2.5,
|
||||
"location": "Wall 1",
|
||||
"orientation": 1
|
||||
},
|
||||
{
|
||||
"name": 8,
|
||||
"type": 8,
|
||||
"width": 1,
|
||||
"height": 2.1,
|
||||
"location": "Wall 3",
|
||||
"orientation": 0
|
||||
}
|
||||
],
|
||||
"construction_year": 2017,
|
||||
"sap_thermal_bridges": {
|
||||
"thermal_bridges": [
|
||||
{
|
||||
"length": 10.97,
|
||||
"psi_value": 0.3,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E2"
|
||||
},
|
||||
{
|
||||
"length": 9.97,
|
||||
"psi_value": 0.04,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E3"
|
||||
},
|
||||
{
|
||||
"length": 39.2,
|
||||
"psi_value": 0.05,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E4"
|
||||
},
|
||||
{
|
||||
"length": 21.96,
|
||||
"psi_value": 0.16,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E5"
|
||||
},
|
||||
{
|
||||
"length": 21.96,
|
||||
"psi_value": 0.07,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E7"
|
||||
},
|
||||
{
|
||||
"length": 10.4,
|
||||
"psi_value": 0.09,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E16"
|
||||
},
|
||||
{
|
||||
"length": 2.6,
|
||||
"psi_value": -0.09,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E17"
|
||||
},
|
||||
{
|
||||
"length": 2.4,
|
||||
"psi_value": 0.06,
|
||||
"psi_value_source": 2,
|
||||
"thermal_bridge_type": "E18"
|
||||
},
|
||||
{
|
||||
"length": 6.68,
|
||||
"psi_value": 0.16,
|
||||
"psi_value_source": 4,
|
||||
"thermal_bridge_type": "P1"
|
||||
},
|
||||
{
|
||||
"length": 6.68,
|
||||
"psi_value": 0,
|
||||
"psi_value_source": 4,
|
||||
"thermal_bridge_type": "P3"
|
||||
}
|
||||
],
|
||||
"thermal_bridge_code": 5
|
||||
},
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"storey": 0,
|
||||
"u_value": 0.12,
|
||||
"floor_type": 2,
|
||||
"description": "Floor 1",
|
||||
"storey_height": 2.6,
|
||||
"heat_loss_area": 49.03,
|
||||
"total_floor_area": 49.03
|
||||
}
|
||||
],
|
||||
"thermal_mass_parameter": 100
|
||||
}
|
||||
],
|
||||
"heating_cost_current": {
|
||||
"value": 185,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"co2_emissions_current": 0.9,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 81,
|
||||
"lighting_cost_current": {
|
||||
"value": 37,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Time and temperature zone control",
|
||||
"energy_efficiency_rating": 5,
|
||||
"environmental_efficiency_rating": 5
|
||||
}
|
||||
],
|
||||
"has_hot_water_cylinder": "false",
|
||||
"heating_cost_potential": {
|
||||
"value": 185,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 58,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"co2_emissions_potential": 0.9,
|
||||
"energy_rating_potential": 81,
|
||||
"lighting_cost_potential": {
|
||||
"value": 37,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"schema_version_original": "LIG-17.0",
|
||||
"hot_water_cost_potential": {
|
||||
"value": 58,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"is_in_smoke_control_area": "unknown",
|
||||
"renewable_heat_incentive": {
|
||||
"rhi_new_dwelling": {
|
||||
"space_heating": 1927,
|
||||
"water_heating": 1458
|
||||
}
|
||||
},
|
||||
"seller_commission_report": "Y",
|
||||
"energy_consumption_current": 108,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_percentage": 100,
|
||||
"calculation_software_version": 6.2,
|
||||
"energy_consumption_potential": 108,
|
||||
"environmental_impact_current": 85,
|
||||
"current_energy_efficiency_band": "B",
|
||||
"environmental_impact_potential": 85,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 19
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,3 +1,4 @@
|
|||
import copy
|
||||
import re
|
||||
from dataclasses import replace
|
||||
from datetime import date
|
||||
|
|
@ -2552,6 +2553,19 @@ class EpcPropertyDataMapper:
|
|||
return EpcPropertyDataMapper.from_sap_schema_17_1(
|
||||
from_dict(SapSchema17_1, data)
|
||||
)
|
||||
if schema == "SAP-Schema-16.2":
|
||||
# SAP-Schema-16.2 is structurally an RdSAP-17.1 cert (reduced fields,
|
||||
# glazed_area band, construction-code building parts) under a different
|
||||
# name + a handful of renamed/omitted fields — normalise it onto the
|
||||
# RdSAP-17.1 shape and reuse that tested mapper. See
|
||||
# `_normalize_sap_schema_16_2`.
|
||||
from datatypes.epc.schema.rdsap_schema_17_1 import RdSapSchema17_1
|
||||
|
||||
return _clear_basement_flag_when_system_built(
|
||||
EpcPropertyDataMapper.from_rdsap_schema_17_1(
|
||||
from_dict(RdSapSchema17_1, _normalize_sap_schema_16_2(data))
|
||||
)
|
||||
)
|
||||
|
||||
raise ValueError(f"Unsupported EPC schema: {schema!r}")
|
||||
|
||||
|
|
@ -3008,6 +3022,81 @@ def _default_missing_post_town(data: Dict[str, Any]) -> Dict[str, Any]:
|
|||
return {**data, "post_town": ""}
|
||||
|
||||
|
||||
def _normalize_sap_schema_16_2(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Rewrite a `SAP-Schema-16.2` API doc onto the `RdSAP-Schema-17.1` shape so
|
||||
it can reuse the tested `from_rdsap_schema_17_1` mapper.
|
||||
|
||||
Despite the "SAP-Schema" name, 16.2 is structurally an RdSAP cert (reduced
|
||||
fields: a `glazed_area` *band* not measured openings, construction-code
|
||||
building parts, `main_gas`/`meter_type` energy source, a PCDB boiler index).
|
||||
It differs from RdSAP-17.1 only by a handful of field names plus three
|
||||
fields it omits:
|
||||
* top-level `windows` (list) → `window` (first element);
|
||||
`schema_version` → `schema_version_original`
|
||||
* sap_heating `wwhrs` → `instantaneous_wwhrs` (identical shape);
|
||||
`immersion_heating_type` defaulted (16.2 omits it — gas cert)
|
||||
* main_heating_details[] `boiler_index_number` → `main_heating_index_number`
|
||||
(preserves PCDB efficiency); `emitter_temperature` defaulted
|
||||
* sap_floor_dimensions[] `party_wall_length` defaulted to 0 (16.2 does not
|
||||
lodge it — note: party-wall heat loss is then unmodelled)
|
||||
* sap_energy_source `main_gas` → `mains_gas`
|
||||
|
||||
Mirrors `_normalize_shower_outlets` — mutates a deep copy, never the
|
||||
caller's dict. Defaults use setdefault so a 16.2 cert that *does* carry the
|
||||
RdSAP-17.1 name is left untouched.
|
||||
"""
|
||||
d: Dict[str, Any] = copy.deepcopy(data)
|
||||
|
||||
def _dicts(value: Any) -> List[Dict[str, Any]]:
|
||||
"""The dict elements of `value` when it is a list, else []."""
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
items: List[Any] = cast(List[Any], value)
|
||||
return [cast(Dict[str, Any], x) for x in items if isinstance(x, dict)]
|
||||
|
||||
windows: Any = d.get("windows")
|
||||
if isinstance(windows, list) and windows:
|
||||
window_list: List[Any] = cast(List[Any], windows)
|
||||
d.setdefault("window", window_list[0])
|
||||
d.setdefault("schema_version_original", d.get("schema_version", ""))
|
||||
|
||||
# 16.2 lodges glazing in BOTH `multiple_glazing_type` (frequently the "ND"
|
||||
# not-defined sentinel) AND the windows[].description. When the numeric field
|
||||
# is undefined, honour an explicit "Single glazed" description so it is not
|
||||
# defaulted to double — RdSAP-21 code 5 = single glazing (cascade single
|
||||
# slot, U≈4.8). Otherwise keep the ND→double-modal default the cascade uses.
|
||||
glazing_type: Any = d.get("multiple_glazing_type")
|
||||
if not isinstance(glazing_type, int):
|
||||
window: Any = d.get("window")
|
||||
description = ""
|
||||
if isinstance(window, dict):
|
||||
description = str(cast(Dict[str, Any], window).get("description") or "").lower()
|
||||
if "single" in description:
|
||||
d["multiple_glazing_type"] = 5
|
||||
|
||||
sap_heating: Any = d.get("sap_heating")
|
||||
if isinstance(sap_heating, dict):
|
||||
heating: Dict[str, Any] = cast(Dict[str, Any], sap_heating)
|
||||
if "wwhrs" in heating:
|
||||
heating.setdefault("instantaneous_wwhrs", heating["wwhrs"])
|
||||
heating.setdefault("immersion_heating_type", None)
|
||||
for mh in _dicts(heating.get("main_heating_details")):
|
||||
if "boiler_index_number" in mh:
|
||||
mh.setdefault("main_heating_index_number", mh["boiler_index_number"])
|
||||
mh.setdefault("emitter_temperature", None)
|
||||
|
||||
for bp in _dicts(d.get("sap_building_parts")):
|
||||
for fd in _dicts(bp.get("sap_floor_dimensions")):
|
||||
fd.setdefault("party_wall_length", 0)
|
||||
|
||||
energy_source: Any = d.get("sap_energy_source")
|
||||
if isinstance(energy_source, dict):
|
||||
source: Dict[str, Any] = cast(Dict[str, Any], energy_source)
|
||||
if "main_gas" in source:
|
||||
source.setdefault("mains_gas", source["main_gas"])
|
||||
return d
|
||||
|
||||
|
||||
def _count_shower_outlets_by_type(
|
||||
schema_shower_outlets: Any,
|
||||
target_type: int,
|
||||
|
|
|
|||
|
|
@ -376,3 +376,54 @@ class TestFromSapSchema17_1Perimeter:
|
|||
schema = from_dict(SapSchema17_1, data)
|
||||
with pytest.raises(UnmappedApiCode):
|
||||
EpcPropertyDataMapper.from_sap_schema_17_1(schema)
|
||||
|
||||
|
||||
class TestFromSapSchema16_2:
|
||||
"""SAP-Schema-16.2 — structurally an RdSAP-17.1 cert under a different name
|
||||
+ a few renamed/omitted fields. `from_api_response` normalises it onto the
|
||||
RdSAP-17.1 shape (`_normalize_sap_schema_16_2`) and reuses that mapper.
|
||||
Regression for uprn_100020933699 (cert 9548-…-0950, End-terrace house)."""
|
||||
|
||||
def test_16_2_dispatches_and_maps_via_rdsap_17_1(self) -> None:
|
||||
# Arrange / Act — the raw 16.2 doc must dispatch (no "Unsupported
|
||||
# schema") and map all the renamed seams: windows→window,
|
||||
# main_gas→mains_gas, boiler_index_number→main_heating_index_number,
|
||||
# wwhrs→instantaneous_wwhrs.
|
||||
epc = EpcPropertyDataMapper.from_api_response(load("sap_16_2.json"))
|
||||
|
||||
# Assert — identity + the seams that would silently drop on a bad rename.
|
||||
assert isinstance(epc, EpcPropertyData)
|
||||
assert epc.uprn == 100020933699
|
||||
assert epc.total_floor_area_m2 == 62.0
|
||||
assert epc.dwelling_type == "End-terrace house"
|
||||
mh = epc.sap_heating.main_heating_details[0]
|
||||
assert mh.main_fuel_type == 26 # mains gas
|
||||
# boiler_index_number must survive as the PCDB index (efficiency source).
|
||||
assert mh.main_heating_index_number == 10321
|
||||
|
||||
def test_16_2_produces_a_sap_score(self) -> None:
|
||||
# The mapped cert must run end-to-end through the SAP-10 engine.
|
||||
from domain.sap10_calculator.calculator import Sap10Calculator
|
||||
|
||||
epc = EpcPropertyDataMapper.from_api_response(load("sap_16_2.json"))
|
||||
result = Sap10Calculator().calculate(epc)
|
||||
# Lodged 70; engine produces 71 (Elmhurst validation still pending).
|
||||
assert result.sap_score == 71
|
||||
|
||||
def test_16_2_single_glazed_description_honoured_over_nd(self) -> None:
|
||||
# 16.2 lodges multiple_glazing_type "ND" but a "Single glazed" windows
|
||||
# description; the normaliser must route to single glazing (cascade slot
|
||||
# 1, U≈4.8) not the ND→double default — else single-glazed older certs
|
||||
# over-rate. Regression for uprn_100020933699.
|
||||
epc = EpcPropertyDataMapper.from_api_response(load("sap_16_2.json"))
|
||||
assert epc.sap_windows, "expected synthesised windows"
|
||||
assert epc.sap_windows[0].glazing_type == 1 # cascade single-glazing slot
|
||||
|
||||
def test_16_2_normalizer_does_not_mutate_caller_dict(self) -> None:
|
||||
# Mirror _normalize_shower_outlets' contract: the caller's dict is
|
||||
# untouched (deep copy), so a re-dispatch sees the original shape.
|
||||
data = load("sap_16_2.json")
|
||||
before_keys = set(data.keys())
|
||||
EpcPropertyDataMapper.from_api_response(data)
|
||||
assert "window" not in data # the rename landed only on the copy
|
||||
assert set(data.keys()) == before_keys
|
||||
|
|
|
|||
310
datatypes/epc/schema/tests/fixtures/sap_16_2.json
vendored
Normal file
310
datatypes/epc/schema/tests/fixtures/sap_16_2.json
vendored
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
{
|
||||
"uprn": 100020933699,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, 100 mm loft insulation",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, filled cavity",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "Solid, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"tenure": 2,
|
||||
"windows": [
|
||||
{
|
||||
"description": "Single glazed",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 1
|
||||
}
|
||||
],
|
||||
"lighting": {
|
||||
"description": "Low energy lighting in 22% of fixed outlets",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
"postcode": "SE18 2PE",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "LONDON",
|
||||
"built_form": 3,
|
||||
"created_at": "2013-01-14 15:52:33.000000",
|
||||
"door_count": 2,
|
||||
"glazed_area": 1,
|
||||
"region_code": 17,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"wwhrs": {
|
||||
"rooms_with_bath_and_or_shower": 1,
|
||||
"rooms_with_mixer_shower_no_bath": 0,
|
||||
"rooms_with_bath_and_mixer_shower": 0
|
||||
},
|
||||
"cylinder_size": 2,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"cylinder_thermostat": "Y",
|
||||
"main_heating_details": [
|
||||
{
|
||||
"has_fghrs": "N",
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "Y",
|
||||
"heat_emitter_type": 1,
|
||||
"boiler_index_number": 10321,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2106,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"main_heating_data_source": 1
|
||||
}
|
||||
],
|
||||
"cylinder_insulation_type": 1,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"cylinder_insulation_thickness": 25
|
||||
},
|
||||
"sap_version": 9.91,
|
||||
"schema_type": "SAP-Schema-16.2",
|
||||
"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": "End-terrace house",
|
||||
"language_code": 1,
|
||||
"property_type": 0,
|
||||
"address_line_1": "1, Acland Close",
|
||||
"schema_version": "LIG-16.1",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2013-01-14",
|
||||
"inspection_date": "2013-01-14",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"total_floor_area": 62,
|
||||
"transaction_type": 8,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 4,
|
||||
"registration_date": "2013-01-14",
|
||||
"restricted_access": 0,
|
||||
"sap_energy_source": {
|
||||
"main_gas": "Y",
|
||||
"meter_type": 2,
|
||||
"photovoltaic_supply": {
|
||||
"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": 280,
|
||||
"floor_heat_loss": 7,
|
||||
"roof_construction": 4,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": 2.4,
|
||||
"floor_insulation": 1,
|
||||
"total_floor_area": 31.06,
|
||||
"floor_construction": 1,
|
||||
"heat_loss_perimeter": 15.8
|
||||
},
|
||||
{
|
||||
"floor": 1,
|
||||
"room_height": 2.4,
|
||||
"total_floor_area": 31.06,
|
||||
"heat_loss_perimeter": 15.8
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 2,
|
||||
"construction_age_band": "G",
|
||||
"wall_thickness_measured": "Y",
|
||||
"roof_insulation_location": 2,
|
||||
"roof_insulation_thickness": "100mm"
|
||||
}
|
||||
],
|
||||
"low_energy_lighting": 22,
|
||||
"solar_water_heating": "N",
|
||||
"bedf_revision_number": 333,
|
||||
"habitable_room_count": 4,
|
||||
"heating_cost_current": {
|
||||
"value": 370,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"insulated_door_count": 0,
|
||||
"co2_emissions_current": 2.2,
|
||||
"energy_rating_average": 60,
|
||||
"energy_rating_current": 70,
|
||||
"lighting_cost_current": {
|
||||
"value": 66,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, room thermostat and TRVs",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
}
|
||||
],
|
||||
"multiple_glazing_type": "ND",
|
||||
"open_fireplaces_count": 0,
|
||||
"has_hot_water_cylinder": "true",
|
||||
"heating_cost_potential": {
|
||||
"value": 296,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_current": {
|
||||
"value": 106,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"mechanical_ventilation": 0,
|
||||
"percent_draughtproofed": 100,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": {
|
||||
"value": 27,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a3800 - \u00a31,200",
|
||||
"improvement_type": "W",
|
||||
"improvement_details": {
|
||||
"improvement_number": 47
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 71,
|
||||
"environmental_impact_rating": 73
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": {
|
||||
"value": 26,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a335",
|
||||
"improvement_type": "E",
|
||||
"improvement_details": {
|
||||
"improvement_number": 35
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 72,
|
||||
"environmental_impact_rating": 74
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": {
|
||||
"value": 33,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a34,000 - \u00a36,000",
|
||||
"improvement_type": "N",
|
||||
"improvement_details": {
|
||||
"improvement_number": 19
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 74,
|
||||
"environmental_impact_rating": 76
|
||||
},
|
||||
{
|
||||
"sequence": 4,
|
||||
"typical_saving": {
|
||||
"value": 52,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a33,300 - \u00a36,500",
|
||||
"improvement_type": "O",
|
||||
"improvement_details": {
|
||||
"improvement_number": 8
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 77,
|
||||
"environmental_impact_rating": 80
|
||||
},
|
||||
{
|
||||
"sequence": 5,
|
||||
"typical_saving": {
|
||||
"value": 236,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a39,000 - \u00a314,000",
|
||||
"improvement_type": "U",
|
||||
"improvement_details": {
|
||||
"improvement_number": 34
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 89,
|
||||
"environmental_impact_rating": 92
|
||||
},
|
||||
{
|
||||
"sequence": 6,
|
||||
"typical_saving": {
|
||||
"value": 19,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"indicative_cost": "\u00a31,500 - \u00a34,000",
|
||||
"improvement_type": "V",
|
||||
"improvement_details": {
|
||||
"improvement_number": 44
|
||||
},
|
||||
"improvement_category": 5,
|
||||
"energy_performance_rating": 90,
|
||||
"environmental_impact_rating": 92
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 0.5,
|
||||
"energy_rating_potential": 90,
|
||||
"lighting_cost_potential": {
|
||||
"value": 37,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"hot_water_cost_potential": {
|
||||
"value": 72,
|
||||
"currency": "GBP"
|
||||
},
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 2577,
|
||||
"impact_of_loft_insulation": -309,
|
||||
"space_heating_existing_dwelling": 5864
|
||||
},
|
||||
"seller_commission_report": "Y",
|
||||
"energy_consumption_current": 189,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 0,
|
||||
"calculation_software_version": 8.0,
|
||||
"energy_consumption_potential": 36,
|
||||
"environmental_impact_current": 71,
|
||||
"fixed_lighting_outlets_count": 9,
|
||||
"current_energy_efficiency_band": "C",
|
||||
"environmental_impact_potential": 92,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "B",
|
||||
"co2_emissions_current_per_floor_area": 36,
|
||||
"low_energy_fixed_lighting_outlets_count": 2
|
||||
}
|
||||
|
|
@ -231,6 +231,20 @@ def _is_flat_or_maisonette(property_type: Optional[str]) -> bool:
|
|||
return property_type in _PROPERTY_TYPES_FLAT_OR_MAISONETTE
|
||||
|
||||
|
||||
def _is_flat_or_maisonette_dwelling(dwelling_type: Optional[str]) -> bool:
|
||||
"""Fallback flat/maisonette detection from `epc.dwelling_type` for certs
|
||||
where `property_type` is absent. Full-SAP / SAP-Schema certs carry the
|
||||
flatness only in dwelling_type (e.g. "Ground-floor flat", "Mid-floor
|
||||
maisonette"), so without this the RdSAP 10 Table 15 footnote * party-wall
|
||||
U=0 default never fires for them and they wrongly take the 0.25 house
|
||||
default. Houses/bungalows ("Detached house", "Mid-Terrace house") don't
|
||||
contain these tokens, so they stay on the house default."""
|
||||
if dwelling_type is None:
|
||||
return False
|
||||
dt = dwelling_type.lower()
|
||||
return "flat" in dt or "maisonette" in dt
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class HeatTransmission:
|
||||
"""SAP 10.2 §3 conduction HLC broken down per element type, summed
|
||||
|
|
@ -1029,7 +1043,10 @@ def heat_transmission_from_cert(
|
|||
# branch (they're houses for party-wall purposes per the spec).
|
||||
upw = u_party_wall(
|
||||
party_wall_construction=party_construction,
|
||||
is_flat=_is_flat_or_maisonette(epc.property_type),
|
||||
is_flat=(
|
||||
_is_flat_or_maisonette(epc.property_type)
|
||||
or _is_flat_or_maisonette_dwelling(epc.dwelling_type)
|
||||
),
|
||||
)
|
||||
# Per-bp `y` for backwards compat: when the bp's own age band
|
||||
# differs from the dwelling's primary, the cascade applies the
|
||||
|
|
|
|||
|
|
@ -129,6 +129,62 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = (
|
|||
cert_num="8000-8495-2839-2607-9683",
|
||||
sap_score=82,
|
||||
),
|
||||
# UPRN 10093116543 → cert 8358-7436-5620-6889-0906. SAP-Schema-17.1 — a
|
||||
# FULL-SAP cert (2017 mains-gas COMBI semi, Emsworth), forced through the
|
||||
# RdSAP SAP-10 engine. Lodged 82; engine produces 81. Built in Elmhurst
|
||||
# RdSAP10 on the lodged inputs (evidence saved: elmhurst_summary.pdf /
|
||||
# elmhurst_worksheet.pdf): Elmhurst worksheet SAP 77. The +4 (81 vs 77) is
|
||||
# the inherent full-SAP-lodged-value vs RdSAP-default methodology gap — the
|
||||
# engine faithfully uses the cert's MEASURED full-SAP data that Elmhurst
|
||||
# RdSAP structurally cannot:
|
||||
# • Ground floor: cert lodges measured U=0.11 (raw floor u_value 0.11,
|
||||
# "Average thermal transmittance 0.11 W/m²K"); engine uses 0.11, Elmhurst
|
||||
# RdSAP solid-floor default = 0.23 (~1.5 SAP). Elmhurst's U-value-known
|
||||
# override can't be set via the entry tool.
|
||||
# • Boiler: cert lodges PCDB index 17644 (88.5%); engine uses 88.5%,
|
||||
# Elmhurst's PCDB/SEDBUK boiler search is disabled in this UI state so the
|
||||
# only automatable combi is the generic SAP Table 4b "BGW condensing
|
||||
# combi" = 84% (~1 SAP). The 89% cascade option is a *regular* boiler
|
||||
# (needs a cylinder) — not valid for this combi/no-cylinder dwelling.
|
||||
# • Remainder: roof band-L 0.16 vs engine band-M 0.15, infiltration /
|
||||
# sheltered-sides 1-vs-2 (~0.5).
|
||||
# Calculator confirmed faithful on the prior sibling cert (engine on
|
||||
# Elmhurst's own parsed inputs ≈ worksheet). A leftover conservatory from the
|
||||
# reused assessment was cleared during the build (worksheet 73→77). PINNED TO
|
||||
# THE OBSERVED 81, not lodged 82 — mapping deliberately untuned; the Elmhurst
|
||||
# delta is the documented full-SAP→RdSAP residual, NOT a mapper bug.
|
||||
RealCertExpectation(
|
||||
schema="SAP-Schema-17.1",
|
||||
sample="uprn_10093116543",
|
||||
cert_num="8358-7436-5620-6889-0906",
|
||||
sap_score=81,
|
||||
),
|
||||
# UPRN 10093116529 → cert 8178-7436-5600-9809-0906. SAP-Schema-17.1 — a
|
||||
# FULL-SAP cert (2017 mains-gas combi GROUND-FLOOR FLAT, Emsworth, TFA 49 m²),
|
||||
# forced through the RdSAP SAP-10 engine. Lodged 81; engine produces 81. Built
|
||||
# in Elmhurst RdSAP10 on the lodged inputs (evidence saved: elmhurst_summary.pdf
|
||||
# / elmhurst_worksheet.pdf): Elmhurst worksheet SAP 78. Calculator confirmed
|
||||
# faithful — fed Elmhurst's own U-values the engine reproduces its HTC to ~0.6
|
||||
# W/K (93.4 vs ~94). The +3 (81 vs 78) is the full-SAP→RdSAP methodology gap:
|
||||
# cert lodges measured U (wall 0.184, floor 0.12), engine uses them, Elmhurst
|
||||
# RdSAP forces band-L defaults; plus the generic 84% combi vs the cert's PCDB
|
||||
# 17644 (88.5%). The "to corridor" wall (17.37 m², U 0.15) is already inside the
|
||||
# heat-loss perimeter (28.45 m × 2.6 = 73.97 m² external), so Elmhurst derives
|
||||
# it as part of the main cavity wall.
|
||||
# This cert surfaced + drove the FLAT PARTY-WALL FIX (heat_transmission.py
|
||||
# `_is_flat_or_maisonette_dwelling`): full-SAP flats carry their flatness in
|
||||
# `dwelling_type` not `property_type`, so the party-wall default was wrongly
|
||||
# the 0.25 house value instead of the RdSAP Table 15 footnote-* flat 0.0. The
|
||||
# cert lodges party `u_value: 0` and Elmhurst's worksheet uses 0.0; the fix
|
||||
# lifts this flat 80→81 (and matches the lodged/Elmhurst party wall).
|
||||
# PINNED TO THE OBSERVED 81, not lodged 81-coincidence — mapping untuned; the
|
||||
# Elmhurst delta is the documented full-SAP residual, not a bug.
|
||||
RealCertExpectation(
|
||||
schema="SAP-Schema-17.1",
|
||||
sample="uprn_10093116529",
|
||||
cert_num="8178-7436-5600-9809-0906",
|
||||
sap_score=81,
|
||||
),
|
||||
# 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. Validated against Elmhurst RdSAP10 on
|
||||
|
|
|
|||
|
|
@ -1150,6 +1150,46 @@ def test_ground_floor_flat_exposure_keeps_floor_drops_roof() -> None:
|
|||
assert ground.roof_w_per_k == 0.0
|
||||
|
||||
|
||||
def test_full_sap_flat_party_wall_uses_flat_zero_default_via_dwelling_type() -> None:
|
||||
# Arrange — a full-SAP / SAP-Schema cert carries its flatness in
|
||||
# `dwelling_type` ("Ground-floor flat"), NOT `property_type` (which is
|
||||
# None). The party-wall default must still fire the RdSAP 10 Table 15
|
||||
# footnote * flat value U=0.0 (party wall between two heated dwellings is
|
||||
# not a heat-loss element), not the 0.25 house default. Regression for
|
||||
# uprn_10093116529: without the dwelling_type fallback the engine charged
|
||||
# 2.82 W/K of phantom party-wall loss (cert lodged party u_value 0;
|
||||
# Elmhurst's worksheet uses 0.0). `party_wall_construction=None` =
|
||||
# "unknown" → the is_flat-dependent default branch.
|
||||
def _build(dwelling_type: str) -> HeatTransmission:
|
||||
main = make_building_part(
|
||||
identifier="Main Dwelling",
|
||||
construction_age_band="L",
|
||||
wall_construction=4, wall_insulation_type=4,
|
||||
party_wall_construction=None, roof_construction=4,
|
||||
floor_dimensions=[
|
||||
make_floor_dimension(
|
||||
total_floor_area_m2=49.0, room_height_m=2.6,
|
||||
party_wall_length_m=5.0, heat_loss_perimeter_m=28.0, floor=0,
|
||||
),
|
||||
],
|
||||
)
|
||||
epc = make_minimal_sap10_epc(
|
||||
total_floor_area_m2=49.0, country_code="ENG",
|
||||
dwelling_type=dwelling_type, property_type=None,
|
||||
sap_building_parts=[main],
|
||||
)
|
||||
return heat_transmission_from_cert(epc)
|
||||
|
||||
# Act
|
||||
flat = _build("Ground-floor flat")
|
||||
house = _build("Mid-terrace house")
|
||||
|
||||
# Assert — flat party wall is a non-heat-loss element (U=0); the same
|
||||
# geometry as a house takes the 0.25 unknown-house default (> 0).
|
||||
assert flat.party_walls_w_per_k == 0.0
|
||||
assert house.party_walls_w_per_k > 0.0
|
||||
|
||||
|
||||
def test_floor_over_another_dwelling_below_zeroes_floor_despite_exposed_flag() -> None:
|
||||
# Arrange — a "Ground-floor flat" lodged with floor_heat_loss=6
|
||||
# ("another dwelling below") sits over a heated dwelling (e.g. a
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue