"""SAP 10.2 Appendix U — climate data lookups. Source: BRE, *The Government's Standard Assessment Procedure for Energy Rating of Dwellings, SAP 10.2* (14-03-2025), Appendix U. Three monthly tables across 22 SAP climate regions (index 0 = UK average, 1-21 = named regions per Table U6 postcode mapping): - Table U1: Mean external temperature (°C) - Table U2: Wind speed (m/s) - Table U3: Mean global solar irradiance on a horizontal plane (W/m²) plus monthly solar declination (°) Month is 1-12 (January = 1). Region indices map to the SAP 10.2 region names; lookup helpers raise `ValueError` on out-of-range inputs so callers can fail fast. """ from __future__ import annotations from typing import Final from domain.sap10_calculator.tables.pcdb.postcode_weather import PostcodeClimate # Table U1 — Mean external temperature (°C), 22 regions × 12 months. # Row order: region 0 (UK average) first, then regions 1-21 in spec order. _TABLE_U1: Final[tuple[tuple[float, ...], ...]] = ( (4.3, 4.9, 6.5, 8.9, 11.7, 14.6, 16.6, 16.4, 14.1, 10.6, 7.1, 4.2), # 0 UK average (5.1, 5.6, 7.4, 9.9, 13.0, 16.0, 17.9, 17.8, 15.2, 11.6, 8.0, 5.1), # 1 Thames (5.0, 5.4, 7.1, 9.5, 12.6, 15.4, 17.4, 17.5, 15.0, 11.7, 8.1, 5.2), # 2 South East England (5.4, 5.7, 7.3, 9.6, 12.6, 15.4, 17.3, 17.3, 15.0, 11.8, 8.4, 5.5), # 3 Southern England (6.1, 6.4, 7.5, 9.3, 11.9, 14.5, 16.2, 16.3, 14.6, 11.8, 9.0, 6.4), # 4 South West England (4.9, 5.3, 7.0, 9.3, 12.2, 15.0, 16.7, 16.7, 14.4, 11.1, 7.8, 4.9), # 5 Severn Wales / Severn England (4.3, 4.8, 6.6, 9.0, 11.8, 14.8, 16.6, 16.5, 14.0, 10.5, 7.1, 4.2), # 6 Midlands (4.7, 5.2, 6.7, 9.1, 12.0, 14.7, 16.4, 16.3, 14.1, 10.7, 7.5, 4.6), # 7 West Pennines Wales / West Pennines England (3.9, 4.3, 5.6, 7.9, 10.7, 13.2, 14.9, 14.8, 12.8, 9.7, 6.6, 3.7), # 8 North West England / South West Scotland (4.0, 4.5, 5.8, 7.9, 10.4, 13.3, 15.2, 15.1, 13.1, 9.7, 6.6, 3.7), # 9 Borders Scotland / Borders England (4.0, 4.6, 6.1, 8.3, 10.9, 13.8, 15.8, 15.6, 13.5, 10.1, 6.7, 3.8), # 10 North East England (4.3, 4.9, 6.5, 8.9, 11.7, 14.6, 16.6, 16.4, 14.1, 10.6, 7.1, 4.2), # 11 East Pennines (4.7, 5.2, 7.0, 9.5, 12.5, 15.4, 17.6, 17.6, 15.0, 11.4, 7.7, 4.7), # 12 East Anglia (5.0, 5.3, 6.5, 8.3, 11.2, 13.7, 15.3, 15.3, 13.5, 10.7, 7.8, 5.2), # 13 Wales (4.0, 4.4, 5.6, 7.9, 10.4, 13.0, 14.5, 14.4, 12.5, 9.3, 6.5, 3.8), # 14 West Scotland (3.6, 4.0, 5.4, 7.7, 10.1, 12.9, 14.6, 14.5, 12.5, 9.2, 6.1, 3.2), # 15 East Scotland (3.3, 3.6, 5.0, 7.1, 9.3, 12.2, 14.0, 13.9, 12.0, 8.8, 5.7, 2.9), # 16 North East Scotland (3.1, 3.2, 4.4, 6.6, 8.9, 11.4, 13.2, 13.1, 11.3, 8.2, 5.4, 2.7), # 17 Highland (5.2, 5.0, 5.8, 7.6, 9.7, 11.8, 13.4, 13.6, 12.1, 9.6, 7.3, 5.2), # 18 Western Isles (4.4, 4.2, 5.0, 7.0, 8.9, 11.2, 13.1, 13.2, 11.7, 9.1, 6.6, 4.3), # 19 Orkney (4.6, 4.1, 4.7, 6.5, 8.3, 10.5, 12.4, 12.8, 11.4, 8.8, 6.5, 4.6), # 20 Shetland (4.8, 5.2, 6.4, 8.4, 10.9, 13.5, 15.0, 14.9, 13.1, 10.0, 7.2, 4.7), # 21 Northern Ireland ) # Table U2 — Wind speed (m/s), 22 regions × 12 months. _TABLE_U2: Final[tuple[tuple[float, ...], ...]] = ( (5.1, 5.0, 4.9, 4.4, 4.3, 3.8, 3.8, 3.7, 4.0, 4.3, 4.5, 4.7), # 0 UK average (4.2, 4.0, 4.0, 3.7, 3.7, 3.3, 3.4, 3.2, 3.3, 3.5, 3.5, 3.8), # 1 Thames (4.8, 4.5, 4.4, 3.9, 3.9, 3.6, 3.7, 3.5, 3.7, 4.0, 4.1, 4.4), # 2 South East England (5.1, 4.7, 4.6, 4.3, 4.3, 4.0, 4.0, 3.9, 4.0, 4.5, 4.4, 4.7), # 3 Southern England (6.0, 5.6, 5.6, 5.0, 5.0, 4.4, 4.4, 4.3, 4.7, 5.4, 5.5, 5.9), # 4 South West England (4.9, 4.6, 4.7, 4.3, 4.3, 3.8, 3.8, 3.7, 3.8, 4.3, 4.3, 4.6), # 5 Severn Wales / Severn England (4.5, 4.5, 4.4, 3.9, 3.8, 3.4, 3.3, 3.3, 3.5, 3.8, 3.9, 4.1), # 6 Midlands (4.8, 4.7, 4.6, 4.2, 4.1, 3.7, 3.7, 3.7, 3.7, 4.2, 4.3, 4.5), # 7 West Pennines Wales / West Pennines England (5.2, 5.2, 5.0, 4.4, 4.3, 3.9, 3.7, 3.7, 4.1, 4.6, 4.8, 4.7), # 8 North West England / South West Scotland (5.2, 5.2, 5.0, 4.4, 4.1, 3.8, 3.5, 3.5, 3.9, 4.2, 4.6, 4.7), # 9 Borders Scotland / Borders England (5.3, 5.2, 5.0, 4.3, 4.2, 3.9, 3.6, 3.6, 4.1, 4.3, 4.6, 4.8), # 10 North East England (5.1, 5.0, 4.9, 4.4, 4.3, 3.8, 3.8, 3.7, 4.0, 4.3, 4.5, 4.7), # 11 East Pennines (4.9, 4.8, 4.7, 4.2, 4.2, 3.7, 3.8, 3.8, 4.0, 4.2, 4.3, 4.5), # 12 East Anglia (6.5, 6.2, 5.9, 5.2, 5.1, 4.7, 4.5, 4.5, 5.0, 5.7, 6.0, 6.0), # 13 Wales (6.2, 6.2, 5.9, 5.2, 4.9, 4.7, 4.3, 4.3, 4.9, 5.4, 5.7, 5.4), # 14 West Scotland (5.7, 5.8, 5.7, 5.0, 4.8, 4.6, 4.1, 4.1, 4.7, 5.0, 5.2, 5.0), # 15 East Scotland (5.7, 5.8, 5.7, 5.0, 4.6, 4.4, 4.0, 4.1, 4.6, 5.2, 5.3, 5.1), # 16 North East Scotland (6.5, 6.8, 6.4, 5.7, 5.1, 5.1, 4.6, 4.5, 5.3, 5.8, 6.1, 5.7), # 17 Highland (8.3, 8.4, 7.9, 6.6, 6.1, 5.6, 5.6, 5.6, 6.3, 7.3, 7.7, 7.5), # 18 Western Isles (7.9, 8.3, 7.9, 7.1, 6.2, 6.1, 5.5, 5.6, 6.4, 7.3, 7.8, 7.3), # 19 Orkney (9.5, 9.4, 8.7, 7.5, 6.6, 6.4, 5.7, 6.0, 7.2, 8.5, 8.9, 8.5), # 20 Shetland (5.4, 5.3, 5.0, 4.7, 4.5, 4.1, 3.9, 3.7, 4.2, 4.6, 5.0, 5.0), # 21 Northern Ireland ) _REGION_COUNT: Final[int] = 22 _MONTHS_PER_YEAR: Final[int] = 12 def _validate_month(month: int) -> None: if not 1 <= month <= _MONTHS_PER_YEAR: raise ValueError(f"month must be 1..12 (January = 1), got {month}") def _validate(region: int, month: int) -> None: if not 0 <= region < _REGION_COUNT: raise ValueError( f"region must be 0..{_REGION_COUNT - 1} (SAP climate region; " f"0 = UK average), got {region}" ) _validate_month(month) def external_temperature_c( region_or_climate: "int | PostcodeClimate", month: int ) -> float: """Mean external temperature (°C) per month. Accepts either a SAP region index (0..21) for the Appendix U fallback tables, or a `PostcodeClimate` record for postcode-specific demand-cascade values from PCDB Table 172.""" if isinstance(region_or_climate, PostcodeClimate): _validate_month(month) return region_or_climate.monthly_external_temp_c[month - 1] _validate(region_or_climate, month) return _TABLE_U1[region_or_climate][month - 1] def wind_speed_m_per_s( region_or_climate: "int | PostcodeClimate", month: int ) -> float: """Mean wind speed (m/s) per month. Accepts either a SAP region index (0..21) or a `PostcodeClimate` record.""" if isinstance(region_or_climate, PostcodeClimate): _validate_month(month) return region_or_climate.monthly_wind_speed_m_per_s[month - 1] _validate(region_or_climate, month) return _TABLE_U2[region_or_climate][month - 1] # Table U3 — Mean global solar irradiance on a horizontal plane (W/m²), # 22 regions × 12 months. Used (with Table U3 declination + per-window # orientation/pitch) to derive surface flux for solar-gains calculation # (SAP 10.2 §6.1). _TABLE_U3: Final[tuple[tuple[float, ...], ...]] = ( (26, 54, 96, 150, 192, 200, 189, 157, 115, 66, 33, 21), # 0 UK average (30, 56, 98, 157, 195, 217, 203, 173, 127, 73, 39, 24), # 1 Thames (32, 59, 104, 170, 208, 231, 216, 182, 133, 77, 41, 25), # 2 South East England (35, 62, 109, 172, 209, 235, 217, 185, 138, 80, 44, 27), # 3 Southern England (36, 63, 111, 174, 210, 233, 204, 182, 136, 78, 44, 28), # 4 South West England (32, 59, 105, 167, 201, 226, 206, 175, 130, 74, 40, 25), # 5 Severn Wales / Severn England (28, 55, 97, 153, 191, 208, 194, 163, 121, 69, 35, 23), # 6 Midlands (24, 51, 95, 152, 191, 203, 186, 152, 115, 65, 31, 20), # 7 West Pennines Wales / West Pennines England (23, 51, 95, 157, 200, 203, 194, 156, 113, 62, 30, 19), # 8 North West England / South West Scotland (23, 50, 92, 151, 200, 196, 187, 153, 111, 61, 30, 18), # 9 Borders Scotland / Borders England (25, 51, 95, 152, 196, 198, 190, 156, 115, 64, 32, 20), # 10 North East England (26, 54, 96, 150, 192, 200, 189, 157, 115, 66, 33, 21), # 11 East Pennines (30, 58, 101, 165, 203, 220, 206, 173, 128, 74, 39, 24), # 12 East Anglia (29, 57, 104, 164, 205, 220, 199, 167, 120, 68, 35, 22), # 13 Wales (19, 46, 88, 148, 196, 193, 185, 150, 101, 55, 25, 15), # 14 West Scotland (21, 46, 89, 146, 198, 191, 183, 150, 106, 57, 27, 15), # 15 East Scotland (19, 45, 89, 143, 194, 188, 177, 144, 101, 54, 25, 14), # 16 North East Scotland (17, 43, 85, 145, 189, 185, 170, 139, 98, 51, 22, 12), # 17 Highland (16, 41, 87, 155, 205, 206, 185, 148, 101, 51, 21, 11), # 18 Western Isles (14, 39, 84, 143, 205, 201, 178, 145, 100, 50, 19, 9), # 19 Orkney (12, 34, 79, 135, 196, 190, 168, 144, 90, 46, 16, 7), # 20 Shetland (24, 52, 96, 155, 201, 198, 183, 150, 107, 61, 30, 18), # 21 Northern Ireland ) def horizontal_solar_irradiance_w_per_m2( region_or_climate: "int | PostcodeClimate", month: int, ) -> float: """Mean global solar irradiance on a horizontal plane (W/m²). Accepts either a SAP region index (0..21) or a `PostcodeClimate` record. The starting point for the per-orientation surface-flux calculation in SAP 10.2 §6.1.""" if isinstance(region_or_climate, PostcodeClimate): _validate_month(month) return region_or_climate.monthly_horizontal_solar_w_per_m2[month - 1] _validate(region_or_climate, month) return float(_TABLE_U3[region_or_climate][month - 1]) # Table U3 footer — Solar declination (°), region-independent (function of # month only). Used together with site latitude and the surface tilt to # convert horizontal irradiance to per-orientation surface flux. _SOLAR_DECLINATION: Final[tuple[float, ...]] = ( -20.7, -12.8, -1.8, 9.8, 18.8, 23.1, 21.2, 13.7, 2.9, -8.7, -18.4, -23.0, ) def solar_declination_deg(month: int) -> float: """Solar declination angle (°) for the given month. SAP 10.2 Appendix U Table U3 footer — independent of region.""" _validate_month(month) return _SOLAR_DECLINATION[month - 1]