mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-27 23:35:01 +00:00
Merge branch 'main' into feature/pashub-map-main-heating-control-1557
This commit is contained in:
commit
ca38c8a403
4 changed files with 425 additions and 10 deletions
|
|
@ -3913,6 +3913,32 @@ def _normalize_sap_schema_16_x(data: Dict[str, Any]) -> Dict[str, Any]:
|
|||
items: List[Any] = cast(List[Any], value)
|
||||
return [cast(Dict[str, Any], x) for x in items if isinstance(x, dict)]
|
||||
|
||||
# Every "Top-floor flat" SAP-Schema-15.0 cert we've seen (confirmed on 21
|
||||
# real certs from task 9d271e98-96e6-4be6-bb50-e9be6f954003, portfolio
|
||||
# 796 — e.g. cert 8204-4998-7729-4026-5693, UPRN 100061740136) lodges a
|
||||
# second `walls[]` entry with both ratings 0 and no `description` key at
|
||||
# all — `{"energy_efficiency_rating": 0,
|
||||
# "environmental_efficiency_rating": 0}`. Per the gov EPC code table
|
||||
# (epc_codes.csv: energy_efficiency_rating 0 == "N/A"), rating 0 just
|
||||
# means "not applicable" generically — the payload carries no other
|
||||
# field (no wall type/construction code), so we cannot tell from the
|
||||
# data alone *why* it's N/A (a party wall to a neighbour is a plausible
|
||||
# cause on a top-floor flat, but unconfirmed). Whatever the cause,
|
||||
# RdSapSchema17_1 requires `description`, so default it to "" rather
|
||||
# than fabricate construction text: `_joined_descriptions`
|
||||
# (heat_transmission.py) already filters out falsy descriptions, so an
|
||||
# N/A-rated element with no description contributes nothing to the wall
|
||||
# U-value derivation either way — this default is calc-neutral, purely
|
||||
# unblocking the parse.
|
||||
for elements_key in ("roofs", "walls", "floors", "windows"):
|
||||
for element in _dicts(d.get(elements_key)):
|
||||
if (
|
||||
"description" not in element
|
||||
and element.get("energy_efficiency_rating") == 0
|
||||
and element.get("environmental_efficiency_rating") == 0
|
||||
):
|
||||
element["description"] = ""
|
||||
|
||||
windows: Any = d.get("windows")
|
||||
if isinstance(windows, list) and windows:
|
||||
window_list: List[Any] = cast(List[Any], windows)
|
||||
|
|
@ -5947,8 +5973,12 @@ def _map_main_building_part(
|
|||
return SapBuildingPart(
|
||||
identifier=BuildingPartIdentifier.MAIN,
|
||||
construction_age_band=_extract_age_band(main.age_range),
|
||||
wall_construction=main.walls_construction_type,
|
||||
wall_insulation_type=main.walls_insulation_type,
|
||||
wall_construction=_pashub_wall_construction_int(
|
||||
main.walls_construction_type
|
||||
),
|
||||
wall_insulation_type=_pashub_wall_insulation_type_int(
|
||||
main.walls_insulation_type
|
||||
),
|
||||
wall_thickness_measured=main.wall_thickness_mm is not None,
|
||||
party_wall_construction=_pashub_party_wall_construction_int(
|
||||
main.party_wall_construction_type
|
||||
|
|
@ -5973,8 +6003,12 @@ def _map_extension_building_part(
|
|||
return SapBuildingPart(
|
||||
identifier=BuildingPartIdentifier.extension(ext_c.id),
|
||||
construction_age_band=_extract_age_band(ext_c.age_range),
|
||||
wall_construction=ext_c.walls_construction_type,
|
||||
wall_insulation_type=ext_c.walls_insulation_type,
|
||||
wall_construction=_pashub_wall_construction_int(
|
||||
ext_c.walls_construction_type
|
||||
),
|
||||
wall_insulation_type=_pashub_wall_insulation_type_int(
|
||||
ext_c.walls_insulation_type
|
||||
),
|
||||
wall_thickness_measured=ext_c.wall_thickness_mm is not None,
|
||||
party_wall_construction=_pashub_party_wall_construction_int(
|
||||
ext_c.party_wall_construction_type
|
||||
|
|
@ -7170,6 +7204,63 @@ def _pashub_party_wall_construction_int(label: Optional[str]) -> Optional[int]:
|
|||
return _PASHUB_PARTY_WALL_CONSTRUCTION_TO_SAP10[key]
|
||||
|
||||
|
||||
# PasHub surveyed `walls_construction_type` label → SAP10 `wall_construction`
|
||||
# code (the WALL_* constants in domain.sap10_ml.rdsap_uvalues) that `u_wall`
|
||||
# consumes. Same target code-space as the GOV.UK-API (`_api_wall_construction_
|
||||
# code`) and Elmhurst (`_ELMHURST_WALL_CODE_TO_SAP10`) siblings — PasHub just
|
||||
# lodges the full human label.
|
||||
_PASHUB_WALL_CONSTRUCTION_TO_SAP10: Dict[str, int] = {
|
||||
"Solid brick": 3, # WALL_SOLID_BRICK
|
||||
"Cavity": 4, # WALL_CAVITY
|
||||
"Timber frame": 5, # WALL_TIMBER_FRAME
|
||||
"System Build (i.e Any Other)": 6, # WALL_SYSTEM_BUILT
|
||||
}
|
||||
|
||||
|
||||
def _pashub_wall_construction_int(label: Optional[str]) -> Union[int, str]:
|
||||
"""Resolve a PasHub surveyed `walls_construction_type` label to its SAP10
|
||||
`wall_construction` code at the mapper boundary. A blank label is "no lodging"
|
||||
and passes through as the empty string (the `SapBuildingPart.wall_construction`
|
||||
field is `Union[int, str]`, and the calculator's `_int_or_none` resolves a
|
||||
non-numeric string to None → the age-band/thickness U-value fallback); a
|
||||
non-empty label the lookup does not cover strict-raises `UnmappedPasHubLabel`
|
||||
so the coverage gap is fixed here rather than silently dropping the
|
||||
construction distinction downstream (#1560)."""
|
||||
if label is None or not label.strip():
|
||||
return ""
|
||||
key = label.strip()
|
||||
if key not in _PASHUB_WALL_CONSTRUCTION_TO_SAP10:
|
||||
raise UnmappedPasHubLabel("wall construction", label)
|
||||
return _PASHUB_WALL_CONSTRUCTION_TO_SAP10[key]
|
||||
|
||||
|
||||
# PasHub surveyed `walls_insulation_type` label → SAP10 wall-insulation code that
|
||||
# `u_wall` consumes. Same target code-space as the Elmhurst sibling
|
||||
# (`_ELMHURST_WALL_INSULATION_TO_SAP10`) — PasHub just lodges the full human
|
||||
# label. "As built" is the assumed/default cascade code (Elmhurst "A"=4).
|
||||
_PASHUB_WALL_INSULATION_TYPE_TO_SAP10: Dict[str, int] = {
|
||||
"As built": 4, # as built / assumed (default cascade)
|
||||
"Filled Cavity": 2, # WALL_INSULATION_FILLED_CAVITY
|
||||
"External": 1, # external wall insulation
|
||||
}
|
||||
|
||||
|
||||
def _pashub_wall_insulation_type_int(label: Optional[str]) -> Union[int, str]:
|
||||
"""Resolve a PasHub surveyed `walls_insulation_type` label to its SAP10
|
||||
wall-insulation code at the mapper boundary. A blank label is "no lodging" and
|
||||
passes through as the empty string (the field is `Union[int, str]`; the
|
||||
calculator's `_int_or_none` resolves a non-numeric string to None); a non-empty
|
||||
label the lookup does not cover strict-raises `UnmappedPasHubLabel` so the
|
||||
coverage gap is fixed here rather than silently dropping the filled-cavity
|
||||
U-value credit downstream (#1561)."""
|
||||
if label is None or not label.strip():
|
||||
return ""
|
||||
key = label.strip()
|
||||
if key not in _PASHUB_WALL_INSULATION_TYPE_TO_SAP10:
|
||||
raise UnmappedPasHubLabel("wall insulation type", label)
|
||||
return _PASHUB_WALL_INSULATION_TYPE_TO_SAP10[key]
|
||||
|
||||
|
||||
def _resolve_elmhurst_underfloor_subtype(
|
||||
main_floor: ElmhurstFloorDetails,
|
||||
main_age_band: str,
|
||||
|
|
|
|||
|
|
@ -140,3 +140,25 @@ class TestFromSapSchema15_0:
|
|||
|
||||
assert epc.has_hot_water_cylinder is False
|
||||
assert epc.sap_heating.cylinder_size == 1
|
||||
|
||||
def test_defaults_description_on_a_bare_n_a_rated_wall_element(self) -> None:
|
||||
# Task 9d271e98-96e6-4be6-bb50-e9be6f954003 (portfolio 796 / scenario
|
||||
# 1268) failed 21 "Top-floor flat" properties with "EnergyElement:
|
||||
# missing required field 'description'": every one lodges a second
|
||||
# `walls[]` entry with both ratings 0 and no description key at all
|
||||
# (`{"energy_efficiency_rating": 0, "environmental_efficiency_rating":
|
||||
# 0}`) — property_id 735232 / UPRN 100061740136 / cert
|
||||
# 8204-4998-7729-4026-5693 here. Rating 0 == "N/A" per the gov EPC
|
||||
# code table (epc_codes.csv); the payload carries no other field, so
|
||||
# the specific cause of the N/A (party wall? something else?) isn't
|
||||
# determinable from the data. Must default to "" rather than fail
|
||||
# loud regardless of cause (calc-neutral — `_joined_descriptions`
|
||||
# already drops falsy descriptions).
|
||||
epc = EpcPropertyDataMapper.from_api_response(
|
||||
load("sap_15_0_uprn_100061740136.json")
|
||||
)
|
||||
|
||||
assert len(epc.walls) == 2
|
||||
assert epc.walls[0].description == "Cavity wall, as built, no insulation (assumed)"
|
||||
assert epc.walls[1].description == ""
|
||||
assert epc.walls[1].energy_efficiency_rating == 0
|
||||
|
|
|
|||
|
|
@ -74,6 +74,83 @@ class TestPartyWallConstructionCoding:
|
|||
EpcPropertyDataMapper.from_site_notes(survey)
|
||||
|
||||
|
||||
class TestWallConstructionCoding:
|
||||
"""`from_site_notes` must int-code the PAS Hub `walls_construction_type` label
|
||||
to the SAP10 `wall_construction` code `u_wall` consumes (WALL_* constants),
|
||||
not copy the raw label. A raw label reads as `None`, so the wall U-value falls
|
||||
back to age band + thickness only and loses the solid/cavity/timber/system
|
||||
distinction (Guinness cohort, issue #1560).
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"label, expected_code",
|
||||
[
|
||||
("Solid brick", 3), # WALL_SOLID_BRICK
|
||||
("Cavity", 4), # WALL_CAVITY
|
||||
("Timber frame", 5), # WALL_TIMBER_FRAME
|
||||
("System Build (i.e Any Other)", 6), # WALL_SYSTEM_BUILT
|
||||
("", ""), # no lodging — passes through (calc nulls it via _int_or_none)
|
||||
],
|
||||
)
|
||||
def test_label_maps_to_sap_code(
|
||||
self, label: str, expected_code: "int | str"
|
||||
) -> None:
|
||||
data = load("pashub_rdsap_site_notes_example1.json")
|
||||
data["building_construction"]["main_building"][
|
||||
"walls_construction_type"
|
||||
] = label
|
||||
survey = from_dict(PasHubRdSapSiteNotes, data)
|
||||
result = EpcPropertyDataMapper.from_site_notes(survey)
|
||||
assert result.sap_building_parts[0].wall_construction == expected_code
|
||||
|
||||
def test_unknown_label_strict_raises(self) -> None:
|
||||
data = load("pashub_rdsap_site_notes_example1.json")
|
||||
data["building_construction"]["main_building"][
|
||||
"walls_construction_type"
|
||||
] = "Woven Basket"
|
||||
survey = from_dict(PasHubRdSapSiteNotes, data)
|
||||
with pytest.raises(UnmappedPasHubLabel):
|
||||
EpcPropertyDataMapper.from_site_notes(survey)
|
||||
|
||||
|
||||
class TestWallInsulationTypeCoding:
|
||||
"""`from_site_notes` must int-code the PAS Hub `walls_insulation_type` label
|
||||
to the SAP10 wall-insulation code `u_wall` consumes (matching the Elmhurst
|
||||
`_ELMHURST_WALL_INSULATION_TO_SAP10` sibling), not copy the raw label. A raw
|
||||
label reads as `None`, so a filled cavity loses its lower-U credit and the
|
||||
wall is over-counted (Guinness cohort, issue #1561).
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"label, expected_code",
|
||||
[
|
||||
("As built", 4), # as built / assumed (default cascade)
|
||||
("Filled Cavity", 2), # WALL_INSULATION_FILLED_CAVITY
|
||||
("External", 1), # external wall insulation
|
||||
("", ""), # no lodging — passes through (calc nulls it via _int_or_none)
|
||||
],
|
||||
)
|
||||
def test_label_maps_to_sap_code(
|
||||
self, label: str, expected_code: "int | str"
|
||||
) -> None:
|
||||
data = load("pashub_rdsap_site_notes_example1.json")
|
||||
data["building_construction"]["main_building"][
|
||||
"walls_insulation_type"
|
||||
] = label
|
||||
survey = from_dict(PasHubRdSapSiteNotes, data)
|
||||
result = EpcPropertyDataMapper.from_site_notes(survey)
|
||||
assert result.sap_building_parts[0].wall_insulation_type == expected_code
|
||||
|
||||
def test_unknown_label_strict_raises(self) -> None:
|
||||
data = load("pashub_rdsap_site_notes_example1.json")
|
||||
data["building_construction"]["main_building"][
|
||||
"walls_insulation_type"
|
||||
] = "Aerogel Blanket"
|
||||
survey = from_dict(PasHubRdSapSiteNotes, data)
|
||||
with pytest.raises(UnmappedPasHubLabel):
|
||||
EpcPropertyDataMapper.from_site_notes(survey)
|
||||
|
||||
|
||||
class TestFromSiteNotesExample1:
|
||||
"""
|
||||
Fixture: pashub_rdsap_site_notes_example1.json
|
||||
|
|
@ -266,12 +343,12 @@ class TestFromSiteNotesExample1:
|
|||
assert result.sap_building_parts[0].construction_age_band == "I"
|
||||
|
||||
def test_wall_construction(self, result: EpcPropertyData) -> None:
|
||||
# main_building.walls_construction_type: "Cavity"
|
||||
assert result.sap_building_parts[0].wall_construction == "Cavity"
|
||||
# main_building.walls_construction_type: "Cavity" → SAP10 code 4 (WALL_CAVITY)
|
||||
assert result.sap_building_parts[0].wall_construction == 4
|
||||
|
||||
def test_wall_insulation_type(self, result: EpcPropertyData) -> None:
|
||||
# main_building.walls_insulation_type: "As built"
|
||||
assert result.sap_building_parts[0].wall_insulation_type == "As built"
|
||||
# main_building.walls_insulation_type: "As built" → SAP10 code 4 (as built/assumed)
|
||||
assert result.sap_building_parts[0].wall_insulation_type == 4
|
||||
|
||||
def test_wall_thickness_measured(self, result: EpcPropertyData) -> None:
|
||||
# main_building.wall_thickness_mm: 280 → thickness was measured
|
||||
|
|
@ -516,8 +593,8 @@ class TestFromSiteNotesExample1:
|
|||
SapBuildingPart(
|
||||
identifier=BuildingPartIdentifier.MAIN,
|
||||
construction_age_band="I",
|
||||
wall_construction="Cavity",
|
||||
wall_insulation_type="As built",
|
||||
wall_construction=4, # "Cavity" → SAP10 code 4 (WALL_CAVITY)
|
||||
wall_insulation_type=4, # "As built" → SAP10 code 4 (as built/assumed)
|
||||
wall_thickness_measured=True,
|
||||
party_wall_construction=4, # "Cavity Masonry, Unfilled" → SAP10 code 4 (U=0.5)
|
||||
sap_floor_dimensions=[
|
||||
|
|
|
|||
225
datatypes/epc/schema/tests/fixtures/sap_15_0_uprn_100061740136.json
vendored
Normal file
225
datatypes/epc/schema/tests/fixtures/sap_15_0_uprn_100061740136.json
vendored
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
{
|
||||
"uprn": 100061740136,
|
||||
"roofs": [
|
||||
{
|
||||
"description": "Pitched, no insulation(assumed)",
|
||||
"energy_efficiency_rating": 1,
|
||||
"environmental_efficiency_rating": 1
|
||||
}
|
||||
],
|
||||
"walls": [
|
||||
{
|
||||
"description": "Cavity wall, as built, no insulation (assumed)",
|
||||
"energy_efficiency_rating": 2,
|
||||
"environmental_efficiency_rating": 2
|
||||
},
|
||||
{
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"floors": [
|
||||
{
|
||||
"description": "(other premises below)",
|
||||
"energy_efficiency_rating": 0,
|
||||
"environmental_efficiency_rating": 0
|
||||
}
|
||||
],
|
||||
"status": "entered",
|
||||
"windows": [
|
||||
{
|
||||
"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": "PO19 3NG",
|
||||
"hot_water": {
|
||||
"description": "From main system",
|
||||
"energy_efficiency_rating": 4,
|
||||
"environmental_efficiency_rating": 4
|
||||
},
|
||||
"post_town": "CHICHESTER",
|
||||
"built_form": 3,
|
||||
"created_at": "2011-06-25 16:07:44",
|
||||
"glazed_area": 1,
|
||||
"region_code": 14,
|
||||
"report_type": 2,
|
||||
"sap_heating": {
|
||||
"cylinder_size": 1,
|
||||
"water_heating_code": 901,
|
||||
"water_heating_fuel": 26,
|
||||
"main_heating_details": [
|
||||
{
|
||||
"main_fuel_type": 26,
|
||||
"boiler_flue_type": 2,
|
||||
"fan_flue_present": "N",
|
||||
"heat_emitter_type": 1,
|
||||
"main_heating_number": 1,
|
||||
"main_heating_control": 2107,
|
||||
"main_heating_category": 2,
|
||||
"main_heating_fraction": 1,
|
||||
"main_heating_data_source": 2
|
||||
}
|
||||
],
|
||||
"has_fixed_air_conditioning": "false"
|
||||
},
|
||||
"sap_version": 9.9,
|
||||
"schema_type": "SAP-Schema-15.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": "Top-floor flat",
|
||||
"language_code": 1,
|
||||
"property_type": 2,
|
||||
"address_line_1": "22, Duncan Road",
|
||||
"schema_version": "LIG-15.0",
|
||||
"assessment_type": "RdSAP",
|
||||
"completion_date": "2011-06-25",
|
||||
"inspection_date": "2011-06-21",
|
||||
"extensions_count": 0,
|
||||
"measurement_type": 1,
|
||||
"sap_flat_details": {
|
||||
"level": 3,
|
||||
"top_storey": "Y",
|
||||
"flat_location": 0,
|
||||
"heat_loss_corridor": 2,
|
||||
"unheated_corridor_length": 8.49
|
||||
},
|
||||
"total_floor_area": 30,
|
||||
"transaction_type": 3,
|
||||
"conservatory_type": 1,
|
||||
"heated_room_count": 1,
|
||||
"registration_date": "2011-06-25",
|
||||
"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",
|
||||
"floor_heat_loss": 6,
|
||||
"roof_construction": 5,
|
||||
"wall_construction": 4,
|
||||
"building_part_number": 1,
|
||||
"sap_floor_dimensions": [
|
||||
{
|
||||
"floor": 0,
|
||||
"room_height": 2.28,
|
||||
"total_floor_area": 29.64,
|
||||
"heat_loss_perimeter": 18.41
|
||||
}
|
||||
],
|
||||
"wall_insulation_type": 4,
|
||||
"construction_age_band": "D",
|
||||
"roof_insulation_location": 4,
|
||||
"roof_insulation_thickness": "NI"
|
||||
}
|
||||
],
|
||||
"low_energy_lighting": 100,
|
||||
"solar_water_heating": "N",
|
||||
"bedf_revision_number": 310,
|
||||
"habitable_room_count": 1,
|
||||
"heating_cost_current": 457,
|
||||
"co2_emissions_current": 2.5,
|
||||
"energy_rating_current": 55,
|
||||
"lighting_cost_current": 18,
|
||||
"main_heating_controls": [
|
||||
{
|
||||
"description": "Programmer, TRVs and bypass",
|
||||
"energy_efficiency_rating": 3,
|
||||
"environmental_efficiency_rating": 3
|
||||
}
|
||||
],
|
||||
"multiple_glazing_type": 1,
|
||||
"open_fireplaces_count": 0,
|
||||
"has_hot_water_cylinder": "false",
|
||||
"heating_cost_potential": 361,
|
||||
"hot_water_cost_current": 66,
|
||||
"mechanical_ventilation": 0,
|
||||
"suggested_improvements": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"typical_saving": 32,
|
||||
"indicative_cost": "?100 - ?300",
|
||||
"improvement_type": "B",
|
||||
"improvement_details": {
|
||||
"improvement_number": 6
|
||||
},
|
||||
"improvement_category": 1,
|
||||
"energy_performance_rating": 58,
|
||||
"environmental_impact_rating": 58
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"typical_saving": 24,
|
||||
"indicative_cost": "?350 - ?450",
|
||||
"improvement_type": "G",
|
||||
"improvement_details": {
|
||||
"improvement_number": 14
|
||||
},
|
||||
"improvement_category": 1,
|
||||
"energy_performance_rating": 60,
|
||||
"environmental_impact_rating": 61
|
||||
},
|
||||
{
|
||||
"sequence": 3,
|
||||
"typical_saving": 52,
|
||||
"indicative_cost": "?1,500 - ?3,500",
|
||||
"improvement_type": "I",
|
||||
"improvement_details": {
|
||||
"improvement_number": 20
|
||||
},
|
||||
"improvement_category": 2,
|
||||
"energy_performance_rating": 64,
|
||||
"environmental_impact_rating": 67
|
||||
}
|
||||
],
|
||||
"co2_emissions_potential": 1.9,
|
||||
"energy_rating_potential": 64,
|
||||
"lighting_cost_potential": 18,
|
||||
"hot_water_cost_potential": 55,
|
||||
"renewable_heat_incentive": {
|
||||
"water_heating": 1418,
|
||||
"space_heating_existing_dwelling": 6225,
|
||||
"space_heating_with_loft_insulation": 3572,
|
||||
"space_heating_with_cavity_insulation": 5600,
|
||||
"space_heating_with_loft_and_cavity_insulation": 2891
|
||||
},
|
||||
"seller_commission_report": "Y",
|
||||
"energy_consumption_current": 438,
|
||||
"has_fixed_air_conditioning": "false",
|
||||
"multiple_glazed_proportion": 100,
|
||||
"calculation_software_version": "1.3.1.0",
|
||||
"energy_consumption_potential": 326,
|
||||
"environmental_impact_current": 55,
|
||||
"fixed_lighting_outlets_count": 4,
|
||||
"current_energy_efficiency_band": "D",
|
||||
"environmental_impact_potential": 67,
|
||||
"has_heated_separate_conservatory": "false",
|
||||
"potential_energy_efficiency_band": "D",
|
||||
"co2_emissions_current_per_floor_area": 85,
|
||||
"low_energy_fixed_lighting_outlets_count": 4
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue