Move sap10_calculator tests to tests/domain/sap10_calculator/ for CI

The calculator tests lived under domain/sap10_calculator/{tests,worksheet/
tests,rdsap/tests,climate/tests,validation/tests}, none of which are in
pytest.ini testpaths — so CI (which collects tests/) never ran them. Relocate
all five dirs to tests/domain/sap10_calculator/{,worksheet,rdsap,climate,
validation}, mirroring the tests/domain/property_baseline/ convention, so the
cascade-pin / golden / e2e conformance suites run in CI.

Mechanics:
- git mv preserves history (110 files).
- Flattening the trailing /tests keeps each file's depth-to-repo-root
  identical, so all 16 repo-root parents[4] fixture refs stay valid. Only
  test_pcdb_etl.py's parents[1] (→ pcdb data) and one hardcoded absolute
  golden-fixture path in test_cert_to_inputs.py needed rebasing.
- Cross-imports rewritten domain.sap10_calculator.worksheet.tests →
  tests.domain.sap10_calculator.worksheet (21 files incl. the external
  importer backend/documents_parser/tests/test_summary_pdf_mapper_chain.py).
- Golden-fixture path strings in test_summary_pdf_mapper_chain.py +
  scripts/fetch_cohort2_api_jsons.py updated to the new location (the JSONs
  moved with the rdsap tests).

load_cells / gitignored worksheet xlsx: the xlsx-pinned tests (test_dimensions
/ ventilation / water_heating) read 2026-05-19-17-18 RdSap10Worksheet.xlsx,
which is gitignored (.gitignore `*.xlsx`) and so absent in CI. _xlsx_loader.
load_cells now pytest.skip()s when the file is absent, so those tests run
locally and skip cleanly in CI instead of erroring — no new CI failures from
the move, and the gitignore policy is respected.

Verified: tests/domain/sap10_calculator + backend/documents_parser +
tests/domain/property_baseline = 2248 pass, 1 skipped; pyright resolves the
new import paths with zero import-resolution errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-02 16:58:00 +00:00 committed by Jun-te Kim
parent 7ffb144e5d
commit a2bcc2c8af
111 changed files with 64 additions and 53 deletions

View file

@ -1,6 +1,6 @@
"""End-to-end validation for the Elmhurst Summary→EpcPropertyData chain.
The 6 Elmhurst worksheet fixtures in `domain.sap10_calculator.worksheet.tests`
The 6 Elmhurst worksheet fixtures in `tests.domain.sap10_calculator.worksheet`
build their `EpcPropertyData` synthetically they validate the
calculator + cascade in isolation from the mapper. This file pins
the OTHER half of the chain: `from_elmhurst_site_notes` must produce
@ -46,7 +46,7 @@ from datatypes.epc.domain.mapper import (
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
from domain.sap10_calculator.rdsap.cert_to_inputs import SAP_10_2_SPEC_PRICES, cert_to_inputs
from domain.sap10_ml.rdsap_uvalues import u_party_wall
from domain.sap10_calculator.worksheet.tests import (
from tests.domain.sap10_calculator.worksheet import (
_elmhurst_worksheet_000474 as _w000474,
_elmhurst_worksheet_000477 as _w000477,
_elmhurst_worksheet_000480 as _w000480,
@ -84,7 +84,7 @@ _SUMMARY_000565_PDF = _FIXTURES / "Summary_000565.pdf" # cert 000565 (5-bp Elmh
# matches worksheet continuous SAP at 1e-4".
_API_001479_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
/ "0535-9020-6509-0821-6222.json"
)
@ -129,7 +129,7 @@ def _summary_pdf_to_textract_style_pages(pdf_path: Path) -> list[str]:
def test_summary_000474_mapper_produces_three_building_parts() -> None:
# Arrange — cert U985-0001-000474 is a mid-terrace with 3 building
# parts (Main + 2 extensions) per the hand-built worksheet fixture
# at domain/sap10_calculator/worksheet/tests/
# at tests/domain/sap10_calculator/worksheet/
# _elmhurst_worksheet_000474.py. Routing the Summary PDF through
# extractor + mapper must yield the same count.
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000474_PDF)
@ -2978,7 +2978,7 @@ def test_summary_mapper_raises_on_unmapped_party_wall_type_code() -> None:
_GOLDEN_FIXTURES_DIR = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
)
@ -3292,13 +3292,13 @@ def test_summary_0380_full_chain_sap_within_spec_floor_of_worksheet() -> None:
_API_0330_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
/ "0330-2249-8150-2326-4121.json"
)
_API_9501_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
/ "9501-3059-8202-7356-0204.json"
)
@ -3358,7 +3358,7 @@ def test_api_9501_photovoltaic_array_surfaced() -> None:
_API_0380_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
/ "0380-2471-3250-2596-8761.json"
)
@ -3479,20 +3479,20 @@ def test_api_0380_heat_pump_no_pumps_fans_kwh_per_table_4f() -> None:
_API_9418_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
/ "9418-3062-8205-3566-7200.json"
)
_API_2225_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
/ "2225-3062-8205-2856-7204.json"
)
_API_2636_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
/ "2636-0525-2600-0401-2296.json"
)
@ -3765,17 +3765,17 @@ def test_api_001479_full_chain_sap_matches_worksheet_pdf_exactly() -> None:
_API_0350_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
/ "0350-2968-2650-2796-5255.json"
)
_API_3800_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
/ "3800-8515-0922-3398-3563.json"
)
_API_9285_JSON = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
/ "9285-3062-0205-7766-7200.json"
)
@ -3878,7 +3878,7 @@ def test_api_9418_full_chain_sap_within_spec_floor_of_worksheet() -> None:
# SAP cascade is the load-bearing equivalence check. Each cert in this
# cohort has both a Summary PDF (under `sap worksheets/additional with
# api 2/<cert>/Summary_*.pdf`) and an API JSON fixture (fetched into
# `domain/sap10_calculator/rdsap/tests/fixtures/golden/<cert>.json` in
# `tests/domain/sap10_calculator/rdsap/fixtures/golden/<cert>.json` in
# Slice S0380.39). Worksheet SAP is the source of truth.
#
# Cohort-2 API-path closure history (each slice closed a distinct
@ -3893,7 +3893,7 @@ def test_api_9418_full_chain_sap_within_spec_floor_of_worksheet() -> None:
_COHORT_2_API_FIXTURE_DIR: Path = (
Path(__file__).parents[3]
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
/ "tests/domain/sap10_calculator/rdsap/fixtures/golden"
)
# (cert_dir, worksheet_unrounded_sap) — 34 cohort-2 certs whose API-path

View file

@ -1,7 +1,7 @@
"""Throwaway one-off: bulk-fetch cohort-2 EPC API JSONs from gov.uk EPB.
Persists the inner `data` payload (as returned by EpcClientService._fetch_certificate)
to domain/sap10_calculator/rdsap/tests/fixtures/golden/<cert>.json. Skips certs
to tests/domain/sap10_calculator/rdsap/fixtures/golden/<cert>.json. Skips certs
whose JSON already exists.
"""
from __future__ import annotations

View file

@ -64,7 +64,7 @@ from domain.sap10_calculator.rdsap.cert_to_inputs import (
ventilation_from_cert,
)
from domain.sap10_calculator.tables.pcdb import GasOilBoilerRecord, gas_oil_boiler_record
from domain.sap10_calculator.worksheet.tests import _elmhurst_worksheet_000477 as _w000477
from tests.domain.sap10_calculator.worksheet import _elmhurst_worksheet_000477 as _w000477
from domain.sap10_calculator.worksheet.water_heating import (
combi_loss_monthly_kwh_table_3b_row_1_instantaneous,
combi_loss_monthly_kwh_table_3c_two_profile_instantaneous,
@ -3605,9 +3605,9 @@ def test_air_source_heat_pump_pcdb_104568_derives_apm_efficiencies_per_sap_app_n
)
doc = json.loads(
Path(
"/workspaces/model/domain/sap10_calculator/rdsap/tests/"
"fixtures/golden/0380-2471-3250-2596-8761.json"
(
Path(__file__).parent
/ "fixtures" / "golden" / "0380-2471-3250-2596-8761.json"
).read_text()
)
epc = EpcPropertyDataMapper.from_api_response(doc)
@ -3744,7 +3744,7 @@ def test_table_4c_no_boiler_interlock_applies_minus_5_dhw_adjustment_when_cylind
# Arrange — use the real cert 000565 fixture (Elmhurst extractor +
# mapper) so the (62)m demand cascade is the worksheet-pinned
# tuple and only the (217)m efficiency step is under test.
from domain.sap10_calculator.worksheet.tests._elmhurst_worksheet_000565 import (
from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000565 import (
build_epc as build_cert_000565,
)
from domain.sap10_calculator.calculator import calculate_sap_from_inputs
@ -4561,7 +4561,7 @@ def test_lighting_co2_factor_blends_table_12a_grid_2_with_table_12d_dual_rate_on
"""
# Arrange — mapper-driven cohort fixture (Dual meter / TEN_HOUR
# tariff, heat-pump main).
from domain.sap10_calculator.worksheet.tests import (
from tests.domain.sap10_calculator.worksheet import (
_elmhurst_worksheet_000565 as _w000565,
)
epc = _w000565.build_epc()
@ -4609,7 +4609,7 @@ def test_rdsap_10_table_32_prices_charge_mains_gas_hot_water_at_3p48_per_kwh() -
"""
# Arrange — mapper-driven cohort fixture (Summary_000565 → cert_to_
# inputs), Dual meter / mains gas DHW.
from domain.sap10_calculator.worksheet.tests import (
from tests.domain.sap10_calculator.worksheet import (
_elmhurst_worksheet_000565 as _w000565,
)
epc = _w000565.build_epc()

View file

@ -24,7 +24,8 @@ from domain.sap10_calculator.tables.pcdb.parser import (
_PCDB_DAT_PATH: Path = (
Path(__file__).resolve().parents[1] / "tables" / "pcdb" / "data" / "pcdb10.dat"
Path(__file__).resolve().parents[3]
/ "domain" / "sap10_calculator" / "tables" / "pcdb" / "data" / "pcdb10.dat"
)

View file

@ -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,
)
from domain.sap10_calculator.worksheet.tests import (
from tests.domain.sap10_calculator.worksheet import (
_elmhurst_worksheet_000474 as w000474,
_elmhurst_worksheet_000477 as w000477,
_elmhurst_worksheet_000480 as w000480,

View file

@ -41,7 +41,7 @@ from domain.sap10_calculator.worksheet.solar_gains import RoofWindowInput, Roofl
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
from domain.sap10_calculator.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
from tests.domain.sap10_calculator.worksheet._elmhurst_fixtures import (
SECTION_8C_ALL_ZERO_MONTHLY,
SECTION_8C_ETA_LOSS_ALL_ONE,
SECTION_8C_INTERMITTENCY_MONTHLY,

View file

@ -39,7 +39,7 @@ from domain.sap10_calculator.worksheet.solar_gains import RoofWindowInput, Roofl
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
from domain.sap10_calculator.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
from tests.domain.sap10_calculator.worksheet._elmhurst_fixtures import (
SECTION_8C_ALL_ZERO_MONTHLY,
SECTION_8C_ETA_LOSS_ALL_ONE,
SECTION_8C_INTERMITTENCY_MONTHLY,

View file

@ -40,7 +40,7 @@ from domain.sap10_calculator.worksheet.solar_gains import RoofWindowInput, Roofl
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
from domain.sap10_calculator.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
from tests.domain.sap10_calculator.worksheet._elmhurst_fixtures import (
SECTION_8C_ALL_ZERO_MONTHLY,
SECTION_8C_ETA_LOSS_ALL_ONE,
SECTION_8C_INTERMITTENCY_MONTHLY,

View file

@ -260,7 +260,7 @@ def build_epc() -> EpcPropertyData:
# ============================================================================
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
from tests.domain.sap10_calculator.worksheet._elmhurst_fixtures import (
SECTION_8C_ALL_ZERO_MONTHLY,
SECTION_8C_ETA_LOSS_ALL_ONE,
SECTION_8C_INTERMITTENCY_MONTHLY,

View file

@ -43,7 +43,7 @@ from domain.sap10_calculator.worksheet.solar_gains import RoofWindowInput, Roofl
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
from domain.sap10_calculator.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
from tests.domain.sap10_calculator.worksheet._elmhurst_fixtures import (
SECTION_8C_ALL_ZERO_MONTHLY,
SECTION_8C_ETA_LOSS_ALL_ONE,
SECTION_8C_INTERMITTENCY_MONTHLY,

View file

@ -46,7 +46,7 @@ from domain.sap10_calculator.worksheet.solar_gains import Orientation, RoofWindo
from domain.sap10_calculator.worksheet.ventilation import MechanicalVentilationKind
from domain.sap10_calculator.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
from tests.domain.sap10_calculator.worksheet._elmhurst_fixtures import (
SECTION_8C_ALL_ZERO_MONTHLY,
SECTION_8C_ETA_LOSS_ALL_ONE,
SECTION_8C_INTERMITTENCY_MONTHLY,

View file

@ -61,8 +61,8 @@ from datatypes.epc.domain.epc_property_data import EpcPropertyData
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
# Repo root → backend fixtures. parents[0]=tests/, parents[1]=worksheet/,
# parents[2]=sap10_calculator/, parents[3]=domain/, parents[4]=repo root.
# Repo root → backend fixtures. parents[0]=worksheet/, parents[1]=sap10_calculator/,
# parents[2]=domain/, parents[3]=tests/, parents[4]=repo root.
_SUMMARY_PDF: Final[Path] = (
Path(__file__).resolve().parents[4]
/ "backend" / "documents_parser" / "tests" / "fixtures"

View file

@ -19,6 +19,7 @@ from pathlib import Path
from typing import Any, Iterable
import openpyxl
import pytest
_REPO_ROOT = Path(__file__).resolve().parents[4]
WORKSHEET_XLSX_PATH = _REPO_ROOT / "2026-05-19-17-18 RdSap10Worksheet.xlsx"
@ -36,5 +37,14 @@ def load_cells(sheet_name: str, cells: Iterable[str]) -> dict[str, Any]:
last-computed result rather than the formula string. Cell refs use
standard Excel notation, e.g. "Q23", "U25".
"""
if not WORKSHEET_XLSX_PATH.exists():
# The canonical worksheet xlsx is a gitignored dev reference
# (`.gitignore` `*.xlsx`), so it isn't available in CI. Tests that
# pin the calculator against it run locally only and skip cleanly
# when it's absent rather than erroring with FileNotFoundError.
pytest.skip(
f"reference worksheet not present ({WORKSHEET_XLSX_PATH.name}); "
"gitignored dev artifact — xlsx-pinned tests are local-only"
)
sheet = _workbook()[sheet_name]
return {ref: sheet[ref].value for ref in cells}

View file

@ -27,7 +27,7 @@ from domain.sap10_ml.tests._fixtures import (
make_minimal_sap10_epc,
)
from domain.sap10_calculator.worksheet.dimensions import Dimensions, dimensions_from_cert
from domain.sap10_calculator.worksheet.tests._xlsx_loader import load_cells
from tests.domain.sap10_calculator.worksheet._xlsx_loader import load_cells
_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 domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ( # noqa: E402
from tests.domain.sap10_calculator.worksheet._elmhurst_fixtures import ( # noqa: E402
ALL_FIXTURES as _ELMHURST_FIXTURES,
fixture_id as _elmhurst_fixture_id,
)

View file

@ -29,7 +29,7 @@ from domain.sap10_calculator.rdsap.cert_to_inputs import (
cert_to_inputs,
water_heating_section_from_cert,
)
from domain.sap10_calculator.worksheet.tests import (
from tests.domain.sap10_calculator.worksheet import (
_elmhurst_worksheet_000474 as _w000474,
_elmhurst_worksheet_000477 as _w000477,
_elmhurst_worksheet_000480 as _w000480,
@ -38,7 +38,7 @@ from domain.sap10_calculator.worksheet.tests import (
_elmhurst_worksheet_000516 as _w000516,
_elmhurst_worksheet_000565 as _w000565,
)
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
from tests.domain.sap10_calculator.worksheet._elmhurst_fixtures import (
ALL_FIXTURES as _ELMHURST_FIXTURES,
fixture_id as _elmhurst_fixture_id,
)

View file

@ -13,7 +13,7 @@ import pytest
from domain.sap10_calculator.worksheet.fabric_energy_efficiency import (
fabric_energy_efficiency_kwh_per_m2_yr,
)
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
from tests.domain.sap10_calculator.worksheet._elmhurst_fixtures import ALL_FIXTURES, fixture_id
def test_fabric_energy_efficiency_sums_heating_per_m2_and_cooling_per_m2() -> None:

View file

@ -1326,11 +1326,11 @@ def test_real_corpus_basement_cert_has_part_with_has_basement_true() -> None:
from types import ModuleType # noqa: E402
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ( # noqa: E402
from tests.domain.sap10_calculator.worksheet._elmhurst_fixtures import ( # noqa: E402
ALL_FIXTURES as _ELMHURST_FIXTURES,
fixture_id as _elmhurst_fixture_id,
)
from domain.sap10_calculator.worksheet.tests import ( # noqa: E402
from tests.domain.sap10_calculator.worksheet import ( # noqa: E402
_elmhurst_worksheet_000474 as _w000474,
_elmhurst_worksheet_000490 as _w000490,
)

View file

@ -44,7 +44,7 @@ from datatypes.epc.domain.epc_property_data import (
SapWindow,
)
from domain.sap10_ml.tests._fixtures import make_minimal_sap10_epc
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import ALL_FIXTURES, fixture_id
from tests.domain.sap10_calculator.worksheet._elmhurst_fixtures import ALL_FIXTURES, fixture_id
def test_metabolic_gains_are_60w_per_occupant_constant_across_months() -> None:

Some files were not shown because too many files have changed in this diff Show more