mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 25a: 000487 §3 full closure — RR detailed surfaces + gable_wall_external + roof-area-as-max + half-up rounding
§3 cascade pins now close at abs=1e-4 for all 6 fixtures (was 5 of 6 with
000487 the holdout). Five spec-grounded changes:
1. SapRoomInRoofSurface gains optional `u_value` override + new kind
`gable_wall_external` per RdSAP10 Table 4 (p.22) row 1 (exposed gable,
U "as common wall" with assessor-lodged override). Routes to (29a)
walls + LINE_31 external area.
2. SapAlternativeWall gains optional `u_value` override — assessor-lodged
measured U bypasses the Table 6 cascade. 000487 Ext1 has a 9-mm
TimberWallOneLayer at U=1.90 outside the Table 6 buckets.
3. _part_geometry uses MAX of floor areas (not top) for roof area, per
RdSAP10 §3.8 (p.20): "Roof area is the greatest of the floor areas
on each level". Fixes 000487 Ext1 where ground=7.13 m² > first=5.63.
4. Replace Python `round()` (banker's) with `_round_half_up` for §15
element-area rounding. Banker's rounds 17.125 → 17.12; SAP convention
rounds half-up → 17.13. Boundary case appears in 000487 Ext1 party
wall area (party_length 6.25 × height 2.74 = 17.125).
5. 000487 fixture lodges 5 detailed RR surfaces (party gable, external
gable @ U=0.86, flat ceiling, stud wall, slope), roof_insulation_
thickness=300 (both parts → U=0.14), is_exposed_floor=True on Ext1
floor 0, and u_value=1.90 on the Ext1 alt wall.
§3 cascade per-fixture:
field | 474 | 477 | 480 | 487 | 490 | 516
LINE_31 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓
LINE_33 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓
LINE_36 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓
LINE_37 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓
Scoreboard:
section_cascade_pins: 274 → 279 PASS (+5: §3 +4 for 000487, §7 +1
cascade)
e2e SapResult: 32 → 32 PASS (unchanged — downstream §8-§12
pins not yet asserted)
§4 (000487) deferred to slice 25b — needs has_electric_shower routing
through the §4 cascade so Nbath uses the "0.13N+0.19" branch when only
electric showers are present.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
6e6bba7e67
commit
015144361a
4 changed files with 133 additions and 40 deletions
|
|
@ -294,12 +294,27 @@ class SapRoomInRoofSurface:
|
|||
sloping ceiling, stud wall, gable wall) per spec Figure 4. The U-value
|
||||
is resolved from Table 17 when `insulation_thickness_mm` is set, or
|
||||
Table 18 col (4) age-band default otherwise.
|
||||
|
||||
RdSAP10 Table 4 (p.22) "U-values of gable-end and other walls in RR"
|
||||
distinguishes four gable types. We model the two we've seen lodged in
|
||||
the U985 corpus:
|
||||
- "gable_wall" — party (U = 0.25 W/m²K per Table 4 row 2)
|
||||
- "gable_wall_external" — exposed gable (U = "as common wall" per
|
||||
Table 4 row 1; when assessor lodges a measured U on the surface,
|
||||
`u_value` overrides the cascade)
|
||||
The other two Table 4 variants ("sheltered" R=0.5 of external, and
|
||||
"connected to heated space" U=0) are not yet seen in the corpus.
|
||||
"""
|
||||
|
||||
kind: str # "slope" | "flat_ceiling" | "stud_wall" | "gable_wall"
|
||||
kind: str # "slope" | "flat_ceiling" | "stud_wall" | "gable_wall" | "gable_wall_external"
|
||||
area_m2: float
|
||||
insulation_thickness_mm: Optional[int] = None
|
||||
insulation_type: Optional[str] = None # "mineral_wool" / "eps" / "pur" / "pir"
|
||||
# Assessor-lodged U override (W/m²K). Used by `gable_wall_external`
|
||||
# when the cert measures U directly (cf. 000487 Gable Wall 2 at
|
||||
# U=0.86 on line 29). When None, the cascade falls back to the main-
|
||||
# wall U via Table 4 "as common wall".
|
||||
u_value: Optional[float] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -342,6 +357,12 @@ class SapAlternativeWall:
|
|||
wall_insulation_type: int
|
||||
wall_thickness_measured: str
|
||||
wall_insulation_thickness: Optional[str] = None
|
||||
# Assessor-lodged U-value (W/m²K) — when set, overrides the
|
||||
# Table 6 cascade for this alt sub-area. Lodged directly on the
|
||||
# cert for some constructions (e.g. 000487 Ext1 TimberWallOneLayer
|
||||
# at U=1.90, where the 9-mm-thick single-layer timber wall doesn't
|
||||
# fit the Table 6 buckets cleanly).
|
||||
u_value: Optional[float] = None
|
||||
|
||||
@property
|
||||
def is_basement_wall(self) -> bool:
|
||||
|
|
|
|||
|
|
@ -133,14 +133,14 @@ 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-26c: section_cascade_pins 274 PASS / 38 FAIL, e2e SapResult
|
||||
32 PASS / 40 FAIL. §3 + §5 + §6 + §7 (mostly) pinned. §7 LINE_85..91
|
||||
+ LINE_87/88/89/90 close at abs=1e-4 for all 5 non-487 fixtures.
|
||||
LINE_92/93 marginal residuals (~0.0001 K, just over threshold) on
|
||||
000474/477/480/490 — investigation needed (possible PDF intermediate
|
||||
rounding precision artefact). 000487 fully cascades from §3/§4 defects
|
||||
(slice 25). e2e SapResult unchanged because cert_to_inputs was already
|
||||
running the §7 calc internally — pin tests just surface it now.)
|
||||
(Post-slice-25a: section_cascade_pins 279 PASS / 33 FAIL, e2e SapResult
|
||||
32 PASS / 40 FAIL. §3 + §5 + §6 + §7 (mostly) pinned. §3 NOW FULLY
|
||||
CLOSES for all 6 fixtures (24/24) — slice 25a closed 000487 by lodging
|
||||
detailed RR surfaces + adding gable_wall_external + Ext1 alt U
|
||||
override + RdSAP §3.8 roof-area-as-max rule + half-up rounding.
|
||||
Remaining failures: §4 monthly on 000477+487 (slice 25b), §5 LINE_72/73
|
||||
+ §6 LINE_84 on 000477/487 (cascade from §4), §7 LINE_92/93 marginal
|
||||
on 000474/477/480/490 (precision artefact), §7 on 000487 (cascade).)
|
||||
|
||||
### B.2 SapResult pin matrix (post-slice-22/23)
|
||||
|
||||
|
|
@ -201,6 +201,7 @@ fixture | section §4 pin status
|
|||
### B.5 Recent slices (in reverse order — newest first)
|
||||
|
||||
```
|
||||
Slice 25a: 000487 §3 closure — detailed RR + gable_wall_external + Ext1 alt U=1.9 + §3.8 max-floor roof + half-up rounding
|
||||
Slice 26c: §7 mean internal temp cascade pin (60 cases, 44 PASS) — LINE_85..94
|
||||
Slice 26b: §6 solar gains cascade pin (12 cases, 10 PASS) + SapRoofWindow solar attrs + plumb to §6 cascade
|
||||
Slice 26: §5 internal gains cascade pin (54 cases, 50 PASS / 4 FAIL) + rooflight plumb to daylight factor
|
||||
|
|
@ -238,21 +239,18 @@ sourced from RdSAP10 Table 24 (p.50/113) "Roof window" column.
|
|||
is the same pre-existing wall-perimeter + per-window curtain precision
|
||||
drift biting 000474/477/480/490 — closes in slice 27.
|
||||
|
||||
### C.2 Slice 25 — 000487 RR + HW + external gable variant
|
||||
### C.2 Slice 25 — ~~000487 §3 RR + external gable variant~~ DONE (slice 25a)
|
||||
|
||||
000487 is the worst remaining fixture. PDF lodges:
|
||||
- **Detailed §3.10 RR with one gable as EXTERNAL** at U=0.86 (line 29a), not
|
||||
party at U=0.25. Our `SapRoomInRoofSurface.kind="gable_wall"` enum only
|
||||
routes to party. New variant needed.
|
||||
- Specific HW lodgement (1 bath, but PDF (43) annual avg HW diverges from
|
||||
what fixture currently produces — likely shower flow rate or bath count).
|
||||
- 000487 also still fails SAP integer (60 vs PDF 62) — the only fixture with
|
||||
Δ_int ≠ 0.
|
||||
§3 now fully closes for 000487. Remaining work: §4 HW lodgement (slice 25b
|
||||
— 000487 cert has 1 bath + 1 electric shower, no mixer outlet; calc treats
|
||||
"no mixer outlets" as "no shower", bumping Nbath from 0.13N+0.19 to
|
||||
0.35N+0.50 and over-counting bath volume 2.5×).
|
||||
|
||||
**Needs spec input from user**: RdSAP 10 Table 4 / Table 6 page reference for
|
||||
the "RR gable as external" routing. The user has given Table 11 (p 188),
|
||||
Table 12 (189), Table 12a (191), Tables 3a/b/c (160-162). Ask for Table 4 +
|
||||
Table 6 pages.
|
||||
Spec source: SAP 10.2 Appendix J step 2a (p.81) — `Nbath = 0.13N + 0.19 if
|
||||
shower also present (including electric); = 0.35N + 0.50 if no shower
|
||||
present`. Fix needs: lodge electric-shower presence on cert, plumb
|
||||
`has_electric_shower` through `water_heating_section_from_cert`, OR the
|
||||
fixture-shower-count refactor that closes 000477 LINE_61 simultaneously.
|
||||
|
||||
### C.3 Slice 26+ — §5 / §6 / §7 / §8 / §9a / §10a / §11a / §12 cascade pins
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,20 @@ from domain.ml.rdsap_uvalues import (
|
|||
u_wall,
|
||||
u_window,
|
||||
)
|
||||
from math import sqrt
|
||||
from math import floor, sqrt
|
||||
|
||||
|
||||
def _round_half_up(value: float, dp: int) -> float:
|
||||
"""Round half AWAY from zero — the convention SAP calculators use
|
||||
(and standard textbook rounding). Python's built-in `round` does
|
||||
banker's rounding (round half to even), which diverges at boundary
|
||||
cases like 17.125 → Python 17.12 / SAP 17.13. The diverging boundary
|
||||
appears in Elmhurst 000487 Ext1 party-wall area; matching SAP closes
|
||||
the LINE_33 residual to abs=1e-4."""
|
||||
factor = 10 ** dp
|
||||
if value >= 0.0:
|
||||
return floor(value * factor + 0.5) / factor
|
||||
return -floor(-value * factor + 0.5) / factor
|
||||
|
||||
|
||||
_WALL_INSULATION_NONE: Final[int] = 4
|
||||
|
|
@ -169,19 +182,23 @@ def _part_geometry(part: SapBuildingPart) -> dict[str, float]:
|
|||
}
|
||||
fds = list(part.sap_floor_dimensions)
|
||||
ground = next((fd for fd in fds if fd.floor == 0), fds[0])
|
||||
indexed = [(fd.floor if fd.floor is not None else 0, fd) for fd in fds]
|
||||
top = max(indexed, key=lambda kv: kv[0])[1]
|
||||
# RdSAP10 §3.8 (p.20): "Roof area is the greatest of the floor areas
|
||||
# on each level". For parts where the top floor area is smaller than
|
||||
# the lower floors (e.g. 000487 Ext1: ground 7.13, first 5.63), the
|
||||
# roof area is the LARGEST floor area, not the top. The RR floor
|
||||
# (where applicable) is then deducted from this max per §3.9.
|
||||
max_floor_area = max((fd.total_floor_area_m2 or 0.0) for fd in fds)
|
||||
# SAP §3 wall area is Σ (perimeter_i × height_i) across each storey of
|
||||
# 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). RdSAP10 §15 rounds the gross to 2 d.p. before it enters the
|
||||
# SAP calculator.
|
||||
gross_wall = round(sum(
|
||||
gross_wall = _round_half_up(sum(
|
||||
(fd.heat_loss_perimeter_m or 0.0) * (fd.room_height_m or _DEFAULT_STOREY_HEIGHT_M)
|
||||
for fd in fds
|
||||
), _AREA_ROUND_DP)
|
||||
party_wall = round(sum(
|
||||
party_wall = _round_half_up(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)
|
||||
|
|
@ -236,7 +253,7 @@ def _part_geometry(part: SapBuildingPart) -> dict[str, float]:
|
|||
rr_gable_area += area
|
||||
return {
|
||||
"ground_floor_area_m2": ground.total_floor_area_m2 or 0.0,
|
||||
"top_floor_area_m2": max(0.0, (top.total_floor_area_m2 or 0.0) - rr_floor_area),
|
||||
"top_floor_area_m2": max(0.0, max_floor_area - rr_floor_area),
|
||||
"gross_wall_area_m2": gross_wall,
|
||||
"party_wall_area_m2": party_wall,
|
||||
"rr_floor_area_m2": rr_floor_area,
|
||||
|
|
@ -276,7 +293,7 @@ def heat_transmission_from_cert(
|
|||
floor_description = _joined_descriptions(epc.floors)
|
||||
|
||||
# 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)
|
||||
door_area = _round_half_up(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
|
||||
|
|
@ -291,7 +308,7 @@ 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 = round(
|
||||
a_w = _round_half_up(
|
||||
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]
|
||||
|
|
@ -310,7 +327,7 @@ def heat_transmission_from_cert(
|
|||
else 0.0
|
||||
)
|
||||
windows_w_per_k_total = (
|
||||
window_u * round(window_total_area_m2, _AREA_ROUND_DP)
|
||||
window_u * _round_half_up(window_total_area_m2, _AREA_ROUND_DP)
|
||||
)
|
||||
|
||||
# SAP10.2 §3 (27a) — per-roof-window curtain transform, same R=0.04
|
||||
|
|
@ -321,7 +338,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 = round(float(rw.area_m2), _AREA_ROUND_DP)
|
||||
a_rw = _round_half_up(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)
|
||||
|
|
@ -422,7 +439,7 @@ def heat_transmission_from_cert(
|
|||
# area, the floor area, and the roof area at the point of use.
|
||||
gross_wall_area = geom["gross_wall_area_m2"]
|
||||
w_area = (
|
||||
round(window_total_area_m2, _AREA_ROUND_DP) if i == 0 else 0.0
|
||||
_round_half_up(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)
|
||||
|
|
@ -431,14 +448,14 @@ def heat_transmission_from_cert(
|
|||
# roof's net area. Allocated to the first (main) part — same
|
||||
# convention as `sap_windows` / `door_area`.
|
||||
rw_area_part = (
|
||||
round(roof_windows_area_total, _AREA_ROUND_DP) if i == 0 else 0.0
|
||||
_round_half_up(roof_windows_area_total, _AREA_ROUND_DP) if i == 0 else 0.0
|
||||
)
|
||||
gross_roof_area = round(
|
||||
gross_roof_area = _round_half_up(
|
||||
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 = round(
|
||||
floor_area_total = _round_half_up(
|
||||
geom["ground_floor_area_m2"] if exposure.has_exposed_floor else 0.0,
|
||||
_AREA_ROUND_DP,
|
||||
)
|
||||
|
|
@ -454,7 +471,7 @@ def heat_transmission_from_cert(
|
|||
if alt_wall is None:
|
||||
continue
|
||||
# RdSAP10 §15 — alt wall area rounded to 2 d.p.
|
||||
alt_walls_total_area += round(alt_wall.wall_area, _AREA_ROUND_DP)
|
||||
alt_walls_total_area += _round_half_up(alt_wall.wall_area, _AREA_ROUND_DP)
|
||||
alt_walls_contribution += _alt_wall_w_per_k(
|
||||
alt_wall=alt_wall,
|
||||
country=country,
|
||||
|
|
@ -497,11 +514,13 @@ def heat_transmission_from_cert(
|
|||
for surf in rir.detailed_surfaces:
|
||||
kind = surf.kind
|
||||
# RdSAP10 §15 — RR detailed sub-area rounded to 2 d.p.
|
||||
area = round(surf.area_m2, _AREA_ROUND_DP)
|
||||
area = _round_half_up(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
|
||||
# `party` and excluded from `rr_detailed_area`.
|
||||
# gable_wall_external routes to (29a) walls AND counts
|
||||
# toward LINE_31 external area (per PDF for 000487).
|
||||
if kind == "slope":
|
||||
rr_detailed_area += area
|
||||
roof += area * u_rr_slope(
|
||||
|
|
@ -525,6 +544,15 @@ def heat_transmission_from_cert(
|
|||
)
|
||||
elif kind == "gable_wall":
|
||||
party += 0.25 * area
|
||||
elif kind == "gable_wall_external":
|
||||
# RdSAP10 Table 4 (p.22) row 1: exposed gable U = "as
|
||||
# common wall" — i.e. the main-wall U of the storey
|
||||
# below (`uw`). Assessor-lodged `u_value` (e.g.
|
||||
# 000487 Gable Wall 2 at U=0.86) overrides the
|
||||
# cascade.
|
||||
u_gable = surf.u_value if surf.u_value is not None else uw
|
||||
rr_detailed_area += area
|
||||
walls += u_gable * area
|
||||
floor += uf * floor_area_total
|
||||
party += upw * party_area
|
||||
# windows: total computed pre-loop (`windows_w_per_k_total`).
|
||||
|
|
@ -573,8 +601,13 @@ 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. Area rounded to 2 d.p. per RdSAP10 §15."""
|
||||
alt_area = round(alt_wall.wall_area, _AREA_ROUND_DP)
|
||||
entirely. Area rounded to 2 d.p. per RdSAP10 §15. An assessor-lodged
|
||||
`u_value` on the alt sub-area overrides the cascade — Elmhurst certs
|
||||
lodge measured U for constructions that don't fit the Table 6 buckets
|
||||
cleanly (e.g. 000487 Ext1 TimberWallOneLayer 9 mm at U=1.90)."""
|
||||
alt_area = _round_half_up(alt_wall.wall_area, _AREA_ROUND_DP)
|
||||
if alt_wall.u_value is not None:
|
||||
return alt_wall.u_value * alt_area
|
||||
if alt_wall.is_basement_wall:
|
||||
return u_basement_wall(age_band) * alt_area
|
||||
alt_thickness = _parse_thickness_mm(alt_wall.wall_insulation_thickness)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from datatypes.epc.domain.epc_property_data import (
|
|||
SapBuildingPart,
|
||||
SapFloorDimension,
|
||||
SapRoomInRoof,
|
||||
SapRoomInRoofSurface,
|
||||
SapVentilation,
|
||||
SapWindow,
|
||||
)
|
||||
|
|
@ -65,7 +66,35 @@ def build_epc() -> EpcPropertyData:
|
|||
],
|
||||
sap_room_in_roof=SapRoomInRoof(
|
||||
floor_area=21.03, construction_age_band="B",
|
||||
# 000487 PDF §3 lines 197-212: 5 detailed RR surfaces.
|
||||
# - Gable Wall 1 (party, line 32): 7.08 × 0.25 = 1.7700 W/K
|
||||
# - Gable Wall 2 (EXTERNAL, line 29a): 7.08 × 0.86 = 6.0888 W/K.
|
||||
# The assessor lodged a measured U=0.86 directly on the
|
||||
# cert (worksheet text line 29: "...0.0, 0.86, Gross"),
|
||||
# overriding the Table 4 "as common wall" cascade.
|
||||
# - Flat Ceiling 1 (line 30, uninsulated age B): 3.27 × 2.30 = 7.5210 W/K
|
||||
# - Stud Wall 1 (line 30, 100mm Table 17): 5.88 × 0.36 = 2.1168 W/K
|
||||
# - Slope 1 (line 30, uninsulated): 20.24 × 2.30 = 46.5520 W/K
|
||||
detailed_surfaces=[
|
||||
SapRoomInRoofSurface(kind="gable_wall", area_m2=7.08),
|
||||
SapRoomInRoofSurface(
|
||||
kind="gable_wall_external", area_m2=7.08, u_value=0.86,
|
||||
),
|
||||
SapRoomInRoofSurface(
|
||||
kind="flat_ceiling", area_m2=3.27, insulation_thickness_mm=0,
|
||||
),
|
||||
SapRoomInRoofSurface(
|
||||
kind="stud_wall", area_m2=5.88,
|
||||
insulation_thickness_mm=100, insulation_type="mineral_wool",
|
||||
),
|
||||
SapRoomInRoofSurface(
|
||||
kind="slope", area_m2=20.24, insulation_thickness_mm=0,
|
||||
),
|
||||
],
|
||||
),
|
||||
# PDF line 30: External roof Main 2.86 × U=0.14 → Table 16
|
||||
# joist insulation 300mm row.
|
||||
roof_insulation_thickness=300,
|
||||
wall_thickness_mm=380,
|
||||
)
|
||||
extension = SapBuildingPart(
|
||||
|
|
@ -85,6 +114,10 @@ def build_epc() -> EpcPropertyData:
|
|||
room_height_m=2.74, total_floor_area_m2=7.13,
|
||||
party_wall_length_m=6.25, heat_loss_perimeter_m=1.50,
|
||||
floor=0,
|
||||
# PDF line 28b: "Exposed floor Ext1 7.13 × 1.20" — the
|
||||
# extension's lowest storey sits over an unheated space
|
||||
# (passageway), Table 20 lookup gives U=1.20.
|
||||
is_exposed_floor=True,
|
||||
),
|
||||
SapFloorDimension(
|
||||
room_height_m=3.10, total_floor_area_m2=5.63,
|
||||
|
|
@ -98,7 +131,15 @@ def build_epc() -> EpcPropertyData:
|
|||
wall_insulation_type=4,
|
||||
wall_thickness_measured="N",
|
||||
wall_insulation_thickness="150",
|
||||
# 000487 worksheet text line 31: "...9, TimberWallOneLayer,
|
||||
# TimberFrame, 0.0, 1.90, Gross". The cert lodges a measured
|
||||
# U=1.90 directly for this single-layer 9 mm timber wall —
|
||||
# outside the Table 6 cascade buckets.
|
||||
u_value=1.90,
|
||||
),
|
||||
# PDF line 30: External roof Ext1 7.13 × U=0.14 → Table 16
|
||||
# joist insulation 300mm row (same as Main).
|
||||
roof_insulation_thickness=300,
|
||||
wall_thickness_mm=380,
|
||||
)
|
||||
return make_minimal_sap10_epc(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue