diff --git a/backend/addresses/Address.py b/backend/addresses/Address.py index 0d957679..feadad63 100644 --- a/backend/addresses/Address.py +++ b/backend/addresses/Address.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -import datatypes.epc as epc_datatypes +import domain.epc as epc_datatypes from typing import Optional, Union diff --git a/backend/addresses/Addresses.py b/backend/addresses/Addresses.py index 44083b86..45a60af2 100644 --- a/backend/addresses/Addresses.py +++ b/backend/addresses/Addresses.py @@ -2,7 +2,7 @@ import warnings import pandas as pd from typing import Iterator from backend.addresses.Address import Address -from datatypes.epc.property_type_built_form import PropertyType +from domain.epc.property_type_built_form import PropertyType class Addresses: diff --git a/backend/app/db/functions/magic_plan_functions.py b/backend/app/db/functions/magic_plan_functions.py index 9400f36f..fce7027c 100644 --- a/backend/app/db/functions/magic_plan_functions.py +++ b/backend/app/db/functions/magic_plan_functions.py @@ -4,7 +4,7 @@ from sqlalchemy import delete, select from sqlalchemy.dialects.postgresql import insert as pg_insert from sqlmodel import Session, col -from datatypes.magicplan.domain.models import Floor, Plan +from domain.magicplan.models import Floor, Plan from backend.app.db.models.magic_plan import ( MagicPlanDoorModel, MagicPlanFloorModel, diff --git a/backend/app/db/functions/tests/test_magic_plan_functions.py b/backend/app/db/functions/tests/test_magic_plan_functions.py index e58d0528..a9926771 100644 --- a/backend/app/db/functions/tests/test_magic_plan_functions.py +++ b/backend/app/db/functions/tests/test_magic_plan_functions.py @@ -7,8 +7,8 @@ from sqlalchemy.orm import Session from sqlmodel import SQLModel from datatypes.magicplan.api.response import MagicPlanPlan -from datatypes.magicplan.domain.mapper import map_plan -from datatypes.magicplan.domain.models import Plan +from domain.magicplan.mapper import map_plan +from domain.magicplan.models import Plan from backend.app.db.functions.magic_plan_functions import save_plan from backend.app.db.models.magic_plan import ( diff --git a/backend/app/db/models/epc_property.py b/backend/app/db/models/epc_property.py index 50523fbb..2c519d42 100644 --- a/backend/app/db/models/epc_property.py +++ b/backend/app/db/models/epc_property.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Optional from sqlmodel import SQLModel, Field -from datatypes.epc.domain.epc_property_data import ( +from domain.epc.epc_property_data import ( EpcPropertyData, EnergyElement, MainHeatingDetail, diff --git a/backend/app/db/models/portfolio.py b/backend/app/db/models/portfolio.py index 452c8d36..285c5f90 100644 --- a/backend/app/db/models/portfolio.py +++ b/backend/app/db/models/portfolio.py @@ -16,7 +16,7 @@ from sqlalchemy import ( from backend.app.db.base import Base from backend.app.db.models.users import UserModel # noqa from backend.app.db.models.materials import MaterialType -from datatypes.epc.domain.epc import Epc +from domain.epc.epc import Epc class PortfolioStatus(enum.Enum): diff --git a/backend/app/db/models/recommendations.py b/backend/app/db/models/recommendations.py index 096cc1de..bb8d163b 100644 --- a/backend/app/db/models/recommendations.py +++ b/backend/app/db/models/recommendations.py @@ -18,7 +18,7 @@ from backend.app.db.base import Base from backend.app.db.models.portfolio import Portfolio, PortfolioGoal, PropertyModel from backend.app.db.models.materials import Material from datatypes.enums import QuantityUnits -from datatypes.epc.domain.epc import Epc +from domain.epc.epc import Epc def portfolio_goal_values(enum_cls: Type[PortfolioGoal]) -> List[str]: diff --git a/backend/app/domain/records/plan_record.py b/backend/app/domain/records/plan_record.py index 9151439f..70b84a12 100644 --- a/backend/app/domain/records/plan_record.py +++ b/backend/app/domain/records/plan_record.py @@ -3,7 +3,7 @@ from datetime import datetime from typing import Optional from backend.app.db.models.recommendations import PlanTypeEnum -from datatypes.epc.domain.epc import Epc +from domain.epc.epc import Epc @dataclass(frozen=True) diff --git a/backend/backlog/prd-extract-domain-to-root.md b/backend/backlog/prd-extract-domain-to-root.md new file mode 100644 index 00000000..b4ef2d7e --- /dev/null +++ b/backend/backlog/prd-extract-domain-to-root.md @@ -0,0 +1,114 @@ +# PRD: Extract Domain Layer to Root-Level `domain/` Package + +## Problem Statement + +Domain objects (models, enums, mappers) shared across microservices are nested under `datatypes/epc/domain/` and `datatypes/magicplan/domain/`. This creates two problems: + +1. **Misleading location**: "Datatypes" implies low-level type definitions, but these folders contain rich domain models and business logic (mappers, historic EPC matching, property data aggregation). New developers cannot discover the domain layer by inspection. +2. **False coupling**: Domain objects belonging to the whole system are buried inside a folder that also contains source-data representations (versioned RdSAP schemas, Elmhurst/Pashub survey shapes, MagicPlan API responses). The domain has no clear home — it is mixed with external data adapters. + +## Solution + +Extract all domain objects (domain models, domain vocabulary enums, mappers) into a dedicated root-level `domain/` package. The `datatypes/` package is repurposed to hold only external/source data shapes: RdSAP schemas, survey provider types, and third-party API response types. + +The result is a clear, discoverable split: + +- `domain/` — canonical domain models, vocabulary enums, and mappers owned by this system +- `datatypes/` — shapes that mirror external systems (schemas, surveys, API responses) + +## User Stories + +1. As a backend engineer, I want domain models to live in a top-level `domain/` package, so that I can find the canonical representation of an EPC or MagicPlan floor plan without navigating into `datatypes/`. +2. As a backend engineer, I want to import EPC domain models with `from domain.epc import ...`, so that the import path reflects the conceptual ownership of the type. +3. As a backend engineer, I want EPC vocabulary enums (wall descriptions, roof descriptions, fuel types, etc.) to live in `domain/epc/`, so that all domain vocabulary is co-located with domain models. +4. As a backend engineer adding a new microservice, I want a clear pattern for importing shared domain objects, so that I do not accidentally reach into `datatypes/` for domain types. +5. As a backend engineer, I want the `datatypes/` package to contain only source-data representations, so that I understand it as the anti-corruption layer and not as the domain. +6. As a backend engineer, I want mappers (which transform external shapes into domain models) to live alongside domain models in `domain/`, so that the translation between external and domain is easy to find. +7. As a backend engineer running tests, I want the pytest test paths to reflect the new `domain/` location, so that `pytest` discovers and runs all domain tests correctly. +8. As a backend engineer, I want no backwards-compatibility shims or re-export stubs left behind in `datatypes/`, so that there is one canonical import path for each domain type. +9. As a code reviewer, I want imports from `domain.*` to clearly signal "this is a domain concept", so that the distinction from `datatypes.*` (external data) is immediately readable. +10. As a backend engineer, I want internal references within moved files (e.g. mapper.py importing models.py) to be updated to their new paths, so that no moved module has a stale import. + +## Implementation Decisions + +### Package structure + +A root-level `domain/` package is created alongside `datatypes/`. It contains two sub-packages mirroring the existing subdomain split: + +- `domain/epc/` — all EPC domain objects +- `domain/magicplan/` — all MagicPlan domain objects + +### What moves into `domain/epc/` + +All of the following move from their current `datatypes/epc/` locations: + +- EPC vocabulary enums: `construction_age_band`, `efficiency`, `floor`, `fuel`, `heating_controls`, `hotwater`, `main_heating`, `property_type_built_form`, `roof`, `walls`, `windows` +- EPC domain models and logic: `epc`, `epc_property_data`, `field_mappings`, `historic_epc`, `historic_epc_matching`, `mapper` +- Domain tests (currently under `datatypes/epc/domain/tests/`) + +`domain/epc/__init__.py` re-exports all vocabulary enums (matching the current `datatypes/epc/__init__.py` interface) so call-sites using the package-level import continue to work after the path change. + +### What moves into `domain/magicplan/` + +- Domain models: `models` (Window, Door, Room, Floor, Plan dataclasses) +- Domain mapper: `mapper` (transforms MagicPlan API response into domain models) +- Domain tests (currently under `datatypes/magicplan/domain/tests/`) + +### What stays in `datatypes/` + +- `datatypes/epc/schema/` — versioned RdSAP XML schemas (17.0 through 21.0.1) +- `datatypes/epc/surveys/` — Elmhurst and Pashub site notes shapes +- `datatypes/epc/loaders/` — historic EPC loading logic +- `datatypes/magicplan/api/` — MagicPlan API response types +- `datatypes/datatypes.py` and `datatypes/enums.py` — pre-existing shared primitives (out of scope for this change) + +### Mapper placement + +Mappers live in `domain/` alongside the models they produce. This reflects the view that a mapper is the domain's intake port — the boundary at which external data becomes domain data — and belongs with the domain, not with the external shapes. + +### Import path changes + +| Old import | New import | +|---|---| +| `from datatypes.epc.domain.X import Y` | `from domain.epc.X import Y` | +| `from datatypes.epc.walls import Y` | `from domain.epc.walls import Y` | +| `from datatypes.epc import EpcWallDescriptions` | `from domain.epc import EpcWallDescriptions` | +| `from datatypes.magicplan.domain.models import Y` | `from domain.magicplan.models import Y` | +| `from datatypes.magicplan.domain.mapper import Y` | `from domain.magicplan.mapper import Y` | + +Imports from `datatypes.epc.schema.*`, `datatypes.epc.surveys.*`, and `datatypes.magicplan.api.*` are unchanged. + +### pytest.ini update + +`testpaths` entries for `datatypes/epc/domain/tests` and `datatypes/magicplan/domain/tests` are replaced with `domain/epc/tests` and `domain/magicplan/tests`. + +### No backwards-compatibility shims + +`datatypes/epc/` enum files and `datatypes/epc/domain/` files are deleted after the move. No re-export stubs are left behind. All consumers are updated in the same change. + +## Testing Decisions + +A good test for this change verifies that the domain objects behave identically under their new import paths — not that the files were moved. + +**Modules to test:** + +- `domain/epc/` — existing tests from `datatypes/epc/domain/tests/` cover mapper (`test_from_rdsap_schema`, `test_from_site_notes`) and historic EPC matching (`test_historic_epc_matching`). These tests are moved verbatim; passing them confirms the domain logic is intact. +- `domain/magicplan/` — existing tests from `datatypes/magicplan/domain/tests/test_mapper.py` are moved verbatim. + +**Prior art:** `datatypes/epc/domain/tests/` and `datatypes/magicplan/domain/tests/` are the direct prior art — same test patterns, same fixtures, relocated. + +**Verification grep:** After the change, `grep -r "from datatypes.epc.domain" . --include="*.py"` and `grep -r "from datatypes.magicplan.domain" . --include="*.py"` should return zero results (excluding `__pycache__`). + +## Out of Scope + +- Moving `datatypes/datatypes.py` (`OpenUprnCoordinateData`) or `datatypes/enums.py` (`QuantityUnits`) — these are pre-existing and will be addressed in a future refactor. +- Renaming `datatypes/` to `external/` or any other name — out of scope for this change. +- Moving `datatypes/epc/schema/`, `datatypes/epc/surveys/`, `datatypes/epc/loaders/`, or `datatypes/magicplan/api/` — these are source-data shapes and stay in `datatypes/`. +- Adding new domain objects or changing domain logic — this is a structural move only. +- Introducing Python packaging (`setup.py`, `pyproject.toml`) changes beyond `pytest.ini`. + +## Further Notes + +- `pytest.ini` sets `pythonpath = .`, so `domain/` at the repo root will be importable immediately without additional configuration. +- `datatypes/epc/__init__.py` currently aggregates enum exports via `__all__`. The equivalent `domain/epc/__init__.py` should preserve this interface so call-sites using `from datatypes.epc import EpcWallDescriptions`-style imports need only a path change, not a restructuring. +- ~40 files across `backend/`, `etl/`, and `scripts/` import from `datatypes.epc.*` domain paths. ~10 files import from `datatypes.magicplan.domain.*`. A single `sed` pass or IDE rename refactor covers the bulk; the verification grep confirms completeness. diff --git a/backend/categorisation/tests/test_plan_is_compliant.py b/backend/categorisation/tests/test_plan_is_compliant.py index c5658b4e..e80f9c3c 100644 --- a/backend/categorisation/tests/test_plan_is_compliant.py +++ b/backend/categorisation/tests/test_plan_is_compliant.py @@ -7,7 +7,7 @@ from backend.app.domain.classes.scenario import Scenario from backend.app.domain.records.plan_record import PlanRecord from backend.app.domain.records.scenario_record import ScenarioRecord from backend.app.db.models.portfolio import PortfolioGoal -from datatypes.epc.domain.epc import Epc +from domain.epc.epc import Epc @pytest.fixture diff --git a/backend/categorisation/tests/test_prioritised_plan_selected.py b/backend/categorisation/tests/test_prioritised_plan_selected.py index 5cffa01a..6699ba7d 100644 --- a/backend/categorisation/tests/test_prioritised_plan_selected.py +++ b/backend/categorisation/tests/test_prioritised_plan_selected.py @@ -8,7 +8,7 @@ from backend.app.domain.records.plan_record import PlanRecord from backend.app.domain.records.scenario_record import ScenarioRecord from backend.app.db.models.portfolio import PortfolioGoal from backend.categorisation.processor import choose_cheapest_relevant_plan -from datatypes.epc.domain.epc import Epc +from domain.epc.epc import Epc @pytest.fixture diff --git a/backend/documents_parser/db_writer.py b/backend/documents_parser/db_writer.py index 2039aabe..8ae24f51 100644 --- a/backend/documents_parser/db_writer.py +++ b/backend/documents_parser/db_writer.py @@ -12,7 +12,7 @@ from backend.app.db.models.epc_property import ( EpcPropertyModel, EpcWindowModel, ) -from datatypes.epc.domain.epc_property_data import EpcPropertyData +from domain.epc.epc_property_data import EpcPropertyData def save_epc_property_data( diff --git a/backend/documents_parser/local_runner.py b/backend/documents_parser/local_runner.py index 89dc7cdb..52fb815e 100644 --- a/backend/documents_parser/local_runner.py +++ b/backend/documents_parser/local_runner.py @@ -21,8 +21,8 @@ from backend.app.db.models.epc_property import ( from backend.documents_parser.elmhurst_extractor import ElmhurstSiteNotesExtractor from backend.documents_parser.extractor import PasHubRdSapSiteNotesExtractor from backend.documents_parser.pdf import pdf_to_pages, pdf_to_text_list -from datatypes.epc.domain.epc_property_data import EnergyElement, EpcPropertyData -from datatypes.epc.domain.mapper import EpcPropertyDataMapper +from domain.epc.epc_property_data import EnergyElement, EpcPropertyData +from domain.epc.mapper import EpcPropertyDataMapper def _parse_pdf(pdf_path: str) -> EpcPropertyData: diff --git a/backend/documents_parser/parser.py b/backend/documents_parser/parser.py index cff21e0e..ffbec47a 100644 --- a/backend/documents_parser/parser.py +++ b/backend/documents_parser/parser.py @@ -1,7 +1,7 @@ from typing import List -from datatypes.epc.domain.epc_property_data import EpcPropertyData -from datatypes.epc.domain.mapper import EpcPropertyDataMapper +from domain.epc.epc_property_data import EpcPropertyData +from domain.epc.mapper import EpcPropertyDataMapper from backend.documents_parser.elmhurst_extractor import ElmhurstSiteNotesExtractor from backend.documents_parser.extractor import PasHubRdSapSiteNotesExtractor diff --git a/backend/documents_parser/tests/test_elmhurst_end_to_end.py b/backend/documents_parser/tests/test_elmhurst_end_to_end.py index 977ea138..390ce4dd 100644 --- a/backend/documents_parser/tests/test_elmhurst_end_to_end.py +++ b/backend/documents_parser/tests/test_elmhurst_end_to_end.py @@ -5,8 +5,8 @@ from datetime import date import pytest from backend.documents_parser.elmhurst_extractor import ElmhurstSiteNotesExtractor -from datatypes.epc.domain.epc_property_data import EpcPropertyData -from datatypes.epc.domain.mapper import EpcPropertyDataMapper +from domain.epc.epc_property_data import EpcPropertyData +from domain.epc.mapper import EpcPropertyDataMapper FIXTURE_PATH = os.path.join( os.path.dirname(__file__), "fixtures", "elmhurst_site_notes_1_text.json" diff --git a/backend/documents_parser/tests/test_end_to_end.py b/backend/documents_parser/tests/test_end_to_end.py index c413b55f..a09cfae6 100644 --- a/backend/documents_parser/tests/test_end_to_end.py +++ b/backend/documents_parser/tests/test_end_to_end.py @@ -5,7 +5,7 @@ import pytest from backend.documents_parser.extractor import PasHubRdSapSiteNotesExtractor from backend.documents_parser.pdf import pdf_to_text_list -from datatypes.epc.domain.epc_property_data import ( +from domain.epc.epc_property_data import ( EpcPropertyData, InstantaneousWwhrs, MainHeatingDetail, @@ -18,7 +18,7 @@ from datatypes.epc.domain.epc_property_data import ( ShowerOutlet, ShowerOutlets, ) -from datatypes.epc.domain.mapper import EpcPropertyDataMapper +from domain.epc.mapper import EpcPropertyDataMapper PDF_PATH = os.path.join(os.path.dirname(__file__), "fixtures", "PasHubSiteNotes_1.pdf") PDF_PATH_2 = os.path.join( diff --git a/backend/ecmk_fetcher/xml_processor.py b/backend/ecmk_fetcher/xml_processor.py index f993038b..e389d33c 100644 --- a/backend/ecmk_fetcher/xml_processor.py +++ b/backend/ecmk_fetcher/xml_processor.py @@ -3,7 +3,7 @@ from typing import Any, List, Optional, TypedDict from backend.ecmk_fetcher.reports import build_property_id -from datatypes.epc.domain.field_mappings import PROPERTY_TYPE_LOOKUP +from domain.epc.field_mappings import PROPERTY_TYPE_LOOKUP # This file should ultimately live somewhere different, probably diff --git a/backend/export/tests/test_export.py b/backend/export/tests/test_export.py index 42177749..f2321852 100644 --- a/backend/export/tests/test_export.py +++ b/backend/export/tests/test_export.py @@ -20,7 +20,7 @@ from backend.app.db.models.recommendations import ( RecommendationMaterials, ) from backend.app.db.models.materials import Material -from datatypes.epc.domain.epc import Epc +from domain.epc.epc import Epc from utils.logger import setup_logger FIXTURE_PATH = Path("backend/export/tests/fixtures") diff --git a/backend/magic_plan/handler.py b/backend/magic_plan/handler.py index a592cc6a..c038ce06 100644 --- a/backend/magic_plan/handler.py +++ b/backend/magic_plan/handler.py @@ -4,7 +4,7 @@ from backend.app.config import get_settings from backend.magic_plan.magic_plan_client import MagicPlanClient from backend.magic_plan.magic_plan_service import MagicPlanService from backend.magic_plan.magic_plan_trigger_request import MagicPlanTriggerRequest -from datatypes.magicplan.domain.models import Plan +from domain.magicplan.models import Plan from backend.utils.subtasks import task_handler from utils.logger import setup_logger diff --git a/backend/magic_plan/magic_plan_service.py b/backend/magic_plan/magic_plan_service.py index 91b3cd13..73081b48 100644 --- a/backend/magic_plan/magic_plan_service.py +++ b/backend/magic_plan/magic_plan_service.py @@ -5,8 +5,8 @@ from datatypes.magicplan.api.response import ( PlanSummary, PlansListResponse, ) -from datatypes.magicplan.domain.mapper import map_plan -from datatypes.magicplan.domain.models import Plan +from domain.magicplan.mapper import map_plan +from domain.magicplan.models import Plan from backend.app.db.connection import db_session from backend.app.db.functions.magic_plan_functions import save_plan diff --git a/backend/magic_plan/tests/test_magic_plan_service.py b/backend/magic_plan/tests/test_magic_plan_service.py index 8e433b87..22fe0572 100644 --- a/backend/magic_plan/tests/test_magic_plan_service.py +++ b/backend/magic_plan/tests/test_magic_plan_service.py @@ -5,8 +5,8 @@ from unittest.mock import MagicMock, patch import pytest from datatypes.magicplan.api.response import MagicPlanPlan, PlanSummary -from datatypes.magicplan.domain.mapper import map_plan -from datatypes.magicplan.domain.models import Plan +from domain.magicplan.mapper import map_plan +from domain.magicplan.models import Plan from backend.magic_plan.magic_plan_client import MagicPlanClient from backend.magic_plan.magic_plan_service import MagicPlanService diff --git a/backend/onboarders/mappings/parity/age_band.py b/backend/onboarders/mappings/parity/age_band.py index 02dfec00..52d16ce9 100644 --- a/backend/onboarders/mappings/parity/age_band.py +++ b/backend/onboarders/mappings/parity/age_band.py @@ -1,4 +1,4 @@ -from datatypes.epc.construction_age_band import EpcConstructionAgeBand +from domain.epc.construction_age_band import EpcConstructionAgeBand parity_map = { "Before 1900": EpcConstructionAgeBand.before_1900, diff --git a/backend/onboarders/mappings/parity/as_built_floor_classifiers.py b/backend/onboarders/mappings/parity/as_built_floor_classifiers.py index 3af3c079..5f4ea0dd 100644 --- a/backend/onboarders/mappings/parity/as_built_floor_classifiers.py +++ b/backend/onboarders/mappings/parity/as_built_floor_classifiers.py @@ -1,5 +1,5 @@ -from datatypes.epc.construction_age_band import EpcConstructionAgeBand -from datatypes.epc.floor import EpcFloorDescriptions +from domain.epc.construction_age_band import EpcConstructionAgeBand +from domain.epc.floor import EpcFloorDescriptions def unknown_floor_as_built(age_band: EpcConstructionAgeBand) -> EpcFloorDescriptions: diff --git a/backend/onboarders/mappings/parity/as_built_roof_classifiers.py b/backend/onboarders/mappings/parity/as_built_roof_classifiers.py index fcb554bd..5f709cde 100644 --- a/backend/onboarders/mappings/parity/as_built_roof_classifiers.py +++ b/backend/onboarders/mappings/parity/as_built_roof_classifiers.py @@ -1,5 +1,5 @@ -from datatypes.epc.roof import EpcRoofDescriptions -from datatypes.epc.construction_age_band import EpcConstructionAgeBand +from domain.epc.roof import EpcRoofDescriptions +from domain.epc.construction_age_band import EpcConstructionAgeBand def map_flat_roof(age_band: EpcConstructionAgeBand) -> EpcRoofDescriptions: diff --git a/backend/onboarders/mappings/parity/as_built_wall_classifiers.py b/backend/onboarders/mappings/parity/as_built_wall_classifiers.py index 480a7e24..e75f4ab6 100644 --- a/backend/onboarders/mappings/parity/as_built_wall_classifiers.py +++ b/backend/onboarders/mappings/parity/as_built_wall_classifiers.py @@ -1,5 +1,5 @@ -from datatypes.epc.construction_age_band import EpcConstructionAgeBand -from datatypes.epc.walls import EpcWallDescriptions +from domain.epc.construction_age_band import EpcConstructionAgeBand +from domain.epc.walls import EpcWallDescriptions def map_cavity_wall_insulation(age_band: EpcConstructionAgeBand): diff --git a/backend/onboarders/mappings/parity/built_form.py b/backend/onboarders/mappings/parity/built_form.py index 12ae6360..8633d0a2 100644 --- a/backend/onboarders/mappings/parity/built_form.py +++ b/backend/onboarders/mappings/parity/built_form.py @@ -1,4 +1,4 @@ -from datatypes.epc.property_type_built_form import BuiltForm +from domain.epc.property_type_built_form import BuiltForm parity_map = { "MidTerrace": BuiltForm.mid_terrace, diff --git a/backend/onboarders/mappings/parity/floor.py b/backend/onboarders/mappings/parity/floor.py index 653d4c68..75744e43 100644 --- a/backend/onboarders/mappings/parity/floor.py +++ b/backend/onboarders/mappings/parity/floor.py @@ -1,5 +1,5 @@ from numpy import nan -from datatypes.epc.floor import EpcFloorDescriptions +from domain.epc.floor import EpcFloorDescriptions floor_map = { # Solid floor diff --git a/backend/onboarders/mappings/parity/glazing.py b/backend/onboarders/mappings/parity/glazing.py index 49a37ddd..1b466640 100644 --- a/backend/onboarders/mappings/parity/glazing.py +++ b/backend/onboarders/mappings/parity/glazing.py @@ -1,5 +1,5 @@ -from datatypes.epc.efficiency import EpcEfficiency -from datatypes.epc.windows import EpcWindowDescriptions +from domain.epc.efficiency import EpcEfficiency +from domain.epc.windows import EpcWindowDescriptions glazing_map = { # (description, energy efficiency, multi_glaze_proportion, glazed_type, glazed_area diff --git a/backend/onboarders/mappings/parity/heating.py b/backend/onboarders/mappings/parity/heating.py index aa74834b..428514a9 100644 --- a/backend/onboarders/mappings/parity/heating.py +++ b/backend/onboarders/mappings/parity/heating.py @@ -1,8 +1,8 @@ -from datatypes.epc.main_heating import EpcHeatingSystems -from datatypes.epc.efficiency import EpcEfficiency -from datatypes.epc.fuel import EpcFuel -from datatypes.epc.heating_controls import EpcHeatingControls -from datatypes.epc.hotwater import EpcHotWaterSystems +from domain.epc.main_heating import EpcHeatingSystems +from domain.epc.efficiency import EpcEfficiency +from domain.epc.fuel import EpcFuel +from domain.epc.heating_controls import EpcHeatingControls +from domain.epc.hotwater import EpcHotWaterSystems heating_map = { # 0 diff --git a/backend/onboarders/mappings/parity/property_type.py b/backend/onboarders/mappings/parity/property_type.py index f91c0c88..005b7e07 100644 --- a/backend/onboarders/mappings/parity/property_type.py +++ b/backend/onboarders/mappings/parity/property_type.py @@ -1,4 +1,4 @@ -from datatypes.epc.property_type_built_form import PropertyType +from domain.epc.property_type_built_form import PropertyType parity_map = { "Flat": PropertyType.flat, diff --git a/backend/onboarders/mappings/parity/roof.py b/backend/onboarders/mappings/parity/roof.py index 02518c3e..b00812fa 100644 --- a/backend/onboarders/mappings/parity/roof.py +++ b/backend/onboarders/mappings/parity/roof.py @@ -2,9 +2,9 @@ import pandas as pd from numpy import nan from typing import Union, Callable from collections.abc import Mapping -from datatypes.epc.roof import EpcRoofDescriptions -from datatypes.epc.efficiency import EpcEfficiency -from datatypes.epc.construction_age_band import EpcConstructionAgeBand +from domain.epc.roof import EpcRoofDescriptions +from domain.epc.efficiency import EpcEfficiency +from domain.epc.construction_age_band import EpcConstructionAgeBand roof_map = { # Dwelling above diff --git a/backend/onboarders/mappings/parity/walls.py b/backend/onboarders/mappings/parity/walls.py index 0ad6d6e1..c16bd6ac 100644 --- a/backend/onboarders/mappings/parity/walls.py +++ b/backend/onboarders/mappings/parity/walls.py @@ -1,8 +1,8 @@ from typing import Callable, Union from collections.abc import Mapping -from datatypes.epc.walls import EpcWallDescriptions -from datatypes.epc.construction_age_band import EpcConstructionAgeBand -from datatypes.epc.efficiency import EpcEfficiency +from domain.epc.walls import EpcWallDescriptions +from domain.epc.construction_age_band import EpcConstructionAgeBand +from domain.epc.efficiency import EpcEfficiency # Unique combinations wall_map = { diff --git a/backend/onboarders/parity.py b/backend/onboarders/parity.py index 5c180ad3..f18a8ce0 100644 --- a/backend/onboarders/parity.py +++ b/backend/onboarders/parity.py @@ -16,11 +16,11 @@ from backend.onboarders.mappings.parity.as_built_roof_classifiers import as_buil from backend.onboarders.mappings.parity.as_built_floor_classifiers import ( as_built_floor_classifiers, unknown_as_built_floor_classifiers ) -from datatypes.epc.roof import EpcRoofDescriptions -from datatypes.epc.floor import EpcFloorDescriptions -from datatypes.epc.construction_age_band import EpcConstructionAgeBand -from datatypes.epc.walls import EpcWallDescriptions -from datatypes.epc.efficiency import EpcEfficiency +from domain.epc.roof import EpcRoofDescriptions +from domain.epc.floor import EpcFloorDescriptions +from domain.epc.construction_age_band import EpcConstructionAgeBand +from domain.epc.walls import EpcWallDescriptions +from domain.epc.efficiency import EpcEfficiency tqdm.pandas() diff --git a/backend/onboarders/tests/test_floor_remapping.py b/backend/onboarders/tests/test_floor_remapping.py index c20372b7..a641cac9 100644 --- a/backend/onboarders/tests/test_floor_remapping.py +++ b/backend/onboarders/tests/test_floor_remapping.py @@ -1,7 +1,7 @@ import pytest -from datatypes.epc.construction_age_band import EpcConstructionAgeBand -from datatypes.epc.floor import EpcFloorDescriptions +from domain.epc.construction_age_band import EpcConstructionAgeBand +from domain.epc.floor import EpcFloorDescriptions from backend.onboarders.mappings.parity.as_built_floor_classifiers import ( unknown_floor_as_built, diff --git a/backend/onboarders/tests/test_roof_remapping.py b/backend/onboarders/tests/test_roof_remapping.py index cc19e057..24c98b48 100644 --- a/backend/onboarders/tests/test_roof_remapping.py +++ b/backend/onboarders/tests/test_roof_remapping.py @@ -1,8 +1,8 @@ import pytest -from datatypes.epc.construction_age_band import EpcConstructionAgeBand -from datatypes.epc.roof import EpcRoofDescriptions -from datatypes.epc.efficiency import EpcEfficiency +from domain.epc.construction_age_band import EpcConstructionAgeBand +from domain.epc.roof import EpcRoofDescriptions +from domain.epc.efficiency import EpcEfficiency from backend.onboarders.mappings.parity.as_built_roof_classifiers import ( map_flat_roof, diff --git a/backend/onboarders/tests/test_wall_remapping.py b/backend/onboarders/tests/test_wall_remapping.py index c9476211..083c40e3 100644 --- a/backend/onboarders/tests/test_wall_remapping.py +++ b/backend/onboarders/tests/test_wall_remapping.py @@ -1,8 +1,8 @@ import pytest -from datatypes.epc.construction_age_band import EpcConstructionAgeBand -from datatypes.epc.walls import EpcWallDescriptions -from datatypes.epc.efficiency import EpcEfficiency +from domain.epc.construction_age_band import EpcConstructionAgeBand +from domain.epc.walls import EpcWallDescriptions +from domain.epc.efficiency import EpcEfficiency from backend.onboarders.mappings.parity.walls import resolve_wall_efficiency from backend.onboarders.mappings.parity.as_built_wall_classifiers import ( diff --git a/backend/pashub_fetcher/pashub_service.py b/backend/pashub_fetcher/pashub_service.py index 316902f4..bf9e069c 100644 --- a/backend/pashub_fetcher/pashub_service.py +++ b/backend/pashub_fetcher/pashub_service.py @@ -16,7 +16,7 @@ from backend.pashub_fetcher.pashub_to_ara_trigger_request import ( PashubToAraTriggerRequest, ) from backend.pashub_fetcher.sharepoint_subfolders import SharepointSubfolders -from datatypes.epc.domain.epc_property_data import EpcPropertyData +from domain.epc.epc_property_data import EpcPropertyData from utils.logger import setup_logger from utils.s3 import upload_file_to_s3 from utils.sharepoint.domna_sharepoint_client import DomnaSharepointClient diff --git a/datatypes/epc/__init__.py b/datatypes/epc/__init__.py index d997816a..8b137891 100644 --- a/datatypes/epc/__init__.py +++ b/datatypes/epc/__init__.py @@ -1,26 +1 @@ -from .construction_age_band import EpcConstructionAgeBand -from .efficiency import EpcEfficiency -from .floor import EpcFloorDescriptions -from .fuel import EpcFuel -from .heating_controls import EpcHeatingControls -from .hotwater import EpcHotWaterSystems -from .main_heating import EpcHeatingSystems -from .property_type_built_form import PropertyType, BuiltForm -from .roof import EpcRoofDescriptions -from .walls import EpcWallDescriptions -from .windows import EpcWindowDescriptions -__all__ = [ - "EpcConstructionAgeBand", - "EpcEfficiency", - "EpcFloorDescriptions", - "EpcFuel", - "EpcHeatingControls", - "EpcHotWaterSystems", - "EpcHeatingSystems", - "PropertyType", - "BuiltForm", - "EpcRoofDescriptions", - "EpcWallDescriptions", - "EpcWindowDescriptions", -] diff --git a/datatypes/epc/loaders/historic_epc.py b/datatypes/epc/loaders/historic_epc.py index a4757d23..eb55c34c 100644 --- a/datatypes/epc/loaders/historic_epc.py +++ b/datatypes/epc/loaders/historic_epc.py @@ -1,6 +1,6 @@ import csv -from datatypes.epc.domain.historic_epc import HistoricEpc +from domain.epc.historic_epc import HistoricEpc def _normalise(value: str | None) -> str: diff --git a/datatypes/epc/schema/tests/test_historic_epc_loading.py b/datatypes/epc/schema/tests/test_historic_epc_loading.py index a42f383e..4506d5c9 100644 --- a/datatypes/epc/schema/tests/test_historic_epc_loading.py +++ b/datatypes/epc/schema/tests/test_historic_epc_loading.py @@ -3,7 +3,7 @@ import os import pytest from datatypes.epc.loaders.historic_epc import read_historic_epc_csv -from datatypes.epc.domain.historic_epc import HistoricEpc +from domain.epc.historic_epc import HistoricEpc FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures") diff --git a/datatypes/epc/domain/__init__.py b/domain/__init__.py similarity index 100% rename from datatypes/epc/domain/__init__.py rename to domain/__init__.py diff --git a/domain/epc/__init__.py b/domain/epc/__init__.py new file mode 100644 index 00000000..d997816a --- /dev/null +++ b/domain/epc/__init__.py @@ -0,0 +1,26 @@ +from .construction_age_band import EpcConstructionAgeBand +from .efficiency import EpcEfficiency +from .floor import EpcFloorDescriptions +from .fuel import EpcFuel +from .heating_controls import EpcHeatingControls +from .hotwater import EpcHotWaterSystems +from .main_heating import EpcHeatingSystems +from .property_type_built_form import PropertyType, BuiltForm +from .roof import EpcRoofDescriptions +from .walls import EpcWallDescriptions +from .windows import EpcWindowDescriptions + +__all__ = [ + "EpcConstructionAgeBand", + "EpcEfficiency", + "EpcFloorDescriptions", + "EpcFuel", + "EpcHeatingControls", + "EpcHotWaterSystems", + "EpcHeatingSystems", + "PropertyType", + "BuiltForm", + "EpcRoofDescriptions", + "EpcWallDescriptions", + "EpcWindowDescriptions", +] diff --git a/datatypes/epc/construction_age_band.py b/domain/epc/construction_age_band.py similarity index 100% rename from datatypes/epc/construction_age_band.py rename to domain/epc/construction_age_band.py diff --git a/datatypes/epc/efficiency.py b/domain/epc/efficiency.py similarity index 100% rename from datatypes/epc/efficiency.py rename to domain/epc/efficiency.py diff --git a/datatypes/epc/domain/epc.py b/domain/epc/epc.py similarity index 100% rename from datatypes/epc/domain/epc.py rename to domain/epc/epc.py diff --git a/datatypes/epc/domain/epc_property_data.py b/domain/epc/epc_property_data.py similarity index 99% rename from datatypes/epc/domain/epc_property_data.py rename to domain/epc/epc_property_data.py index 8795b389..726e14ba 100644 --- a/datatypes/epc/domain/epc_property_data.py +++ b/domain/epc/epc_property_data.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from datetime import date from typing import List, Optional, Union -from datatypes.epc.domain.epc import Epc +from domain.epc.epc import Epc @dataclass diff --git a/datatypes/epc/domain/field_mappings.py b/domain/epc/field_mappings.py similarity index 100% rename from datatypes/epc/domain/field_mappings.py rename to domain/epc/field_mappings.py diff --git a/datatypes/epc/floor.py b/domain/epc/floor.py similarity index 100% rename from datatypes/epc/floor.py rename to domain/epc/floor.py diff --git a/datatypes/epc/fuel.py b/domain/epc/fuel.py similarity index 100% rename from datatypes/epc/fuel.py rename to domain/epc/fuel.py diff --git a/datatypes/epc/heating_controls.py b/domain/epc/heating_controls.py similarity index 100% rename from datatypes/epc/heating_controls.py rename to domain/epc/heating_controls.py diff --git a/datatypes/epc/domain/historic_epc.py b/domain/epc/historic_epc.py similarity index 100% rename from datatypes/epc/domain/historic_epc.py rename to domain/epc/historic_epc.py diff --git a/datatypes/epc/domain/historic_epc_matching.py b/domain/epc/historic_epc_matching.py similarity index 98% rename from datatypes/epc/domain/historic_epc_matching.py rename to domain/epc/historic_epc_matching.py index 95ca9d9f..0dd3efb4 100644 --- a/datatypes/epc/domain/historic_epc_matching.py +++ b/domain/epc/historic_epc_matching.py @@ -6,7 +6,7 @@ from botocore.exceptions import ClientError from backend.address2UPRN.scoring import get_uprn_candidates from backend.utils.addressMatch import AddressMatch -from datatypes.epc.domain.historic_epc import HistoricEpc +from domain.epc.historic_epc import HistoricEpc from utils.pandas_utils import pandas_cell_to_str from utils.s3 import parse_s3_uri, read_csv_gz_from_s3 diff --git a/datatypes/epc/hotwater.py b/domain/epc/hotwater.py similarity index 100% rename from datatypes/epc/hotwater.py rename to domain/epc/hotwater.py diff --git a/datatypes/epc/main_heating.py b/domain/epc/main_heating.py similarity index 100% rename from datatypes/epc/main_heating.py rename to domain/epc/main_heating.py diff --git a/datatypes/epc/domain/mapper.py b/domain/epc/mapper.py similarity index 99% rename from datatypes/epc/domain/mapper.py rename to domain/epc/mapper.py index 054b951f..d3a9f5ff 100644 --- a/datatypes/epc/domain/mapper.py +++ b/domain/epc/mapper.py @@ -1,7 +1,7 @@ from datetime import date from typing import List, Optional, Sequence, Union -from datatypes.epc.domain.epc_property_data import ( +from domain.epc.epc_property_data import ( EnergyElement, EpcPropertyData, InstantaneousWwhrs, diff --git a/datatypes/epc/property_type_built_form.py b/domain/epc/property_type_built_form.py similarity index 100% rename from datatypes/epc/property_type_built_form.py rename to domain/epc/property_type_built_form.py diff --git a/datatypes/epc/roof.py b/domain/epc/roof.py similarity index 100% rename from datatypes/epc/roof.py rename to domain/epc/roof.py diff --git a/datatypes/epc/domain/tests/__init__.py b/domain/epc/tests/__init__.py similarity index 100% rename from datatypes/epc/domain/tests/__init__.py rename to domain/epc/tests/__init__.py diff --git a/domain/epc/tests/fixtures/17_0.json b/domain/epc/tests/fixtures/17_0.json new file mode 100644 index 00000000..b17ca7f3 --- /dev/null +++ b/domain/epc/tests/fixtures/17_0.json @@ -0,0 +1,218 @@ +{ + "uprn": 12457, + "roofs": [ + { + "description": {"value": "(another dwelling above)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "walls": [ + { + "description": {"value": "System built, with internal insulation", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "floors": [ + { + "description": {"value": "(another dwelling below)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "status": "entered", + "tenure": 2, + "window": { + "description": {"value": "Fully double glazed", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "lighting": { + "description": {"value": "Low energy lighting in 57% of fixed outlets", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + "postcode": "PT5 4RZ", + "hot_water": { + "description": {"value": "Electric immersion, off-peak", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 2 + }, + "post_town": "POSTTOWN", + "built_form": 2, + "door_count": 2, + "glazed_area": 1, + "glazing_gap": "16+", + "region_code": 3, + "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": 0 + }, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 29, + "heat_emitter_type": 0, + "emitter_temperature": "NA", + "main_heating_number": 1, + "main_heating_control": 2401, + "main_heating_category": 7, + "main_heating_fraction": 1, + "sap_main_heating_code": 402, + "main_heating_data_source": 2 + } + ], + "immersion_heating_type": 1, + "cylinder_insulation_type": 0, + "has_fixed_air_conditioning": "false" + }, + "sap_version": 9.92, + "schema_type": "RdSAP-Schema-17.0", + "uprn_source": "Energy Assessor", + "country_code": "EAW", + "main_heating": [ + { + "description": {"value": "Electric storage heaters", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 1 + } + ], + "dwelling_type": {"value": "Mid-floor flat", "language": "1"}, + "language_code": 1, + "property_type": 2, + "address_line_1": "42, Moria Mines Lane", + "assessment_type": "RdSAP", + "completion_date": "2016-01-12", + "inspection_date": "2016-01-12", + "extensions_count": 0, + "measurement_type": 1, + "sap_flat_details": { + "level": 2, + "top_storey": "N", + "flat_location": 1, + "heat_loss_corridor": 0 + }, + "total_floor_area": 55, + "transaction_type": 8, + "conservatory_type": 1, + "heated_room_count": 1, + "pvc_window_frames": "true", + "registration_date": "2016-01-12", + "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": {"value": "Portable electric heaters (assumed)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "lzc_energy_sources": [11], + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "wall_thickness": 240, + "floor_heat_loss": 6, + "roof_construction": 3, + "wall_construction": 8, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.4, "quantity": "metres"}, + "total_floor_area": {"value": 54.6, "quantity": "square metres"}, + "party_wall_length": {"value": 7.3, "quantity": "metres"}, + "heat_loss_perimeter": {"value": 23.3, "quantity": "metres"} + } + ], + "wall_insulation_type": 3, + "construction_age_band": "D", + "party_wall_construction": 0, + "wall_thickness_measured": "Y", + "roof_insulation_location": "ND", + "roof_insulation_thickness": "ND", + "wall_insulation_thickness": "50mm" + } + ], + "low_energy_lighting": 57, + "solar_water_heating": "N", + "habitable_room_count": 3, + "heating_cost_current": {"value": 214, "currency": "GBP"}, + "insulated_door_count": 0, + "co2_emissions_current": 3.9, + "energy_rating_average": 60, + "energy_rating_current": 66, + "lighting_cost_current": {"value": 61, "currency": "GBP"}, + "main_heating_controls": [ + { + "description": {"value": "Manual charge control", "language": "1"}, + "energy_efficiency_rating": 2, + "environmental_efficiency_rating": 2 + } + ], + "multiple_glazing_type": 3, + "open_fireplaces_count": 0, + "has_hot_water_cylinder": "true", + "heating_cost_potential": {"value": 216, "currency": "GBP"}, + "hot_water_cost_current": {"value": 396, "currency": "GBP"}, + "mechanical_ventilation": 0, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": {"value": 158, "currency": "GBP"}, + "indicative_cost": "£15 - £30", + "improvement_type": "C", + "improvement_details": {"improvement_number": 1}, + "improvement_category": 5, + "energy_performance_rating": 74, + "environmental_impact_rating": 60 + } + ], + "co2_emissions_potential": 2.5, + "energy_rating_potential": 79, + "lighting_cost_potential": {"value": 42, "currency": "GBP"}, + "schema_version_original": "LIG-17.0", + "alternative_improvements": [ + { + "sequence": 1, + "typical_saving": {"value": 141, "currency": "GBP"}, + "improvement_type": "J2", + "improvement_details": {"improvement_number": 54}, + "improvement_category": 6, + "energy_performance_rating": 81, + "environmental_impact_rating": 96 + } + ], + "hot_water_cost_potential": {"value": 154, "currency": "GBP"}, + "renewable_heat_incentive": { + "water_heating": 4818, + "space_heating_existing_dwelling": 2415 + }, + "energy_consumption_current": 427, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "9.0.0", + "energy_consumption_potential": 267, + "environmental_impact_current": 48, + "fixed_lighting_outlets_count": 7, + "current_energy_efficiency_band": "D", + "environmental_impact_potential": 66, + "has_heated_separate_conservatory": "false", + "potential_energy_efficiency_band": "C", + "co2_emissions_current_per_floor_area": 72, + "low_energy_fixed_lighting_outlets_count": 4 +} diff --git a/domain/epc/tests/fixtures/17_1.json b/domain/epc/tests/fixtures/17_1.json new file mode 100644 index 00000000..ef0613d1 --- /dev/null +++ b/domain/epc/tests/fixtures/17_1.json @@ -0,0 +1,243 @@ +{ + "uprn": 12457, + "roofs": [ + { + "description": {"value": "Pitched, 100 mm loft insulation", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + { + "description": {"value": "Pitched, insulated (assumed)", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "walls": [ + { + "description": {"value": "Cavity wall, filled cavity", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "floors": [ + { + "description": {"value": "Solid, no insulation (assumed)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "status": "entered", + "tenure": 1, + "window": { + "description": {"value": "Fully double glazed", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "lighting": { + "description": {"value": "Low energy lighting in 23% of fixed outlets", "language": "1"}, + "energy_efficiency_rating": 2, + "environmental_efficiency_rating": 2 + }, + "postcode": "PT42 5HL", + "hot_water": { + "description": {"value": "From main system", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "post_town": "POSTTOWN", + "built_form": 1, + "door_count": 4, + "glazed_area": 1, + "glazing_gap": "16+", + "region_code": 17, + "report_type": 2, + "sap_heating": { + "cylinder_size": 2, + "water_heating_code": 901, + "water_heating_fuel": 28, + "cylinder_thermostat": "Y", + "instantaneous_wwhrs": { + "rooms_with_bath_and_or_shower": 1, + "rooms_with_mixer_shower_no_bath": 0, + "rooms_with_bath_and_mixer_shower": 1 + }, + "secondary_fuel_type": 29, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 28, + "boiler_flue_type": 1, + "fan_flue_present": "N", + "heat_emitter_type": 1, + "emitter_temperature": "NA", + "main_heating_number": 1, + "main_heating_control": 2104, + "main_heating_category": 2, + "main_heating_fraction": 1, + "mcs_installed_heat_pump": "false", + "main_heating_data_source": 1, + "main_heating_index_number": 9049 + } + ], + "immersion_heating_type": "NA", + "secondary_heating_type": 691, + "cylinder_insulation_type": 1, + "has_fixed_air_conditioning": "false", + "cylinder_insulation_thickness": 12 + }, + "sap_version": 9.92, + "schema_type": "RdSAP-Schema-17.1", + "uprn_source": "Energy Assessor", + "country_code": "EAW", + "main_heating": [ + { + "description": {"value": "Boiler and radiators, oil", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + } + ], + "dwelling_type": {"value": "Detached house", "language": "1"}, + "language_code": 1, + "property_type": 0, + "address_line_1": "15, Hedge Lane", + "address_line_2": "Lower Moria", + "assessment_type": "RdSAP", + "completion_date": "2018-05-29", + "inspection_date": "2018-05-29", + "extensions_count": 1, + "measurement_type": 2, + "total_floor_area": 101, + "transaction_type": 1, + "conservatory_type": 1, + "heated_room_count": 7, + "pvc_window_frames": "true", + "registration_date": "2018-05-27", + "sap_energy_source": { + "mains_gas": "N", + "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": {"value": "Room heaters, electric", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "lzc_energy_sources": [11], + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "wall_thickness": 270, + "floor_heat_loss": 7, + "roof_construction": 4, + "wall_construction": 4, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.4, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 48.45, "quantity": "square metres"}, + "party_wall_length": {"value": 0, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 22.3, "quantity": "metres"} + } + ], + "wall_insulation_type": 2, + "construction_age_band": "G", + "party_wall_construction": "NA", + "wall_thickness_measured": "Y", + "roof_insulation_location": 2, + "roof_insulation_thickness": "100mm" + }, + { + "identifier": "Extension 1", + "wall_dry_lined": "N", + "wall_thickness": 260, + "floor_heat_loss": 7, + "roof_construction": 5, + "wall_construction": 4, + "building_part_number": 2, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.4, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 21.84, "quantity": "square metres"}, + "party_wall_length": 0, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 16.6, "quantity": "metres"} + } + ], + "wall_insulation_type": 4, + "construction_age_band": "H", + "party_wall_construction": "NA", + "wall_thickness_measured": "Y", + "roof_insulation_location": 4, + "roof_insulation_thickness": 0, + "wall_insulation_thickness": "NI" + } + ], + "low_energy_lighting": 23, + "solar_water_heating": "N", + "habitable_room_count": 7, + "heating_cost_current": {"value": 659, "currency": "GBP"}, + "insulated_door_count": 0, + "co2_emissions_current": 5.8, + "energy_rating_average": 60, + "energy_rating_current": 53, + "lighting_cost_current": {"value": 115, "currency": "GBP"}, + "main_heating_controls": [ + { + "description": {"value": "Programmer and room thermostat", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + } + ], + "multiple_glazing_type": 3, + "open_fireplaces_count": 0, + "has_hot_water_cylinder": "true", + "heating_cost_potential": {"value": 470, "currency": "GBP"}, + "hot_water_cost_current": {"value": 161, "currency": "GBP"}, + "mechanical_ventilation": 0, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": {"value": 25, "currency": "GBP"}, + "indicative_cost": "£100 - £350", + "improvement_type": "A", + "improvement_details": {"improvement_number": 5}, + "improvement_category": 5, + "energy_performance_rating": 54, + "environmental_impact_rating": 47 + } + ], + "co2_emissions_potential": 2.7, + "energy_rating_potential": 78, + "lighting_cost_potential": {"value": 65, "currency": "GBP"}, + "schema_version_original": "LIG-17.1", + "hot_water_cost_potential": {"value": 77, "currency": "GBP"}, + "renewable_heat_incentive": { + "water_heating": 3301, + "impact_of_loft_insulation": -565, + "space_heating_existing_dwelling": 11351 + }, + "energy_consumption_current": 234, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "v92.0.1.1", + "energy_consumption_potential": 95, + "environmental_impact_current": 46, + "fixed_lighting_outlets_count": 13, + "current_energy_efficiency_band": "E", + "environmental_impact_potential": 72, + "has_heated_separate_conservatory": "false", + "potential_energy_efficiency_band": "C", + "co2_emissions_current_per_floor_area": 57, + "low_energy_fixed_lighting_outlets_count": 3 +} diff --git a/domain/epc/tests/fixtures/18_0.json b/domain/epc/tests/fixtures/18_0.json new file mode 100644 index 00000000..502ac329 --- /dev/null +++ b/domain/epc/tests/fixtures/18_0.json @@ -0,0 +1,251 @@ +{ + "uprn": 12457, + "roofs": [ + { + "description": {"value": "Pitched, 100 mm loft insulation", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + { + "description": {"value": "Flat, insulated", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + { + "description": {"value": "Roof room(s), insulated (assumed)", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "walls": [ + { + "description": {"value": "Solid brick, as built, no insulation (assumed)", "language": "1"}, + "energy_efficiency_rating": 1, + "environmental_efficiency_rating": 1 + } + ], + "floors": [ + { + "description": {"value": "Solid, no insulation (assumed)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "status": "entered", + "tenure": 1, + "window": { + "description": {"value": "Fully double glazed", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "lighting": { + "description": {"value": "Low energy lighting in 67% of fixed outlets", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + "postcode": "PT11 4RF", + "hot_water": { + "description": {"value": "From main system", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + "post_town": "POSTTOWN", + "built_form": 4, + "door_count": 2, + "glazed_area": 1, + "glazing_gap": 12, + "region_code": 1, + "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": 0 + }, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 26, + "boiler_flue_type": 2, + "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": 16137 + } + ], + "immersion_heating_type": "NA", + "has_fixed_air_conditioning": "false" + }, + "sap_version": 9.92, + "schema_type": "RdSAP-Schema-18.0", + "uprn_source": "Energy Assessor", + "country_code": "EAW", + "main_heating": [ + { + "description": {"value": "Boiler and radiators, mains gas", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "dwelling_type": {"value": "Mid-terrace house", "language": "1"}, + "language_code": 1, + "property_type": 0, + "address_line_1": "1, Bagshot Lane", + "address_line_2": "Village", + "assessment_type": "RdSAP", + "completion_date": "2017-03-19", + "inspection_date": "2017-03-19", + "extensions_count": 1, + "measurement_type": 1, + "total_floor_area": 93, + "transaction_type": 5, + "conservatory_type": 1, + "heated_room_count": 5, + "pvc_window_frames": "true", + "registration_date": "2017-03-19", + "sap_energy_source": { + "mains_gas": "Y", + "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": {"value": "None", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "lzc_energy_sources": [11], + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "wall_thickness": 330, + "floor_heat_loss": 7, + "sap_room_in_roof": { + "floor_area": {"value": 9, "quantity": "square metres"}, + "insulation": "AB", + "roof_room_connected": "N", + "construction_age_band": "G" + }, + "roof_construction": 4, + "wall_construction": 3, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.4, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 29.12, "quantity": "square metres"}, + "party_wall_length": {"value": 11.2, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 5.2, "quantity": "metres"} + } + ], + "wall_insulation_type": 4, + "construction_age_band": "C", + "party_wall_construction": 0, + "wall_thickness_measured": "Y", + "roof_insulation_location": 2, + "roof_insulation_thickness": "100mm", + "wall_insulation_thickness": "NI" + }, + { + "identifier": "Extension", + "wall_dry_lined": "N", + "wall_thickness": 290, + "floor_heat_loss": 7, + "roof_construction": 1, + "wall_construction": 4, + "building_part_number": 2, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.4, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 15.6, "quantity": "square metres"}, + "party_wall_length": {"value": 6, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 5.2, "quantity": "metres"} + } + ], + "wall_insulation_type": 4, + "construction_age_band": "G", + "party_wall_construction": 0, + "wall_thickness_measured": "Y", + "roof_insulation_location": 6, + "roof_insulation_thickness": "NI", + "wall_insulation_thickness": "NI", + "flat_roof_insulation_thickness": "NI" + } + ], + "low_energy_lighting": 67, + "solar_water_heating": "N", + "habitable_room_count": 5, + "heating_cost_current": {"value": 619, "currency": "GBP"}, + "insulated_door_count": 0, + "co2_emissions_current": 3.4, + "energy_rating_average": 60, + "energy_rating_current": 69, + "lighting_cost_current": {"value": 81, "currency": "GBP"}, + "main_heating_controls": [ + { + "description": {"value": "Programmer, room thermostat and TRVs", "language": "1"}, + "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": 534, "currency": "GBP"}, + "hot_water_cost_current": {"value": 100, "currency": "GBP"}, + "mechanical_ventilation": 0, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": {"value": 88, "currency": "GBP"}, + "indicative_cost": "£4,000 - £14,000", + "improvement_type": "Q", + "improvement_details": {"improvement_number": 7}, + "improvement_category": 5, + "energy_performance_rating": 72, + "environmental_impact_rating": 71 + } + ], + "co2_emissions_potential": 1.7, + "energy_rating_potential": 85, + "lighting_cost_potential": {"value": 61, "currency": "GBP"}, + "schema_version_original": "LIG-17.0", + "hot_water_cost_potential": {"value": 68, "currency": "GBP"}, + "renewable_heat_incentive": { + "water_heating": 2087, + "impact_of_loft_insulation": -214, + "impact_of_solid_wall_insulation": -1864, + "space_heating_existing_dwelling": 10483 + }, + "energy_consumption_current": 230, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "2.0.0.0", + "energy_consumption_potential": 115, + "environmental_impact_current": 66, + "fixed_lighting_outlets_count": 9, + "current_energy_efficiency_band": "C", + "environmental_impact_potential": 83, + "has_heated_separate_conservatory": "false", + "potential_energy_efficiency_band": "B", + "co2_emissions_current_per_floor_area": 41, + "low_energy_fixed_lighting_outlets_count": 6 +} diff --git a/domain/epc/tests/fixtures/19_0.json b/domain/epc/tests/fixtures/19_0.json new file mode 100644 index 00000000..f9995286 --- /dev/null +++ b/domain/epc/tests/fixtures/19_0.json @@ -0,0 +1,213 @@ +{ + "uprn": 12457, + "roofs": [ + { + "description": {"value": "Pitched, 150 mm loft insulation", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "walls": [ + { + "description": {"value": "Cavity wall, filled cavity", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + } + ], + "floors": [ + { + "description": {"value": "Solid, no insulation (assumed)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "status": "entered", + "tenure": 3, + "window": { + "description": {"value": "Fully double glazed", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "lighting": { + "description": {"value": "Low energy lighting in 87% of fixed outlets", "language": "1"}, + "energy_efficiency_rating": 5, + "environmental_efficiency_rating": 5 + }, + "postcode": "A1 1AA", + "hot_water": { + "description": {"value": "From main system", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + "post_town": "Town", + "built_form": 2, + "door_count": 1, + "glazed_area": 1, + "region_code": 19, + "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": 0 + }, + "secondary_fuel_type": 9, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 26, + "boiler_flue_type": 2, + "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": 15274 + } + ], + "immersion_heating_type": "NA", + "secondary_heating_type": 631, + "has_fixed_air_conditioning": "false" + }, + "sap_version": 9.94, + "schema_type": "RdSAP-Schema-19.0", + "uprn_source": "Energy Assessor", + "country_code": "EAW", + "main_heating": [ + { + "description": {"value": "Boiler and radiators, mains gas", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "dwelling_type": {"value": "Semi-detached house", "language": "1"}, + "language_code": 1, + "property_type": 0, + "address_line_1": "15, Address Lane", + "address_line_2": "New Town", + "assessment_type": "RdSAP", + "completion_date": "2020-06-04", + "inspection_date": "2020-06-03", + "extensions_count": 1, + "measurement_type": 1, + "total_floor_area": 94, + "transaction_type": 8, + "conservatory_type": 2, + "heated_room_count": 5, + "pvc_window_frames": "false", + "registration_date": "2020-06-04", + "sap_energy_source": { + "mains_gas": "Y", + "meter_type": 3, + "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": {"value": "Room heaters, dual fuel (mineral and wood)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "lzc_energy_sources": [11, 9], + "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.47, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 39.91, "quantity": "square metres"}, + "party_wall_length": {"value": 8.81, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 13.65, "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": "150mm", + "wall_insulation_thickness": "NI", + "floor_insulation_thickness": "NI" + } + ], + "low_energy_lighting": 87, + "solar_water_heating": "N", + "habitable_room_count": 5, + "heating_cost_current": {"value": 666, "currency": "GBP"}, + "insulated_door_count": 0, + "co2_emissions_current": 3.8, + "energy_rating_average": 60, + "energy_rating_current": 66, + "lighting_cost_current": {"value": 81, "currency": "GBP"}, + "main_heating_controls": [ + { + "description": {"value": "Programmer, room thermostat and TRVs", "language": "1"}, + "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": 615, "currency": "GBP"}, + "hot_water_cost_current": {"value": 107, "currency": "GBP"}, + "mechanical_ventilation": 0, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": {"value": 51, "currency": "GBP"}, + "indicative_cost": "£4,000 - £6,000", + "improvement_type": "W2", + "improvement_details": {"improvement_number": 58}, + "improvement_category": 5, + "energy_performance_rating": 68, + "environmental_impact_rating": 65 + } + ], + "co2_emissions_potential": 2.4, + "energy_rating_potential": 79, + "lighting_cost_potential": {"value": 81, "currency": "GBP"}, + "schema_version_original": "LIG-19.0", + "hot_water_cost_potential": {"value": 74, "currency": "GBP"}, + "renewable_heat_incentive": { + "water_heating": 2207, + "impact_of_loft_insulation": -394, + "space_heating_existing_dwelling": 9825 + }, + "energy_consumption_current": 222, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "2.1.1.0", + "energy_consumption_potential": 137, + "environmental_impact_current": 62, + "fixed_lighting_outlets_count": 15, + "windows_transmission_details": { + "u_value": 3.1, + "data_source": 2, + "solar_transmittance": 0.76 + }, + "current_energy_efficiency_band": "D", + "environmental_impact_potential": 75, + "has_heated_separate_conservatory": "false", + "potential_energy_efficiency_band": "C", + "co2_emissions_current_per_floor_area": 41, + "low_energy_fixed_lighting_outlets_count": 13 +} diff --git a/domain/epc/tests/fixtures/20_0_0.json b/domain/epc/tests/fixtures/20_0_0.json new file mode 100644 index 00000000..397c2758 --- /dev/null +++ b/domain/epc/tests/fixtures/20_0_0.json @@ -0,0 +1,225 @@ +{ + "uprn": 12457, + "roofs": [ + {"description": "Pitched, 25 mm loft insulation", "energy_efficiency_rating": 2, "environmental_efficiency_rating": 2}, + {"description": "Pitched, 250 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}, + {"description": "Cavity wall, as built, insulated (assumed)", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "floors": [ + {"description": "Suspended, no insulation (assumed)", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0}, + {"description": "Solid, insulated (assumed)", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0} + ], + "status": "entered", + "tenure": 1, + "window": {"description": "Fully double glazed", "energy_efficiency_rating": 3, "environmental_efficiency_rating": 3}, + "addendum": { + "stone_walls": "true", + "system_build": "true", + "addendum_numbers": [1, 8] + }, + "lighting": {"description": "Low energy lighting in 50% of fixed outlets", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "postcode": "A0 0AA", + "hot_water": {"description": "From main system", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "post_town": "Whitbury", + "built_form": 2, + "door_count": 2, + "glazed_area": 1, + "region_code": 1, + "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": 0 + }, + "secondary_fuel_type": 25, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 26, + "boiler_flue_type": 2, + "fan_flue_present": "N", + "heat_emitter_type": 1, + "emitter_temperature": 0, + "main_heating_number": 1, + "main_heating_control": 2106, + "main_heating_category": 2, + "main_heating_fraction": 1, + "sap_main_heating_code": 101, + "central_heating_pump_age": 0, + "main_heating_data_source": 1, + "main_heating_index_number": 17507 + } + ], + "immersion_heating_type": "NA", + "has_fixed_air_conditioning": "false" + }, + "sap_version": 9.8, + "sap_windows": [ + {"orientation": 1, "window_area": 200.1, "window_type": 2, "glazing_type": 1, "window_location": 0}, + {"orientation": 2, "window_area": 180.2, "window_type": 1, "glazing_type": 2, "window_location": 1} + ], + "schema_type": "RdSAP-Schema-20.0.0", + "uprn_source": "Energy Assessor", + "country_code": "EAW", + "main_heating": [ + {"description": "Boiler and radiators, anthracite", "energy_efficiency_rating": 3, "environmental_efficiency_rating": 1}, + {"description": "Boiler and radiators, mains gas", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "dwelling_type": "Mid-terrace house", + "language_code": 1, + "property_type": 0, + "address_line_1": "1 Some Street", + "assessment_type": "RdSAP", + "completion_date": "2020-05-04", + "inspection_date": "2020-05-04", + "extensions_count": 0, + "measurement_type": 1, + "sap_flat_details": { + "level": 1, + "top_storey": "N", + "storey_count": 3, + "flat_location": 1, + "heat_loss_corridor": 2, + "unheated_corridor_length": 10 + }, + "total_floor_area": 55, + "transaction_type": 1, + "conservatory_type": 1, + "heated_room_count": 5, + "registration_date": "2020-05-04", + "sap_energy_source": { + "mains_gas": "Y", + "meter_type": 2, + "photovoltaic_supply": { + "none_or_no_details": {"pv_connection": 0, "percent_roof_area": 50} + }, + "wind_turbines_count": 0, + "wind_turbines_terrain_type": 2 + }, + "secondary_heating": {"description": "Room heaters, electric", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0}, + "lzc_energy_sources": [11], + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "floor_heat_loss": 7, + "sap_room_in_roof": { + "floor_area": 100, + "insulation": "AB", + "roof_room_connected": "N", + "construction_age_band": "B" + }, + "roof_construction": 4, + "wall_construction": 4, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.45, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 45.82, "quantity": "square metres"}, + "party_wall_length": {"value": 7.9, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 19.5, "quantity": "metres"} + } + ], + "wall_insulation_type": 2, + "construction_age_band": "K", + "party_wall_construction": 0, + "wall_thickness_measured": "N", + "roof_insulation_location": 2, + "roof_insulation_thickness": "200mm", + "wall_insulation_thickness": "NI", + "floor_insulation_thickness": "NI" + } + ], + "low_energy_lighting": 100, + "solar_water_heating": "N", + "habitable_room_count": 5, + "heating_cost_current": 365.98, + "insulated_door_count": 2, + "co2_emissions_current": 2.4, + "energy_rating_average": 60, + "energy_rating_current": 50, + "lighting_cost_current": 123.45, + "main_heating_controls": [ + {"description": "Programmer, room thermostat and TRVs", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + {"description": "Time and temperature zone control", "energy_efficiency_rating": 5, "environmental_efficiency_rating": 5} + ], + "multiple_glazing_type": 2, + "open_fireplaces_count": 0, + "heating_cost_potential": 250.34, + "hot_water_cost_current": 200.4, + "insulated_door_u_value": 3, + "mechanical_ventilation": 0, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": 360, + "indicative_cost": "£100 - £350", + "improvement_type": "Z3", + "improvement_details": {"improvement_number": 5}, + "improvement_category": 6, + "energy_performance_rating": 50, + "environmental_impact_rating": 50 + }, + { + "sequence": 2, + "typical_saving": 99, + "indicative_cost": 2000, + "improvement_type": "Z2", + "improvement_details": {"improvement_number": 1}, + "improvement_category": 2, + "energy_performance_rating": 60, + "environmental_impact_rating": 64 + }, + { + "sequence": 3, + "typical_saving": 99, + "indicative_cost": 1000, + "improvement_type": "Z2", + "improvement_details": { + "improvement_texts": { + "improvement_summary": "An improvement summary", + "improvement_description": "An improvement desc" + } + }, + "improvement_category": 2, + "energy_performance_rating": 60, + "environmental_impact_rating": 64 + } + ], + "co2_emissions_potential": 1.4, + "energy_rating_potential": 72, + "lighting_cost_potential": 84.23, + "schema_version_original": "SAP-19.0", + "hot_water_cost_potential": 180.43, + "renewable_heat_incentive": { + "water_heating": 2285, + "impact_of_loft_insulation": -2114, + "impact_of_cavity_insulation": -122, + "impact_of_solid_wall_insulation": -3560, + "space_heating_existing_dwelling": 13120 + }, + "energy_consumption_current": 230, + "multiple_glazed_proportion": 100, + "calculation_software_version": "13.05r16", + "energy_consumption_potential": 88, + "environmental_impact_current": 52, + "fixed_lighting_outlets_count": 16, + "windows_transmission_details": {"u_value": 2, "data_source": 2, "solar_transmittance": 0.72}, + "multiple_glazed_proportion_nr": "NR", + "current_energy_efficiency_band": "E", + "environmental_impact_potential": 74, + "potential_energy_efficiency_band": "C", + "co2_emissions_current_per_floor_area": 20, + "low_energy_fixed_lighting_outlets_count": 16 +} diff --git a/domain/epc/tests/fixtures/21_0_0.json b/domain/epc/tests/fixtures/21_0_0.json new file mode 100644 index 00000000..6781ac6e --- /dev/null +++ b/domain/epc/tests/fixtures/21_0_0.json @@ -0,0 +1,245 @@ +{ + "uprn": 12457, + "roofs": [ + {"description": "Pitched, 25 mm loft insulation", "energy_efficiency_rating": 2, "environmental_efficiency_rating": 2}, + {"description": "Pitched, 250 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}, + {"description": "Cavity wall, as built, insulated (assumed)", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "floors": [ + {"description": "Suspended, no insulation (assumed)", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0}, + {"description": "Solid, insulated (assumed)", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0} + ], + "status": "entered", + "tenure": 1, + "window": {"description": "Fully double glazed", "energy_efficiency_rating": 3, "environmental_efficiency_rating": 3}, + "addendum": {"stone_walls": "true", "system_build": "true", "addendum_numbers": [1, 8]}, + "lighting": {"description": "Low energy lighting in 50% of fixed outlets", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "postcode": "A0 0AA", + "hot_water": {"description": "From main system", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "post_town": "Whitbury", + "built_form": 2, + "door_count": 3, + "region_code": 1, + "report_type": 2, + "sap_heating": { + "cylinder_size": 1, + "shower_outlets": { + "shower_outlet": {"shower_wwhrs": 1, "shower_outlet_type": 1} + }, + "water_heating_code": 901, + "water_heating_fuel": 26, + "instantaneous_wwhrs": {"wwhrs_index_number1": 1, "wwhrs_index_number2": 2}, + "secondary_fuel_type": 25, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 26, + "boiler_flue_type": 2, + "fan_flue_present": "N", + "heat_emitter_type": 1, + "emitter_temperature": 0, + "main_heating_number": 1, + "boiler_ignition_type": 1, + "main_heating_control": 2106, + "main_heating_category": 2, + "main_heating_fraction": 1, + "sap_main_heating_code": 101, + "central_heating_pump_age": 0, + "main_heating_data_source": 1, + "main_heating_index_number": 17507 + } + ], + "immersion_heating_type": "NA", + "has_fixed_air_conditioning": "false" + }, + "sap_version": 10.2, + "sap_windows": [ + { + "pvc_frame": "false", + "glazing_gap": 6, + "orientation": 1, + "window_type": 2, + "frame_factor": 1.0, + "glazing_type": 14, + "window_width": 1.2, + "window_height": 2.0, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 6, + "permanent_shutters_present": "N", + "window_transmission_details": {"u_value": 1.0, "data_source": 2, "solar_transmittance": 1.0}, + "permanent_shutters_insulated": "N" + } + ], + "schema_type": "RdSAP-Schema-21.0.0", + "uprn_source": "Energy Assessor", + "country_code": "ENG", + "main_heating": [ + {"description": "Boiler and radiators, anthracite", "energy_efficiency_rating": 3, "environmental_efficiency_rating": 1}, + {"description": "Boiler and radiators, mains gas", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "dwelling_type": "Mid-terrace house", + "language_code": 1, + "pressure_test": 6, + "property_type": 0, + "address_line_1": "1 Some Street", + "assessment_type": "RdSAP", + "completion_date": "2023-12-01", + "inspection_date": "2023-12-01", + "wet_rooms_count": 0, + "extensions_count": 0, + "measurement_type": 1, + "sap_flat_details": { + "level": 1, + "top_storey": "N", + "storey_count": 3, + "flat_location": 1, + "heat_loss_corridor": 2, + "unheated_corridor_length": 10 + }, + "total_floor_area": 55, + "transaction_type": 16, + "conservatory_type": 1, + "heated_room_count": 5, + "registration_date": "2023-12-01", + "sap_energy_source": { + "mains_gas": "Y", + "meter_type": 2, + "pv_batteries": {"pv_battery": {"battery_capacity": 5}}, + "pv_connection": 0, + "pv_battery_count": 1, + "photovoltaic_supply": {"none_or_no_details": {"percent_roof_area": 0}}, + "wind_turbines_count": 0, + "wind_turbine_details": {"hub_height": 0, "rotor_diameter": 0}, + "gas_smart_meter_present": "false", + "is_dwelling_export_capable": "false", + "wind_turbines_terrain_type": 4, + "electricity_smart_meter_present": "true" + }, + "secondary_heating": {"description": "Room heaters, electric", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0}, + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "floor_heat_loss": 7, + "sap_room_in_roof": {"floor_area": 100, "construction_age_band": "B"}, + "roof_construction": 4, + "wall_construction": 4, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.45, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 45.82, "quantity": "square metres"}, + "party_wall_length": {"value": 7.9, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 19.5, "quantity": "metres"} + } + ], + "wall_insulation_type": 2, + "construction_age_band": "M", + "sap_alternative_wall_1": { + "wall_area": 10.4, + "wall_dry_lined": "N", + "wall_construction": 4, + "wall_insulation_type": 2, + "wall_thickness_measured": "N" + }, + "sap_alternative_wall_2": { + "wall_area": 10.8, + "wall_dry_lined": "N", + "wall_construction": 4, + "wall_insulation_type": 2, + "wall_thickness_measured": "N" + }, + "party_wall_construction": 0, + "wall_thickness_measured": "N", + "roof_insulation_location": 2, + "roof_insulation_thickness": "200mm", + "wall_insulation_thickness": "NI", + "floor_insulation_thickness": "NI" + } + ], + "open_chimneys_count": 1, + "solar_water_heating": "N", + "habitable_room_count": 5, + "heating_cost_current": 365.98, + "insulated_door_count": 2, + "co2_emissions_current": 2.4, + "energy_rating_average": 60, + "energy_rating_current": 50, + "lighting_cost_current": 123.45, + "main_heating_controls": [ + {"description": "Programmer, room thermostat and TRVs", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + {"description": "Time and temperature zone control", "energy_efficiency_rating": 5, "environmental_efficiency_rating": 5} + ], + "has_hot_water_cylinder": "true", + "heating_cost_potential": 250.34, + "hot_water_cost_current": 200.4, + "insulated_door_u_value": 3, + "mechanical_ventilation": 6, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": 360, + "indicative_cost": "£100 - £350", + "improvement_type": "Z3", + "improvement_details": {"improvement_number": 5}, + "improvement_category": 6, + "energy_performance_rating": 50, + "environmental_impact_rating": 50 + }, + { + "sequence": 3, + "typical_saving": 99, + "indicative_cost": 1000, + "improvement_type": "Z2", + "improvement_details": { + "improvement_texts": {"improvement_description": "Improvement desc"} + }, + "improvement_category": 2, + "energy_performance_rating": 60, + "environmental_impact_rating": 64 + } + ], + "co2_emissions_potential": 1.4, + "energy_rating_potential": 72, + "lighting_cost_potential": 84.23, + "schema_version_original": "SAP-19.0", + "hot_water_cost_potential": 180.43, + "renewable_heat_incentive": { + "water_heating": 2285, + "impact_of_loft_insulation": -2114, + "impact_of_cavity_insulation": -122, + "impact_of_solid_wall_insulation": -3560, + "space_heating_existing_dwelling": 13120 + }, + "draughtproofed_door_count": 1, + "mechanical_vent_duct_type": 3, + "energy_consumption_current": 230, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "13.05r16", + "energy_consumption_potential": 88, + "environmental_impact_current": 52, + "windows_transmission_details": {"u_value": 2, "data_source": 2, "solar_transmittance": 0.72}, + "cfl_fixed_lighting_bulbs_count": 5, + "current_energy_efficiency_band": "E", + "environmental_impact_potential": 74, + "led_fixed_lighting_bulbs_count": 10, + "mechanical_vent_duct_placement": 2, + "mechanical_vent_duct_insulation": 2, + "potential_energy_efficiency_band": "C", + "pressure_test_certificate_number": 0, + "mechanical_ventilation_index_number": 12, + "co2_emissions_current_per_floor_area": 20, + "low_energy_fixed_lighting_bulbs_count": 16, + "mechanical_vent_duct_insulation_level": 2, + "mechanical_vent_measured_installation": "false", + "incandescent_fixed_lighting_bulbs_count": 5 +} diff --git a/domain/epc/tests/fixtures/21_0_1.json b/domain/epc/tests/fixtures/21_0_1.json new file mode 100644 index 00000000..45361227 --- /dev/null +++ b/domain/epc/tests/fixtures/21_0_1.json @@ -0,0 +1,222 @@ +{ + "uprn": 12457, + "roofs": [ + {"description": {"value": "Pitched, 25 mm loft insulation", "language": "1"}, "energy_efficiency_rating": 2, "environmental_efficiency_rating": 2}, + {"description": {"value": "Pitched, 250 mm loft insulation", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "walls": [ + {"description": {"value": "Solid brick, as built, no insulation (assumed)", "language": "1"}, "energy_efficiency_rating": 1, "environmental_efficiency_rating": 1}, + {"description": {"value": "Cavity wall, as built, insulated (assumed)", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "floors": [ + {"description": {"value": "Suspended, no insulation (assumed)", "language": "1"}, "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0} + ], + "status": "entered", + "tenure": 1, + "window": {"description": {"value": "Fully double glazed", "language": "1"}, "energy_efficiency_rating": 3, "environmental_efficiency_rating": 3}, + "addendum": {"stone_walls": "true", "system_build": "true", "addendum_numbers": [1, 13]}, + "lighting": {"description": {"value": "Low energy lighting in 50% of fixed outlets", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "postcode": "A0 0AA", + "hot_water": {"description": {"value": "From main system", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "post_town": "Whitbury", + "built_form": 2, + "door_count": 3, + "region_code": 1, + "report_type": 2, + "sap_heating": { + "cylinder_size": 1, + "shower_outlets": {"shower_outlet": {"shower_wwhrs": 1, "shower_outlet_type": 1}}, + "water_heating_code": 901, + "water_heating_fuel": 26, + "instantaneous_wwhrs": {"wwhrs_index_number1": 1, "wwhrs_index_number2": 2}, + "secondary_fuel_type": 25, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 26, + "boiler_flue_type": 2, + "fan_flue_present": "N", + "heat_emitter_type": 1, + "emitter_temperature": 0, + "main_heating_number": 1, + "boiler_ignition_type": 1, + "main_heating_control": 2106, + "main_heating_category": 2, + "main_heating_fraction": 1, + "sap_main_heating_code": 101, + "central_heating_pump_age": 0, + "main_heating_data_source": 1, + "main_heating_index_number": 17507 + } + ], + "immersion_heating_type": "NA", + "has_fixed_air_conditioning": "false" + }, + "sap_version": 10.2, + "sap_windows": [ + { + "pvc_frame": "false", + "glazing_gap": 6, + "orientation": 1, + "window_type": 2, + "frame_factor": 1.0, + "glazing_type": 14, + "window_width": 1.2, + "window_height": 2.0, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 6, + "permanent_shutters_present": "N", + "window_transmission_details": {"u_value": 1.0, "data_source": 2, "solar_transmittance": 1.0}, + "permanent_shutters_insulated": "N" + } + ], + "schema_type": "RdSAP-Schema-21.0.1", + "uprn_source": "Energy Assessor", + "country_code": "ENG", + "main_heating": [ + {"description": {"value": "Boiler and radiators, anthracite", "language": "1"}, "energy_efficiency_rating": 3, "environmental_efficiency_rating": 1}, + {"description": {"value": "Boiler and radiators, mains gas", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "dwelling_type": "Mid-terrace house", + "language_code": 1, + "pressure_test": 6, + "property_type": 0, + "address_line_1": "1 Some Street", + "assessment_type": "RdSAP", + "completion_date": "2025-04-04", + "inspection_date": "2025-04-04", + "wet_rooms_count": 0, + "extensions_count": 0, + "measurement_type": 1, + "sap_flat_details": { + "level": 1, + "top_storey": "N", + "storey_count": 3, + "flat_location": 1, + "heat_loss_corridor": 2, + "unheated_corridor_length": {"value": 10, "quantity": "metres"} + }, + "total_floor_area": 55, + "transaction_type": 16, + "conservatory_type": 1, + "heated_room_count": 5, + "registration_date": "2025-04-04", + "sap_energy_source": { + "mains_gas": "Y", + "meter_type": 2, + "pv_batteries": {"pv_battery": {"battery_capacity": 5}}, + "pv_connection": 0, + "pv_battery_count": 1, + "photovoltaic_supply": {"none_or_no_details": {"percent_roof_area": 0}}, + "wind_turbines_count": 0, + "wind_turbine_details": {"hub_height": 0, "rotor_diameter": 0}, + "gas_smart_meter_present": "false", + "is_dwelling_export_capable": "false", + "wind_turbines_terrain_type": 4, + "electricity_smart_meter_present": "true" + }, + "secondary_heating": { + "description": {"value": "Room heaters, electric", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "floor_heat_loss": 7, + "sap_room_in_roof": {"floor_area": 100, "construction_age_band": "B"}, + "roof_construction": 4, + "wall_construction": 4, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.45, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 45.82, "quantity": "square metres"}, + "party_wall_length": {"value": 7.9, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 19.5, "quantity": "metres"} + } + ], + "wall_insulation_type": 2, + "construction_age_band": "M", + "sap_alternative_wall_1": {"wall_area": 10.4, "wall_dry_lined": "N", "wall_construction": 4, "wall_insulation_type": 2, "wall_thickness_measured": "N"}, + "sap_alternative_wall_2": {"wall_area": 10.8, "wall_dry_lined": "N", "wall_construction": 4, "wall_insulation_type": 2, "wall_thickness_measured": "N"}, + "party_wall_construction": 0, + "wall_thickness_measured": "N", + "roof_insulation_location": 2, + "roof_insulation_thickness": "200mm", + "wall_insulation_thickness": "NI", + "floor_insulation_thickness": "NI" + } + ], + "open_chimneys_count": 1, + "solar_water_heating": "N", + "habitable_room_count": 5, + "heating_cost_current": 365.98, + "insulated_door_count": 2, + "co2_emissions_current": 2.4, + "energy_rating_average": 60, + "energy_rating_current": 50, + "lighting_cost_current": 123.45, + "main_heating_controls": [ + {"description": {"value": "Programmer, room thermostat and TRVs", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + {"description": {"value": "Time and temperature zone control", "language": "1"}, "energy_efficiency_rating": 5, "environmental_efficiency_rating": 5} + ], + "has_hot_water_cylinder": "true", + "heating_cost_potential": 250.34, + "hot_water_cost_current": 200.4, + "insulated_door_u_value": 3, + "mechanical_ventilation": 6, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": 139, + "indicative_cost": "£220 - £250", + "improvement_type": "G", + "improvement_details": {"improvement_number": 66}, + "improvement_category": 5, + "energy_performance_rating": 70, + "environmental_impact_rating": 70 + } + ], + "co2_emissions_potential": 1.4, + "energy_rating_potential": 72, + "lighting_cost_potential": 84.23, + "schema_version_original": "SAP-19.0", + "hot_water_cost_potential": 180.43, + "renewable_heat_incentive": { + "water_heating": 2285, + "impact_of_loft_insulation": -2114, + "impact_of_cavity_insulation": -122, + "impact_of_solid_wall_insulation": -3560, + "space_heating_existing_dwelling": 13120 + }, + "draughtproofed_door_count": 1, + "mechanical_vent_duct_type": 3, + "energy_consumption_current": 230, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "13.05r16", + "energy_consumption_potential": 88, + "environmental_impact_current": 52, + "windows_transmission_details": {"u_value": 2, "data_source": 2, "solar_transmittance": 0.72}, + "cfl_fixed_lighting_bulbs_count": 5, + "current_energy_efficiency_band": "E", + "environmental_impact_potential": 74, + "led_fixed_lighting_bulbs_count": 10, + "mechanical_vent_duct_placement": 2, + "mechanical_vent_duct_insulation": 2, + "potential_energy_efficiency_band": "C", + "pressure_test_certificate_number": 0, + "mechanical_ventilation_index_number": 12, + "co2_emissions_current_per_floor_area": 20, + "low_energy_fixed_lighting_bulbs_count": 16, + "mechanical_vent_duct_insulation_level": 2, + "mechanical_vent_measured_installation": "false", + "incandescent_fixed_lighting_bulbs_count": 0 +} diff --git a/domain/epc/tests/fixtures/pashub_rdsap_site_notes_example1.json b/domain/epc/tests/fixtures/pashub_rdsap_site_notes_example1.json new file mode 100644 index 00000000..f19bea20 --- /dev/null +++ b/domain/epc/tests/fixtures/pashub_rdsap_site_notes_example1.json @@ -0,0 +1,232 @@ +{ + "inspection_metadata": { + "inspection_surveyor": "test", + "email_address": "test@test.com", + "report_reference": "49D422A9-0779-44DD-9665-464D35DFF1A8", + "created_on": "2026-03-31", + "date_of_inspection": "2026-03-31", + "property_address": "1, Test Street, Test Town, Test County, TE1 1ST" + }, + "general": { + "epc_checked_before_assessment": true, + "epc_exists_at_point_of_assessment": false, + "inspection_date": "2026-03-31", + "transaction_type": "None of the Above", + "tenure": "Rented Social", + "property_type": "House", + "detachment_type": "Mid-terrace", + "number_of_storeys": 2, + "terrain_type": "Suburban", + "number_of_extensions": 0, + "electricity_smart_meter": true, + "electric_meter_type": "Single", + "dwelling_export_capable": true, + "mains_gas_available": true, + "gas_smart_meter": true, + "gas_meter_accessible": true, + "measurements_location": "Internal" + }, + "building_construction": { + "main_building": { + "age_range": "I: 1996 - 2002", + "age_indicators": "local knowledge", + "walls_construction_type": "Cavity", + "cavity_construction_indicators": "stretcher bond", + "walls_insulation_type": "As built", + "thermal_conductivity_of_wall_insulation": "Unknown", + "wall_u_value_known": false, + "wall_thickness_mm": 280, + "party_wall_construction_type": "Cavity Masonry, Unfilled" + }, + "floor": { + "floor_type": "Ground Floor", + "floor_construction": "Suspended, not timber", + "floor_insulation_type": "As Built", + "floor_u_value_known": false + } + }, + "building_measurements": { + "main_building": { + "floors": [ + { + "name": "Floor 1", + "area_m2": 24.78, + "height_m": 2.37, + "heat_loss_perimeter_m": 14.21, + "pwl_m": 6.15 + }, + { + "name": "Floor 0", + "area_m2": 24.78, + "height_m": 2.35, + "heat_loss_perimeter_m": 14.21, + "pwl_m": 6.15 + } + ] + } + }, + "roof_space": { + "main_building": { + "construction_type": "Pitched roof (Slates or tiles), Access to loft", + "insulation_at": "Joists", + "roof_u_value_known": false, + "insulation_thickness_mm": 100, + "cavity_wall_construction_indicators": "No indicator of construction visible", + "rooms_in_roof": false + } + }, + "windows": [ + { + "id": 1, + "location": "Main Building", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 1.36, + "width_m": 1.0, + "orientation": "South East" + }, + { + "id": 2, + "location": "Main Building", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 1.33, + "width_m": 0.96, + "orientation": "South East" + }, + { + "id": 3, + "location": "Main Building", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 1.04, + "width_m": 0.96, + "orientation": "North West" + }, + { + "id": 4, + "location": "Main Building", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 1.02, + "width_m": 0.97, + "orientation": "North West" + } + ], + "heating_and_hot_water": { + "main_heating": { + "selection_method": "PCDF Search", + "system_type": "Boiler with radiators or underfloor heating", + "product_id": 18400, + "manufacturer": "Vaillant", + "model": "ecoFIT sustain 415", + "orig_manufacturer": "Vaillant", + "fuel": "Mains gas", + "summer_efficiency": 0, + "type": "Regular", + "condensing": true, + "year": "2018 - current", + "mount": "Wall", + "open_flue": "Room-sealed", + "fan_assist": true, + "status": "Normal status for an actual product", + "central_heating_pump_age": "Unknown", + "controls": "Programmer, room thermostat and TRVs", + "flue_gas_heat_recovery_system": false, + "weather_compensator": false, + "emitter": "Radiators", + "emitter_temperature": "Unknown" + }, + "secondary_heating": { + "secondary_fuel": "No Secondary Heating" + }, + "water_heating": { + "type": "Regular", + "system": "From main heating 1", + "cylinder_size": "Normal (90-130 litres)", + "cylinder_measured_heat_loss": "Not known", + "insulation_type": "Factory fitted", + "insulation_thickness_mm": 12, + "has_thermostat": true + } + }, + "ventilation": { + "ventilation_type": "Natural", + "has_fixed_air_conditioning": false, + "number_of_open_flues": 0, + "number_of_closed_flues": 0, + "number_of_boiler_flues": 0, + "number_of_other_flues": 0, + "number_of_extract_fans": 2, + "number_of_passive_vents": 0, + "number_of_flueless_gas_fires": 0, + "pressure_test": "No test", + "draught_lobby": false + }, + "conservatories": { + "has_conservatory": false + }, + "renewables": { + "wind_turbines": false, + "solar_hot_water": false, + "photovoltaic_array": false, + "number_of_pv_batteries": 0, + "hydro": false + }, + "room_count_elements": { + "number_of_habitable_rooms": 2, + "any_unheated_rooms": true, + "number_of_heated_rooms": 0, + "number_of_external_doors": 2, + "number_of_insulated_external_doors": 0, + "number_of_draughtproofed_external_doors": 2, + "number_of_open_chimneys": 0, + "number_of_blocked_chimneys": 0, + "number_of_fixed_incandescent_bulbs": 0, + "exact_led_cfl_count_known": true, + "number_of_fixed_led_bulbs": 5, + "number_of_fixed_cfl_bulbs": 4, + "waste_water_heat_recovery": "None" + }, + "water_use": { + "number_of_baths": 1, + "number_of_special_features": 0, + "showers": [ + { + "id": 1, + "outlet_type": "Non-Electric Shower" + } + ] + }, + "customer_response": { + "customer_present": true, + "willing_to_answer_satisfaction_survey": false + }, + "addendum": { + "addendum": "PV Recommended", + "related_party_disclosure": "No related party", + "hard_to_treat_cavity_access_issues": false, + "hard_to_treat_cavity_high_exposure": false, + "hard_to_treat_cavity_narrow_cavities": false + } +} \ No newline at end of file diff --git a/domain/epc/tests/fixtures/pashub_rdsap_site_notes_example2.json b/domain/epc/tests/fixtures/pashub_rdsap_site_notes_example2.json new file mode 100644 index 00000000..1d9c38f5 --- /dev/null +++ b/domain/epc/tests/fixtures/pashub_rdsap_site_notes_example2.json @@ -0,0 +1,330 @@ +{ + "inspection_metadata": { + "inspection_surveyor": "test", + "email_address": "test@test.com", + "report_reference": "6EA2A86D-94CE-4792-8D49-AB495C744EDD", + "created_on": "2025-11-10", + "date_of_inspection": "2025-09-25", + "property_address": "test", + "property_photo": true + }, + "general": { + "epc_checked_before_assessment": true, + "epc_exists_at_point_of_assessment": false, + "inspection_date": "2025-09-25", + "transaction_type": "Grant-Scheme (ECO, RHI, etc.)", + "tenure": "Rented Social", + "property_type": "House", + "detachment_type": "Mid-terrace", + "number_of_storeys": 2, + "terrain_type": "Suburban", + "number_of_extensions": 1, + "electricity_smart_meter": true, + "electric_meter_type": "Single", + "dwelling_export_capable": true, + "mains_gas_available": true, + "gas_smart_meter": true, + "gas_meter_accessible": true, + "measurements_location": "Internal" + }, + "building_construction": { + "main_building": { + "age_range": "1950-1966", + "age_indicators": "local knowledge, enquiries of owner", + "walls_construction_type": "Cavity", + "cavity_construction_indicators": "wall thickness over 270 mm", + "walls_insulation_type": "Filled Cavity", + "filled_cavity_indicators": "evidence of cavity fill drill holes", + "thermal_conductivity_of_wall_insulation": "Unknown", + "wall_u_value_known": false, + "wall_thickness_mm": 310, + "party_wall_construction_type": "Cavity Masonry, Filled" + }, + "extensions": [ + { + "id": 1, + "age_range": "2003-2006", + "age_indicators": "local knowledge, enquiries of owner", + "walls_construction_type": "Cavity", + "cavity_construction_indicators": "wall thickness over 270 mm", + "walls_insulation_type": "As built", + "thermal_conductivity_of_wall_insulation": "Unknown", + "wall_u_value_known": false, + "wall_thickness_mm": 310, + "party_wall_construction_type": "Cavity Masonry, Filled" + } + ], + "floor": { + "floor_type": "Ground Floor", + "floor_construction": "Solid", + "floor_insulation_type": "As Built", + "floor_u_value_known": false + } + }, + "building_measurements": { + "main_building": { + "floors": [ + { + "name": "Floor 1", + "area_m2": 35.68, + "height_m": 2.19, + "heat_loss_perimeter_m": 13.44, + "pwl_m": 10.62 + }, + { + "name": "Floor 0", + "area_m2": 35.68, + "height_m": 2.17, + "heat_loss_perimeter_m": 11.0, + "pwl_m": 10.62 + } + ] + }, + "extensions": [ + { + "id": 1, + "floors": [ + { + "name": "Floor 0", + "area_m2": 3.8, + "height_m": 2.0, + "heat_loss_perimeter_m": 5.7, + "pwl_m": 0.0 + } + ] + } + ] + }, + "roof_space": { + "main_building": { + "construction_type": "Pitched roof (Slates or tiles), Access to loft", + "insulation_at": "Joists", + "roof_u_value_known": false, + "insulation_thickness_mm": 100, + "cavity_wall_construction_indicators": "cavity visible in roof space", + "rooms_in_roof": false + }, + "extensions": [ + { + "id": 1, + "construction_type": "Pitched roof, Sloping ceiling", + "insulation_at": "Sloping ceiling insulation", + "roof_u_value_known": false, + "insulation_thickness": "As built", + "cavity_wall_construction_indicators": "No indicator of construction visible", + "rooms_in_roof": false + } + ] + }, + "windows": [ + { + "id": 1, + "location": "Main Building", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 1.2, + "width_m": 2.3, + "orientation": "North West" + }, + { + "id": 2, + "location": "Main Building", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 1.2, + "width_m": 1.0, + "orientation": "North West" + }, + { + "id": 3, + "location": "Main Building", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 0.9, + "width_m": 1.0, + "orientation": "North East" + }, + { + "id": 4, + "location": "Extension 1", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 0.9, + "width_m": 1.0, + "orientation": "North" + }, + { + "id": 5, + "location": "Extension 1", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 0.9, + "width_m": 1.7, + "orientation": "North East" + }, + { + "id": 6, + "location": "Extension 1", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 0.9, + "width_m": 2.3, + "orientation": "North West" + }, + { + "id": 7, + "location": "Extension 1", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 1.0, + "width_m": 1.2, + "orientation": "North West" + }, + { + "id": 8, + "location": "Extension 1", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 0.9, + "width_m": 1.0, + "orientation": "North East" + } + ], + "heating_and_hot_water": { + "main_heating": { + "selection_method": "PCDF Search", + "system_type": "Boiler with radiators or underfloor heating", + "product_id": 16839, + "manufacturer": "Vaillant", + "model": "ecoTEC pro 28", + "orig_manufacturer": "Vaillant", + "fuel": "Mains gas", + "summer_efficiency": 0, + "type": "Combi", + "condensing": true, + "year": "2005 - 2015", + "mount": "Wall", + "open_flue": "Room-sealed", + "fan_assist": true, + "status": "Normal status for an actual product", + "central_heating_pump_age": "Unknown", + "controls": "Programmer, room thermostat and TRVs", + "flue_gas_heat_recovery_system": false, + "weather_compensator": false, + "emitter": "Radiators", + "emitter_temperature": "Unknown" + }, + "secondary_heating": { + "secondary_fuel": "No Secondary Heating" + }, + "water_heating": { + "type": "Regular", + "system": "From main heating 1", + "cylinder_size": "No Cylinder", + "cylinder_measured_heat_loss": null, + "insulation_type": null, + "insulation_thickness_mm": null, + "has_thermostat": null + } + }, + "ventilation": { + "ventilation_type": "Mechanical Extract - Decentralised", + "ventilation_in_pcdf_database": false, + "has_fixed_air_conditioning": false, + "number_of_open_flues": 0, + "number_of_closed_flues": 0, + "number_of_boiler_flues": 0, + "number_of_other_flues": 0, + "number_of_extract_fans": 0, + "number_of_passive_vents": 0, + "number_of_flueless_gas_fires": 0, + "pressure_test": "No test", + "draught_lobby": false + }, + "conservatories": { + "has_conservatory": false + }, + "renewables": { + "wind_turbines": false, + "solar_hot_water": false, + "photovoltaic_array": false, + "number_of_pv_batteries": 0, + "hydro": false + }, + "room_count_elements": { + "number_of_habitable_rooms": 3, + "any_unheated_rooms": false, + "number_of_heated_rooms": null, + "number_of_external_doors": 2, + "number_of_insulated_external_doors": 0, + "number_of_draughtproofed_external_doors": 2, + "number_of_open_chimneys": 0, + "number_of_blocked_chimneys": 0, + "number_of_fixed_incandescent_bulbs": 4, + "exact_led_cfl_count_known": true, + "number_of_fixed_led_bulbs": 0, + "number_of_fixed_cfl_bulbs": 1, + "waste_water_heat_recovery": "None" + }, + "water_use": { + "number_of_baths": 1, + "number_of_special_features": 0, + "showers": [ + { + "id": 1, + "outlet_type": "Non-Electric Shower" + } + ] + }, + "customer_response": { + "customer_present": true, + "willing_to_answer_satisfaction_survey": false + }, + "addendum": { + "addendum": "None", + "related_party_disclosure": "No related party", + "hard_to_treat_cavity_access_issues": false, + "hard_to_treat_cavity_high_exposure": false, + "hard_to_treat_cavity_narrow_cavities": false + } +} diff --git a/domain/epc/tests/fixtures/pashub_rdsap_site_notes_example_unmeasurable_wall.json b/domain/epc/tests/fixtures/pashub_rdsap_site_notes_example_unmeasurable_wall.json new file mode 100644 index 00000000..261160f4 --- /dev/null +++ b/domain/epc/tests/fixtures/pashub_rdsap_site_notes_example_unmeasurable_wall.json @@ -0,0 +1,190 @@ +{ + "inspection_metadata": { + "inspection_surveyor": "test", + "email_address": "test@test.com", + "report_reference": "49D422A9-0779-44DD-9665-464D35DFF1A8", + "created_on": "2026-03-31", + "date_of_inspection": "2026-03-31", + "property_address": "1, Test Street, Test Town, Test County, TE1 1ST" + }, + "general": { + "epc_checked_before_assessment": true, + "epc_exists_at_point_of_assessment": false, + "inspection_date": "2026-03-31", + "transaction_type": "None of the Above", + "tenure": "Rented Social", + "property_type": "House", + "detachment_type": "Mid-terrace", + "number_of_storeys": 2, + "terrain_type": "Suburban", + "number_of_extensions": 0, + "electricity_smart_meter": true, + "electric_meter_type": "Single", + "dwelling_export_capable": true, + "mains_gas_available": true, + "gas_smart_meter": true, + "gas_meter_accessible": true, + "measurements_location": "Internal" + }, + "building_construction": { + "main_building": { + "age_range": "I: 1996 - 2002", + "age_indicators": "local knowledge", + "walls_construction_type": "Cavity", + "cavity_construction_indicators": "stretcher bond", + "walls_insulation_type": "As built", + "thermal_conductivity_of_wall_insulation": "Unknown", + "wall_u_value_known": false, + "wall_thickness_mm": null, + "party_wall_construction_type": "Cavity Masonry, Unfilled" + }, + "floor": { + "floor_type": "Ground Floor", + "floor_construction": "Suspended, not timber", + "floor_insulation_type": "As Built", + "floor_u_value_known": false + } + }, + "building_measurements": { + "main_building": { + "floors": [ + { + "name": "Floor 1", + "area_m2": 24.78, + "height_m": 2.37, + "heat_loss_perimeter_m": 14.21, + "pwl_m": 6.15 + }, + { + "name": "Floor 0", + "area_m2": 24.78, + "height_m": 2.35, + "heat_loss_perimeter_m": 14.21, + "pwl_m": 6.15 + } + ] + } + }, + "roof_space": { + "main_building": { + "construction_type": "Pitched roof (Slates or tiles), Access to loft", + "insulation_at": "Joists", + "roof_u_value_known": false, + "insulation_thickness_mm": 100, + "cavity_wall_construction_indicators": "No indicator of construction visible", + "rooms_in_roof": false + } + }, + "windows": [ + { + "id": 1, + "location": "Main Building", + "wall_type": "External wall", + "glazing_type": "Double glazing, Unknown install date", + "window_type": "Window", + "frame_type": "Wooden or PVC", + "glazing_gap": "16 mm or more", + "draught_proofed": true, + "permanent_shutters": false, + "height_m": 1.36, + "width_m": 1.0, + "orientation": "South East" + } + ], + "heating_and_hot_water": { + "main_heating": { + "selection_method": "PCDF Search", + "system_type": "Boiler with radiators or underfloor heating", + "product_id": 18400, + "manufacturer": "Vaillant", + "model": "ecoFIT sustain 415", + "orig_manufacturer": "Vaillant", + "fuel": "Mains gas", + "summer_efficiency": 0, + "type": "Regular", + "condensing": true, + "year": "2018 - current", + "mount": "Wall", + "open_flue": "Room-sealed", + "fan_assist": true, + "status": "Normal status for an actual product", + "central_heating_pump_age": "Unknown", + "controls": "Programmer, room thermostat and TRVs", + "flue_gas_heat_recovery_system": false, + "weather_compensator": false, + "emitter": "Radiators", + "emitter_temperature": "Unknown" + }, + "secondary_heating": { + "secondary_fuel": "No Secondary Heating" + }, + "water_heating": { + "type": "Regular", + "system": "From main heating 1", + "cylinder_size": "Normal (90-130 litres)", + "cylinder_measured_heat_loss": "Not known", + "insulation_type": "Factory fitted", + "insulation_thickness_mm": 12, + "has_thermostat": true + } + }, + "ventilation": { + "ventilation_type": "Natural", + "has_fixed_air_conditioning": false, + "number_of_open_flues": 0, + "number_of_closed_flues": 0, + "number_of_boiler_flues": 0, + "number_of_other_flues": 0, + "number_of_extract_fans": 2, + "number_of_passive_vents": 0, + "number_of_flueless_gas_fires": 0, + "pressure_test": "No test", + "draught_lobby": false + }, + "conservatories": { + "has_conservatory": false + }, + "renewables": { + "wind_turbines": false, + "solar_hot_water": false, + "photovoltaic_array": false, + "number_of_pv_batteries": 0, + "hydro": false + }, + "room_count_elements": { + "number_of_habitable_rooms": 2, + "any_unheated_rooms": true, + "number_of_heated_rooms": 0, + "number_of_external_doors": 2, + "number_of_insulated_external_doors": 0, + "number_of_draughtproofed_external_doors": 2, + "number_of_open_chimneys": 0, + "number_of_blocked_chimneys": 0, + "number_of_fixed_incandescent_bulbs": 0, + "exact_led_cfl_count_known": true, + "number_of_fixed_led_bulbs": 5, + "number_of_fixed_cfl_bulbs": 4, + "waste_water_heat_recovery": "None" + }, + "water_use": { + "number_of_baths": 1, + "number_of_special_features": 0, + "showers": [ + { + "id": 1, + "outlet_type": "Non-Electric Shower" + } + ] + }, + "customer_response": { + "customer_present": true, + "willing_to_answer_satisfaction_survey": false + }, + "addendum": { + "addendum": "None", + "related_party_disclosure": "No related party", + "hard_to_treat_cavity_access_issues": false, + "hard_to_treat_cavity_high_exposure": false, + "hard_to_treat_cavity_narrow_cavities": false + } +} diff --git a/datatypes/epc/domain/tests/test_from_rdsap_schema.py b/domain/epc/tests/test_from_rdsap_schema.py similarity index 98% rename from datatypes/epc/domain/tests/test_from_rdsap_schema.py rename to domain/epc/tests/test_from_rdsap_schema.py index 9e86ae42..1f63ab45 100644 --- a/datatypes/epc/domain/tests/test_from_rdsap_schema.py +++ b/domain/epc/tests/test_from_rdsap_schema.py @@ -5,8 +5,8 @@ from typing import Any, Dict import pytest -from datatypes.epc.domain.epc_property_data import EpcPropertyData -from datatypes.epc.domain.mapper import EpcPropertyDataMapper +from domain.epc.epc_property_data import EpcPropertyData +from domain.epc.mapper import EpcPropertyDataMapper from datatypes.epc.schema.rdsap_schema_17_0 import RdSapSchema17_0 from datatypes.epc.schema.rdsap_schema_17_1 import RdSapSchema17_1 from datatypes.epc.schema.rdsap_schema_18_0 import RdSapSchema18_0 @@ -16,7 +16,7 @@ from datatypes.epc.schema.rdsap_schema_21_0_0 import RdSapSchema21_0_0 from datatypes.epc.schema.rdsap_schema_21_0_1 import RdSapSchema21_0_1 from datatypes.epc.schema.tests.helpers import from_dict -FIXTURES = os.path.join(os.path.dirname(__file__), "../../schema/tests/fixtures") +FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures") def load(filename: str) -> Dict[str, Any]: diff --git a/datatypes/epc/domain/tests/test_from_site_notes.py b/domain/epc/tests/test_from_site_notes.py similarity index 99% rename from datatypes/epc/domain/tests/test_from_site_notes.py rename to domain/epc/tests/test_from_site_notes.py index ae1dbb3b..d59d820d 100644 --- a/datatypes/epc/domain/tests/test_from_site_notes.py +++ b/domain/epc/tests/test_from_site_notes.py @@ -5,7 +5,7 @@ from typing import Any, Dict import pytest -from datatypes.epc.domain.epc_property_data import ( +from domain.epc.epc_property_data import ( EpcPropertyData, InstantaneousWwhrs, MainHeatingDetail, @@ -18,14 +18,11 @@ from datatypes.epc.domain.epc_property_data import ( ShowerOutlet, ShowerOutlets, ) -from datatypes.epc.domain.mapper import EpcPropertyDataMapper +from domain.epc.mapper import EpcPropertyDataMapper from datatypes.epc.schema.tests.helpers import from_dict from datatypes.epc.surveys.pashub_rdsap_site_notes import PasHubRdSapSiteNotes -FIXTURES = os.path.join( - os.path.dirname(__file__), - "../../surveys/tests/fixtures", -) +FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures") def load(filename: str) -> Dict[str, Any]: diff --git a/datatypes/epc/domain/tests/test_historic_epc_matching.py b/domain/epc/tests/test_historic_epc_matching.py similarity index 98% rename from datatypes/epc/domain/tests/test_historic_epc_matching.py rename to domain/epc/tests/test_historic_epc_matching.py index 1c3ee6d4..dd42d2c0 100644 --- a/datatypes/epc/domain/tests/test_historic_epc_matching.py +++ b/domain/epc/tests/test_historic_epc_matching.py @@ -5,8 +5,8 @@ import pandas as pd import pytest from botocore.exceptions import ClientError -from datatypes.epc.domain import historic_epc_matching as matcher_mod -from datatypes.epc.domain.historic_epc_matching import ( +from domain.epc import historic_epc_matching as matcher_mod +from domain.epc.historic_epc_matching import ( HistoricEpcMatches, ScoredHistoricEpc, _sanitise_postcode, diff --git a/datatypes/epc/walls.py b/domain/epc/walls.py similarity index 100% rename from datatypes/epc/walls.py rename to domain/epc/walls.py diff --git a/datatypes/epc/windows.py b/domain/epc/windows.py similarity index 100% rename from datatypes/epc/windows.py rename to domain/epc/windows.py diff --git a/datatypes/magicplan/domain/__init__.py b/domain/magicplan/__init__.py similarity index 100% rename from datatypes/magicplan/domain/__init__.py rename to domain/magicplan/__init__.py diff --git a/datatypes/magicplan/domain/mapper.py b/domain/magicplan/mapper.py similarity index 96% rename from datatypes/magicplan/domain/mapper.py rename to domain/magicplan/mapper.py index 1804e58e..5d48ab5e 100644 --- a/datatypes/magicplan/domain/mapper.py +++ b/domain/magicplan/mapper.py @@ -2,7 +2,7 @@ from typing import Optional import datatypes.magicplan.api.response as api from datatypes.magicplan.api.response import MagicPlanPlan -from datatypes.magicplan.domain.models import Plan, Floor, Room, Window, Door +from domain.magicplan.models import Plan, Floor, Room, Window, Door def map_plan(mp: MagicPlanPlan) -> Plan: diff --git a/datatypes/magicplan/domain/models.py b/domain/magicplan/models.py similarity index 100% rename from datatypes/magicplan/domain/models.py rename to domain/magicplan/models.py diff --git a/datatypes/magicplan/domain/tests/__init__.py b/domain/magicplan/tests/__init__.py similarity index 100% rename from datatypes/magicplan/domain/tests/__init__.py rename to domain/magicplan/tests/__init__.py diff --git a/datatypes/magicplan/domain/tests/test_mapper.py b/domain/magicplan/tests/test_mapper.py similarity index 97% rename from datatypes/magicplan/domain/tests/test_mapper.py rename to domain/magicplan/tests/test_mapper.py index 78977939..5edf76c7 100644 --- a/datatypes/magicplan/domain/tests/test_mapper.py +++ b/domain/magicplan/tests/test_mapper.py @@ -5,10 +5,10 @@ from typing import Any import pytest from datatypes.magicplan.api.response import MagicPlanPlan -from datatypes.magicplan.domain.mapper import map_plan -from datatypes.magicplan.domain.models import Plan +from domain.magicplan.mapper import map_plan +from domain.magicplan.models import Plan -FIXTURE_DIR = Path(__file__).parents[4] / "backend" / "magic_plan" +FIXTURE_DIR = Path(__file__).parents[3] / "backend" / "magic_plan" PLAN_ID = "a7285ed1-878d-47eb-8aa6-85ef9e187516" PLAN_ID_2 = "9f9889ff-793e-4e9a-a6f0-e22f5b0f5365" diff --git a/etl/customers/peabody/Nov 2025 Consulting Project/g_rebaselining_installed_measrues.py b/etl/customers/peabody/Nov 2025 Consulting Project/g_rebaselining_installed_measrues.py index cb7e65cd..561b6d82 100644 --- a/etl/customers/peabody/Nov 2025 Consulting Project/g_rebaselining_installed_measrues.py +++ b/etl/customers/peabody/Nov 2025 Consulting Project/g_rebaselining_installed_measrues.py @@ -12,7 +12,7 @@ from backend.app.db.models.recommendations import ( from backend.app.db.models.portfolio import PropertyModel, PropertyDetailsEpcModel from backend.app.utils import sap_to_epc from typing import Dict, List, Set -from datatypes.epc.domain.epc import Epc +from domain.epc.epc import Epc from recommendations.Costs import Costs pd.set_option("display.max_rows", 500) diff --git a/pytest.ini b/pytest.ini index 398c5b71..b2256d20 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,6 +3,6 @@ pythonpath = . log_cli = true log_cli_level = INFO addopts = --cov-report term-missing --cov=etl/epc --cov=recommendations --cov=backend --cov=etl/epc_clean --cov=etl/spatial -testpaths = recommendations/tests backend/tests etl/epc/tests etl/epc_clean/tests etl/spatial/tests backend/condition/tests backend/address2UPRN/tests backend/onboarders/tests backend/categorisation/tests backend/export/tests etl/hubspot/tests backend/hubspot_trigger_orchestrator/tests datatypes/epc/schema/tests datatypes/epc/surveys/tests datatypes/epc/domain/tests backend/ecmk_fetcher/tests/ backend/pashub_fetcher/tests backend/documents_parser/tests backend/magic_plan/tests datatypes/magicplan/api/tests datatypes/magicplan/domain/tests backend/app/db/functions/tests +testpaths = recommendations/tests backend/tests etl/epc/tests etl/epc_clean/tests etl/spatial/tests backend/condition/tests backend/address2UPRN/tests backend/onboarders/tests backend/categorisation/tests backend/export/tests etl/hubspot/tests backend/hubspot_trigger_orchestrator/tests datatypes/epc/schema/tests datatypes/epc/surveys/tests domain/epc/tests backend/ecmk_fetcher/tests/ backend/pashub_fetcher/tests backend/documents_parser/tests backend/magic_plan/tests datatypes/magicplan/api/tests domain/magicplan/tests backend/app/db/functions/tests markers = integration: mark a test as an integration test diff --git a/scripts/historic_epc_demo.py b/scripts/historic_epc_demo.py index b47c3a3c..83e55df0 100644 --- a/scripts/historic_epc_demo.py +++ b/scripts/historic_epc_demo.py @@ -11,7 +11,7 @@ Usage: import sys -from datatypes.epc.domain.historic_epc_matching import match_addresses_for_postcode +from domain.epc.historic_epc_matching import match_addresses_for_postcode def main(user_address: str, postcode: str) -> None: