mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
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>
87 lines
2.6 KiB
Python
87 lines
2.6 KiB
Python
"""Tests for the PCDB Table 172 (postcode weather) lookup module.
|
|
|
|
The lookup parses pcdb10.dat at first use and caches it as a
|
|
`{(area, district): PostcodeClimate}` dict. Callers invoke
|
|
`postcode_climate(postcode_str)` to obtain the per-district monthly
|
|
weather (temp, wind, solar) used by the demand-side cascade for EPC
|
|
emissions / primary energy.
|
|
|
|
Reference: BRE PCDB pcdb10.dat Table 172 (Postcodes).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from domain.sap10_calculator.tables.pcdb.postcode_weather import (
|
|
PostcodeClimate,
|
|
postcode_climate,
|
|
)
|
|
|
|
|
|
def test_postcode_climate_returns_bd3_record() -> None:
|
|
"""Bradford district 3 (BD3) is the postcode for Elmhurst fixture 000474.
|
|
Verified against U985 Block 2 wind speed (5.2, 5.2, 5.0, ..., 4.9) which
|
|
is the EPC demand-cascade climate."""
|
|
# Arrange
|
|
# Act
|
|
climate = postcode_climate("bd3 8aq")
|
|
|
|
# Assert
|
|
assert climate is not None
|
|
assert climate.area == "BD"
|
|
assert climate.district == 3
|
|
assert climate.region == 11 # East Pennines
|
|
# Block 2 of U985-0001-000474.txt: Wind speed
|
|
# 5.2 5.2 5.0 4.4 4.3 3.9 4.0 3.8 4.1 4.4 4.6 4.9 (22)
|
|
assert climate.monthly_wind_speed_m_per_s == (
|
|
5.2, 5.2, 5.0, 4.4, 4.3, 3.9, 4.0, 3.8, 4.1, 4.4, 4.6, 4.9,
|
|
)
|
|
|
|
|
|
def test_postcode_climate_parses_mixed_case() -> None:
|
|
"""Postcode is normalised to upper-case so "bd3 8aq" and "BD3 8AQ" hit
|
|
the same record."""
|
|
# Arrange
|
|
lower = "bd4 7jr"
|
|
upper = "BD4 7JR"
|
|
|
|
# Act
|
|
a = postcode_climate(lower)
|
|
b = postcode_climate(upper)
|
|
|
|
# Assert
|
|
assert a is not None
|
|
assert b is not None
|
|
assert a == b
|
|
|
|
|
|
def test_postcode_climate_handles_two_digit_district() -> None:
|
|
"""Two-digit district numbers ("BD19") parse correctly — the digit
|
|
consumption walks past the alpha prefix and grabs all digits."""
|
|
# Arrange
|
|
# Act
|
|
climate = postcode_climate("bd19 3tf")
|
|
|
|
# Assert
|
|
assert climate is not None
|
|
assert climate.area == "BD"
|
|
assert climate.district == 19
|
|
|
|
|
|
def test_postcode_climate_returns_none_for_unknown_postcode() -> None:
|
|
"""Postcodes with no Table 172 entry (e.g. synthetic test data) yield
|
|
None so callers can fall back to UK-average climate."""
|
|
# Arrange
|
|
# Act
|
|
result = postcode_climate("ZZ99 9ZZ")
|
|
|
|
# Assert
|
|
assert result is None
|
|
|
|
|
|
def test_postcode_climate_returns_none_for_malformed() -> None:
|
|
"""Empty or letter-only postcodes return None rather than raising."""
|
|
# Arrange
|
|
# Act
|
|
# Assert
|
|
assert postcode_climate("") is None
|
|
assert postcode_climate(None) is None
|
|
assert postcode_climate("XYZ") is None
|