docs: Thread 2 CO2/PE collision fixed by S0380.212; cost +4 tail open

Record the heat-network fuel-code collision fix (EPC 20 'mains gas
(community)' → Table-12 51), case-14 validation, and the remaining
cost-scaling gap (heat-network cost path missing 1/heat_source_eff).
Bump HEAD/next-slice; update shipped + audit tables.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-03 23:27:45 +00:00
parent 08dd0b4c73
commit f658f7ce71

View file

@ -6,13 +6,13 @@ state after closing the 0240 investigation and fixing the first of several
API-mapper/cascade bugs the audit surfaced.
- **Branch:** `feature/per-cert-mapper-validation`
- **HEAD:** `90f6720c` (S0380.211). Confirm with `git rev-parse HEAD`.
- **HEAD:** `08dd0b4c` (S0380.212). Confirm with `git rev-parse HEAD`.
- **Baseline:** `2384 passed, 1 skipped, 0 failed` (AGENT_GUIDE §4 suite command).
ALSO run `domain/sap10_ml/tests/` when touching `rdsap_uvalues.py` — 2 PRE-EXISTING
stone-formula failures there, see Thread 1.
- **Next slice number:** **S0380.212**.
- **Open:** Thread 2 (community 9390) — UNBLOCKED: **case 14** (code-301 boiler + mains
gas) is on disk, target `(386)` heat-network CO2 = 0.2640. Threads 1 + 3 **CLOSED**.
- **Next slice number:** **S0380.213**.
- **Open:** Thread 2 *cost* tail only — 9390 SAP +4 (heat-network cost missing
1/heat_source_eff scaling). CO2/PE collision FIXED (S0380.212). Threads 1 + 3 **CLOSED**.
---
@ -24,6 +24,7 @@ API-mapper/cascade bugs the audit surfaced.
| **S0380.209** | Fixed the **API-path wall U** "as built, insulated (assumed)" bug — routes to the as-built age-band row, not the 50 mm retrofit bucket. New `_described_as_retrofit_insulated` in `heat_transmission.py`. Worksheet-validated by case 9 (sandstone J → 0.35) + case 10 (solid brick J → 0.35). Re-pinned 0240 PE +1.8687 → +5.5044, CO2 +0.0907 → +0.2757 (SAP integer 72 unchanged). |
| **S0380.210** | **CLOSED cert 0390** (Thread 3). Cavity wall "as built, **partial** insulation (assumed)" (type 4) was mis-routed to the Table 6 "Filled cavity" row (band F 0.40) → should be "Cavity as built" (band F 1.0). New `_cavity_described_as_filled` in `rdsap_uvalues.py` excludes "partial insulation" from the filled trigger (keeps "insulated (assumed)" → filled). SAP +7 → +0, PE 27.97 → +0.53, CO2 2.71 → 0.12. Mirrors S0380.209 on the cavity path. |
| **S0380.211** | **CLOSED Thread 1 (roof).** 0240 Ext1 vaulted (code 5) NI roof returned 0.68 (§5.11.4 50 mm) → should be Table 18 col (1) age-band (band J 0.16), matching 33 cohort-2 `ND` vaulted roofs. New `u_roof(is_sloping_ceiling=...)` flag threaded from heat_transmission (codes 5/8). 0240 PE +5.50 → +1.52, CO2 +0.28 → +0.07 (SAP 72). Also corrected the S0380.210 cavity unit test in `domain/sap10_ml/tests/` (suite-command gap — see Thread 1). |
| **S0380.212** | **Thread 2 CO2/PE collision FIXED.** EPC fuel 20 = "mains gas (community)" collided with Table-12 biomass code 20 → community CO2 6.4× low. New `_heat_network_factor_fuel_code` translates 20→51 for heat-network mains only (5 sites: SH+HW CO2/PE/price). 9390 CO2 0.44→3.03 t (lodged 2.8), PE 204→220. Case-14-validated ((367) 0.2100 / (467) 1.1300). Cost +4 tail open. |
Both also carry a memory: [[project_case7_combi_exonerated]], [[project_as_built_insulated_assumed_bug]].
@ -100,51 +101,36 @@ formula 3.7408 vs Table-6 1.7 cap, granite + sandstone band A) — they fail at
---
## OPEN THREAD 2 — Community fuel-code collision (cert 9390) — UNBLOCKED, **case 14 in hand**
## THREAD 2 — Community fuel-code collision (cert 9390) — **CO2/PE FIXED (S0380.212); cost +4 open**
Cert **9390-2722-3520** (TFA 75.17): SAP +4 (calc 71 / lodged 67), PE ≈ matches
(204 vs 205), but **CO2 5.86 kg/m² vs lodged 38** (0.44 t vs 2.8 t — 6.5× low).
Community scheme (`sap_main_heating_code=301`, `main_fuel_type=20`).
Cert **9390-2722-3520** (community mains-gas boiler, `sap_main_heating_code=301`,
`main_fuel_type=20`). Authoritative: `datatypes/epc/domain/epc_codes.csv`
(RdSAP-Schema-17.0) `main_fuel,20,mains gas (community)`.
**Arbitrating worksheet (case 14, `simulated case 14/`):** community **Boilers**
(`(303a)`=1.0, no CHP) + **Mains Gas** + **SAP code 301** — 9390's exact scheme.
Key targets: `(386)` **Overall heat-network CO2 factor = 0.2640**, `(306)`
distribution loss factor (Table 12c) = **1.49**, `(385)` EI rating 69. So a
community mains-gas BOILER scheme should drive CO2 at the heat-network factor
**0.2640** (≈ base mains-gas 0.210 × 1.257 source/loss scaling), NOT the biomass
0.028 the collision currently returns. **API `main_fuel_type=20` = community mains
gas → Table 12 code 51** (base CO2 0.210, PEF 1.130). Cases 12 (CHP coal, 302) +
13 (CHP gas, 302) also on disk — useful for the CHP `(303a/303b)` split but NOT
9390's boiler scheme.
**Root cause (the collision):** the EPC `main_fuel_type` enum and the SAP Table 12 /
Table 32 numbering overlap in **1825** — EPC 20='mains gas (community)' but Table-12
code 20 is solid biomass (CO2 0.028). `co2_factor_kg_per_kwh`/`primary_energy_factor`/
`unit_price_p_per_kwh` check the Table-12 dict FIRST, so the EPC community fuel got the
biomass factor instead of translating 20→51 (community mains gas: CO2 0.210, PE 1.130).
**Root cause:** `co2_factor_kg_per_kwh` / `unit_price_p_per_kwh` /
`primary_energy_factor` in `tables/table_12.py` + `table_32.py` use an
"accept-either-API-or-Table-12-code" lookup that checks the **Table-12 code first**:
**S0380.212 fix:** new `_heat_network_factor_fuel_code(main)` translates the EPC community
fuel → Table-12 code via `API_FUEL_TO_TABLE_12`, but ONLY for heat-network mains
(`_is_heat_network_main`) so a genuine biomass boiler keeps its raw factor. Applied at
**five** sites — space-heating CO2/PE/unit-price + water-heating (WHC 901) CO2/PE (9390's
HW is ALSO community gas, so both paths needed it). Worksheet-validated by **case 14**
(community boilers + mains gas, code 301): `(367)` CO2 0.2100, `(467)` PE 1.1300 = the
Table-12 code-51 values. 9390 CO2 **0.44 → 3.03 t** (lodged 2.8 — spec-correct factor over
the API-only register residual; 9390 is unpinned, retired P2.2 per ADR-0010 §10), PE
**204 → 220** (the prior 204≈205 was the collision coinciding with the register residual).
Summary path uses code 1 (no collision) → CH1-6 corpus untouched. Locked by 2 unit tests
in `test_cert_to_inputs.py`.
```python
if fuel_code in CO2_KG_PER_KWH: # API 20 IS ALSO a T12 code (wood logs, 0.028)
return CO2_KG_PER_KWH[fuel_code] # ← returns 0.028, never translates
translated = API_FUEL_TO_TABLE_12.get(fuel_code) # 20 → 51 (community gas, 0.21) — unreached
```
API community fuels **1825** (`API_FUEL_TO_TABLE_12`: 18→75,19→76,20→51,21→52,22→53,
23→55,24→54,25→41) all **collide** with Table-12 codes 1825 (biomass), so they silently
get the biomass factor. The heat-network CO2 path
(`cert_to_inputs.py` ~L2801-2819: `_co2_factor_kg_per_kwh(main) * scaling`) thus emits
`0.028 × 1.25 (1/0.80 heat-source-eff) = 0.035` instead of `0.21 × 1.25`.
**Why the fix needs care (don't just translate):** a naive translate of the heat-network
fuel via `API_FUEL_TO_TABLE_32` corrects CO2 (0.44 → 3.03 t, ≈ lodged 2.8) but
**over-corrects PE** (204 → 219.9 vs lodged 205) and **doesn't move the SAP +4** (cost
is a *separate* community issue — price/standing-charge/heat-source-eff scaling). The
community scheme's declared PE/CO2/cost factors must be pinned against a **9390 Elmhurst
worksheet** before committing. This touches the community-heating corpus (the "touchy"
S0380.180-184 area — see [[project_heating_systems_corpus]]); run the heating-systems
corpus test after any change.
The right long-term shape: translate API→Table-12 **explicitly** at the known-API-code
boundary instead of the ambiguous T12-first "accept-either" — but verify it against the
whole cohort.
**STILL OPEN — 9390 SAP +4 (separate cost-scaling gap):** the heat-network cost path
(`_fuel_cost_gbp_per_kwh`, cert_to_inputs.py ~L1888) does NOT apply the
`1/heat_source_eff` (1/0.80) scaling the CO2/PE paths do, so community fuel cost
under-counts → SAP over-reads. Validate the fix against case 14's **10b fuel-cost block**
(+ Table 32 note (l) £120 community standing charge). Run the heating-systems corpus after
(touchy area, [[project_heating_systems_corpus]]).
---
@ -186,7 +172,7 @@ that test.
| cert | SAP resid | diagnosis |
|---|---|---|
| 0390-2954-3640 | ~~+7~~ **+0** | **CLOSED S0380.210** — cavity partial-insulation → as-built row |
| 9390-2722-3520 | +4 | community fuel-code collision → CO2 6× low (Thread 2, OPEN) |
| 9390-2722-3520 | +4 (unpinned) | **CO2/PE collision FIXED S0380.212** (CO2 0.44→3.03 t); SAP +4 cost-scaling tail open |
| 0240-0200-5706 | 1 | NOT a bug — unpreserved 2013+ pump; true SAP 72. Roof PE-pin tightened by **S0380.211** (PE +5.50 → +1.52) |
| 2130-1033-4050 | +1 | minor fabric precision (multi-part solid-brick wall); low value |
| 7536-3827-0600 | +1 | minor fabric precision (multi-bp D/L/F cavity); low value |