mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
refactor: lift-and-shift packages/domain/src/domain/sap → domain/sap10_calculator
Migration of the SAP 10.2 calculator package from the uv-workspace
src-layout (`packages/domain/src/domain/sap`) to the root-level layout
(`domain/sap10_calculator`), matching the pattern already used by
`domain.addresses` / `domain.tasks` / `domain.postcode`.
Changes:
- `git mv packages/domain/src/domain/sap → domain/sap10_calculator`
(92 files; git auto-detected all as renames so blame/history is
preserved).
- Subpackage rename: `domain.sap` → `domain.sap10_calculator`. 48
Python files rewritten (`from domain.sap.X` → `from domain.sap10_
calculator.X`); zero remaining `domain.sap` refs after the sed pass.
- Path-string updates: 3 .py files (test fixtures + xlsx loader) +
6 markdown docs (CONTEXT.md, 2 ADRs, 3 sap-spec docs, sap10_
calculator/README.md) had hard-coded `packages/domain/src/domain/
sap/...` paths rewritten to `domain/sap10_calculator/...`.
- `Path(__file__).parents[N]` rebasing: the old tree was 3 levels
deeper than the new one (`packages/domain/src/`), so 4× `parents[7]`
became `parents[4]` and 1× `parents[6]` became `parents[3]` across
`tables/pcdb/{__init__.py, postcode_weather.py, etl.py}`,
`worksheet/tests/_xlsx_loader.py`, and `tests/test_pcdb_etl.py`.
- PEP 420 namespace package: deleted both `domain/__init__.py`
(root + workspace, both load-bearing only as empty/docstring) so
Python combines `domain.sap10_calculator` (root) and `domain.ml`
(workspace) into one namespace package. Confirmed via
`domain.__path__ == ['/workspaces/model/domain',
'/workspaces/model/packages/domain/src/domain']`. Without this,
the root `domain/__init__.py` shadowed the workspace one and
`domain.ml` was unreachable.
Verified:
- Full sweep (`backend/documents_parser/tests/test_summary_pdf_
mapper_chain.py + domain/sap10_calculator/worksheet/tests/test_
e2e_elmhurst_sap_score.py + domain/sap10_calculator/rdsap/tests/
test_golden_fixtures.py`): 99 passed / 19 failed — exact same
counts as pre-refactor. All 19 failures pre-existing (9 hand-built
001479 + 6 cohort diff + 4 cohort chain non-spec).
- Wider sweep (all sap10_calculator + domain.ml): 1654 passed /
20 failed (the +1 vs the focused sweep is the pre-existing
`test_roof_insulated_assumed_with_ni_thickness_uses_50mm_per_
section_5_11_4` which was already failing on the previous baseline).
- Pyright net-zero on the three load-bearing baselines:
`heat_transmission.py` 13, `cert_to_inputs.py` 35, `mapper.py` 33.
Lift-and-shift only — no semantic renames (`Sap10Calculator` stays
`Sap10Calculator`), no testpaths edits in pytest.ini (sap tests
continue to be invoked by explicit pytest paths).
Note: `domain.ml` still lives at `packages/domain/src/domain/ml/`.
Migrating it would close out the dual-`domain/` layout but is
out of scope for this commit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
87b6045c97
commit
29ac35ccbe
98 changed files with 200 additions and 204 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
"""End-to-end validation for the Elmhurst Summary→EpcPropertyData chain.
|
"""End-to-end validation for the Elmhurst Summary→EpcPropertyData chain.
|
||||||
|
|
||||||
The 6 Elmhurst worksheet fixtures in `domain.sap.worksheet.tests`
|
The 6 Elmhurst worksheet fixtures in `domain.sap10_calculator.worksheet.tests`
|
||||||
build their `EpcPropertyData` synthetically — they validate the
|
build their `EpcPropertyData` synthetically — they validate the
|
||||||
calculator + cascade in isolation from the mapper. This file pins
|
calculator + cascade in isolation from the mapper. This file pins
|
||||||
the OTHER half of the chain: `from_elmhurst_site_notes` must produce
|
the OTHER half of the chain: `from_elmhurst_site_notes` must produce
|
||||||
|
|
@ -37,9 +37,9 @@ from typing import cast
|
||||||
|
|
||||||
from backend.documents_parser.elmhurst_extractor import ElmhurstSiteNotesExtractor
|
from backend.documents_parser.elmhurst_extractor import ElmhurstSiteNotesExtractor
|
||||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||||
from domain.sap.calculator import calculate_sap_from_inputs
|
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
|
||||||
from domain.sap.rdsap.cert_to_inputs import SAP_10_2_SPEC_PRICES, cert_to_inputs
|
from domain.sap10_calculator.rdsap.cert_to_inputs import SAP_10_2_SPEC_PRICES, cert_to_inputs
|
||||||
from domain.sap.worksheet.tests import (
|
from domain.sap10_calculator.worksheet.tests import (
|
||||||
_elmhurst_worksheet_000474 as _w000474,
|
_elmhurst_worksheet_000474 as _w000474,
|
||||||
_elmhurst_worksheet_000477 as _w000477,
|
_elmhurst_worksheet_000477 as _w000477,
|
||||||
_elmhurst_worksheet_000480 as _w000480,
|
_elmhurst_worksheet_000480 as _w000480,
|
||||||
|
|
@ -63,7 +63,7 @@ _SUMMARY_001479_PDF = _FIXTURES / "Summary_001479.pdf"
|
||||||
# matches worksheet continuous SAP at 1e-4".
|
# matches worksheet continuous SAP at 1e-4".
|
||||||
_API_001479_JSON = (
|
_API_001479_JSON = (
|
||||||
Path(__file__).parents[3]
|
Path(__file__).parents[3]
|
||||||
/ "packages/domain/src/domain/sap/rdsap/tests/fixtures/golden"
|
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
|
||||||
/ "0535-9020-6509-0821-6222.json"
|
/ "0535-9020-6509-0821-6222.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -108,7 +108,7 @@ def _summary_pdf_to_textract_style_pages(pdf_path: Path) -> list[str]:
|
||||||
def test_summary_000474_mapper_produces_three_building_parts() -> None:
|
def test_summary_000474_mapper_produces_three_building_parts() -> None:
|
||||||
# Arrange — cert U985-0001-000474 is a mid-terrace with 3 building
|
# Arrange — cert U985-0001-000474 is a mid-terrace with 3 building
|
||||||
# parts (Main + 2 extensions) per the hand-built worksheet fixture
|
# parts (Main + 2 extensions) per the hand-built worksheet fixture
|
||||||
# at packages/domain/src/domain/sap/worksheet/tests/
|
# at domain/sap10_calculator/worksheet/tests/
|
||||||
# _elmhurst_worksheet_000474.py. Routing the Summary PDF through
|
# _elmhurst_worksheet_000474.py. Routing the Summary PDF through
|
||||||
# extractor + mapper must yield the same count.
|
# extractor + mapper must yield the same count.
|
||||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000474_PDF)
|
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000474_PDF)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Two boundary factories convert raw inputs to canonical members:
|
||||||
- `BuildingPartIdentifier.extension(n)` (site-notes / construction id)
|
- `BuildingPartIdentifier.extension(n)` (site-notes / construction id)
|
||||||
|
|
||||||
P6.1 starts P6 (strict-type EpcPropertyData) from the documented pain
|
P6.1 starts P6 (strict-type EpcPropertyData) from the documented pain
|
||||||
point in packages/domain/src/domain/sap/worksheet/dimensions.py:74-82.
|
point in domain/sap10_calculator/worksheet/dimensions.py:74-82.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ The ML model is **not deprecated**. It is repurposed as a **residual learner** a
|
||||||
A full SAP 10.3 worksheet plus the data-extraction rules from RdSAP 10 Appendix S. Module organisation:
|
A full SAP 10.3 worksheet plus the data-extraction rules from RdSAP 10 Appendix S. Module organisation:
|
||||||
|
|
||||||
```
|
```
|
||||||
packages/domain/src/domain/sap/
|
domain/sap10_calculator/
|
||||||
__init__.py # Sap10Calculator entry point + SapResult dataclass
|
__init__.py # Sap10Calculator entry point + SapResult dataclass
|
||||||
worksheet/
|
worksheet/
|
||||||
dimensions.py # §1
|
dimensions.py # §1
|
||||||
|
|
@ -79,7 +79,7 @@ packages/domain/src/domain/sap/
|
||||||
cascade_defaults.py # the RdSAP10 "assume-typical" rules (currently in rdsap_uvalues.py)
|
cascade_defaults.py # the RdSAP10 "assume-typical" rules (currently in rdsap_uvalues.py)
|
||||||
```
|
```
|
||||||
|
|
||||||
The existing `domain.ml.*` modules stay where they are during Session A; they continue serving the live ML pipeline. Session B promotes them into `domain.sap.*` once parity is reached.
|
The existing `domain.ml.*` modules stay where they are during Session A; they continue serving the live ML pipeline. Session B promotes them into `domain.sap10_calculator.*` once parity is reached.
|
||||||
|
|
||||||
## Sap10Calculator interface
|
## Sap10Calculator interface
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@ We do **not** retire the existing ML pipeline until both validations pass.
|
||||||
- **The six ML targets remain those from ADR-0007.** The residual head predicts deltas against the same six quantities.
|
- **The six ML targets remain those from ADR-0007.** The residual head predicts deltas against the same six quantities.
|
||||||
- **ADR-0008's physics-as-feature pattern stays valid for the ML residual head.** The residual head probably needs fewer features, but the cascade U-value defaults and SAP efficiency lookups remain useful as feature builders if the calculator subset alone underfits.
|
- **ADR-0008's physics-as-feature pattern stays valid for the ML residual head.** The residual head probably needs fewer features, but the cascade U-value defaults and SAP efficiency lookups remain useful as feature builders if the calculator subset alone underfits.
|
||||||
- **`energy_rating_current` remains excluded from features.** Same leakage rule.
|
- **`energy_rating_current` remains excluded from features.** Same leakage rule.
|
||||||
- **RdSAP 10 cert-extraction rules are now first-class in the codebase.** Rules that were ad-hoc in `transform.py` move into `domain.sap.rdsap.appendix_s`.
|
- **RdSAP 10 cert-extraction rules are now first-class in the codebase.** Rules that were ad-hoc in `transform.py` move into `domain.sap10_calculator.rdsap.appendix_s`.
|
||||||
- **The training parquet schema continues at v2.x.** A new column `calculator_sap_score` lands as a non-breaking addition once Session A reaches parity. The schema version bumps to v3.0.0 only when the residual targets replace the raw targets — a coordinated AutoGluon-repo deploy, per ADR-0008's cutover discipline.
|
- **The training parquet schema continues at v2.x.** A new column `calculator_sap_score` lands as a non-breaking addition once Session A reaches parity. The schema version bumps to v3.0.0 only when the residual targets replace the raw targets — a coordinated AutoGluon-repo deploy, per ADR-0008's cutover discipline.
|
||||||
|
|
||||||
## SAP 10.2 → SAP 10.3 implications
|
## SAP 10.2 → SAP 10.3 implications
|
||||||
|
|
@ -147,7 +147,7 @@ Re-derivation work is bounded — a few hundred numbers across tables — and th
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
- A new top-level domain area `domain.sap.*` is introduced; over Sessions B/C it absorbs `domain.ml.{envelope,demand,ecf,rdsap_uvalues,sap_efficiencies,ventilation}.py`. The ML transform stops shipping those as standalone features once the residual head takes over.
|
- A new top-level domain area `domain.sap10_calculator.*` is introduced; over Sessions B/C it absorbs `domain.ml.{envelope,demand,ecf,rdsap_uvalues,sap_efficiencies,ventilation}.py`. The ML transform stops shipping those as standalone features once the residual head takes over.
|
||||||
- The codebase carries two SAP outputs: cert-reported `sap_score` (ground truth at training time) and calculator-emitted `sap_score` (ground truth at inference time for any RdSAP cert input). The product layer chooses; for "score this hypothetical post-retrofit state", calculator wins.
|
- The codebase carries two SAP outputs: cert-reported `sap_score` (ground truth at training time) and calculator-emitted `sap_score` (ground truth at inference time for any RdSAP cert input). The product layer chooses; for "score this hypothetical post-retrofit state", calculator wins.
|
||||||
- The deterministic calculator is **version-bound to SAP 10.3.** A future SAP 10.4 is a calculator MAJOR bump and an ADR. The ML residual head is SAP-version-agnostic only insofar as the residual distribution it learns stays stationary; in practice a spec bump retrains the residual head.
|
- The deterministic calculator is **version-bound to SAP 10.3.** A future SAP 10.4 is a calculator MAJOR bump and an ADR. The ML residual head is SAP-version-agnostic only insofar as the residual distribution it learns stays stationary; in practice a spec bump retrains the residual head.
|
||||||
- Spec PDFs live in `docs/sap-spec/` (this repo). The repo now carries the canonical reference for what the calculator computes. License: SAP 10.3 © Crown copyright 2026; RdSAP 10 © BRE — both are public-interest references for SAP-compliant software, included for traceability.
|
- Spec PDFs live in `docs/sap-spec/` (this repo). The repo now carries the canonical reference for what the calculator computes. License: SAP 10.3 © Crown copyright 2026; RdSAP 10 © BRE — both are public-interest references for SAP-compliant software, included for traceability.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
## Why this ADR exists
|
## Why this ADR exists
|
||||||
|
|
||||||
ADR-0009 was written before a second-order problem in the validation corpus was visible: the 250k-cert training parquet spans **multiple SAP spec versions** (SAP 10.1 from 2019, SAP 10.2 pre- and post-14-March-2025 amendment), each of which was the active table when its certs were lodged. The prior session's `domain.sap.tables.table_12_cert_calibration` layer was implicitly absorbing this version mixture into a single "best fit" price set ~10–25 % lower than the SAP 10.2 (14-03-2025) spec — closer to the SAP 10.1 era prices. Every spec-correctness slice that touched a downstream component (HW cylinder zero-loss, gas standing charges, Table 12a fractional blending) registered as a regression on the parity probe because the cert-cal layer had been numerically calibrated against the buggy state of every other component.
|
ADR-0009 was written before a second-order problem in the validation corpus was visible: the 250k-cert training parquet spans **multiple SAP spec versions** (SAP 10.1 from 2019, SAP 10.2 pre- and post-14-March-2025 amendment), each of which was the active table when its certs were lodged. The prior session's `domain.sap10_calculator.tables.table_12_cert_calibration` layer was implicitly absorbing this version mixture into a single "best fit" price set ~10–25 % lower than the SAP 10.2 (14-03-2025) spec — closer to the SAP 10.1 era prices. Every spec-correctness slice that touched a downstream component (HW cylinder zero-loss, gas standing charges, Table 12a fractional blending) registered as a regression on the parity probe because the cert-cal layer had been numerically calibrated against the buggy state of every other component.
|
||||||
|
|
||||||
This ADR resolves four entangled decisions at once. They are coupled — none of them is the right call in isolation.
|
This ADR resolves four entangled decisions at once. They are coupled — none of them is the right call in isolation.
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ This ADR resolves four entangled decisions at once. They are coupled — none of
|
||||||
|
|
||||||
ADR-0009 named SAP 10.3 (13-01-2026) as the calculator's target. No SAP-10.3-lodged certs exist in the corpus; assessor software has not migrated. Targeting SAP 10.3 produces a calculator whose output is verifiable against no cert. The active target is SAP 10.2 (14-03-2025 amendment) — both the document RdSAP 10 (10-06-2025) cross-references for heating-system identification, and the amendment that current assessor software is on.
|
ADR-0009 named SAP 10.3 (13-01-2026) as the calculator's target. No SAP-10.3-lodged certs exist in the corpus; assessor software has not migrated. Targeting SAP 10.3 produces a calculator whose output is verifiable against no cert. The active target is SAP 10.2 (14-03-2025 amendment) — both the document RdSAP 10 (10-06-2025) cross-references for heating-system identification, and the amendment that current assessor software is on.
|
||||||
|
|
||||||
`packages/domain/src/domain/sap/tables/table_12.py` is re-labelled as SAP 10.2 (14-03-2025). Its CO2 factors are corrected to spec (0.210 kg/kWh mains gas, 0.136 kg/kWh standard electricity — the file currently has SAP 10.3 values 0.214 and 0.086). Prices already match SAP 10.2 (3.64 p mains gas, 16.49 p standard electricity, etc.) — the misleading "+25 % shift from SAP 10.2 to 10.3" comment is removed; the 13.19 p figure is from SAP 10.1, not SAP 10.2.
|
`domain/sap10_calculator/tables/table_12.py` is re-labelled as SAP 10.2 (14-03-2025). Its CO2 factors are corrected to spec (0.210 kg/kWh mains gas, 0.136 kg/kWh standard electricity — the file currently has SAP 10.3 values 0.214 and 0.086). Prices already match SAP 10.2 (3.64 p mains gas, 16.49 p standard electricity, etc.) — the misleading "+25 % shift from SAP 10.2 to 10.3" comment is removed; the 13.19 p figure is from SAP 10.1, not SAP 10.2.
|
||||||
|
|
||||||
A future ADR retargets to SAP 10.3 once the cert corpus migrates (expected late 2026 or 2027 once BRE updates RdSAP to reference SAP 10.3).
|
A future ADR retargets to SAP 10.3 once the cert corpus migrates (expected late 2026 or 2027 once BRE updates RdSAP to reference SAP 10.3).
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ The cert-calibration table is bug-masking. Its prices are pre-March-2025 SAP val
|
||||||
|
|
||||||
This includes the `cert_calibration_e7_codes` extension that routes codes 191–196 (direct-electric) and 691–696 (room heaters) to off-peak rates — Table 12a is explicit that "other direct-acting electric heating" bills 100 % at the high rate on a 7-hour tariff. The S-B14 finding that motivated this hack is in §8 of the handover as a documented dead-end.
|
This includes the `cert_calibration_e7_codes` extension that routes codes 191–196 (direct-electric) and 691–696 (room heaters) to off-peak rates — Table 12a is explicit that "other direct-acting electric heating" bills 100 % at the high rate on a 7-hour tariff. The S-B14 finding that motivated this hack is in §8 of the handover as a documented dead-end.
|
||||||
|
|
||||||
`domain.sap.tables.table_12.unit_price_p_per_kwh` becomes the only price API. Parity probes are updated to use it.
|
`domain.sap10_calculator.tables.table_12.unit_price_p_per_kwh` becomes the only price API. Parity probes are updated to use it.
|
||||||
|
|
||||||
### 3. Validation Cohort is filtered to a single spec-version window
|
### 3. Validation Cohort is filtered to a single spec-version window
|
||||||
|
|
||||||
|
|
@ -84,9 +84,9 @@ The 000490 Elmhurst fixture had a recorded -12.5% cost gap (£706 vs £807 PDF)
|
||||||
|
|
||||||
### Consequences
|
### Consequences
|
||||||
|
|
||||||
- **`packages/domain/src/domain/sap/tables/table_32.py`** ships the RdSAP10 unit prices + standing charges + Table 12 note (a) gating function. Table 12 keeps the CO2 + PEF columns.
|
- **`domain/sap10_calculator/tables/table_32.py`** ships the RdSAP10 unit prices + standing charges + Table 12 note (a) gating function. Table 12 keeps the CO2 + PEF columns.
|
||||||
- **`packages/domain/src/domain/sap/tables/table_12a.py`** ships the high-rate-fraction lookups for off-peak split (Table 12a in SAP 10.2 PDF page 191 — RdSAP10 §19.1 cross-references this table directly). `Tariff.TEN_HOUR` carried for spec completeness even though RdSAP cert `meter_type` enum (1..5) has no 10-hour code.
|
- **`domain/sap10_calculator/tables/table_12a.py`** ships the high-rate-fraction lookups for off-peak split (Table 12a in SAP 10.2 PDF page 191 — RdSAP10 §19.1 cross-references this table directly). `Tariff.TEN_HOUR` carried for spec completeness even though RdSAP cert `meter_type` enum (1..5) has no 10-hour code.
|
||||||
- **`packages/domain/src/domain/sap/worksheet/fuel_cost.py`** ships the §10a orchestrator producing `FuelCostResult` (32 fields, line refs (240)..(255)). `cert_to_inputs._fuel_cost` precompute wires it from cert state.
|
- **`domain/sap10_calculator/worksheet/fuel_cost.py`** ships the §10a orchestrator producing `FuelCostResult` (32 fields, line refs (240)..(255)). `cert_to_inputs._fuel_cost` precompute wires it from cert state.
|
||||||
- The 000474 Elmhurst fixture cost residual widened from -0.6% to +10.7% (SAP rating ceiling loosened 2 → 4) because the pre-amendment wrong-table-but-cancels-kWh accidentally compensated for upstream §4 HW kWh + Appendix L lighting overestimates. **§4 HW worksheet tightening is the next ticket** — see project memory `project_section_4_hw_next_ticket`. Ceiling drops back to 2 (or below) when that lands.
|
- The 000474 Elmhurst fixture cost residual widened from -0.6% to +10.7% (SAP rating ceiling loosened 2 → 4) because the pre-amendment wrong-table-but-cancels-kWh accidentally compensated for upstream §4 HW kWh + Appendix L lighting overestimates. **§4 HW worksheet tightening is the next ticket** — see project memory `project_section_4_hw_next_ticket`. Ceiling drops back to 2 (or below) when that lands.
|
||||||
- Golden corpus SAP tolerance widened ±7 → ±11 per the Validation Cohort discipline (oil unit price +55% from Table 12 → Table 32 moves oil-heated golden certs whose lodged SAP scores pre-date Table 32).
|
- Golden corpus SAP tolerance widened ±7 → ±11 per the Validation Cohort discipline (oil unit price +55% from Table 12 → Table 32 moves oil-heated golden certs whose lodged SAP scores pre-date Table 32).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,14 +41,14 @@ plumbing fails loudly.
|
||||||
### Public API (the only thing you need from the SAP module)
|
### Public API (the only thing you need from the SAP module)
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from domain.sap.rdsap.cert_to_inputs import (
|
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||||
cert_to_inputs, # Rating cascade
|
cert_to_inputs, # Rating cascade
|
||||||
cert_to_demand_inputs, # Demand cascade
|
cert_to_demand_inputs, # Demand cascade
|
||||||
local_climate_for_cert,
|
local_climate_for_cert,
|
||||||
environmental_section_from_cert,
|
environmental_section_from_cert,
|
||||||
primary_energy_section_from_cert,
|
primary_energy_section_from_cert,
|
||||||
)
|
)
|
||||||
from domain.sap.calculator import calculate_sap_from_inputs, SapResult
|
from domain.sap10_calculator.calculator import calculate_sap_from_inputs, SapResult
|
||||||
```
|
```
|
||||||
|
|
||||||
See `SAP_CALCULATOR.md` §2 for the recommended `dwelling_outputs(epc)`
|
See `SAP_CALCULATOR.md` §2 for the recommended `dwelling_outputs(epc)`
|
||||||
|
|
@ -101,8 +101,8 @@ side is what you need to map out:
|
||||||
schema versions (SAP-Schema-18/19, RdSAP-Schema-18) to
|
schema versions (SAP-Schema-18/19, RdSAP-Schema-18) to
|
||||||
`EpcPropertyData` lives there.
|
`EpcPropertyData` lives there.
|
||||||
3. **Is SAP scoring already wired to the API?** Search the backend for
|
3. **Is SAP scoring already wired to the API?** Search the backend for
|
||||||
imports of `domain.sap.rdsap.cert_to_inputs` or
|
imports of `domain.sap10_calculator.rdsap.cert_to_inputs` or
|
||||||
`domain.sap.calculator`. If it's not yet wired, the integration test
|
`domain.sap10_calculator.calculator`. If it's not yet wired, the integration test
|
||||||
is a forcing function for wiring it.
|
is a forcing function for wiring it.
|
||||||
4. **What's the response shape?** The 4 outputs above are what the EPC
|
4. **What's the response shape?** The 4 outputs above are what the EPC
|
||||||
publishes; the API may already expose them, or may expose a wider
|
publishes; the API may already expose them, or may expose a wider
|
||||||
|
|
@ -132,10 +132,10 @@ expanding.
|
||||||
| File | Why |
|
| File | Why |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [`docs/sap-spec/SAP_CALCULATOR.md`](./SAP_CALCULATOR.md) | Module API + architecture (you're heading there) |
|
| [`docs/sap-spec/SAP_CALCULATOR.md`](./SAP_CALCULATOR.md) | Module API + architecture (you're heading there) |
|
||||||
| [`packages/domain/src/domain/sap/calculator.py`](../../packages/domain/src/domain/sap/calculator.py) | `SapResult` fields you'll assert against |
|
| [`domain/sap10_calculator/calculator.py`](../../domain/sap10_calculator/calculator.py) | `SapResult` fields you'll assert against |
|
||||||
| [`packages/domain/src/domain/sap/rdsap/cert_to_inputs.py`](../../packages/domain/src/domain/sap/rdsap/cert_to_inputs.py) | The 3 public entry points + the section helpers |
|
| [`domain/sap10_calculator/rdsap/cert_to_inputs.py`](../../domain/sap10_calculator/rdsap/cert_to_inputs.py) | The 3 public entry points + the section helpers |
|
||||||
| [`packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000474.py`](../../packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000474.py) | A reference fixture — `build_epc()` shows the EpcPropertyData shape |
|
| [`domain/sap10_calculator/worksheet/tests/_elmhurst_worksheet_000474.py`](../../domain/sap10_calculator/worksheet/tests/_elmhurst_worksheet_000474.py) | A reference fixture — `build_epc()` shows the EpcPropertyData shape |
|
||||||
| [`packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py`](../../packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py) | The current e2e test pattern — model your integration test on this |
|
| [`domain/sap10_calculator/worksheet/tests/test_e2e_elmhurst_sap_score.py`](../../domain/sap10_calculator/worksheet/tests/test_e2e_elmhurst_sap_score.py) | The current e2e test pattern — model your integration test on this |
|
||||||
| `backend/` (explore) | API entry points |
|
| `backend/` (explore) | API entry points |
|
||||||
| [`datatypes/epc/domain/mapper.py`](../../datatypes/epc/domain/mapper.py) | Schema → EpcPropertyData mappers |
|
| [`datatypes/epc/domain/mapper.py`](../../datatypes/epc/domain/mapper.py) | Schema → EpcPropertyData mappers |
|
||||||
|
|
||||||
|
|
@ -146,18 +146,18 @@ expanding.
|
||||||
```bash
|
```bash
|
||||||
# Confirm SAP calculator is still 930/930 green
|
# Confirm SAP calculator is still 930/930 green
|
||||||
python -m pytest \
|
python -m pytest \
|
||||||
packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py \
|
domain/sap10_calculator/worksheet/tests/test_section_cascade_pins.py \
|
||||||
packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py \
|
domain/sap10_calculator/worksheet/tests/test_e2e_elmhurst_sap_score.py \
|
||||||
--no-cov --no-header --tb=no -q
|
--no-cov --no-header --tb=no -q
|
||||||
|
|
||||||
# Show the 4 EPC outputs for fixture 000474
|
# Show the 4 EPC outputs for fixture 000474
|
||||||
cd packages/domain/src && python -c "
|
cd packages/domain/src && python -c "
|
||||||
from domain.sap.rdsap.cert_to_inputs import (
|
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||||
cert_to_inputs, local_climate_for_cert,
|
cert_to_inputs, local_climate_for_cert,
|
||||||
environmental_section_from_cert, primary_energy_section_from_cert,
|
environmental_section_from_cert, primary_energy_section_from_cert,
|
||||||
)
|
)
|
||||||
from domain.sap.calculator import calculate_sap_from_inputs
|
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
|
||||||
from domain.sap.worksheet.tests import _elmhurst_worksheet_000474 as w
|
from domain.sap10_calculator.worksheet.tests import _elmhurst_worksheet_000474 as w
|
||||||
epc = w.build_epc()
|
epc = w.build_epc()
|
||||||
pc = local_climate_for_cert(epc)
|
pc = local_climate_for_cert(epc)
|
||||||
rating = calculate_sap_from_inputs(cert_to_inputs(epc))
|
rating = calculate_sap_from_inputs(cert_to_inputs(epc))
|
||||||
|
|
@ -183,7 +183,7 @@ postcode-conditions EI value, not what the EPC publishes.
|
||||||
- **Extending the SAP calculator.** It's closed at the EPC-output layer.
|
- **Extending the SAP calculator.** It's closed at the EPC-output layer.
|
||||||
If you find an additional cert-shape variation that breaks the
|
If you find an additional cert-shape variation that breaks the
|
||||||
calculator, capture it as a new conformance fixture (see
|
calculator, capture it as a new conformance fixture (see
|
||||||
`packages/domain/src/domain/sap/README.md`) — don't paper over it in
|
`domain/sap10_calculator/README.md`) — don't paper over it in
|
||||||
the integration test.
|
the integration test.
|
||||||
- **BEDF fuel pricing.** The Fuel Bill on the EPC uses postcode-specific
|
- **BEDF fuel pricing.** The Fuel Bill on the EPC uses postcode-specific
|
||||||
BEDF prices (PCDB Table 200), which are deferred. The 4 outputs above
|
BEDF prices (PCDB Table 200), which are deferred. The 4 outputs above
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||||
import json, dataclasses
|
import json, dataclasses
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
api = json.loads(Path('/workspaces/model/packages/domain/src/domain/sap/rdsap/tests/fixtures/golden/0535-9020-6509-0821-6222.json').read_text())
|
api = json.loads(Path('/workspaces/model/domain/sap10_calculator/rdsap/tests/fixtures/golden/0535-9020-6509-0821-6222.json').read_text())
|
||||||
api_mapped = EpcPropertyDataMapper.from_api_response(api)
|
api_mapped = EpcPropertyDataMapper.from_api_response(api)
|
||||||
pages = _summary_pdf_to_textract_style_pages(Path('/workspaces/model/backend/documents_parser/tests/fixtures/Summary_001479.pdf'))
|
pages = _summary_pdf_to_textract_style_pages(Path('/workspaces/model/backend/documents_parser/tests/fixtures/Summary_001479.pdf'))
|
||||||
sn = ElmhurstSiteNotesExtractor(pages).extract()
|
sn = ElmhurstSiteNotesExtractor(pages).extract()
|
||||||
|
|
@ -234,7 +234,7 @@ override field, (c) wait for more cert pairs to confirm pattern.
|
||||||
|
|
||||||
## Cached artefacts
|
## Cached artefacts
|
||||||
|
|
||||||
- `packages/domain/src/domain/sap/rdsap/tests/fixtures/golden/0535-
|
- `domain/sap10_calculator/rdsap/tests/fixtures/golden/0535-
|
||||||
9020-6509-0821-6222.json` — API JSON for cert 001479 (RdSAP-Schema-
|
9020-6509-0821-6222.json` — API JSON for cert 001479 (RdSAP-Schema-
|
||||||
21.0.1).
|
21.0.1).
|
||||||
- `backend/documents_parser/tests/fixtures/Summary_001479.pdf` —
|
- `backend/documents_parser/tests/fixtures/Summary_001479.pdf` —
|
||||||
|
|
@ -273,8 +273,8 @@ before this rewrite).
|
||||||
```bash
|
```bash
|
||||||
PYTHONPATH=/workspaces/model:/workspaces/model/packages/domain/src \
|
PYTHONPATH=/workspaces/model:/workspaces/model/packages/domain/src \
|
||||||
python -m pytest backend/documents_parser/tests/test_summary_pdf_mapper_chain.py \
|
python -m pytest backend/documents_parser/tests/test_summary_pdf_mapper_chain.py \
|
||||||
packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py \
|
domain/sap10_calculator/worksheet/tests/test_e2e_elmhurst_sap_score.py \
|
||||||
packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py \
|
domain/sap10_calculator/rdsap/tests/test_golden_fixtures.py \
|
||||||
--no-cov -q
|
--no-cov -q
|
||||||
```
|
```
|
||||||
Expect **99 passed / 19 failed**. All 19 failures pre-existing:
|
Expect **99 passed / 19 failed**. All 19 failures pre-existing:
|
||||||
|
|
|
||||||
|
|
@ -10,21 +10,21 @@ Current Carbon, Current Primary Energy, and Fuel Bill).
|
||||||
**Current state: 930/930 pins green** (768 rating + 90 demand + 72 e2e).
|
**Current state: 930/930 pins green** (768 rating + 90 demand + 72 e2e).
|
||||||
|
|
||||||
This document is the public API + architecture reference. For fixture
|
This document is the public API + architecture reference. For fixture
|
||||||
authoring see [`packages/domain/src/domain/sap/README.md`](../../packages/domain/src/domain/sap/README.md).
|
authoring see [`domain/sap10_calculator/README.md`](../../domain/sap10_calculator/README.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Public API
|
## 1. Public API
|
||||||
|
|
||||||
Three entry points, all in `domain.sap.rdsap.cert_to_inputs`:
|
Three entry points, all in `domain.sap10_calculator.rdsap.cert_to_inputs`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from domain.sap.rdsap.cert_to_inputs import (
|
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||||
cert_to_inputs, # SAP rating + EI rating (UK-avg climate)
|
cert_to_inputs, # SAP rating + EI rating (UK-avg climate)
|
||||||
cert_to_demand_inputs, # Current Carbon + Current PE (postcode climate)
|
cert_to_demand_inputs, # Current Carbon + Current PE (postcode climate)
|
||||||
local_climate_for_cert, # postcode → PostcodeClimate (None on miss)
|
local_climate_for_cert, # postcode → PostcodeClimate (None on miss)
|
||||||
)
|
)
|
||||||
from domain.sap.calculator import calculate_sap_from_inputs, SapResult
|
from domain.sap10_calculator.calculator import calculate_sap_from_inputs, SapResult
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1.1 Rating cascade — `cert_to_inputs(epc)`
|
### 1.1 Rating cascade — `cert_to_inputs(epc)`
|
||||||
|
|
@ -93,11 +93,11 @@ upgrade wall insulation), re-run, observe the delta. The shape:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from domain.sap.rdsap.cert_to_inputs import (
|
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||||
cert_to_inputs, local_climate_for_cert,
|
cert_to_inputs, local_climate_for_cert,
|
||||||
environmental_section_from_cert, primary_energy_section_from_cert,
|
environmental_section_from_cert, primary_energy_section_from_cert,
|
||||||
)
|
)
|
||||||
from domain.sap.calculator import calculate_sap_from_inputs
|
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
|
||||||
|
|
||||||
def dwelling_outputs(epc):
|
def dwelling_outputs(epc):
|
||||||
"""The 4 EPC-facing outputs for any cert.
|
"""The 4 EPC-facing outputs for any cert.
|
||||||
|
|
@ -165,7 +165,7 @@ Two cascades stacked on a shared physics core:
|
||||||
Climate is the only difference between the two cascades. Internally, the
|
Climate is the only difference between the two cascades. Internally, the
|
||||||
climate is plumbed through as either an `int` region index (0..21) or a
|
climate is plumbed through as either an `int` region index (0..21) or a
|
||||||
`PostcodeClimate` instance (PCDB Table 172). Four functions in
|
`PostcodeClimate` instance (PCDB Table 172). Four functions in
|
||||||
`domain.sap.climate.appendix_u` dispatch on `isinstance`:
|
`domain.sap10_calculator.climate.appendix_u` dispatch on `isinstance`:
|
||||||
`external_temperature_c`, `wind_speed_m_per_s`,
|
`external_temperature_c`, `wind_speed_m_per_s`,
|
||||||
`horizontal_solar_irradiance_w_per_m2`, plus `_latitude_deg` in
|
`horizontal_solar_irradiance_w_per_m2`, plus `_latitude_deg` in
|
||||||
`worksheet/solar_gains.py`.
|
`worksheet/solar_gains.py`.
|
||||||
|
|
@ -192,7 +192,7 @@ on `CalculatorInputs`.
|
||||||
## 4. File map
|
## 4. File map
|
||||||
|
|
||||||
```
|
```
|
||||||
packages/domain/src/domain/sap/
|
domain/sap10_calculator/
|
||||||
├── calculator.py # Top-level orchestrator (CalculatorInputs → SapResult)
|
├── calculator.py # Top-level orchestrator (CalculatorInputs → SapResult)
|
||||||
├── README.md # Fixture authoring cookbook
|
├── README.md # Fixture authoring cookbook
|
||||||
├── rdsap/
|
├── rdsap/
|
||||||
|
|
@ -288,12 +288,12 @@ monthly_infiltration_ach 6/6
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Full SAP calculator suite (cascade pins + e2e + helpers)
|
# Full SAP calculator suite (cascade pins + e2e + helpers)
|
||||||
python -m pytest packages/domain/src/domain/sap/ --no-cov
|
python -m pytest domain/sap10_calculator/ --no-cov
|
||||||
|
|
||||||
# Cascade pins only (the conformance suite)
|
# Cascade pins only (the conformance suite)
|
||||||
python -m pytest \
|
python -m pytest \
|
||||||
packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py \
|
domain/sap10_calculator/worksheet/tests/test_section_cascade_pins.py \
|
||||||
packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py \
|
domain/sap10_calculator/worksheet/tests/test_e2e_elmhurst_sap_score.py \
|
||||||
--no-cov --no-header --tb=no -q
|
--no-cov --no-header --tb=no -q
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -316,7 +316,7 @@ These are non-negotiable per `[[feedback-zero-error-strict]]` /
|
||||||
|
|
||||||
## 6. Adding a new conformance fixture
|
## 6. Adding a new conformance fixture
|
||||||
|
|
||||||
See [`packages/domain/src/domain/sap/README.md#adding-a-new-elmhurst-conformance-fixture`](../../packages/domain/src/domain/sap/README.md#adding-a-new-elmhurst-conformance-fixture)
|
See [`domain/sap10_calculator/README.md#adding-a-new-elmhurst-conformance-fixture`](../../domain/sap10_calculator/README.md#adding-a-new-elmhurst-conformance-fixture)
|
||||||
for the step-by-step cookbook. Summary:
|
for the step-by-step cookbook. Summary:
|
||||||
|
|
||||||
1. Drop a fixture module at `worksheet/tests/_elmhurst_worksheet_NNNNNN.py`
|
1. Drop a fixture module at `worksheet/tests/_elmhurst_worksheet_NNNNNN.py`
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ The assessor exports two PDFs from Elmhurst's RdSAP tool:
|
||||||
|
|
||||||
5. **Run the conformance tests**:
|
5. **Run the conformance tests**:
|
||||||
```
|
```
|
||||||
python -m pytest packages/domain/src/domain/sap/worksheet/tests/ \
|
python -m pytest domain/sap10_calculator/worksheet/tests/ \
|
||||||
-k elmhurst --no-cov -v
|
-k elmhurst --no-cov -v
|
||||||
```
|
```
|
||||||
Each fixture appears 3× (one parametrize per section), pytest id = the cert ref number.
|
Each fixture appears 3× (one parametrize per section), pytest id = the cert ref number.
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Drives the 12-month heat-balance loop from a typed `CalculatorInputs`
|
Drives the 12-month heat-balance loop from a typed `CalculatorInputs`
|
||||||
aggregate and emits a typed `SapResult`. This module is the physics
|
aggregate and emits a typed `SapResult`. This module is the physics
|
||||||
assembly only — the RdSAP cert→inputs mapping lives in
|
assembly only — the RdSAP cert→inputs mapping lives in
|
||||||
`domain.sap.rdsap.cert_to_inputs`. Splitting the two keeps orchestration
|
`domain.sap10_calculator.rdsap.cert_to_inputs`. Splitting the two keeps orchestration
|
||||||
testable against synthetic inputs without dragging in cert-shape
|
testable against synthetic inputs without dragging in cert-shape
|
||||||
assumptions.
|
assumptions.
|
||||||
|
|
||||||
|
|
@ -44,15 +44,15 @@ from __future__ import annotations
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Final, Optional, TYPE_CHECKING
|
from typing import Final, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from domain.sap.climate.appendix_u import external_temperature_c
|
from domain.sap10_calculator.climate.appendix_u import external_temperature_c
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from datatypes.epc.domain.epc_property_data import EpcPropertyData
|
from datatypes.epc.domain.epc_property_data import EpcPropertyData
|
||||||
from domain.sap.worksheet.dimensions import Dimensions
|
from domain.sap10_calculator.worksheet.dimensions import Dimensions
|
||||||
from domain.sap.worksheet.energy_requirements import EnergyRequirementsResult
|
from domain.sap10_calculator.worksheet.energy_requirements import EnergyRequirementsResult
|
||||||
from domain.sap.worksheet.fuel_cost import FuelCostResult
|
from domain.sap10_calculator.worksheet.fuel_cost import FuelCostResult
|
||||||
from domain.sap.worksheet.heat_transmission import HeatTransmission
|
from domain.sap10_calculator.worksheet.heat_transmission import HeatTransmission
|
||||||
from domain.sap.worksheet.rating import (
|
from domain.sap10_calculator.worksheet.rating import (
|
||||||
ECF_LOG_THRESHOLD,
|
ECF_LOG_THRESHOLD,
|
||||||
ENERGY_COST_DEFLATOR,
|
ENERGY_COST_DEFLATOR,
|
||||||
FLOOR_AREA_OFFSET_M2,
|
FLOOR_AREA_OFFSET_M2,
|
||||||
|
|
@ -621,6 +621,6 @@ class Sap10Calculator:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def calculate(self, epc: "EpcPropertyData") -> SapResult:
|
def calculate(self, epc: "EpcPropertyData") -> SapResult:
|
||||||
from domain.sap.rdsap.cert_to_inputs import cert_to_inputs
|
from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_inputs
|
||||||
|
|
||||||
return calculate_sap_from_inputs(cert_to_inputs(epc))
|
return calculate_sap_from_inputs(cert_to_inputs(epc))
|
||||||
|
|
@ -20,7 +20,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from domain.sap.tables.pcdb.postcode_weather import PostcodeClimate
|
from domain.sap10_calculator.tables.pcdb.postcode_weather import PostcodeClimate
|
||||||
|
|
||||||
|
|
||||||
# Table U1 — Mean external temperature (°C), 22 regions × 12 months.
|
# Table U1 — Mean external temperature (°C), 22 regions × 12 months.
|
||||||
|
|
@ -8,7 +8,7 @@ global solar irradiance on a horizontal plane and monthly solar declination.
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.climate.appendix_u import (
|
from domain.sap10_calculator.climate.appendix_u import (
|
||||||
external_temperature_c,
|
external_temperature_c,
|
||||||
horizontal_solar_irradiance_w_per_m2,
|
horizontal_solar_irradiance_w_per_m2,
|
||||||
solar_declination_deg,
|
solar_declination_deg,
|
||||||
|
|
@ -67,82 +67,82 @@ from domain.ml.sap_efficiencies import (
|
||||||
seasonal_efficiency,
|
seasonal_efficiency,
|
||||||
water_heating_efficiency as _legacy_water_heating_efficiency,
|
water_heating_efficiency as _legacy_water_heating_efficiency,
|
||||||
)
|
)
|
||||||
from domain.sap.calculator import CalculatorInputs
|
from domain.sap10_calculator.calculator import CalculatorInputs
|
||||||
from domain.sap.tables.pcdb import gas_oil_boiler_record
|
from domain.sap10_calculator.tables.pcdb import gas_oil_boiler_record
|
||||||
from domain.sap.tables.pcdb.parser import GasOilBoilerRecord
|
from domain.sap10_calculator.tables.pcdb.parser import GasOilBoilerRecord
|
||||||
from domain.sap.tables.pcdb.postcode_weather import (
|
from domain.sap10_calculator.tables.pcdb.postcode_weather import (
|
||||||
PostcodeClimate,
|
PostcodeClimate,
|
||||||
postcode_climate,
|
postcode_climate,
|
||||||
)
|
)
|
||||||
from domain.sap.tables.table_12 import (
|
from domain.sap10_calculator.tables.table_12 import (
|
||||||
co2_monthly_factors_kg_per_kwh,
|
co2_monthly_factors_kg_per_kwh,
|
||||||
co2_factor_kg_per_kwh,
|
co2_factor_kg_per_kwh,
|
||||||
pe_monthly_factors_kwh_per_kwh,
|
pe_monthly_factors_kwh_per_kwh,
|
||||||
primary_energy_factor,
|
primary_energy_factor,
|
||||||
unit_price_p_per_kwh,
|
unit_price_p_per_kwh,
|
||||||
)
|
)
|
||||||
from domain.sap.tables.table_12a import (
|
from domain.sap10_calculator.tables.table_12a import (
|
||||||
Tariff,
|
Tariff,
|
||||||
tariff_from_meter_type,
|
tariff_from_meter_type,
|
||||||
)
|
)
|
||||||
from domain.sap.tables.table_32 import (
|
from domain.sap10_calculator.tables.table_32 import (
|
||||||
additional_standing_charges_gbp,
|
additional_standing_charges_gbp,
|
||||||
unit_price_p_per_kwh as table_32_unit_price_p_per_kwh,
|
unit_price_p_per_kwh as table_32_unit_price_p_per_kwh,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.fuel_cost import FuelCostResult, fuel_cost
|
from domain.sap10_calculator.worksheet.fuel_cost import FuelCostResult, fuel_cost
|
||||||
from domain.sap.worksheet.rating import (
|
from domain.sap10_calculator.worksheet.rating import (
|
||||||
ENERGY_COST_DEFLATOR,
|
ENERGY_COST_DEFLATOR,
|
||||||
energy_cost_factor,
|
energy_cost_factor,
|
||||||
environmental_impact_rating,
|
environmental_impact_rating,
|
||||||
sap_rating,
|
sap_rating,
|
||||||
sap_rating_integer,
|
sap_rating_integer,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.dimensions import dimensions_from_cert
|
from domain.sap10_calculator.worksheet.dimensions import dimensions_from_cert
|
||||||
from domain.sap.worksheet.internal_gains import (
|
from domain.sap10_calculator.worksheet.internal_gains import (
|
||||||
InternalGainsResult,
|
InternalGainsResult,
|
||||||
OvershadingCategory,
|
OvershadingCategory,
|
||||||
internal_gains_from_cert,
|
internal_gains_from_cert,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.solar_gains import (
|
from domain.sap10_calculator.worksheet.solar_gains import (
|
||||||
ORIENTATION_BY_SAP10_CODE,
|
ORIENTATION_BY_SAP10_CODE,
|
||||||
RoofWindowInput,
|
RoofWindowInput,
|
||||||
SolarGainsResult,
|
SolarGainsResult,
|
||||||
solar_gains_from_cert,
|
solar_gains_from_cert,
|
||||||
surface_solar_flux_w_per_m2,
|
surface_solar_flux_w_per_m2,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.heat_transmission import (
|
from domain.sap10_calculator.worksheet.heat_transmission import (
|
||||||
DwellingExposure,
|
DwellingExposure,
|
||||||
HeatTransmission,
|
HeatTransmission,
|
||||||
_AREA_ROUND_DP,
|
_AREA_ROUND_DP,
|
||||||
_round_half_up,
|
_round_half_up,
|
||||||
heat_transmission_from_cert,
|
heat_transmission_from_cert,
|
||||||
)
|
)
|
||||||
from domain.sap.climate.appendix_u import external_temperature_c
|
from domain.sap10_calculator.climate.appendix_u import external_temperature_c
|
||||||
from domain.sap.worksheet.mean_internal_temperature import (
|
from domain.sap10_calculator.worksheet.mean_internal_temperature import (
|
||||||
MeanInternalTemperatureResult,
|
MeanInternalTemperatureResult,
|
||||||
mean_internal_temperature_monthly,
|
mean_internal_temperature_monthly,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.energy_requirements import (
|
from domain.sap10_calculator.worksheet.energy_requirements import (
|
||||||
EnergyRequirementsResult,
|
EnergyRequirementsResult,
|
||||||
space_heating_fuel_monthly_kwh,
|
space_heating_fuel_monthly_kwh,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.fabric_energy_efficiency import (
|
from domain.sap10_calculator.worksheet.fabric_energy_efficiency import (
|
||||||
fabric_energy_efficiency_kwh_per_m2_yr,
|
fabric_energy_efficiency_kwh_per_m2_yr,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.space_cooling import (
|
from domain.sap10_calculator.worksheet.space_cooling import (
|
||||||
SpaceCoolingResult,
|
SpaceCoolingResult,
|
||||||
space_cooling_monthly_kwh,
|
space_cooling_monthly_kwh,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.space_heating import (
|
from domain.sap10_calculator.worksheet.space_heating import (
|
||||||
SpaceHeatingResult,
|
SpaceHeatingResult,
|
||||||
space_heating_monthly_kwh,
|
space_heating_monthly_kwh,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.ventilation import (
|
from domain.sap10_calculator.worksheet.ventilation import (
|
||||||
MechanicalVentilationKind,
|
MechanicalVentilationKind,
|
||||||
VentilationResult,
|
VentilationResult,
|
||||||
ventilation_from_inputs,
|
ventilation_from_inputs,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.water_heating import (
|
from domain.sap10_calculator.worksheet.water_heating import (
|
||||||
TABLE_J1_TCOLD_FROM_MAINS_C,
|
TABLE_J1_TCOLD_FROM_MAINS_C,
|
||||||
WaterHeatingResult,
|
WaterHeatingResult,
|
||||||
combi_loss_monthly_kwh_table_3b_row_1_instantaneous,
|
combi_loss_monthly_kwh_table_3b_row_1_instantaneous,
|
||||||
|
|
@ -27,15 +27,15 @@ from domain.ml.tests._fixtures import (
|
||||||
make_sap_heating,
|
make_sap_heating,
|
||||||
make_window,
|
make_window,
|
||||||
)
|
)
|
||||||
from domain.sap.calculator import Sap10Calculator, SapResult
|
from domain.sap10_calculator.calculator import Sap10Calculator, SapResult
|
||||||
from domain.sap.rdsap.cert_to_inputs import (
|
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||||
cert_to_demand_inputs,
|
cert_to_demand_inputs,
|
||||||
cert_to_inputs,
|
cert_to_inputs,
|
||||||
pcdb_combi_loss_override,
|
pcdb_combi_loss_override,
|
||||||
)
|
)
|
||||||
from domain.sap.tables.pcdb import GasOilBoilerRecord, gas_oil_boiler_record
|
from domain.sap10_calculator.tables.pcdb import GasOilBoilerRecord, gas_oil_boiler_record
|
||||||
from domain.sap.worksheet.tests import _elmhurst_worksheet_000477 as _w000477
|
from domain.sap10_calculator.worksheet.tests import _elmhurst_worksheet_000477 as _w000477
|
||||||
from domain.sap.worksheet.water_heating import (
|
from domain.sap10_calculator.worksheet.water_heating import (
|
||||||
combi_loss_monthly_kwh_table_3b_row_1_instantaneous,
|
combi_loss_monthly_kwh_table_3b_row_1_instantaneous,
|
||||||
combi_loss_monthly_kwh_table_3c_two_profile_instantaneous,
|
combi_loss_monthly_kwh_table_3c_two_profile_instantaneous,
|
||||||
)
|
)
|
||||||
|
|
@ -36,8 +36,8 @@ from typing import Any
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||||
from domain.sap.calculator import calculate_sap_from_inputs
|
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
|
||||||
from domain.sap.rdsap.cert_to_inputs import (
|
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||||
SAP_10_2_SPEC_PRICES,
|
SAP_10_2_SPEC_PRICES,
|
||||||
cert_to_demand_inputs,
|
cert_to_demand_inputs,
|
||||||
cert_to_inputs,
|
cert_to_inputs,
|
||||||
|
|
@ -27,13 +27,13 @@ import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Final, Optional
|
from typing import Final, Optional
|
||||||
|
|
||||||
from domain.sap.tables.pcdb.parser import GasOilBoilerRecord
|
from domain.sap10_calculator.tables.pcdb.parser import GasOilBoilerRecord
|
||||||
|
|
||||||
__all__ = ["GasOilBoilerRecord", "gas_oil_boiler_record"]
|
__all__ = ["GasOilBoilerRecord", "gas_oil_boiler_record"]
|
||||||
|
|
||||||
|
|
||||||
_REPO_SAP_SPEC_DIR: Final[Path] = (
|
_REPO_SAP_SPEC_DIR: Final[Path] = (
|
||||||
Path(__file__).resolve().parents[7] / "docs" / "sap-spec"
|
Path(__file__).resolve().parents[4] / "docs" / "sap-spec"
|
||||||
)
|
)
|
||||||
_TABLE_105_JSONL: Final[Path] = (
|
_TABLE_105_JSONL: Final[Path] = (
|
||||||
_REPO_SAP_SPEC_DIR / "pcdb_table_105_gas_oil_boilers.jsonl"
|
_REPO_SAP_SPEC_DIR / "pcdb_table_105_gas_oil_boilers.jsonl"
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Idempotent. Re-run when BRE publishes an updated pcdb10.dat. JSON files
|
Idempotent. Re-run when BRE publishes an updated pcdb10.dat. JSON files
|
||||||
are committed in-repo alongside the source .dat so callers can load
|
are committed in-repo alongside the source .dat so callers can load
|
||||||
without a build step. Run via `python -m domain.sap.tables.pcdb.etl`.
|
without a build step. Run via `python -m domain.sap10_calculator.tables.pcdb.etl`.
|
||||||
|
|
||||||
Reference: BRE PCDB pcdb10.dat (April 2026 revision).
|
Reference: BRE PCDB pcdb10.dat (April 2026 revision).
|
||||||
"""
|
"""
|
||||||
|
|
@ -13,7 +13,7 @@ import json
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from domain.sap.tables.pcdb.parser import (
|
from domain.sap10_calculator.tables.pcdb.parser import (
|
||||||
GasOilBoilerRecord,
|
GasOilBoilerRecord,
|
||||||
RawPcdbRecord,
|
RawPcdbRecord,
|
||||||
parse_table_105,
|
parse_table_105,
|
||||||
|
|
@ -75,7 +75,7 @@ def run_etl(*, source: Path, output_dir: Path) -> None:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": # pragma: no cover — manual ETL invocation
|
if __name__ == "__main__": # pragma: no cover — manual ETL invocation
|
||||||
repo_root = Path(__file__).resolve().parents[7]
|
repo_root = Path(__file__).resolve().parents[4]
|
||||||
run_etl(
|
run_etl(
|
||||||
source=repo_root / "docs" / "sap-spec" / "pcdb10.dat",
|
source=repo_root / "docs" / "sap-spec" / "pcdb10.dat",
|
||||||
output_dir=repo_root / "docs" / "sap-spec",
|
output_dir=repo_root / "docs" / "sap-spec",
|
||||||
|
|
@ -21,7 +21,7 @@ from typing import Final, Optional
|
||||||
|
|
||||||
|
|
||||||
_PCDB_DAT_PATH: Final[Path] = (
|
_PCDB_DAT_PATH: Final[Path] = (
|
||||||
Path(__file__).resolve().parents[7] / "docs" / "sap-spec" / "pcdb10.dat"
|
Path(__file__).resolve().parents[4] / "docs" / "sap-spec" / "pcdb10.dat"
|
||||||
)
|
)
|
||||||
_TABLE_172_TAG: Final[str] = "$172"
|
_TABLE_172_TAG: Final[str] = "$172"
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ factors are largely unchanged. When the corpus migrates to SAP 10.3
|
||||||
this module re-points to those values.
|
this module re-points to those values.
|
||||||
|
|
||||||
The Energy Cost Deflator stays at 0.36 (used in ECF — see
|
The Energy Cost Deflator stays at 0.36 (used in ECF — see
|
||||||
`domain.sap.worksheet.rating`).
|
`domain.sap10_calculator.worksheet.rating`).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
@ -7,14 +7,14 @@ targets RdSAP10 cost per ADR-0010 amendment.
|
||||||
|
|
||||||
CO2 emission factors and primary energy factors are unchanged from
|
CO2 emission factors and primary energy factors are unchanged from
|
||||||
SAP10.2 Table 12 (RdSAP10 §19.2), so they continue to live in
|
SAP10.2 Table 12 (RdSAP10 §19.2), so they continue to live in
|
||||||
`domain.sap.tables.table_12` rather than being duplicated here.
|
`domain.sap10_calculator.tables.table_12` rather than being duplicated here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Final, Optional
|
from typing import Final, Optional
|
||||||
|
|
||||||
from domain.sap.tables.table_12a import Tariff
|
from domain.sap10_calculator.tables.table_12a import Tariff
|
||||||
|
|
||||||
|
|
||||||
_DEFAULT_P_PER_KWH: Final[float] = 3.48 # fall back to mains gas
|
_DEFAULT_P_PER_KWH: Final[float] = 3.48 # fall back to mains gas
|
||||||
|
|
@ -23,17 +23,17 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.calculator import (
|
from domain.sap10_calculator.calculator import (
|
||||||
CalculatorInputs,
|
CalculatorInputs,
|
||||||
calculate_sap_from_inputs,
|
calculate_sap_from_inputs,
|
||||||
)
|
)
|
||||||
from domain.sap.climate.appendix_u import external_temperature_c
|
from domain.sap10_calculator.climate.appendix_u import external_temperature_c
|
||||||
from domain.sap.worksheet.dimensions import Dimensions
|
from domain.sap10_calculator.worksheet.dimensions import Dimensions
|
||||||
from domain.sap.worksheet.heat_transmission import HeatTransmission
|
from domain.sap10_calculator.worksheet.heat_transmission import HeatTransmission
|
||||||
from domain.sap.worksheet.mean_internal_temperature import (
|
from domain.sap10_calculator.worksheet.mean_internal_temperature import (
|
||||||
mean_internal_temperature_monthly,
|
mean_internal_temperature_monthly,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.space_heating import space_heating_monthly_kwh
|
from domain.sap10_calculator.worksheet.space_heating import space_heating_monthly_kwh
|
||||||
|
|
||||||
|
|
||||||
def _baseline_dwelling() -> CalculatorInputs:
|
def _baseline_dwelling() -> CalculatorInputs:
|
||||||
|
|
@ -20,18 +20,18 @@ from dataclasses import replace
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.calculator import (
|
from domain.sap10_calculator.calculator import (
|
||||||
CalculatorInputs,
|
CalculatorInputs,
|
||||||
SapResult,
|
SapResult,
|
||||||
calculate_sap_from_inputs,
|
calculate_sap_from_inputs,
|
||||||
)
|
)
|
||||||
from domain.sap.climate.appendix_u import external_temperature_c
|
from domain.sap10_calculator.climate.appendix_u import external_temperature_c
|
||||||
from domain.sap.worksheet.dimensions import Dimensions
|
from domain.sap10_calculator.worksheet.dimensions import Dimensions
|
||||||
from domain.sap.worksheet.heat_transmission import HeatTransmission
|
from domain.sap10_calculator.worksheet.heat_transmission import HeatTransmission
|
||||||
from domain.sap.worksheet.mean_internal_temperature import (
|
from domain.sap10_calculator.worksheet.mean_internal_temperature import (
|
||||||
mean_internal_temperature_monthly,
|
mean_internal_temperature_monthly,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.space_heating import space_heating_monthly_kwh
|
from domain.sap10_calculator.worksheet.space_heating import space_heating_monthly_kwh
|
||||||
|
|
||||||
|
|
||||||
def _baseline_inputs() -> CalculatorInputs:
|
def _baseline_inputs() -> CalculatorInputs:
|
||||||
|
|
@ -15,15 +15,15 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.tables.pcdb.etl import run_etl
|
from domain.sap10_calculator.tables.pcdb.etl import run_etl
|
||||||
from domain.sap.tables.pcdb.parser import (
|
from domain.sap10_calculator.tables.pcdb.parser import (
|
||||||
parse_table_105,
|
parse_table_105,
|
||||||
parse_table_105_row,
|
parse_table_105_row,
|
||||||
parse_table_raw,
|
parse_table_raw,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_REPO_ROOT: Path = Path(__file__).resolve().parents[6]
|
_REPO_ROOT: Path = Path(__file__).resolve().parents[3]
|
||||||
_PCDB_DAT_PATH: Path = _REPO_ROOT / "docs" / "sap-spec" / "pcdb10.dat"
|
_PCDB_DAT_PATH: Path = _REPO_ROOT / "docs" / "sap-spec" / "pcdb10.dat"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ Reference: BRE PCDB pcdb10.dat (April 2026); user-verified records.
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from domain.sap.tables.pcdb import gas_oil_boiler_record
|
from domain.sap10_calculator.tables.pcdb import gas_oil_boiler_record
|
||||||
|
|
||||||
|
|
||||||
def test_gas_oil_boiler_record_returns_verified_baxi_98() -> None:
|
def test_gas_oil_boiler_record_returns_verified_baxi_98() -> None:
|
||||||
|
|
@ -10,7 +10,7 @@ Reference: BRE PCDB pcdb10.dat Table 172 (Postcodes).
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from domain.sap.tables.pcdb.postcode_weather import (
|
from domain.sap10_calculator.tables.pcdb.postcode_weather import (
|
||||||
PostcodeClimate,
|
PostcodeClimate,
|
||||||
postcode_climate,
|
postcode_climate,
|
||||||
)
|
)
|
||||||
|
|
@ -15,7 +15,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.tables.table_12 import (
|
from domain.sap10_calculator.tables.table_12 import (
|
||||||
co2_factor_kg_per_kwh,
|
co2_factor_kg_per_kwh,
|
||||||
primary_energy_factor,
|
primary_energy_factor,
|
||||||
unit_price_p_per_kwh,
|
unit_price_p_per_kwh,
|
||||||
|
|
@ -12,7 +12,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.tables.table_12a import (
|
from domain.sap10_calculator.tables.table_12a import (
|
||||||
OtherUse,
|
OtherUse,
|
||||||
Table12aSystem,
|
Table12aSystem,
|
||||||
Tariff,
|
Tariff,
|
||||||
|
|
@ -13,8 +13,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.tables.table_12a import Tariff
|
from domain.sap10_calculator.tables.table_12a import Tariff
|
||||||
from domain.sap.tables.table_32 import (
|
from domain.sap10_calculator.tables.table_32 import (
|
||||||
additional_standing_charges_gbp,
|
additional_standing_charges_gbp,
|
||||||
standing_charge_gbp,
|
standing_charge_gbp,
|
||||||
unit_price_p_per_kwh,
|
unit_price_p_per_kwh,
|
||||||
|
|
@ -15,7 +15,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.validation.parity_report import (
|
from domain.sap10_calculator.validation.parity_report import (
|
||||||
ParityCase,
|
ParityCase,
|
||||||
ParityReport,
|
ParityReport,
|
||||||
build_parity_report,
|
build_parity_report,
|
||||||
|
|
@ -30,7 +30,7 @@ will retire envelope.py in favour of this module (ADR-0009 §"Module
|
||||||
layout").
|
layout").
|
||||||
|
|
||||||
U-value lookups cascade through `domain.ml.rdsap_uvalues` — migrating to
|
U-value lookups cascade through `domain.ml.rdsap_uvalues` — migrating to
|
||||||
`domain.sap.rdsap.cascade_defaults` in Session B.
|
`domain.sap10_calculator.rdsap.cascade_defaults` in Session B.
|
||||||
|
|
||||||
Reference: SAP 10.2 specification §3 (pages 17-22); RdSAP 10 §5 (Tables
|
Reference: SAP 10.2 specification §3 (pages 17-22); RdSAP 10 §5 (Tables
|
||||||
6-24); xlsx worked example at `2026-05-19-17-18 RdSap10Worksheet.xlsx`,
|
6-24); xlsx worked example at `2026-05-19-17-18 RdSap10Worksheet.xlsx`,
|
||||||
|
|
@ -23,7 +23,7 @@ from __future__ import annotations
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from domain.sap.worksheet.utilisation_factor import utilisation_factor
|
from domain.sap10_calculator.worksheet.utilisation_factor import utilisation_factor
|
||||||
|
|
||||||
|
|
||||||
_T_H1_C: Final[float] = 21.0
|
_T_H1_C: Final[float] = 21.0
|
||||||
|
|
@ -6,7 +6,7 @@ Two layers:
|
||||||
implements the §U3.2 polynomial that converts the horizontal solar
|
implements the §U3.2 polynomial that converts the horizontal solar
|
||||||
irradiance from Table U3 into a per-orientation per-pitch surface flux.
|
irradiance from Table U3 into a per-orientation per-pitch surface flux.
|
||||||
Reads:
|
Reads:
|
||||||
- S_h,m from Appendix U Table U3 (already in `domain.sap.climate.appendix_u`)
|
- S_h,m from Appendix U Table U3 (already in `domain.sap10_calculator.climate.appendix_u`)
|
||||||
- δ from Appendix U Table U3 footer (already in `appendix_u.solar_declination_deg`)
|
- δ from Appendix U Table U3 footer (already in `appendix_u.solar_declination_deg`)
|
||||||
- φ from Appendix U Table U4 (this module)
|
- φ from Appendix U Table U4 (this module)
|
||||||
- k1..k9 from Appendix U Table U5 (this module)
|
- k1..k9 from Appendix U Table U5 (this module)
|
||||||
|
|
@ -35,12 +35,12 @@ from math import cos, floor, radians, sin
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from datatypes.epc.domain.epc_property_data import EpcPropertyData, SapWindow
|
from datatypes.epc.domain.epc_property_data import EpcPropertyData, SapWindow
|
||||||
from domain.sap.tables.pcdb.postcode_weather import PostcodeClimate
|
from domain.sap10_calculator.tables.pcdb.postcode_weather import PostcodeClimate
|
||||||
from domain.sap.climate.appendix_u import (
|
from domain.sap10_calculator.climate.appendix_u import (
|
||||||
horizontal_solar_irradiance_w_per_m2,
|
horizontal_solar_irradiance_w_per_m2,
|
||||||
solar_declination_deg,
|
solar_declination_deg,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.internal_gains import OvershadingCategory
|
from domain.sap10_calculator.worksheet.internal_gains import OvershadingCategory
|
||||||
|
|
||||||
|
|
||||||
def _round_area_2dp(value: float) -> float:
|
def _round_area_2dp(value: float) -> float:
|
||||||
|
|
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from domain.sap.worksheet.heat_transmission import _round_half_up
|
from domain.sap10_calculator.worksheet.heat_transmission import _round_half_up
|
||||||
|
|
||||||
|
|
||||||
_MIN_KWH_PER_MONTH: Final[float] = 1.0
|
_MIN_KWH_PER_MONTH: Final[float] = 1.0
|
||||||
|
|
@ -24,7 +24,7 @@ SECTION_8C_INTERMITTENCY_MONTHLY: tuple[float, ...] = (
|
||||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.25, 0.25, 0.0, 0.0, 0.0, 0.0,
|
0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.25, 0.25, 0.0, 0.0, 0.0, 0.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
from domain.sap.worksheet.tests import (
|
from domain.sap10_calculator.worksheet.tests import (
|
||||||
_elmhurst_worksheet_000474 as w000474,
|
_elmhurst_worksheet_000474 as w000474,
|
||||||
_elmhurst_worksheet_000477 as w000477,
|
_elmhurst_worksheet_000477 as w000477,
|
||||||
_elmhurst_worksheet_000480 as w000480,
|
_elmhurst_worksheet_000480 as w000480,
|
||||||
|
|
@ -37,11 +37,11 @@ from domain.ml.tests._fixtures import (
|
||||||
make_sap_heating,
|
make_sap_heating,
|
||||||
make_window,
|
make_window,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
from domain.sap10_calculator.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
||||||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
|
||||||
from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
from domain.sap10_calculator.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
||||||
|
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
|
||||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||||
|
|
@ -35,11 +35,11 @@ from domain.ml.tests._fixtures import (
|
||||||
make_sap_heating,
|
make_sap_heating,
|
||||||
make_window,
|
make_window,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
from domain.sap10_calculator.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
||||||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
|
||||||
from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
from domain.sap10_calculator.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
||||||
|
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
|
||||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||||
|
|
@ -36,11 +36,11 @@ from domain.ml.tests._fixtures import (
|
||||||
make_sap_heating,
|
make_sap_heating,
|
||||||
make_window,
|
make_window,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
from domain.sap10_calculator.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
||||||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
|
||||||
from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
from domain.sap10_calculator.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
||||||
|
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
|
||||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||||
|
|
@ -34,8 +34,8 @@ from domain.ml.tests._fixtures import (
|
||||||
make_sap_heating,
|
make_sap_heating,
|
||||||
make_window,
|
make_window,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
from domain.sap10_calculator.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
||||||
from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
from domain.sap10_calculator.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
||||||
|
|
||||||
# RdSAP wall_construction code seen in the cert→worksheet mapping. The
|
# RdSAP wall_construction code seen in the cert→worksheet mapping. The
|
||||||
# Summary lists "CA Cavity" for both main and extension walls. The alt
|
# Summary lists "CA Cavity" for both main and extension walls. The alt
|
||||||
|
|
@ -258,9 +258,9 @@ def build_epc() -> EpcPropertyData:
|
||||||
# on the EpcPropertyData domain object — we pass these into
|
# on the EpcPropertyData domain object — we pass these into
|
||||||
# `ventilation_from_inputs` alongside the cert-derived geometry).
|
# `ventilation_from_inputs` alongside the cert-derived geometry).
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
|
||||||
|
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
|
||||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||||
|
|
@ -39,11 +39,11 @@ from domain.ml.tests._fixtures import (
|
||||||
make_sap_heating,
|
make_sap_heating,
|
||||||
make_window,
|
make_window,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
from domain.sap10_calculator.worksheet.solar_gains import RoofWindowInput, RooflightInput
|
||||||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
|
||||||
from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
from domain.sap10_calculator.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
||||||
|
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
|
||||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||||
|
|
@ -42,11 +42,11 @@ from domain.ml.tests._fixtures import (
|
||||||
make_sap_heating,
|
make_sap_heating,
|
||||||
make_window,
|
make_window,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.solar_gains import Orientation, RoofWindowInput, RooflightInput
|
from domain.sap10_calculator.worksheet.solar_gains import Orientation, RoofWindowInput, RooflightInput
|
||||||
from domain.sap.worksheet.ventilation import MechanicalVentilationKind
|
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
|
||||||
from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
from domain.sap10_calculator.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
|
||||||
|
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
|
||||||
SECTION_8C_ALL_ZERO_MONTHLY,
|
SECTION_8C_ALL_ZERO_MONTHLY,
|
||||||
SECTION_8C_ETA_LOSS_ALL_ONE,
|
SECTION_8C_ETA_LOSS_ALL_ONE,
|
||||||
SECTION_8C_INTERMITTENCY_MONTHLY,
|
SECTION_8C_INTERMITTENCY_MONTHLY,
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""Reader for the canonical SAP10.2 worksheet Excel — the source of
|
"""Reader for the canonical SAP10.2 worksheet Excel — the source of
|
||||||
truth used to build line-by-line conformance tests against
|
truth used to build line-by-line conformance tests against
|
||||||
`packages/domain/src/domain/sap/worksheet/`.
|
`domain/sap10_calculator/worksheet/`.
|
||||||
|
|
||||||
The Excel file lives at the repo root and has two sheets — both contain
|
The Excel file lives at the repo root and has two sheets — both contain
|
||||||
the full worksheet, differing only on weather-data source:
|
the full worksheet, differing only on weather-data source:
|
||||||
|
|
@ -20,7 +20,7 @@ from typing import Any, Iterable
|
||||||
|
|
||||||
import openpyxl
|
import openpyxl
|
||||||
|
|
||||||
_REPO_ROOT = Path(__file__).resolve().parents[7]
|
_REPO_ROOT = Path(__file__).resolve().parents[4]
|
||||||
WORKSHEET_XLSX_PATH = _REPO_ROOT / "2026-05-19-17-18 RdSap10Worksheet.xlsx"
|
WORKSHEET_XLSX_PATH = _REPO_ROOT / "2026-05-19-17-18 RdSap10Worksheet.xlsx"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,8 +26,8 @@ from domain.ml.tests._fixtures import (
|
||||||
make_floor_dimension,
|
make_floor_dimension,
|
||||||
make_minimal_sap10_epc,
|
make_minimal_sap10_epc,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.dimensions import Dimensions, dimensions_from_cert
|
from domain.sap10_calculator.worksheet.dimensions import Dimensions, dimensions_from_cert
|
||||||
from domain.sap.worksheet.tests._xlsx_loader import load_cells
|
from domain.sap10_calculator.worksheet.tests._xlsx_loader import load_cells
|
||||||
|
|
||||||
_RIR_FIXTURES_DIR = Path(__file__).parent / "fixtures" / "rir"
|
_RIR_FIXTURES_DIR = Path(__file__).parent / "fixtures" / "rir"
|
||||||
|
|
||||||
|
|
@ -507,7 +507,7 @@ def test_all_rir_shapes_apply_section_1_2_45m_convention_uniformly(
|
||||||
|
|
||||||
|
|
||||||
from types import ModuleType # noqa: E402 (kept near the Elmhurst tests)
|
from types import ModuleType # noqa: E402 (kept near the Elmhurst tests)
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ( # noqa: E402
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ( # noqa: E402
|
||||||
ALL_FIXTURES as _ELMHURST_FIXTURES,
|
ALL_FIXTURES as _ELMHURST_FIXTURES,
|
||||||
fixture_id as _elmhurst_fixture_id,
|
fixture_id as _elmhurst_fixture_id,
|
||||||
)
|
)
|
||||||
|
|
@ -24,9 +24,9 @@ from typing import Final
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.calculator import Sap10Calculator
|
from domain.sap10_calculator.calculator import Sap10Calculator
|
||||||
from domain.sap.rdsap.cert_to_inputs import cert_to_inputs
|
from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_inputs
|
||||||
from domain.sap.worksheet.tests import (
|
from domain.sap10_calculator.worksheet.tests import (
|
||||||
_elmhurst_worksheet_000474 as _w000474,
|
_elmhurst_worksheet_000474 as _w000474,
|
||||||
_elmhurst_worksheet_000477 as _w000477,
|
_elmhurst_worksheet_000477 as _w000477,
|
||||||
_elmhurst_worksheet_000480 as _w000480,
|
_elmhurst_worksheet_000480 as _w000480,
|
||||||
|
|
@ -35,7 +35,7 @@ from domain.sap.worksheet.tests import (
|
||||||
_elmhurst_worksheet_000516 as _w000516,
|
_elmhurst_worksheet_000516 as _w000516,
|
||||||
_elmhurst_worksheet_001479 as _w001479,
|
_elmhurst_worksheet_001479 as _w001479,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import (
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
|
||||||
ALL_FIXTURES as _ELMHURST_FIXTURES,
|
ALL_FIXTURES as _ELMHURST_FIXTURES,
|
||||||
fixture_id as _elmhurst_fixture_id,
|
fixture_id as _elmhurst_fixture_id,
|
||||||
)
|
)
|
||||||
|
|
@ -6,7 +6,7 @@ Reference: SAP 10.2 specification (14-03-2025) worksheet block §9a
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from domain.sap.worksheet.energy_requirements import (
|
from domain.sap10_calculator.worksheet.energy_requirements import (
|
||||||
space_heating_fuel_monthly_kwh,
|
space_heating_fuel_monthly_kwh,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -10,10 +10,10 @@ from types import ModuleType
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.worksheet.fabric_energy_efficiency import (
|
from domain.sap10_calculator.worksheet.fabric_energy_efficiency import (
|
||||||
fabric_energy_efficiency_kwh_per_m2_yr,
|
fabric_energy_efficiency_kwh_per_m2_yr,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
||||||
|
|
||||||
|
|
||||||
def test_fabric_energy_efficiency_sums_heating_per_m2_and_cooling_per_m2() -> None:
|
def test_fabric_energy_efficiency_sums_heating_per_m2_and_cooling_per_m2() -> None:
|
||||||
|
|
@ -8,8 +8,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.rdsap.cert_to_inputs import cert_to_inputs
|
from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_inputs
|
||||||
from domain.sap.worksheet.fuel_cost import FuelCostResult, fuel_cost
|
from domain.sap10_calculator.worksheet.fuel_cost import FuelCostResult, fuel_cost
|
||||||
|
|
||||||
|
|
||||||
def test_single_rate_main_only_bills_kwh_at_high_rate_price() -> None:
|
def test_single_rate_main_only_bills_kwh_at_high_rate_price() -> None:
|
||||||
|
|
@ -7,7 +7,7 @@ a typed `HeatTransmission` breakdown so callers can audit each
|
||||||
worksheet contribution.
|
worksheet contribution.
|
||||||
|
|
||||||
U-values cascade through the RdSAP 10 §5 defaults (currently implemented
|
U-values cascade through the RdSAP 10 §5 defaults (currently implemented
|
||||||
in `domain.ml.rdsap_uvalues` — migrates to `domain.sap.rdsap.cascade_defaults`
|
in `domain.ml.rdsap_uvalues` — migrates to `domain.sap10_calculator.rdsap.cascade_defaults`
|
||||||
in Session B).
|
in Session B).
|
||||||
|
|
||||||
Reference: SAP 10.3 specification §3 (pages 17-22);
|
Reference: SAP 10.3 specification §3 (pages 17-22);
|
||||||
|
|
@ -30,7 +30,7 @@ from domain.ml.tests._fixtures import (
|
||||||
make_floor_dimension,
|
make_floor_dimension,
|
||||||
make_minimal_sap10_epc,
|
make_minimal_sap10_epc,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.heat_transmission import (
|
from domain.sap10_calculator.worksheet.heat_transmission import (
|
||||||
DwellingExposure,
|
DwellingExposure,
|
||||||
HeatTransmission,
|
HeatTransmission,
|
||||||
heat_transmission_from_cert,
|
heat_transmission_from_cert,
|
||||||
|
|
@ -1132,11 +1132,11 @@ def test_real_corpus_basement_cert_has_part_with_has_basement_true() -> None:
|
||||||
|
|
||||||
|
|
||||||
from types import ModuleType # noqa: E402
|
from types import ModuleType # noqa: E402
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ( # noqa: E402
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ( # noqa: E402
|
||||||
ALL_FIXTURES as _ELMHURST_FIXTURES,
|
ALL_FIXTURES as _ELMHURST_FIXTURES,
|
||||||
fixture_id as _elmhurst_fixture_id,
|
fixture_id as _elmhurst_fixture_id,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.tests import ( # noqa: E402
|
from domain.sap10_calculator.worksheet.tests import ( # noqa: E402
|
||||||
_elmhurst_worksheet_000474 as _w000474,
|
_elmhurst_worksheet_000474 as _w000474,
|
||||||
_elmhurst_worksheet_000490 as _w000490,
|
_elmhurst_worksheet_000490 as _w000490,
|
||||||
)
|
)
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""Tests for SAP 10.2 §5 + Appendix L — internal gains.
|
"""Tests for SAP 10.2 §5 + Appendix L — internal gains.
|
||||||
|
|
||||||
Worksheet line refs (66)..(73) land in `domain.sap.worksheet.internal_gains`
|
Worksheet line refs (66)..(73) land in `domain.sap10_calculator.worksheet.internal_gains`
|
||||||
as monthly 12-tuple outputs. Each leaf function is unit-tested against the
|
as monthly 12-tuple outputs. Each leaf function is unit-tested against the
|
||||||
SAP 10.2 spec formula; the orchestrator is parametrized against every
|
SAP 10.2 spec formula; the orchestrator is parametrized against every
|
||||||
Elmhurst conformance fixture in `_elmhurst_fixtures.ALL_FIXTURES`.
|
Elmhurst conformance fixture in `_elmhurst_fixtures.ALL_FIXTURES`.
|
||||||
|
|
@ -12,7 +12,7 @@ Table 5a + Appendix L (lighting/appliances/cooking) + Appendix J Table 1b
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.worksheet.internal_gains import (
|
from domain.sap10_calculator.worksheet.internal_gains import (
|
||||||
InternalGainsResult,
|
InternalGainsResult,
|
||||||
OvershadingCategory,
|
OvershadingCategory,
|
||||||
PumpDateCategory,
|
PumpDateCategory,
|
||||||
|
|
@ -44,7 +44,7 @@ from datatypes.epc.domain.epc_property_data import (
|
||||||
SapWindow,
|
SapWindow,
|
||||||
)
|
)
|
||||||
from domain.ml.tests._fixtures import make_minimal_sap10_epc
|
from domain.ml.tests._fixtures import make_minimal_sap10_epc
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
||||||
|
|
||||||
|
|
||||||
def test_metabolic_gains_are_60w_per_occupant_constant_across_months() -> None:
|
def test_metabolic_gains_are_60w_per_occupant_constant_across_months() -> None:
|
||||||
|
|
@ -14,14 +14,14 @@ from types import ModuleType
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.climate.appendix_u import external_temperature_c
|
from domain.sap10_calculator.climate.appendix_u import external_temperature_c
|
||||||
from domain.sap.worksheet.mean_internal_temperature import (
|
from domain.sap10_calculator.worksheet.mean_internal_temperature import (
|
||||||
MeanInternalTemperatureResult,
|
MeanInternalTemperatureResult,
|
||||||
elsewhere_heating_temperature_c,
|
elsewhere_heating_temperature_c,
|
||||||
mean_internal_temperature_monthly,
|
mean_internal_temperature_monthly,
|
||||||
off_period_temperature_reduction_c,
|
off_period_temperature_reduction_c,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
||||||
|
|
||||||
|
|
||||||
# UK-average climate (region 0) external temperatures, Appendix U Table U1.
|
# UK-average climate (region 0) external temperatures, Appendix U Table U1.
|
||||||
|
|
@ -19,7 +19,7 @@ target is SAP 10.2.
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.worksheet.rating import (
|
from domain.sap10_calculator.worksheet.rating import (
|
||||||
energy_cost_factor,
|
energy_cost_factor,
|
||||||
environmental_impact_rating,
|
environmental_impact_rating,
|
||||||
sap_rating,
|
sap_rating,
|
||||||
|
|
@ -16,7 +16,7 @@ from typing import Final
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.rdsap.cert_to_inputs import (
|
from domain.sap10_calculator.rdsap.cert_to_inputs import (
|
||||||
cert_to_inputs,
|
cert_to_inputs,
|
||||||
energy_requirements_section_from_cert,
|
energy_requirements_section_from_cert,
|
||||||
environmental_section_from_cert,
|
environmental_section_from_cert,
|
||||||
|
|
@ -34,8 +34,8 @@ from domain.sap.rdsap.cert_to_inputs import (
|
||||||
ventilation_from_cert,
|
ventilation_from_cert,
|
||||||
water_heating_section_from_cert,
|
water_heating_section_from_cert,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.dimensions import dimensions_from_cert
|
from domain.sap10_calculator.worksheet.dimensions import dimensions_from_cert
|
||||||
from domain.sap.worksheet.tests import (
|
from domain.sap10_calculator.worksheet.tests import (
|
||||||
_elmhurst_worksheet_000474 as _w000474,
|
_elmhurst_worksheet_000474 as _w000474,
|
||||||
_elmhurst_worksheet_000477 as _w000477,
|
_elmhurst_worksheet_000477 as _w000477,
|
||||||
_elmhurst_worksheet_000480 as _w000480,
|
_elmhurst_worksheet_000480 as _w000480,
|
||||||
|
|
@ -19,8 +19,8 @@ from datatypes.epc.domain.epc_property_data import (
|
||||||
WindowTransmissionDetails,
|
WindowTransmissionDetails,
|
||||||
)
|
)
|
||||||
from domain.ml.tests._fixtures import make_minimal_sap10_epc, make_window
|
from domain.ml.tests._fixtures import make_minimal_sap10_epc, make_window
|
||||||
from domain.sap.worksheet.internal_gains import OvershadingCategory
|
from domain.sap10_calculator.worksheet.internal_gains import OvershadingCategory
|
||||||
from domain.sap.worksheet.solar_gains import (
|
from domain.sap10_calculator.worksheet.solar_gains import (
|
||||||
Orientation,
|
Orientation,
|
||||||
RoofWindowInput,
|
RoofWindowInput,
|
||||||
RooflightInput,
|
RooflightInput,
|
||||||
|
|
@ -29,7 +29,7 @@ from domain.sap.worksheet.solar_gains import (
|
||||||
window_solar_gain_w,
|
window_solar_gain_w,
|
||||||
z_solar_for_overshading,
|
z_solar_for_overshading,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
||||||
|
|
||||||
|
|
||||||
# Worksheet U985-0001-000490 reference (UK-avg weather, region 0):
|
# Worksheet U985-0001-000490 reference (UK-avg weather, region 0):
|
||||||
|
|
@ -9,12 +9,12 @@ from types import ModuleType
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.climate.appendix_u import external_temperature_c
|
from domain.sap10_calculator.climate.appendix_u import external_temperature_c
|
||||||
from domain.sap.worksheet.space_cooling import (
|
from domain.sap10_calculator.worksheet.space_cooling import (
|
||||||
space_cooling_monthly_kwh,
|
space_cooling_monthly_kwh,
|
||||||
utilisation_factor_loss,
|
utilisation_factor_loss,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
||||||
|
|
||||||
|
|
||||||
_FULLY_INACTIVE_GAINS_WINTER_TE_C: float = -10.0
|
_FULLY_INACTIVE_GAINS_WINTER_TE_C: float = -10.0
|
||||||
|
|
@ -11,13 +11,13 @@ from types import ModuleType
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.climate.appendix_u import external_temperature_c
|
from domain.sap10_calculator.climate.appendix_u import external_temperature_c
|
||||||
from domain.sap.worksheet.space_heating import (
|
from domain.sap10_calculator.worksheet.space_heating import (
|
||||||
SpaceHeatingResult,
|
SpaceHeatingResult,
|
||||||
monthly_heat_requirement_kwh,
|
monthly_heat_requirement_kwh,
|
||||||
space_heating_monthly_kwh,
|
space_heating_monthly_kwh,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
||||||
|
|
||||||
|
|
||||||
_UK_AVG_EXT_TEMP_C: tuple[float, ...] = tuple(
|
_UK_AVG_EXT_TEMP_C: tuple[float, ...] = tuple(
|
||||||
|
|
@ -9,7 +9,7 @@ Reference: SAP 10.3 specification (13-01-2026) Table 9a (page 184).
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.worksheet.utilisation_factor import utilisation_factor
|
from domain.sap10_calculator.worksheet.utilisation_factor import utilisation_factor
|
||||||
|
|
||||||
|
|
||||||
def test_small_gain_loss_ratio_returns_eta_close_to_one() -> None:
|
def test_small_gain_loss_ratio_returns_eta_close_to_one() -> None:
|
||||||
|
|
@ -12,8 +12,8 @@ Canonical worked example: `2026-05-19-17-18 RdSap10Worksheet.xlsx`,
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.worksheet.tests._xlsx_loader import load_cells
|
from domain.sap10_calculator.worksheet.tests._xlsx_loader import load_cells
|
||||||
from domain.sap.worksheet.ventilation import (
|
from domain.sap10_calculator.worksheet.ventilation import (
|
||||||
MechanicalVentilationKind,
|
MechanicalVentilationKind,
|
||||||
TABLE_U2_NON_REGIONAL_WIND_SPEED_M_S,
|
TABLE_U2_NON_REGIONAL_WIND_SPEED_M_S,
|
||||||
VentilationResult,
|
VentilationResult,
|
||||||
|
|
@ -461,7 +461,7 @@ def test_excel_worksheet_conformance_section_2_lines_6a_to_25m() -> None:
|
||||||
|
|
||||||
|
|
||||||
from types import ModuleType # noqa: E402
|
from types import ModuleType # noqa: E402
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ( # noqa: E402
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ( # noqa: E402
|
||||||
ALL_FIXTURES as _ELMHURST_FIXTURES,
|
ALL_FIXTURES as _ELMHURST_FIXTURES,
|
||||||
fixture_id as _elmhurst_fixture_id,
|
fixture_id as _elmhurst_fixture_id,
|
||||||
)
|
)
|
||||||
|
|
@ -479,7 +479,7 @@ def test_section_2_matches_elmhurst_worksheet(fixture: ModuleType) -> None:
|
||||||
worksheet's (9) value.
|
worksheet's (9) value.
|
||||||
"""
|
"""
|
||||||
# Arrange
|
# Arrange
|
||||||
from domain.sap.worksheet.dimensions import dimensions_from_cert
|
from domain.sap10_calculator.worksheet.dimensions import dimensions_from_cert
|
||||||
dims = dimensions_from_cert(fixture.build_epc())
|
dims = dimensions_from_cert(fixture.build_epc())
|
||||||
assert dims.storey_count == fixture.LINE_9_STOREYS, (
|
assert dims.storey_count == fixture.LINE_9_STOREYS, (
|
||||||
f"dims.storey_count={dims.storey_count} should equal worksheet (9) "
|
f"dims.storey_count={dims.storey_count} should equal worksheet (9) "
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""Tests for SAP 10.2 §4 — water heating energy requirements.
|
"""Tests for SAP 10.2 §4 — water heating energy requirements.
|
||||||
|
|
||||||
Worksheet line refs land in `domain.sap.worksheet.water_heating`. Each
|
Worksheet line refs land in `domain.sap10_calculator.worksheet.water_heating`. Each
|
||||||
test asserts a single line-ref output against the canonical xlsx worked
|
test asserts a single line-ref output against the canonical xlsx worked
|
||||||
example and/or Elmhurst conformance fixtures.
|
example and/or Elmhurst conformance fixtures.
|
||||||
|
|
||||||
|
|
@ -12,14 +12,14 @@ from types import ModuleType
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from domain.sap.worksheet.tests import (
|
from domain.sap10_calculator.worksheet.tests import (
|
||||||
_elmhurst_worksheet_000474 as _w000474,
|
_elmhurst_worksheet_000474 as _w000474,
|
||||||
_elmhurst_worksheet_000477 as _w000477,
|
_elmhurst_worksheet_000477 as _w000477,
|
||||||
_elmhurst_worksheet_000490 as _w000490,
|
_elmhurst_worksheet_000490 as _w000490,
|
||||||
)
|
)
|
||||||
from domain.sap.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
|
||||||
from domain.sap.worksheet.tests._xlsx_loader import load_cells
|
from domain.sap10_calculator.worksheet.tests._xlsx_loader import load_cells
|
||||||
from domain.sap.worksheet.water_heating import (
|
from domain.sap10_calculator.worksheet.water_heating import (
|
||||||
TABLE_J1_TCOLD_FROM_MAINS_C,
|
TABLE_J1_TCOLD_FROM_MAINS_C,
|
||||||
annual_average_hot_water_l_per_day,
|
annual_average_hot_water_l_per_day,
|
||||||
annual_average_hot_water_other_uses_l_per_day,
|
annual_average_hot_water_other_uses_l_per_day,
|
||||||
|
|
@ -654,7 +654,7 @@ def test_000474_cert_to_inputs_hot_water_kwh_closes_within_1pct_post_slice_2() -
|
||||||
Equation D1 monthly cascade → effective annual η ~88% (vs the
|
Equation D1 monthly cascade → effective annual η ~88% (vs the
|
||||||
87.0% summer scalar) → HW kWh 2320 → ~2290 (+0% target)."""
|
87.0% summer scalar) → HW kWh 2320 → ~2290 (+0% target)."""
|
||||||
# Arrange
|
# Arrange
|
||||||
from domain.sap.rdsap.cert_to_inputs import cert_to_inputs
|
from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_inputs
|
||||||
|
|
||||||
epc = _w000474.build_epc()
|
epc = _w000474.build_epc()
|
||||||
|
|
||||||
|
|
@ -675,8 +675,8 @@ def test_000490_cert_to_inputs_hot_water_kwh_closes_via_equation_d1() -> None:
|
||||||
(+6.2%). Post-slice-2 Equation D1 cascade → HW kWh closes toward
|
(+6.2%). Post-slice-2 Equation D1 cascade → HW kWh closes toward
|
||||||
2851 (target ±2%)."""
|
2851 (target ±2%)."""
|
||||||
# Arrange
|
# Arrange
|
||||||
from domain.sap.rdsap.cert_to_inputs import cert_to_inputs
|
from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_inputs
|
||||||
from domain.sap.worksheet.tests import _elmhurst_worksheet_000490 as _w000490
|
from domain.sap10_calculator.worksheet.tests import _elmhurst_worksheet_000490 as _w000490
|
||||||
|
|
||||||
epc = _w000490.build_epc()
|
epc = _w000490.build_epc()
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
"""Shared domain types for the Ara modelling pipeline and sibling Domna services.
|
|
||||||
|
|
||||||
No persistence, no IO, no business logic. See README.md for layout.
|
|
||||||
"""
|
|
||||||
|
|
@ -232,7 +232,7 @@ def predicted_lighting_kwh(
|
||||||
Missing counts treated as zero.
|
Missing counts treated as zero.
|
||||||
|
|
||||||
DEPRECATED for SAP rating use. The spec-faithful Appendix L L1-L11
|
DEPRECATED for SAP rating use. The spec-faithful Appendix L L1-L11
|
||||||
cascade is in `domain.sap.worksheet.internal_gains.annual_lighting_kwh`
|
cascade is in `domain.sap10_calculator.worksheet.internal_gains.annual_lighting_kwh`
|
||||||
and is what `cert_to_inputs` now plumbs into `inputs.lighting_kwh_per_yr`.
|
and is what `cert_to_inputs` now plumbs into `inputs.lighting_kwh_per_yr`.
|
||||||
This heuristic over-counts ~3× on the Elmhurst cohort (528 vs 140 kWh
|
This heuristic over-counts ~3× on the Elmhurst cohort (528 vs 140 kWh
|
||||||
on 000474). Kept only for `domain.ml.ecf.energy_cost_factor` and
|
on 000474). Kept only for `domain.ml.ecf.energy_cost_factor` and
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue