diff --git a/docs/adr/0009-deterministic-sap-calculator.md b/docs/adr/0009-deterministic-sap-calculator.md index 8727e7f6..a41456e5 100644 --- a/docs/adr/0009-deterministic-sap-calculator.md +++ b/docs/adr/0009-deterministic-sap-calculator.md @@ -9,7 +9,7 @@ Seven open questions resolved through a `/grill-with-docs` session before Sessio | # | Question | Decision | |---|---|---| | 0 | Domain placement | **Option B** — new term **Calculated SAP10 Performance**, parallel to Effective Performance (ML) and Lodged Performance (gov register). Effective Performance is **not** retired now; a future ADR may promote Calculated to its current role once parity is confirmed. Process named **SAP10 Calculation**. | -| 1 | PCDB heat-pump COP source for Session A | **Stub-seam.** Define `PcdbLookup` Protocol, ship `NoOpPcdbLookup` returning None, fall back to Table 4a. Session C bundles a CSV PCDB extract under `docs/sap-spec/` and implements the lookup. | +| 1 | PCDB heat-pump COP source for Session A | **Stub-seam.** Define `PcdbLookup` Protocol, ship `NoOpPcdbLookup` returning None, fall back to Table 4a. Session C bundles a CSV PCDB extract under `domain/sap10_calculator/tables/pcdb/data/` and implements the lookup. | | 2 | MCS installation factors | **Boolean input on calculator inputs, default `False`.** Plumbing in Session A; no behaviour change until the input is populated. Slice 18f (separate, tracked in HANDOFF §7-D0) lifts `mcs_installed_heat_pump` from gov API → `EpcPropertyData.MainHeatingDetail` so calculator can apply the factor on the ~1.5% of HP certs that carry it. | | 3 | Thermal bridging | **Global y factor** (the path SAP 10.3 specifies for RdSAP-driven assessments). Per-junction Table R2 sum requires junction-count inputs the cert doesn't carry — not available on the RdSAP-driven flow. | | 4 | Living-area fraction default | **RdSAP 10 Table 27** — direct lookup from `habitable_rooms_count`. Unambiguous, one-line table. | @@ -20,7 +20,7 @@ Seven open questions resolved through a `/grill-with-docs` session before Sessio ## Additional findings from the grill that change Session A scope - **SAP rating formula belongs to RdSAP, not SAP 10.3.** RdSAP §19 ("RdSAP10-specific SAP rating equations referred to as EER") defines the SAP-score equation used for RdSAP-driven assessments. SAP 10.3 §13 defines the rating for new-build assessments. The cert's `energy_rating_current` was computed by RdSAP §19, so parity validation must compute against RdSAP §19, not SAP 10.3 §13. -- **RdSAP 10 (June 2025) cross-references SAP 10.2 (March 2025) for heating-system identification (Appendix A).** RdSAP was published before SAP 10.3 (Jan 2026). Until BRE updates RdSAP to reference SAP 10.3, the calculator's heating-identification logic reads SAP 10.2 Appendix A while everything else reads SAP 10.3. Keep both PDFs in `docs/sap-spec/`. +- **RdSAP 10 (June 2025) cross-references SAP 10.2 (March 2025) for heating-system identification (Appendix A).** RdSAP was published before SAP 10.3 (Jan 2026). Until BRE updates RdSAP to reference SAP 10.3, the calculator's heating-identification logic reads SAP 10.2 Appendix A while everything else reads SAP 10.3. Keep both PDFs in `domain/sap10_calculator/docs/specs/`. - **RdSAP Table 29 ("Heating and hot water parameters") is a 20+-entry defaulting table** that the `cascade_defaults.py` module needs to encode. Current scope of `rdsap_uvalues.py` is U-values only; Table 29 extends the cascade pattern to cylinder insulation, primary-pipework insulation, boiler interlock, emitter temperature, underfloor-heating routing, solar-panel parameters, heat-network defaults. Adds ~1-2 hrs to Session A (effective Session A.5 if not split). - **MCS field exists in gov API** but is dropped by the current mapper. Slice 18f (lift `mcs_installed_heat_pump` into `EpcPropertyData`) is a prerequisite for the MCS-factor path. ~30 min slice; can ship before Session A or in parallel. @@ -35,7 +35,7 @@ These cannot be closed by another tree feature. They require executing the calcu ## Decision -Build a deterministic **`Sap10Calculator`** that reads `EpcPropertyData` and emits the same outputs the certificate's BRE-approved assessor software emits: `sap_score`, `co2_emissions`, `peui_raw`, `peui_ucl`, `space_heating_kwh`, `hot_water_kwh`. Target the SAP 10.3 specification (DESNZ/BRE, 13-01-2026) and the RdSAP 10 specification (BRE, 10-06-2025), both held in `docs/sap-spec/`. +Build a deterministic **`Sap10Calculator`** that reads `EpcPropertyData` and emits the same outputs the certificate's BRE-approved assessor software emits: `sap_score`, `co2_emissions`, `peui_raw`, `peui_ucl`, `space_heating_kwh`, `hot_water_kwh`. Target the SAP 10.3 specification (DESNZ/BRE, 13-01-2026) and the RdSAP 10 specification (BRE, 10-06-2025), both held in `domain/sap10_calculator/docs/specs/`. The ML model is **not deprecated**. It is repurposed as a **residual learner** against `actual_sap − calculator_sap` (and similar deltas for the other five targets). Residual distributions are much narrower than the raw target distributions (calculator is within ~1 SAP-point on 95% of typical certs, per the working hypothesis), so the ML residual head should fit the corrections with far fewer features and reach the MAE ≤ 0.5 target. @@ -150,4 +150,4 @@ Re-derivation work is bounded — a few hundred numbers across tables — and th - A new top-level domain area `domain.sap10_calculator.*` is introduced; over Sessions B/C it absorbs `domain.sap10_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 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 `domain/sap10_calculator/docs/specs/` (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. diff --git a/docs/adr/0010-sap10-calculator-spec-target-and-validation.md b/docs/adr/0010-sap10-calculator-spec-target-and-validation.md index b6d4a375..19c9b12c 100644 --- a/docs/adr/0010-sap10-calculator-spec-target-and-validation.md +++ b/docs/adr/0010-sap10-calculator-spec-target-and-validation.md @@ -58,7 +58,7 @@ Each `domain/sap/worksheet/*.py` module must mirror the SAP 10.2 worksheet struc - ADR-0009's "MAE ≤ 1.0 SAP-point on typical subset" success criterion is restated against the Validation Cohort (not the full corpus). The "typical subset" exclusions in ADR-0009 (sap_score ≤ 5, ≥ 100, multi-heating, conservatory, RIR) still apply on top of the cohort filter. - The training parquet schema bumps when `inspection_date` is added — a non-breaking MINOR addition under [ADR-0008](0008-physics-as-feature.md)'s `Feature Schema Version` discipline. -- The handover document `docs/sap-spec/HANDOVER_SYSTEMATIC_REVIEW.md` is rewritten in lockstep: §3 (diagnosis), §4 (scope), §7 (state-A-vs-state-B framing deleted), §7b (findings re-framed), §10 (fixture strategy), and a new §2.5 listing the five prerequisites. +- The handover document `domain/sap10_calculator/docs/HANDOVER_SYSTEMATIC_REVIEW.md` is rewritten in lockstep: §3 (diagnosis), §4 (scope), §7 (state-A-vs-state-B framing deleted), §7b (findings re-framed), §10 (fixture strategy), and a new §2.5 listing the five prerequisites. - Sessions A/B/C from ADR-0009 collapse into a single sequence: prerequisites land, then the section sweep runs against a clean probe with PCDB available. ## Considered alternatives diff --git a/domain/sap10_calculator/README.md b/domain/sap10_calculator/README.md index d2a3d0ef..a7fb7097 100644 --- a/domain/sap10_calculator/README.md +++ b/domain/sap10_calculator/README.md @@ -19,9 +19,9 @@ sap/ └── tables/ # Table U2 wind, Table 6 walls, Table 21 bridging, … ``` -Spec references: `docs/sap-spec/sap-10-2-full-specification-2025-03-14.pdf` (SAP 10.2, the active target per ADR-0010), `docs/sap-spec/RdSAP 10 Specification 10-06-2025.pdf` (RdSAP cascade). Canonical worked example: `2026-05-19-17-18 RdSap10Worksheet.xlsx` at repo root — loaded by `_xlsx_loader.py`. +Spec references: `domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf` (SAP 10.2, the active target per ADR-0010), `domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf` (RdSAP cascade). Canonical worked example: `2026-05-19-17-18 RdSap10Worksheet.xlsx` at repo root — loaded by `_xlsx_loader.py`. -**Validation contract.** Per `[[feedback-zero-error-strict]]` the 6 Elmhurst U985 fixtures are deterministic test vectors: every line ref of every output must pin against the U985 PDF at `abs=1e-4`. See `worksheet/tests/test_section_cascade_pins.py` (per-section line refs, 768 rating + 90 demand pins) and `test_e2e_elmhurst_sap_score.py::test_sap_result_pin` (top-level SapResult fields). Tolerances are never widened. **Current state: 930/930 pins green.** The public API + architecture overview lives in `docs/sap-spec/SAP_CALCULATOR.md`. +**Validation contract.** Per `[[feedback-zero-error-strict]]` the 6 Elmhurst U985 fixtures are deterministic test vectors: every line ref of every output must pin against the U985 PDF at `abs=1e-4`. See `worksheet/tests/test_section_cascade_pins.py` (per-section line refs, 768 rating + 90 demand pins) and `test_e2e_elmhurst_sap_score.py::test_sap_result_pin` (top-level SapResult fields). Tolerances are never widened. **Current state: 930/930 pins green.** The public API + architecture overview lives in `domain/sap10_calculator/docs/SAP_CALCULATOR.md`. ## Adding a new Elmhurst conformance fixture diff --git a/docs/sap-spec/HANDOVER_NEXT.md b/domain/sap10_calculator/docs/HANDOVER_NEXT.md similarity index 98% rename from docs/sap-spec/HANDOVER_NEXT.md rename to domain/sap10_calculator/docs/HANDOVER_NEXT.md index b39d1799..3b2b364c 100644 --- a/docs/sap-spec/HANDOVER_NEXT.md +++ b/domain/sap10_calculator/docs/HANDOVER_NEXT.md @@ -131,7 +131,7 @@ expanding. | File | Why | |---|---| -| [`docs/sap-spec/SAP_CALCULATOR.md`](./SAP_CALCULATOR.md) | Module API + architecture (you're heading there) | +| [`domain/sap10_calculator/docs/SAP_CALCULATOR.md`](./SAP_CALCULATOR.md) | Module API + architecture (you're heading there) | | [`domain/sap10_calculator/calculator.py`](../../domain/sap10_calculator/calculator.py) | `SapResult` fields you'll assert against | | [`domain/sap10_calculator/rdsap/cert_to_inputs.py`](../../domain/sap10_calculator/rdsap/cert_to_inputs.py) | The 3 public entry points + the section helpers | | [`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 | diff --git a/docs/sap-spec/NEXT_AGENT_PROMPT.md b/domain/sap10_calculator/docs/NEXT_AGENT_PROMPT.md similarity index 98% rename from docs/sap-spec/NEXT_AGENT_PROMPT.md rename to domain/sap10_calculator/docs/NEXT_AGENT_PROMPT.md index 5b693578..569eac5a 100644 --- a/docs/sap-spec/NEXT_AGENT_PROMPT.md +++ b/domain/sap10_calculator/docs/NEXT_AGENT_PROMPT.md @@ -44,7 +44,7 @@ Layer 4: API mapper cascade SAP = worksheet SAP at 1e-4 (production goal) The big breakthrough: implementing the RdSAP 10 §5 (12) spec rule (`Floor infiltration (suspended timber ground floor only)` — page 29 -of `docs/sap-spec/RdSAP 10 Specification 10-06-2025.pdf`) revealed a +of `domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf`) revealed a series of API-mapper coverage gaps that all needed fixing for the spec rule's premise to be met. Each slice closed one gap: @@ -262,7 +262,7 @@ f863598d Slice 85: bulk-update cohort 000516 hand-built for Cat A diff parity ``` Earlier slice context (71-86 closed cohort Layer 2) is in the prior -handover at commit `86eff23f` (`docs/sap-spec/NEXT_AGENT_PROMPT.md` +handover at commit `86eff23f` (`domain/sap10_calculator/docs/NEXT_AGENT_PROMPT.md` before this rewrite). ## First action diff --git a/docs/sap-spec/SAP_CALCULATOR.md b/domain/sap10_calculator/docs/SAP_CALCULATOR.md similarity index 98% rename from docs/sap-spec/SAP_CALCULATOR.md rename to domain/sap10_calculator/docs/SAP_CALCULATOR.md index 7e6049f5..5f51dd84 100644 --- a/docs/sap-spec/SAP_CALCULATOR.md +++ b/domain/sap10_calculator/docs/SAP_CALCULATOR.md @@ -228,7 +228,7 @@ domain/sap10_calculator/ ├── parser.py # PCDB row parsers └── (other PCDB tables) -docs/sap-spec/ +domain/sap10_calculator/docs/specs/ ├── sap-10-2-full-specification-2025-03-14.pdf # SAP 10.2 spec ├── RdSAP 10 Specification 10-06-2025.pdf # RdSAP 10 spec ├── pcdb10.dat # PCDB raw data (Table 172 + others) @@ -370,6 +370,6 @@ RdSAP 10 (10-06-2025): Table 12a (standing/off-peak) p.191 PCDB10: - Table 105 (gas/oil boilers) docs/sap-spec/pcdb_table_105_... - Table 172 (postcode-district weather) docs/sap-spec/pcdb10.dat + Table 105 (gas/oil boilers) domain/sap10_calculator/docs/specs/pcdb_table_105_... + Table 172 (postcode-district weather) domain/sap10_calculator/tables/pcdb/data/pcdb10.dat ``` diff --git a/docs/sap-spec/PCDF_Spec_Rev-06b_12_May_2021.pdf b/domain/sap10_calculator/docs/specs/PCDF_Spec_Rev-06b_12_May_2021.pdf similarity index 100% rename from docs/sap-spec/PCDF_Spec_Rev-06b_12_May_2021.pdf rename to domain/sap10_calculator/docs/specs/PCDF_Spec_Rev-06b_12_May_2021.pdf diff --git a/docs/sap-spec/RdSAP 10 Specification 10-06-2025.pdf b/domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf similarity index 100% rename from docs/sap-spec/RdSAP 10 Specification 10-06-2025.pdf rename to domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf diff --git a/docs/sap-spec/sap-10-2-full-specification-2025-03-14.pdf b/domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf similarity index 100% rename from docs/sap-spec/sap-10-2-full-specification-2025-03-14.pdf rename to domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf diff --git a/docs/sap-spec/sap-10-3-full-specification-2026-01-13.pdf b/domain/sap10_calculator/docs/specs/sap-10-3-full-specification-2026-01-13.pdf similarity index 100% rename from docs/sap-spec/sap-10-3-full-specification-2026-01-13.pdf rename to domain/sap10_calculator/docs/specs/sap-10-3-full-specification-2026-01-13.pdf diff --git a/domain/sap10_calculator/tables/pcdb/__init__.py b/domain/sap10_calculator/tables/pcdb/__init__.py index a1939712..4382fffd 100644 --- a/domain/sap10_calculator/tables/pcdb/__init__.py +++ b/domain/sap10_calculator/tables/pcdb/__init__.py @@ -15,7 +15,7 @@ Public surface: - `parser.py`: per-table row parsers (Table 105 typed; raw walker for the other 7 tables). - `etl.py`: walks the multi-table `pcdb10.dat` source and writes one - newline-delimited JSON file per table under `docs/sap-spec/`. + newline-delimited JSON file per table under `domain/sap10_calculator/tables/pcdb/data/`. Reference: BRE PCDB pcdb10.dat (April 2026 revision); SAP 10.2 specification (14-03-2025) Appendix D2.1. @@ -32,11 +32,9 @@ from domain.sap10_calculator.tables.pcdb.parser import GasOilBoilerRecord __all__ = ["GasOilBoilerRecord", "gas_oil_boiler_record"] -_REPO_SAP_SPEC_DIR: Final[Path] = ( - Path(__file__).resolve().parents[4] / "docs" / "sap-spec" -) +_PCDB_DATA_DIR: Final[Path] = Path(__file__).resolve().parent / "data" _TABLE_105_JSONL: Final[Path] = ( - _REPO_SAP_SPEC_DIR / "pcdb_table_105_gas_oil_boilers.jsonl" + _PCDB_DATA_DIR / "pcdb_table_105_gas_oil_boilers.jsonl" ) diff --git a/docs/sap-spec/pcdb10.dat b/domain/sap10_calculator/tables/pcdb/data/pcdb10.dat similarity index 100% rename from docs/sap-spec/pcdb10.dat rename to domain/sap10_calculator/tables/pcdb/data/pcdb10.dat diff --git a/docs/sap-spec/pcdb_table_105_gas_oil_boilers.jsonl b/domain/sap10_calculator/tables/pcdb/data/pcdb_table_105_gas_oil_boilers.jsonl similarity index 100% rename from docs/sap-spec/pcdb_table_105_gas_oil_boilers.jsonl rename to domain/sap10_calculator/tables/pcdb/data/pcdb_table_105_gas_oil_boilers.jsonl diff --git a/docs/sap-spec/pcdb_table_122_solid_fuel_boilers.jsonl b/domain/sap10_calculator/tables/pcdb/data/pcdb_table_122_solid_fuel_boilers.jsonl similarity index 100% rename from docs/sap-spec/pcdb_table_122_solid_fuel_boilers.jsonl rename to domain/sap10_calculator/tables/pcdb/data/pcdb_table_122_solid_fuel_boilers.jsonl diff --git a/docs/sap-spec/pcdb_table_143_micro_cogen.jsonl b/domain/sap10_calculator/tables/pcdb/data/pcdb_table_143_micro_cogen.jsonl similarity index 100% rename from docs/sap-spec/pcdb_table_143_micro_cogen.jsonl rename to domain/sap10_calculator/tables/pcdb/data/pcdb_table_143_micro_cogen.jsonl diff --git a/docs/sap-spec/pcdb_table_313_flue_gas_heat_recovery.jsonl b/domain/sap10_calculator/tables/pcdb/data/pcdb_table_313_flue_gas_heat_recovery.jsonl similarity index 100% rename from docs/sap-spec/pcdb_table_313_flue_gas_heat_recovery.jsonl rename to domain/sap10_calculator/tables/pcdb/data/pcdb_table_313_flue_gas_heat_recovery.jsonl diff --git a/docs/sap-spec/pcdb_table_353_waste_water_heat_recovery.jsonl b/domain/sap10_calculator/tables/pcdb/data/pcdb_table_353_waste_water_heat_recovery.jsonl similarity index 100% rename from docs/sap-spec/pcdb_table_353_waste_water_heat_recovery.jsonl rename to domain/sap10_calculator/tables/pcdb/data/pcdb_table_353_waste_water_heat_recovery.jsonl diff --git a/docs/sap-spec/pcdb_table_362_heat_pumps.jsonl b/domain/sap10_calculator/tables/pcdb/data/pcdb_table_362_heat_pumps.jsonl similarity index 100% rename from docs/sap-spec/pcdb_table_362_heat_pumps.jsonl rename to domain/sap10_calculator/tables/pcdb/data/pcdb_table_362_heat_pumps.jsonl diff --git a/docs/sap-spec/pcdb_table_391_high_heat_retention_storage_heaters.jsonl b/domain/sap10_calculator/tables/pcdb/data/pcdb_table_391_high_heat_retention_storage_heaters.jsonl similarity index 100% rename from docs/sap-spec/pcdb_table_391_high_heat_retention_storage_heaters.jsonl rename to domain/sap10_calculator/tables/pcdb/data/pcdb_table_391_high_heat_retention_storage_heaters.jsonl diff --git a/docs/sap-spec/pcdb_table_506_heat_interface_units.jsonl b/domain/sap10_calculator/tables/pcdb/data/pcdb_table_506_heat_interface_units.jsonl similarity index 100% rename from docs/sap-spec/pcdb_table_506_heat_interface_units.jsonl rename to domain/sap10_calculator/tables/pcdb/data/pcdb_table_506_heat_interface_units.jsonl diff --git a/domain/sap10_calculator/tables/pcdb/etl.py b/domain/sap10_calculator/tables/pcdb/etl.py index 85dc6df6..161da592 100644 --- a/domain/sap10_calculator/tables/pcdb/etl.py +++ b/domain/sap10_calculator/tables/pcdb/etl.py @@ -75,8 +75,8 @@ def run_etl(*, source: Path, output_dir: Path) -> None: if __name__ == "__main__": # pragma: no cover — manual ETL invocation - repo_root = Path(__file__).resolve().parents[4] + data_dir = Path(__file__).resolve().parent / "data" run_etl( - source=repo_root / "docs" / "sap-spec" / "pcdb10.dat", - output_dir=repo_root / "docs" / "sap-spec", + source=data_dir / "pcdb10.dat", + output_dir=data_dir, ) diff --git a/domain/sap10_calculator/tables/pcdb/parser.py b/domain/sap10_calculator/tables/pcdb/parser.py index 58439f64..29bf20d2 100644 --- a/domain/sap10_calculator/tables/pcdb/parser.py +++ b/domain/sap10_calculator/tables/pcdb/parser.py @@ -66,7 +66,7 @@ class GasOilBoilerRecord: final_year_of_manufacture: Optional[int] # SAP10.2 Appendix J Table 3b/3c — combi-loss fields per BRE PCDF Spec # Rev 6b (12 May 2021), Gas and Oil Boiler Table, fields 48 / 51 / 52 - # / 56 / 57 (see `docs/sap-spec/PCDF_Spec_Rev-06b_12_May_2021.pdf` + # / 56 / 57 (see `domain/sap10_calculator/docs/specs/PCDF_Spec_Rev-06b_12_May_2021.pdf` # pp. 14-15). Populated only for boilers EN 13203-2 / OPS 26 tested; # SAP-default boilers leave them all blank → `separate_dhw_tests=0` # and (61)m falls back to Table 3a. Field 48 encodes the test diff --git a/domain/sap10_calculator/tables/pcdb/postcode_weather.py b/domain/sap10_calculator/tables/pcdb/postcode_weather.py index b808bbf8..97ae7694 100644 --- a/domain/sap10_calculator/tables/pcdb/postcode_weather.py +++ b/domain/sap10_calculator/tables/pcdb/postcode_weather.py @@ -9,7 +9,7 @@ The "rating" cascade (SAP rating, EI rating) uses UK-average climate per Appendix U; the "demand" cascade (EPC emissions, primary energy, fuel cost) uses the postcode-specific climate from this table. -Reference: PCDB10 data file `docs/sap-spec/pcdb10.dat`. +Reference: PCDB10 data file `domain/sap10_calculator/tables/pcdb/data/pcdb10.dat`. """ from __future__ import annotations @@ -21,7 +21,7 @@ from typing import Final, Optional _PCDB_DAT_PATH: Final[Path] = ( - Path(__file__).resolve().parents[4] / "docs" / "sap-spec" / "pcdb10.dat" + Path(__file__).resolve().parent / "data" / "pcdb10.dat" ) _TABLE_172_TAG: Final[str] = "$172" diff --git a/domain/sap10_calculator/tables/table_12a.py b/domain/sap10_calculator/tables/table_12a.py index d9822261..fe04aaaa 100644 --- a/domain/sap10_calculator/tables/table_12a.py +++ b/domain/sap10_calculator/tables/table_12a.py @@ -1,6 +1,6 @@ """SAP 10.2 Table 12a — high-rate fractions for off-peak tariffs. -Sourced verbatim from `docs/sap-spec/sap-10-2-full-specification-2025- +Sourced verbatim from `domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025- 03-14.pdf`, page 191 (Table 12a). RdSAP10 §19.1 cross-references this table from RdSAP10 §10a/§10b — the table is not duplicated in the RdSAP10 PDF. diff --git a/domain/sap10_calculator/tables/table_32.py b/domain/sap10_calculator/tables/table_32.py index b063e23a..0cddf8f6 100644 --- a/domain/sap10_calculator/tables/table_32.py +++ b/domain/sap10_calculator/tables/table_32.py @@ -1,6 +1,6 @@ """RdSAP10 Table 32 — fuel prices, standing charges, PV export credit. -Sourced verbatim from `docs/sap-spec/RdSAP 10 Specification 10-06-2025.pdf`, +Sourced verbatim from `domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf`, page 95 (Table 32). RdSAP10 §19.1: SAP rating for RdSAP10 is calculated using Table 32 prices (not Table 12) for §10a and §10b. The calculator targets RdSAP10 cost per ADR-0010 amendment. diff --git a/domain/sap10_calculator/tests/test_bre_worked_examples.py b/domain/sap10_calculator/tests/test_bre_worked_examples.py index 3166856d..7aac35ee 100644 --- a/domain/sap10_calculator/tests/test_bre_worked_examples.py +++ b/domain/sap10_calculator/tests/test_bre_worked_examples.py @@ -2,14 +2,14 @@ synthetic baseline dwelling. **Provenance.** The SAP 10.2 worksheet template (pages 131–148 of -docs/sap-spec/sap-10-2-full-specification-2025-03-14.pdf) is the canonical +domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf) is the canonical calculation form every SAP implementation must mirror, using the item reference numbers (1a), (4), (33), (39), (40), (91), (92), (93), (257), (272), (286) etc. The PDF's form fields are non-functional, so this test is **spec-formula-derived** — each expected value is computed independently from the worksheet formulas applied to the baseline inputs below, not from BRE-published worked-example tables. BRE worked-example values were not -located in any of the three SAP-spec PDFs in docs/sap-spec/; if they +located in any of the three SAP-spec PDFs in domain/sap10_calculator/docs/specs/; if they surface later, only the expected numbers need updating, not this file's structure. diff --git a/domain/sap10_calculator/tests/test_pcdb_etl.py b/domain/sap10_calculator/tests/test_pcdb_etl.py index 87d0957e..1d068536 100644 --- a/domain/sap10_calculator/tests/test_pcdb_etl.py +++ b/domain/sap10_calculator/tests/test_pcdb_etl.py @@ -23,8 +23,9 @@ from domain.sap10_calculator.tables.pcdb.parser import ( ) -_REPO_ROOT: Path = Path(__file__).resolve().parents[3] -_PCDB_DAT_PATH: Path = _REPO_ROOT / "docs" / "sap-spec" / "pcdb10.dat" +_PCDB_DAT_PATH: Path = ( + Path(__file__).resolve().parents[1] / "tables" / "pcdb" / "data" / "pcdb10.dat" +) # Verified by user against ncm-pcdb.org.uk: Baxi Heating Wm 20/3rs. diff --git a/domain/sap10_calculator/tests/test_table_12.py b/domain/sap10_calculator/tests/test_table_12.py index c611a6b3..8ee902cf 100644 --- a/domain/sap10_calculator/tests/test_table_12.py +++ b/domain/sap10_calculator/tests/test_table_12.py @@ -2,7 +2,7 @@ Locks the CO2 emission factors and primary energy factors against the published SAP 10.2 specification at -`docs/sap-spec/sap-10-2-full-specification-2025-03-14.pdf`, page 189. +`domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf`, page 189. The price column (`UNIT_PRICE_P_PER_KWH`) was already SAP 10.2-correct when the calculator code was authored; the CO2 column was authored diff --git a/domain/sap10_calculator/tests/test_table_12a.py b/domain/sap10_calculator/tests/test_table_12a.py index 1811d5e4..3135881e 100644 --- a/domain/sap10_calculator/tests/test_table_12a.py +++ b/domain/sap10_calculator/tests/test_table_12a.py @@ -3,7 +3,7 @@ Locks the `Tariff` enum, the `tariff_from_meter_type` cert resolver, and the per-system / per-use high-rate-fraction lookups against the published SAP10.2 specification at -`docs/sap-spec/sap-10-2-full-specification-2025-03-14.pdf`, page 191. +`domain/sap10_calculator/docs/specs/sap-10-2-full-specification-2025-03-14.pdf`, page 191. RdSAP10 §19.1 cross-references Table 12a in SAP10.2 for off-peak splitting — the table itself is not duplicated in the RdSAP10 PDF. diff --git a/domain/sap10_calculator/tests/test_table_32.py b/domain/sap10_calculator/tests/test_table_32.py index f44328d0..6569cb98 100644 --- a/domain/sap10_calculator/tests/test_table_32.py +++ b/domain/sap10_calculator/tests/test_table_32.py @@ -2,7 +2,7 @@ Locks unit prices, standing charges, PV export credit, and the Table 12 note (a) standing-charge gating against the published RdSAP10 -specification at `docs/sap-spec/RdSAP 10 Specification 10-06-2025.pdf`, +specification at `domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf`, page 95 (Table 32). RdSAP10 §19.1: "The SAP rating for RdSAP 10 is to be calculated using diff --git a/domain/sap10_calculator/worksheet/tests/test_dimensions.py b/domain/sap10_calculator/worksheet/tests/test_dimensions.py index e28ec662..87cc5167 100644 --- a/domain/sap10_calculator/worksheet/tests/test_dimensions.py +++ b/domain/sap10_calculator/worksheet/tests/test_dimensions.py @@ -6,7 +6,7 @@ extension). Reuses the existing fixtures from the ML test pack so tests match the shape `transform.py` already sees in production. SAP 10.3 specification (13-01-2026), §1 reference at -docs/sap-spec/sap-10-3-full-specification-2026-01-13.pdf pages 11-12. +domain/sap10_calculator/docs/specs/sap-10-3-full-specification-2026-01-13.pdf pages 11-12. """ import json @@ -468,7 +468,7 @@ def test_all_rir_shapes_apply_section_1_2_45m_convention_uniformly( ) -> None: """RdSAP §3.9.2 wall-area formulas and §3.10 detailed measurements are for §3 heat-loss U-value calculation, **not** §1 dimensions — - confirmed at `docs/sap-spec/RdSAP 10 Specification 10-06-2025.pdf` + confirmed at `domain/sap10_calculator/docs/specs/RdSAP 10 Specification 10-06-2025.pdf` pages 22-24. The §1 storey-height convention of 2.45 m from §3.9.1 extends uniformly to every RR shape: each contributes exactly `floor_area` to TFA, `floor_area × 2.45` to volume, and +1 storey. diff --git a/domain/sap10_ml/tests/test_rdsap_uvalues.py b/domain/sap10_ml/tests/test_rdsap_uvalues.py index 01cd98be..ad185a1a 100644 --- a/domain/sap10_ml/tests/test_rdsap_uvalues.py +++ b/domain/sap10_ml/tests/test_rdsap_uvalues.py @@ -210,7 +210,7 @@ def test_u_wall_filled_cavity_england_age_band_e_returns_table6_value() -> None: # Arrange — RdSAP 10 Table 6 (England) row "Filled cavity", age band E # (1967-1975) -> 0.7 W/m^2K. The cert records this as the triple # (wall_construction=4 cavity, wall_insulation_type=2 filled, - # wall_insulation_thickness="NI"). Spec: docs/sap-spec/rdsap-10- + # wall_insulation_thickness="NI"). Spec: domain/sap10_calculator/docs/specs/rdsap-10- # specification-2025-06-10.pdf page 33. # Act