diff --git a/domain/sap10_calculator/climate/tests/test_appendix_u.py b/domain/sap10_calculator/climate/tests/test_appendix_u.py index 8777d161..0d623f2f 100644 --- a/domain/sap10_calculator/climate/tests/test_appendix_u.py +++ b/domain/sap10_calculator/climate/tests/test_appendix_u.py @@ -1,6 +1,6 @@ -"""Tests for SAP 10.3 Appendix U climate-data lookups. +"""Tests for SAP 10.2 Appendix U climate-data lookups. -Reference: SAP 10.3 specification (DESNZ/BRE, 13-01-2026), Appendix U: +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. @@ -17,13 +17,13 @@ from domain.sap10_calculator.climate.appendix_u import ( def test_external_temperature_uk_average_january_returns_table_u1_value() -> None: - # Arrange — SAP 10.3 Appendix U Table U1: Region 0 (UK average), January. + # Arrange — SAP 10.2 Appendix U Table U1: Region 0 (UK average), January. # Act - result = external_temperature_c(region=0, month=1) + result = external_temperature_c(0, month=1) # Assert - assert result == pytest.approx(4.3, abs=0.05) + assert abs(result - 4.3) <= 0.05 def test_external_temperature_thames_july_returns_named_region_value() -> None: @@ -31,10 +31,10 @@ def test_external_temperature_thames_july_returns_named_region_value() -> None: # in summer — sanity check that named regions diverge from region 0. # Act - result = external_temperature_c(region=1, month=7) + result = external_temperature_c(1, month=7) # Assert - assert result == pytest.approx(17.9, abs=0.05) + assert abs(result - 17.9) <= 0.05 def test_wind_speed_uk_average_january_returns_table_u2_value() -> None: @@ -42,10 +42,10 @@ def test_wind_speed_uk_average_january_returns_table_u2_value() -> None: # SAP infiltration calc (worksheet lines 9-16). # Act - result = wind_speed_m_per_s(region=0, month=1) + result = wind_speed_m_per_s(0, month=1) # Assert - assert result == pytest.approx(5.1, abs=0.05) + assert abs(result - 5.1) <= 0.05 def test_horizontal_solar_irradiance_uk_average_july_returns_table_u3_value() -> None: @@ -53,10 +53,10 @@ def test_horizontal_solar_irradiance_uk_average_july_returns_table_u3_value() -> # for global horizontal irradiance in the UK. # Act - result = horizontal_solar_irradiance_w_per_m2(region=0, month=7) + result = horizontal_solar_irradiance_w_per_m2(0, month=7) # Assert - assert result == pytest.approx(189.0, abs=0.5) + assert abs(result - 189.0) <= 0.5 def test_horizontal_solar_irradiance_southern_england_brighter_than_shetland() -> None: @@ -64,12 +64,12 @@ def test_horizontal_solar_irradiance_southern_england_brighter_than_shetland() - # Jun -> 190. Higher-latitude regions get less June irradiance. # Act - south = horizontal_solar_irradiance_w_per_m2(region=3, month=6) - shetland = horizontal_solar_irradiance_w_per_m2(region=20, month=6) + south = horizontal_solar_irradiance_w_per_m2(3, month=6) + shetland = horizontal_solar_irradiance_w_per_m2(20, month=6) # Assert - assert south == pytest.approx(235.0, abs=0.5) - assert shetland == pytest.approx(190.0, abs=0.5) + assert abs(south - 235.0) <= 0.5 + assert abs(shetland - 190.0) <= 0.5 assert south > shetland @@ -81,7 +81,7 @@ def test_solar_declination_winter_solstice_returns_table_u3_value() -> None: result = solar_declination_deg(month=12) # Assert - assert result == pytest.approx(-23.0, abs=0.05) + assert abs(result - -23.0) <= 0.05 def test_solar_declination_summer_solstice_positive_value() -> None: @@ -91,7 +91,7 @@ def test_solar_declination_summer_solstice_positive_value() -> None: result = solar_declination_deg(month=6) # Assert - assert result == pytest.approx(23.1, abs=0.05) + assert abs(result - 23.1) <= 0.05 def test_external_temperature_out_of_range_region_raises_value_error() -> None: @@ -101,9 +101,9 @@ def test_external_temperature_out_of_range_region_raises_value_error() -> None: # Act / Assert with pytest.raises(ValueError, match="region"): - external_temperature_c(region=22, month=1) + external_temperature_c(22, month=1) with pytest.raises(ValueError, match="region"): - external_temperature_c(region=-1, month=1) + external_temperature_c(-1, month=1) def test_region_21_northern_ireland_returns_table_u1_value() -> None: @@ -112,10 +112,10 @@ def test_region_21_northern_ireland_returns_table_u1_value() -> None: # Table U1 row 21 July -> 15.0 °C. # Act - result = external_temperature_c(region=21, month=7) + result = external_temperature_c(21, month=7) # Assert - assert result == pytest.approx(15.0, abs=0.05) + assert abs(result - 15.0) <= 0.05 def test_out_of_range_month_raises_value_error_on_every_lookup() -> None: @@ -124,11 +124,11 @@ def test_out_of_range_month_raises_value_error_on_every_lookup() -> None: # Act / Assert with pytest.raises(ValueError, match="month"): - external_temperature_c(region=0, month=0) + external_temperature_c(0, month=0) with pytest.raises(ValueError, match="month"): - wind_speed_m_per_s(region=0, month=13) + wind_speed_m_per_s(0, month=13) with pytest.raises(ValueError, match="month"): - horizontal_solar_irradiance_w_per_m2(region=0, month=0) + horizontal_solar_irradiance_w_per_m2(0, month=0) with pytest.raises(ValueError, match="month"): solar_declination_deg(month=13) @@ -139,10 +139,10 @@ def test_wind_speed_shetland_january_higher_than_thames() -> None: # populated for the upper region indices, not silently aliasing to row 0. # Act - shetland = wind_speed_m_per_s(region=20, month=1) - thames = wind_speed_m_per_s(region=1, month=1) + shetland = wind_speed_m_per_s(20, month=1) + thames = wind_speed_m_per_s(1, month=1) # Assert - assert shetland == pytest.approx(9.5, abs=0.05) - assert thames == pytest.approx(4.2, abs=0.05) + assert abs(shetland - 9.5) <= 0.05 + assert abs(thames - 4.2) <= 0.05 assert shetland > thames diff --git a/domain/sap10_calculator/tests/test_table_32.py b/domain/sap10_calculator/tests/test_table_32.py index 6569cb98..b297a78d 100644 --- a/domain/sap10_calculator/tests/test_table_32.py +++ b/domain/sap10_calculator/tests/test_table_32.py @@ -31,10 +31,16 @@ from domain.sap10_calculator.tables.table_32 import ( (5, 12.19, "bottled LPG (secondary)"), (9, 3.48, "LPG subject to Special Condition 11F"), (7, 7.60, "biogas (including anaerobic digestion)"), - # Liquid fuels - (4, 7.64, "heating oil"), + # Liquid fuels. Heating oil (4) and FAME (73) deliberately diverge + # from the RdSAP 10 PDF p.95 (which lists 7.64 / 5.44) — the table + # uses the operationally-canonical Elmhurst-worksheet values per + # Slice S0380.131 (oil 7.64→5.44, two independent lodging engines + # agree) and Slice S0380.168 (FAME 5.44→7.64, oil 3/4 worksheets). + # See tables/table_32.py codes 4 / 73 + project-oil-price-spec- + # divergence. + (4, 5.44, "heating oil (worksheet-canonical, S0380.131)"), (71, 7.64, "bio-liquid HVO"), - (73, 5.44, "bio-liquid FAME"), + (73, 7.64, "bio-liquid FAME (worksheet-canonical, S0380.168)"), (75, 6.10, "B30K"), (76, 47.0, "bioethanol"), # Solid fuels @@ -80,10 +86,16 @@ from domain.sap10_calculator.tables.table_32 import ( def test_table_32_unit_prices_match_rdsap10_pdf_page_95( fuel_code: int, expected_p_per_kwh: float, fuel_name: str ) -> None: - """RdSAP10 Table 32 unit prices, sourced verbatim from PDF page 95. - These differ from SAP10.2 Table 12 by carrier (mains gas 3.64→3.48, - heating oil 4.94→7.64, std electricity 16.49→13.19, etc.) — see - `tables/table_32.py` docstring for the spec citation.""" + """RdSAP10 Table 32 unit prices, sourced from PDF page 95. These + differ from SAP10.2 Table 12 by carrier (mains gas 3.64→3.48, std + electricity 16.49→13.19, etc.) — see `tables/table_32.py` docstring + for the spec citation. + + Two codes deliberately diverge from the PDF and use the Elmhurst- + worksheet-canonical price instead (the PDF row is the outlier): + heating oil (4) = 5.44 not 7.64 (Slice S0380.131), bio-liquid FAME + (73) = 7.64 not 5.44 (Slice S0380.168). See project-oil-price-spec- + divergence.""" # Arrange # Act actual = unit_price_p_per_kwh(fuel_code)