diff --git a/packages/domain/src/domain/sap/rdsap/cert_to_inputs.py b/packages/domain/src/domain/sap/rdsap/cert_to_inputs.py index fe18d5ec..91968326 100644 --- a/packages/domain/src/domain/sap/rdsap/cert_to_inputs.py +++ b/packages/domain/src/domain/sap/rdsap/cert_to_inputs.py @@ -720,6 +720,10 @@ def cert_to_inputs( passive_vents=vc.passive_vents, flueless_gas_fires=vc.flueless_gas_fires, window_pct_draught_proofed=float(epc.percent_draughtproofed or 0), + # SAP §2 shelter factor: 2 sheltered sides default per typical + # UK terraced/semi-detached layout. The cert doesn't lodge a + # sheltered-sides count, so we apply the spec's typical default. + sheltered_sides=2, ) main = _first_main_heating(epc) diff --git a/packages/domain/src/domain/sap/worksheet/ventilation.py b/packages/domain/src/domain/sap/worksheet/ventilation.py index 10dc26e2..ca00393e 100644 --- a/packages/domain/src/domain/sap/worksheet/ventilation.py +++ b/packages/domain/src/domain/sap/worksheet/ventilation.py @@ -70,9 +70,14 @@ def infiltration_ach( suspended_timber_floor_sealed: bool = False, has_draught_lobby: bool = False, window_pct_draught_proofed: float = 0.0, + sheltered_sides: int = 0, ) -> InfiltrationBreakdown: """Air-change rate (ach) per SAP 10.3 §2 / RdSAP10 §4.1, no pressure - test path.""" + test path. `sheltered_sides` defaults to 0 (no shelter; spec-pure + intermediate value). Callers can pass 2 (typical UK terraced / + semi-detached) to apply the SAP §2 shelter factor + (1 - 0.075 × sheltered_sides) so the returned total_ach is the + effective rate after wind shelter.""" if volume_m3 <= 0: raise ValueError(f"volume_m3 must be > 0, got {volume_m3}") openings_m3_h = ( @@ -95,7 +100,11 @@ def infiltration_ach( floor = 0.0 draught_lobby = 0.0 if has_draught_lobby else 0.05 window = 0.25 - 0.2 * (window_pct_draught_proofed / 100.0) - total = openings + additional + structural + floor + draught_lobby + window + raw_total = openings + additional + structural + floor + draught_lobby + window + # SAP §2 worksheet line 22 shelter factor: 1 - 0.075 × sheltered_sides. + # 2 sheltered sides → multiply by 0.85. + shelter_factor = 1.0 - 0.075 * max(0, min(4, sheltered_sides)) + total = raw_total * shelter_factor return InfiltrationBreakdown( openings_ach=openings, additional_ach=additional,