"""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