"""SAP 10.2 Table 4b (PDF p.168) — "Seasonal efficiency for gas and liquid fuel boilers", winter / summer pair per Table 4b sub-row code (`sap_main_heating_code` 101-141). This table is the spec-canonical fallback when a gas / oil boiler is NOT in the PCDB. Winter efficiency feeds (206)..(212) space heating; summer efficiency feeds Appendix D §D2.1 (2) Equation D1 alongside winter to derive the worksheet (217)m monthly water-heating efficiency. Codes are grouped in Table 4b by boiler type: 101-109 Gas boilers (mains, LPG, biogas) 1998 or later 110-114 Gas pre-1998 with fan-assisted flue 115-119 Gas pre-1998 with balanced / open flue 120-123 Combined Primary Storage Units (CPSU) 124-132 Liquid fuel boilers (oil, etc.) 133-141 Range cooker boilers (gas + liquid fuel) The winter column is duplicated in `domain.sap10_ml.sap_efficiencies. _SPACE_EFF_BY_CODE` for backward-compat with that module's interim ML cascade; the canonical source for new cascade work is here per [[sap10_ml deprecation]] memory. """ from __future__ import annotations from typing import Final, Optional # Verbatim from SAP 10.2 spec PDF p.168 (the "Boiler ... Efficiency, % # Winter / Summer" table). All values percent. _TABLE_4B_SEASONAL_EFF_PCT_BY_CODE: Final[dict[int, tuple[float, float]]] = { # Gas boilers (including mains gas, LPG and biogas) 1998 or later 101: (74.0, 64.0), # Regular non-condensing with automatic ignition 102: (84.0, 74.0), # Regular condensing with automatic ignition 103: (74.0, 65.0), # Non-condensing combi with automatic ignition 104: (84.0, 75.0), # Condensing combi with automatic ignition 105: (70.0, 60.0), # Regular non-condensing with permanent pilot 106: (80.0, 70.0), # Regular condensing with permanent pilot 107: (70.0, 61.0), # Non-condensing combi with permanent pilot 108: (80.0, 71.0), # Condensing combi with permanent pilot 109: (66.0, 56.0), # Back boiler to radiators # Gas pre-1998 with fan-assisted flue 110: (73.0, 63.0), # Regular, low thermal capacity 111: (69.0, 59.0), # Regular, high or unknown thermal capacity 112: (71.0, 62.0), # Combi 113: (84.0, 75.0), # Condensing combi 114: (84.0, 74.0), # Regular, condensing # Gas pre-1998 with balanced or open flue 115: (66.0, 56.0), # Regular, wall mounted 116: (56.0, 46.0), # Regular, floor mounted, pre 1979 117: (66.0, 56.0), # Regular, floor mounted, 1979 to 1997 118: (66.0, 57.0), # Combi 119: (66.0, 56.0), # Back boiler to radiators # Combined Primary Storage Units (CPSU) 120: (74.0, 72.0), # With automatic ignition (non-condensing) 121: (83.0, 81.0), # With automatic ignition (condensing) 122: (70.0, 68.0), # With permanent pilot (non-condensing) 123: (79.0, 77.0), # With permanent pilot (condensing) # Liquid fuel boilers 124: (66.0, 54.0), # Standard oil boiler pre-1985 125: (71.0, 59.0), # Standard oil boiler 1985 to 1997 126: (80.0, 68.0), # Standard oil boiler, 1998 or later 127: (84.0, 72.0), # Condensing oil boiler 128: (71.0, 62.0), # Combi oil boiler, pre-1998 129: (77.0, 68.0), # Combi oil boiler, 1998 or later 130: (82.0, 73.0), # Condensing combi oil boiler 131: (66.0, 54.0), # Oil room heater with boiler to radiators, pre 2000 132: (71.0, 59.0), # Oil room heater with boiler to radiators, 2000 or later # Range cooker boilers (mains gas, LPG and biogas) 133: (47.0, 37.0), # Single burner with permanent pilot 134: (51.0, 41.0), # Single burner with automatic ignition 135: (61.0, 51.0), # Twin burner with permanent pilot (non-condensing) pre 1998 136: (66.0, 56.0), # Twin burner with automatic ignition (non-condensing) pre 1998 137: (66.0, 56.0), # Twin burner with permanent pilot (non-condensing) 1998 or later 138: (71.0, 61.0), # Twin burner with automatic ignition (non-condensing) 1998 or later # Range cooker boilers (liquid fuel) 139: (61.0, 49.0), # Single burner 140: (71.0, 59.0), # Twin burner (non-condensing) pre 1998 141: (76.0, 64.0), # Twin burner (non-condensing) 1998 or later } def table_4b_seasonal_efficiencies_pct( sap_main_heating_code: Optional[int], ) -> Optional[tuple[float, float]]: """Return the SAP 10.2 Table 4b `(winter, summer)` efficiency pair as percentages, or `None` when the lodged code is not a Table 4b boiler sub-row (e.g. Table 4a category code, no lodging). Total contract — never raises; non-Table-4b codes fall through to None so the caller can route to the scalar / category cascade. """ if sap_main_heating_code is None: return None return _TABLE_4B_SEASONAL_EFF_PCT_BY_CODE.get(sap_main_heating_code)