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>
148 lines
4.8 KiB
Python
148 lines
4.8 KiB
Python
"""Tests for SAP 10.2 Appendix U climate-data lookups.
|
|
|
|
Reference: SAP 10.2 specification (BRE, 14-03-2025), Appendix U:
|
|
Table U1 mean external temperature, Table U2 wind speed, Table U3 mean
|
|
global solar irradiance on a horizontal plane and monthly solar declination.
|
|
22 regions (0 = UK average, 1-21 = SAP climate regions) by 12 months.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from domain.sap10_calculator.climate.appendix_u import (
|
|
external_temperature_c,
|
|
horizontal_solar_irradiance_w_per_m2,
|
|
solar_declination_deg,
|
|
wind_speed_m_per_s,
|
|
)
|
|
|
|
|
|
def test_external_temperature_uk_average_january_returns_table_u1_value() -> None:
|
|
# Arrange — SAP 10.2 Appendix U Table U1: Region 0 (UK average), January.
|
|
|
|
# Act
|
|
result = external_temperature_c(0, month=1)
|
|
|
|
# Assert
|
|
assert abs(result - 4.3) <= 0.05
|
|
|
|
|
|
def test_external_temperature_thames_july_returns_named_region_value() -> None:
|
|
# Arrange — Table U1: Region 1 (Thames), July. Hotter than the UK average
|
|
# in summer — sanity check that named regions diverge from region 0.
|
|
|
|
# Act
|
|
result = external_temperature_c(1, month=7)
|
|
|
|
# Assert
|
|
assert abs(result - 17.9) <= 0.05
|
|
|
|
|
|
def test_wind_speed_uk_average_january_returns_table_u2_value() -> None:
|
|
# Arrange — Table U2 row 0 (UK average) column Jan -> 5.1 m/s. Used by the
|
|
# SAP infiltration calc (worksheet lines 9-16).
|
|
|
|
# Act
|
|
result = wind_speed_m_per_s(0, month=1)
|
|
|
|
# Assert
|
|
assert abs(result - 5.1) <= 0.05
|
|
|
|
|
|
def test_horizontal_solar_irradiance_uk_average_july_returns_table_u3_value() -> None:
|
|
# Arrange — Table U3 row 0 (UK average) column Jul -> 189 W/m². Peak month
|
|
# for global horizontal irradiance in the UK.
|
|
|
|
# Act
|
|
result = horizontal_solar_irradiance_w_per_m2(0, month=7)
|
|
|
|
# Assert
|
|
assert abs(result - 189.0) <= 0.5
|
|
|
|
|
|
def test_horizontal_solar_irradiance_southern_england_brighter_than_shetland() -> None:
|
|
# Arrange — Table U3 row 3 (Southern England) Jun -> 235, row 20 (Shetland)
|
|
# Jun -> 190. Higher-latitude regions get less June irradiance.
|
|
|
|
# Act
|
|
south = horizontal_solar_irradiance_w_per_m2(3, month=6)
|
|
shetland = horizontal_solar_irradiance_w_per_m2(20, month=6)
|
|
|
|
# Assert
|
|
assert abs(south - 235.0) <= 0.5
|
|
assert abs(shetland - 190.0) <= 0.5
|
|
assert south > shetland
|
|
|
|
|
|
def test_solar_declination_winter_solstice_returns_table_u3_value() -> None:
|
|
# Arrange — Table U3 footer "Solar declination" row: December = -23.0°.
|
|
# Declination is region-independent (function only of month).
|
|
|
|
# Act
|
|
result = solar_declination_deg(month=12)
|
|
|
|
# Assert
|
|
assert abs(result - -23.0) <= 0.05
|
|
|
|
|
|
def test_solar_declination_summer_solstice_positive_value() -> None:
|
|
# Arrange — Table U3 footer: June declination = +23.1°.
|
|
|
|
# Act
|
|
result = solar_declination_deg(month=6)
|
|
|
|
# Assert
|
|
assert abs(result - 23.1) <= 0.05
|
|
|
|
|
|
def test_external_temperature_out_of_range_region_raises_value_error() -> None:
|
|
# Arrange — there are 22 regions (0-21); 22 is the first invalid index.
|
|
# The callers (postcode resolver in particular) should fail fast on a
|
|
# bad region rather than silently aliasing to row 0 or wrapping around.
|
|
|
|
# Act / Assert
|
|
with pytest.raises(ValueError, match="region"):
|
|
external_temperature_c(22, month=1)
|
|
with pytest.raises(ValueError, match="region"):
|
|
external_temperature_c(-1, month=1)
|
|
|
|
|
|
def test_region_21_northern_ireland_returns_table_u1_value() -> None:
|
|
# Arrange — region 21 (Northern Ireland) is the last valid region. Catches
|
|
# off-by-one errors in the region-bound check (would otherwise reject 21).
|
|
# Table U1 row 21 July -> 15.0 °C.
|
|
|
|
# Act
|
|
result = external_temperature_c(21, month=7)
|
|
|
|
# Assert
|
|
assert abs(result - 15.0) <= 0.05
|
|
|
|
|
|
def test_out_of_range_month_raises_value_error_on_every_lookup() -> None:
|
|
# Arrange — months are 1..12. Month 0 and month 13 must reject across
|
|
# all four climate lookups, including the region-independent declination.
|
|
|
|
# Act / Assert
|
|
with pytest.raises(ValueError, match="month"):
|
|
external_temperature_c(0, month=0)
|
|
with pytest.raises(ValueError, match="month"):
|
|
wind_speed_m_per_s(0, month=13)
|
|
with pytest.raises(ValueError, match="month"):
|
|
horizontal_solar_irradiance_w_per_m2(0, month=0)
|
|
with pytest.raises(ValueError, match="month"):
|
|
solar_declination_deg(month=13)
|
|
|
|
|
|
def test_wind_speed_shetland_january_higher_than_thames() -> None:
|
|
# Arrange — Table U2 row 20 (Shetland), the windiest UK region by a wide
|
|
# margin: 9.5 m/s in January vs Thames 4.2 m/s. Sanity check the table is
|
|
# populated for the upper region indices, not silently aliasing to row 0.
|
|
|
|
# Act
|
|
shetland = wind_speed_m_per_s(20, month=1)
|
|
thames = wind_speed_m_per_s(1, month=1)
|
|
|
|
# Assert
|
|
assert abs(shetland - 9.5) <= 0.05
|
|
assert abs(thames - 4.2) <= 0.05
|
|
assert shetland > thames
|