mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.116: A_RR_shell rounded to 2 d.p. per RdSAP 10 §15 (p.66)
RdSAP 10 Specification §15 "Rounding of data" (PDF p.66):
"For consistency of application, after expanding the RdSAP data into
SAP data using the rules in this Appendix, the data are rounded
before being passed to the SAP calculator. The rounding rules are:
U-values: 2 d.p.
All element areas (gross) including window areas and conservatory
wall area: 2 d.p."
The §3.9.1 / §3.10.1 shell formula A_RR_shell = 12.5 × √(A_RR_floor /
1.5) produces a gross element area for the room-in-roof. Pre-slice the
cascade kept the raw float (e.g. cert 000565 BP[0]: 12.5 × √30 =
68.46532...), then subtracted lodged wall surfaces to obtain the (30)
residual roof area. The worksheet rounds A_RR_shell to 2 d.p. (68.47)
BEFORE the subtraction — per §15 above.
Cert 000565 has three BPs that fire this path (Main, Ext1, Ext3 — all
have detailed wall surfaces with no `slope` / `flat_ceiling` /
`stud_wall` lodgement, so §3.10.1 residual fires). Each contributes a
sub-rounding residual that the unrounded cascade was missing:
BP[0] Main: 68.4653 → 68.47; residual 43.9653 → 43.97 (+0.0016 W/K)
BP[1] Ext1: 59.5119 → 59.51; residual 18.2519 → 18.25 (−0.0007 W/K)
BP[3] Ext3: 57.7350 → 57.74; residual 17.3450 → 17.35 (+0.0017 W/K)
Movement (HEAD `d0268a5b` → this slice) for cert 000565:
roof_w_per_k 51.3768 → 51.3795 ✓ EXACT (Δ −0.0027 → 0.0)
thermal_bridging 128.6448 → 128.6460 ✓ EXACT (Δ −0.0012 → 0.0)
total_external_a 857.6323 → 857.6400 ✓ EXACT (Δ −0.0077 → 0.0)
space_heating_kwh 59008.2363 → 59008.3499 ✓ EXACT (Δ −0.1136 → 0.0)
main_fuel_kwh 34710.7272 → 34710.7941 ✓ EXACT (Δ −0.0669 → 0.0)
total_fuel_cost 4680.2515 → 4680.2593 ✓ EXACT (Δ −0.0078 → 0.0)
co2_kg_per_yr 6447.6161 → 6447.6263 ✓ EXACT (Δ −0.0102 → 0.0)
sap_score_cont 28.5087 → 28.5087 ✓ EXACT (Δ +4.2e-5 → −4.7e-5)
sap_score (int) 29 ✓ EXACT (preserved)
ecf 5.38682 → 5.38683 (vs ws 5.3868, Δ +3.2e-5)
Cert 000565 truly closes — every SAP-result field within 1e-4 of the
worksheet PDF.
Cohort safety: 6 cohort certs (000474..000516) unchanged — cohort
000516's roof routes through the Detailed branch with `slope` /
`flat_ceiling` / `stud_wall` lodgements, so `has_roof_lodgement=True`
short-circuits the §3.10.1 residual block. Cohort certs 000474/477/
480/487/490 are pre-S0380.103 hand-built fixtures whose RR fields don't
exercise the simplified A_RR_shell path (rir.floor_area=0 or
detailed_surfaces only).
Test added: `test_summary_000565_a_rr_shell_rounded_2_dp_closes_roof_
w_per_k_per_rdsap_10_section_15` pins the cascade roof_w_per_k = 51.3795
exactly (Δ ≤ 1e-4 vs worksheet (30) Σ).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
d0268a5b5c
commit
f2e8b657ce
2 changed files with 72 additions and 3 deletions
|
|
@ -1692,6 +1692,63 @@ def test_summary_000565_detailed_rr_residual_area_closes_total_external_area_per
|
|||
)
|
||||
|
||||
|
||||
def test_summary_000565_a_rr_shell_rounded_2_dp_closes_roof_w_per_k_per_rdsap_10_section_15() -> None:
|
||||
# Arrange — RdSAP 10 §15 "Rounding of data" (PDF p.66):
|
||||
# "For consistency of application, after expanding the RdSAP data
|
||||
# into SAP data using the rules in this Appendix, the data are
|
||||
# rounded before being passed to the SAP calculator. The rounding
|
||||
# rules are: ... All element areas (gross) including window areas
|
||||
# and conservatory wall area: 2 d.p."
|
||||
# The §3.9.1 / §3.10.1 simplified-formula A_RR_shell = 12.5 × √(A_RR_
|
||||
# floor / 1.5) produces a gross element area for the room-in-roof
|
||||
# shell. Pre-slice the cascade kept the raw float (e.g. cert 000565
|
||||
# BP[0]: 12.5 × √(45/1.5) = 68.46532...), then subtracted lodged
|
||||
# wall surfaces to obtain the residual roof area. The worksheet
|
||||
# rounds A_RR_shell to 2 d.p. (68.47) BEFORE the subtraction —
|
||||
# which moves Main's residual from 43.97 − 0.0047 = 43.9653 (cascade)
|
||||
# to exactly 43.97 (worksheet) per RdSAP 10 §15.
|
||||
#
|
||||
# Cert 000565 has three BPs that hit this path (Main, Ext1, Ext3 —
|
||||
# all have detailed wall surfaces with no `slope` / `flat_ceiling`
|
||||
# / `stud_wall` lodgement, so the §3.10.1 residual fires). Each
|
||||
# contributes a sub-rounding residual ≤ 0.005 m² × U_RR_default that
|
||||
# the unrounded cascade was missing:
|
||||
#
|
||||
# BP[0] Main: A_RR=68.4653 raw → 68.47 rounded; residual
|
||||
# 43.9653 → 43.97 (+0.0047 m² × U=0.35 = +0.0016 W/K)
|
||||
# BP[1] Ext1: A_RR=59.5119 raw → 59.51 rounded; residual
|
||||
# 18.2519 → 18.25 (−0.0019 m² × U=0.35 = −0.00068 W/K)
|
||||
# BP[3] Ext3: A_RR=57.7350 raw → 57.74 rounded; residual
|
||||
# 17.3450 → 17.35 (+0.005 m² × U=0.35 = +0.0017 W/K)
|
||||
#
|
||||
# Worksheet (30) per-line breakdown (U985-0001-000565.pdf):
|
||||
# Main remaining area 43.97 × 0.35 = 15.3895
|
||||
# Ext1 remaining area 18.25 × 0.35 = 6.3875
|
||||
# Ext2 stud + slope + external roof = 14.9800
|
||||
# Ext3 remaining area 17.35 × 0.35 = 6.0725
|
||||
# Ext4 flat ceilings + slope = 8.5500
|
||||
# Σ (30) = 51.3795
|
||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000565_PDF)
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
||||
|
||||
# Act
|
||||
from domain.sap10_calculator.worksheet.heat_transmission import (
|
||||
heat_transmission_from_cert,
|
||||
)
|
||||
ht = heat_transmission_from_cert(epc, door_count=epc.door_count or 0)
|
||||
|
||||
# Assert — cascade roof_w_per_k pins to worksheet (30) Σ at abs=1e-4.
|
||||
expected_roof_w_per_k = 51.3795
|
||||
diff = abs(ht.roof_w_per_k - expected_roof_w_per_k)
|
||||
assert diff <= 1e-4, (
|
||||
f"cascade roof_w_per_k={ht.roof_w_per_k:.6f} vs worksheet (30) Σ="
|
||||
f"{expected_roof_w_per_k}; diff={diff:.6f}. Per RdSAP 10 §15 (p.66) "
|
||||
f"the A_RR_shell formula 12.5 × √(A_RR_floor / 1.5) must round to "
|
||||
f"2 d.p. before the §3.10.1 residual subtraction."
|
||||
)
|
||||
|
||||
|
||||
def test_summary_000565_ext2_stud_wall_2_extracts_400_plus_mm_pur_or_pir_lodgement() -> None:
|
||||
# Arrange — cert 000565 Summary §8.1 BP[2] Ext2 (Detailed) lodges
|
||||
# "Stud Wall 2: 2.00 × 2.00, 400+ mm, PUR or PIR" with Default
|
||||
|
|
|
|||
|
|
@ -374,9 +374,14 @@ def _part_geometry(part: SapBuildingPart) -> dict[str, float]:
|
|||
rr_floor_area = float(rir.floor_area)
|
||||
# Simplified A_RR formula only fires when no Detailed (§3.10)
|
||||
# per-surface lodgement is present. With Detailed lodgement the
|
||||
# main loop iterates `rir.detailed_surfaces` directly.
|
||||
# main loop iterates `rir.detailed_surfaces` directly. The shell
|
||||
# area `12.5 × √(A_RR_floor / 1.5)` is a gross element area;
|
||||
# RdSAP 10 §15 (p.66) "All element areas (gross) ... 2 d.p."
|
||||
# requires it be rounded before the (30) residual subtraction
|
||||
# — cert 000565 BP[0] 12.5 × √30 = 68.4653 → 68.47 closes the
|
||||
# remaining_area_main = 43.97 worksheet pin to 1e-4.
|
||||
if not rir.detailed_surfaces:
|
||||
rr_a_rr = 12.5 * sqrt(rr_floor_area / 1.5)
|
||||
rr_a_rr = _round_half_up(12.5 * sqrt(rr_floor_area / 1.5), _AREA_ROUND_DP)
|
||||
# RdSAP10 §3.9.2 Simplified Type 2 — accessible common walls
|
||||
# under 1.8 m treat the space as RR. Common wall area = L ×
|
||||
# (0.25 + H). The 0.25 m accounts for the structural gap between
|
||||
|
|
@ -977,7 +982,14 @@ def heat_transmission_from_cert(
|
|||
has_roof_lodgement = bool(kinds & roof_kinds)
|
||||
rr_floor_for_a_rr = float(rir.floor_area)
|
||||
if not has_roof_lodgement and rr_floor_for_a_rr > 0.0:
|
||||
a_rr_shell = 12.5 * sqrt(rr_floor_for_a_rr / 1.5)
|
||||
# RdSAP 10 §15 (p.66) "All element areas (gross) ... 2
|
||||
# d.p." — round A_RR_shell before the (30) residual
|
||||
# subtraction so the cascade matches the worksheet's
|
||||
# 2-d.p. element-area convention (cert 000565 BP[0]
|
||||
# 68.4653 → 68.47, BP[3] 57.7350 → 57.74).
|
||||
a_rr_shell = _round_half_up(
|
||||
12.5 * sqrt(rr_floor_for_a_rr / 1.5), _AREA_ROUND_DP,
|
||||
)
|
||||
residual_area = max(0.0, a_rr_shell - rr_walls_in_a_rr_area)
|
||||
if residual_area > 0.0:
|
||||
rr_detailed_area += residual_area
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue