mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 27b: §3 element-area rounding to 2 d.p. per RdSAP10 §15 (p.66)
Spec text (RdSAP 10 §15, 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. / [...]" Applied 2-d.p. rounding to every per-element gross area inside heat_transmission_from_cert: gross_wall + party_wall (in _part_geometry), window total area, door area, top_floor (roof) area, ground_floor area, roof-window area, alt-wall area, RR-detailed-surface area. U-values already came from table lookups at 2 d.p. §3 cascade pins (LINE_31/33/36/37) now close at abs=1e-4 for 5 of 6 fixtures. 000487 remains failing on the RR defect (slice 25). Scoreboard: section_cascade_pins: 151 → 170 PASS (+19) e2e SapResult: 27 → 29 PASS (+2) Per-fixture §3 status: field | 474 | 477 | 480 | 487 | 490 | 516 LINE_31 | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ LINE_33 | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ LINE_36 | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ LINE_37 | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
1821f3fef3
commit
d4c090fc7c
2 changed files with 74 additions and 49 deletions
|
|
@ -133,9 +133,12 @@ Two test files contain the strict pins:
|
|||
Total: **169 PASS / 83 FAIL** across the strict pins. 4 of 6 fixtures fully
|
||||
close §1+§2+§4. 000487 is the worst (RR fixture defect propagates everywhere).
|
||||
|
||||
(Post-slice-24: pin counts unchanged at abs=1e-4 — closure was numeric, not
|
||||
gate-clearing. 000516 LINE_33 went 0.8215 → 0.0038 W/K; still > 1e-4 due to
|
||||
unrelated pre-existing wall + window precision drift.)
|
||||
(Post-slice-27b: section_cascade_pins 170 PASS / 16 FAIL, e2e SapResult
|
||||
29 PASS / 43 FAIL. §3 fully closes for 5 of 6 fixtures at abs=1e-4 — every
|
||||
LINE_31/33/36/37 pin passes on 000474/477/480/490/516. Remaining cascade
|
||||
failures are §4 monthly (000477/487 HW defects, slice 25), §3 (000487 RR
|
||||
defect, slice 25), and downstream SapResult pins still drifting because
|
||||
of §5–§9a precision not yet pinned.)
|
||||
|
||||
### B.2 SapResult pin matrix (post-slice-22/23)
|
||||
|
||||
|
|
@ -159,29 +162,27 @@ pumps_fans_kwh_per_yr | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|||
is still off by sub-SAP-point amounts on every fixture — none of `sap_score_
|
||||
continuous` is closed at abs=1e-4.
|
||||
|
||||
### B.3 §3 residuals after slice 27 (floor U §5.12 rounding)
|
||||
### B.3 §3 residuals after slice 27b (RdSAP10 §15 element-area rounding)
|
||||
|
||||
```
|
||||
fixture | LINE_31 Δ | LINE_33 Δ | LINE_36 Δ | LINE_37 Δ
|
||||
000474 | 0.0014 | 0.0032 | 0.0002 | 0.0030
|
||||
000477 | 0.0004 | 0.0013 | ✓ | 0.0013
|
||||
000480 | 0.0060 | 0.0075 | 0.0009 | 0.0084
|
||||
000487 | 8.82 | 37.88 | 1.32 | 39.21
|
||||
000490 | 0.0010 | 0.0013 | 0.0002 | 0.0014
|
||||
000516 | 0.0025 | 0.0038 | 0.0004 | 0.0042
|
||||
000474 | ✓ | ✓ | ✓ | ✓
|
||||
000477 | ✓ | ✓ | ✓ | ✓
|
||||
000480 | ✓ | ✓ | ✓ | ✓
|
||||
000487 | 8.83 | 37.79 | 1.32 | 39.11
|
||||
000490 | ✓ | ✓ | ✓ | ✓
|
||||
000516 | ✓ | ✓ | ✓ | ✓
|
||||
```
|
||||
|
||||
5 of 6 fixtures now have §3 LINE_33 residuals under 0.01 W/K. Slice 27
|
||||
applied the §5.12 mandated 2-d.p. rounding to BS EN ISO 13370 floor
|
||||
U-values, closing 90–95% of the residual on 000474/477/490 (000516 is
|
||||
exposed-floor only; 000487 still has the RR defect).
|
||||
**§3 now closes for 5 of 6 fixtures at abs=1e-4.** Slice 27b applied the
|
||||
RdSAP10 §15 (p.66) rounding policy: "All element areas (gross) including
|
||||
window areas: 2 d.p." Per-element gross wall / party / roof / floor /
|
||||
window / door / alt-wall / RR-sub-area inputs to the §3 cascade are now
|
||||
rounded to 2 d.p. before A × U.
|
||||
|
||||
Remaining 0.0013–0.0075 W/K comes from wall + party-wall area precision
|
||||
(my calc carries `36.4492 m²` where the PDF stores `36.4500` — clearly
|
||||
2-d.p. rounded). The spec page for §3 element-area rounding hasn't been
|
||||
read; if a "round to 0.01 m²" rule exists for §3 areas, applying it
|
||||
would close these. 000487's huge gaps are the RR fixture defect + the
|
||||
U=0.86 external-gable variant our `gable_wall` enum doesn't handle.
|
||||
The remaining work is on 000487 — the worst fixture — driven by an
|
||||
RR detailed-surface lodgement defect + a U=0.86 external-gable variant
|
||||
our `gable_wall` enum doesn't handle. That's slice 25.
|
||||
|
||||
### B.4 §4 residuals
|
||||
|
||||
|
|
@ -198,6 +199,7 @@ fixture | section §4 pin status
|
|||
### B.5 Recent slices (in reverse order — newest first)
|
||||
|
||||
```
|
||||
Slice 27b: §3 element-area + door-area rounding to 2 d.p. per RdSAP10 §15 (p.66)
|
||||
Slice 27: BS EN ISO 13370 floor U rounded to 2 d.p. per RdSAP10 §5.12
|
||||
Slice 24: rooflight (line 27a) — SapRoofWindow datatype + 000516 cascade closure
|
||||
ac68cf88 Slice 23: 000516 detailed RR + exposed_floor + door_count fixture lodgement
|
||||
|
|
@ -290,18 +292,14 @@ PDF stores 2-d.p.-rounded element areas (e.g. `36.4500 m²` for a wall I
|
|||
compute as `36.4492 m²`). Closing these needs the §3 area-rounding spec
|
||||
rule — see slice 27b below.
|
||||
|
||||
### C.4b Slice 27b — Wall + party-wall area precision (PDF rounds to 2 d.p.)
|
||||
### C.4b Slice 27b — ~~§3 element-area rounding~~ DONE
|
||||
|
||||
The 0.0013–0.0075 W/K LINE_33 residual on 000474/477/480/490/516 is
|
||||
consistently traceable to gross wall-area and party-wall-area values:
|
||||
- 000474 Main wall area: my 36.4492 vs PDF 36.4500 (Δ × 1.5 U = 0.0012 W/K)
|
||||
- 000516 wall area: my 45.3675 vs PDF 45.3700 (Δ × 1.5 U = 0.00375 W/K)
|
||||
- per-window U_eff aggregation: my per-window curtain transform diverges
|
||||
from PDF's aggregate by ~0.0001 per fixture (slice 22 trade-off)
|
||||
|
||||
If §3 mandates area rounding to 0.01 m² (or 4 d.p.) at the element level,
|
||||
applying it would close LINE_33 to ≤ 1e-4. Need SAP 10.2 §3 page reference
|
||||
from the user.
|
||||
Done. RdSAP10 §15 (p.66) lodges the rounding policy: "All element areas
|
||||
(gross) including window areas: 2 d.p." Applied to gross wall + party
|
||||
wall + roof + floor + window + door + alt-wall + RR-sub-area inputs in
|
||||
`heat_transmission_from_cert`. §3 cascade pins (LINE_31/33/36/37) now
|
||||
close at abs=1e-4 for 5 of 6 fixtures; 000487 alone remains failing on
|
||||
the RR defect (slice 25).
|
||||
|
||||
### C.5 Slice 28 — Continuous SAP / fuel cost / CO2 closure
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,12 @@ _DEFAULT_STOREY_HEIGHT_M: Final[float] = 2.5
|
|||
# SAP10.2 §3.2 curtain/blind thermal resistance applied to windows (and
|
||||
# roof windows) — turns raw window U into the worksheet's (27) effective U.
|
||||
_WINDOW_CURTAIN_RESISTANCE_M2K_PER_W: Final[float] = 0.04
|
||||
# RdSAP10 §15 "Rounding of data" (p.66): "All element areas (gross)
|
||||
# including window areas and conservatory wall area: 2 d.p." plus
|
||||
# "U-values: 2 d.p.". This is the data-passed-to-SAP-calculator
|
||||
# rounding policy — applied to gross wall / roof / floor / party / window
|
||||
# / door / alt-wall / RR sub-area inputs to the §3 cascade.
|
||||
_AREA_ROUND_DP: Final[int] = 2
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -169,15 +175,16 @@ def _part_geometry(part: SapBuildingPart) -> dict[str, float]:
|
|||
# the part — same convention as dimensions.gross_wall_area_m2. The
|
||||
# ground-perim × avg × count short-cut over-counts upper storeys when
|
||||
# the perimeter shrinks (e.g. Elmhurst 000474 Main: ground 7.07, first
|
||||
# 5.27).
|
||||
gross_wall = sum(
|
||||
# 5.27). RdSAP10 §15 rounds the gross to 2 d.p. before it enters the
|
||||
# SAP calculator.
|
||||
gross_wall = round(sum(
|
||||
(fd.heat_loss_perimeter_m or 0.0) * (fd.room_height_m or _DEFAULT_STOREY_HEIGHT_M)
|
||||
for fd in fds
|
||||
)
|
||||
party_wall = sum(
|
||||
), _AREA_ROUND_DP)
|
||||
party_wall = round(sum(
|
||||
(fd.party_wall_length_m or 0.0) * (fd.room_height_m or _DEFAULT_STOREY_HEIGHT_M)
|
||||
for fd in fds
|
||||
)
|
||||
), _AREA_ROUND_DP)
|
||||
# RdSAP10 §3.9.1 Simplified Type 1 (True Room-in-Roof): when an RR is
|
||||
# lodged with only its floor area (no gable/party/sheltered/connected
|
||||
# wall lengths), the spec's empirical formula treats it as one chunk
|
||||
|
|
@ -268,7 +275,8 @@ def heat_transmission_from_cert(
|
|||
wall_description = _joined_descriptions(epc.walls)
|
||||
floor_description = _joined_descriptions(epc.floors)
|
||||
|
||||
door_area = max(0, door_count) * _DEFAULT_DOOR_AREA_M2
|
||||
# RdSAP10 §15 — door area rounds to 2 d.p. before entering the calc.
|
||||
door_area = round(max(0, door_count) * _DEFAULT_DOOR_AREA_M2, _AREA_ROUND_DP)
|
||||
# SAP10.2 §3.2: effective window U includes the 0.04 m²K/W curtain
|
||||
# resistance — `(27)` worksheet column applies it per-window. When
|
||||
# sap_windows have per-window U lodgements (mixed glazing types in
|
||||
|
|
@ -283,7 +291,9 @@ def heat_transmission_from_cert(
|
|||
if windows_have_per_window_u:
|
||||
windows_w_per_k_total = 0.0
|
||||
for w in epc.sap_windows or []:
|
||||
a_w = float(w.window_width) * float(w.window_height)
|
||||
a_w = round(
|
||||
float(w.window_width) * float(w.window_height), _AREA_ROUND_DP
|
||||
)
|
||||
u_raw_w = float(w.window_transmission_details.u_value) # type: ignore[union-attr]
|
||||
u_eff_w = (
|
||||
1.0 / (1.0 / u_raw_w + _WINDOW_CURTAIN_RESISTANCE_M2K_PER_W)
|
||||
|
|
@ -299,7 +309,9 @@ def heat_transmission_from_cert(
|
|||
if window_u_raw > 0
|
||||
else 0.0
|
||||
)
|
||||
windows_w_per_k_total = window_u * window_total_area_m2
|
||||
windows_w_per_k_total = (
|
||||
window_u * round(window_total_area_m2, _AREA_ROUND_DP)
|
||||
)
|
||||
|
||||
# SAP10.2 §3 (27a) — per-roof-window curtain transform, same R=0.04
|
||||
# rule as (27). Total area is apportioned to the first (main) part
|
||||
|
|
@ -309,7 +321,7 @@ def heat_transmission_from_cert(
|
|||
roof_windows_w_per_k_total = 0.0
|
||||
roof_windows_area_total = 0.0
|
||||
for rw in roof_windows_list:
|
||||
a_rw = float(rw.area_m2)
|
||||
a_rw = round(float(rw.area_m2), _AREA_ROUND_DP)
|
||||
u_raw_rw = float(rw.u_value_raw)
|
||||
u_eff_rw = (
|
||||
1.0 / (1.0 / u_raw_rw + _WINDOW_CURTAIN_RESISTANCE_M2K_PER_W)
|
||||
|
|
@ -404,20 +416,32 @@ def heat_transmission_from_cert(
|
|||
upw = u_party_wall(party_wall_construction=party_construction)
|
||||
y = thermal_bridging_y(age_band=age_band)
|
||||
|
||||
# RdSAP10 §15 — element gross areas enter the SAP calculator at
|
||||
# 2 d.p. precision. `_part_geometry` rounds gross wall + party
|
||||
# wall; here we round the per-window aggregate area, the door
|
||||
# area, the floor area, and the roof area at the point of use.
|
||||
gross_wall_area = geom["gross_wall_area_m2"]
|
||||
w_area = window_total_area_m2 if i == 0 else 0.0
|
||||
w_area = (
|
||||
round(window_total_area_m2, _AREA_ROUND_DP) if i == 0 else 0.0
|
||||
)
|
||||
d_area = door_area if i == 0 else 0.0
|
||||
net_wall_area = max(0.0, gross_wall_area - w_area - d_area)
|
||||
party_area = geom["party_wall_area_m2"]
|
||||
# Roof windows cut into the storey-below roof, reducing the regular
|
||||
# roof's net area. Allocated to the first (main) part — same
|
||||
# convention as `sap_windows` / `door_area`.
|
||||
rw_area_part = roof_windows_area_total if i == 0 else 0.0
|
||||
gross_roof_area = (
|
||||
geom["top_floor_area_m2"] if exposure.has_exposed_roof else 0.0
|
||||
rw_area_part = (
|
||||
round(roof_windows_area_total, _AREA_ROUND_DP) if i == 0 else 0.0
|
||||
)
|
||||
gross_roof_area = round(
|
||||
geom["top_floor_area_m2"] if exposure.has_exposed_roof else 0.0,
|
||||
_AREA_ROUND_DP,
|
||||
)
|
||||
roof_area = max(0.0, gross_roof_area - rw_area_part)
|
||||
floor_area_total = geom["ground_floor_area_m2"] if exposure.has_exposed_floor else 0.0
|
||||
floor_area_total = round(
|
||||
geom["ground_floor_area_m2"] if exposure.has_exposed_floor else 0.0,
|
||||
_AREA_ROUND_DP,
|
||||
)
|
||||
|
||||
# RdSAP §1.4.2: a building part can have up to 2 alternative walls,
|
||||
# each a sub-area of the gross wall with its OWN construction +
|
||||
|
|
@ -429,7 +453,8 @@ def heat_transmission_from_cert(
|
|||
for alt_wall in (part.sap_alternative_wall_1, part.sap_alternative_wall_2):
|
||||
if alt_wall is None:
|
||||
continue
|
||||
alt_walls_total_area += alt_wall.wall_area
|
||||
# RdSAP10 §15 — alt wall area rounded to 2 d.p.
|
||||
alt_walls_total_area += round(alt_wall.wall_area, _AREA_ROUND_DP)
|
||||
alt_walls_contribution += _alt_wall_w_per_k(
|
||||
alt_wall=alt_wall,
|
||||
country=country,
|
||||
|
|
@ -471,7 +496,8 @@ def heat_transmission_from_cert(
|
|||
rir = part.sap_room_in_roof
|
||||
for surf in rir.detailed_surfaces:
|
||||
kind = surf.kind
|
||||
area = surf.area_m2
|
||||
# RdSAP10 §15 — RR detailed sub-area rounded to 2 d.p.
|
||||
area = round(surf.area_m2, _AREA_ROUND_DP)
|
||||
# Only (26)-(30) elements contribute to the external area
|
||||
# aggregate (LINE_31) — gable_wall sits on (32) alongside
|
||||
# the regular party walls, so its area is bookkept under
|
||||
|
|
@ -547,9 +573,10 @@ def _alt_wall_w_per_k(
|
|||
"""U × A for one alternative-wall sub-area. RdSAP §1.4.2: inherits the
|
||||
part's age band but carries its own construction + insulation. A
|
||||
basement-wall sub-area (RdSAP §5.17 / Table 23) bypasses the cascade
|
||||
entirely."""
|
||||
entirely. Area rounded to 2 d.p. per RdSAP10 §15."""
|
||||
alt_area = round(alt_wall.wall_area, _AREA_ROUND_DP)
|
||||
if alt_wall.is_basement_wall:
|
||||
return u_basement_wall(age_band) * alt_wall.wall_area
|
||||
return u_basement_wall(age_band) * alt_area
|
||||
alt_thickness = _parse_thickness_mm(alt_wall.wall_insulation_thickness)
|
||||
alt_insulation_present = (
|
||||
alt_wall.wall_insulation_type != _WALL_INSULATION_NONE
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue