Commit graph

9 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
433f4a49ce Slice S0380.99: PCDB Table 329 (MV In-Use Factors) ETL + parser + lookup (PCDF Spec §A.20)
PCDF Spec Rev 6b §A.20 (May 2021) Format 430 — Mechanical Ventilation
In-Use Factors Table. Pcdb10.dat carries Format 432 (header
`$329,432,4,2021,11,25,2`), an extended-field version where Format
430 fields 1-4 (system_type + 3 SFP factors for the "no approved
scheme" variant) align at positions 0..3. The remainder of Format
432 carries MVHR adjustments + "with approved scheme" variants +
additional Format 432 columns, preserved verbatim in `raw` for
follow-up slices.

Per PCDF Spec §A.20 field 1 — system types:
  1  = centralised MEV
  2  = decentralised MEV
  3  = balanced whole-house MV (with or without heat recovery)
  5  = positive input ventilation (PIV)
  10 = default data (used with SAP Table 4g defaults)

Decentralised MEV (system_type=2) IUFs:
  SFP × ducting type:
    flexible:   1.45 (field 2)
    rigid:      1.30 (field 3)
    no-duct:    1.15 (field 4 — through-wall fans)

Per spec Note: "If there is no applicable approved installation
scheme the values for with and without scheme are the same." Cert
000565 lodges "Approved Installation: No" → use the "no scheme"
IUFs.

Validation for cert 000565 against worksheet line (230a):
  Σ(SFP_j × FR_j × IUF_j) for the 4 lodged fans:
    in-room kitchen:        1×0.15×13×1.45 = 2.8275
    in-room other wet:      1×0.15× 8×1.45 = 1.7400
    through-wall kitchen:   2×0.11×13×1.15 = 3.2890
    through-wall other wet: 3×0.14× 8×1.15 = 3.8640
  Σ = 11.7205 W (matches worksheet "total watage = 11.7205")
  Σ(FR_j) = 92.0 l/s (matches worksheet "total flow = 92.0000")
  SFPav = 11.7205 / 92.0 = 0.1274 W/(l/s) ✓ matches worksheet

Foundation only this slice — typed parser + ETL + runtime lookup
`mv_in_use_factors_record(system_type)`. No cascade integration; no
behavioural change on any cert. Next slice S0380.100 wires the
SFPav formula.

5 Table 329 records ingested. Pyright net-zero per touched file.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 15:20:02 +00:00
Khalim Conn-Kowlessar
b3330821e7 Slice S0380.98: PCDB Table 322 (Decentralised MEV) ETL + parser + lookup (PCDF Spec §A.19)
PCDF Spec Rev 6b §A.19 (May 2021) Format 427 — Decentralised MEV
Systems Table. Pcdb10.dat carries the per-fan-configuration block in
Format 428 (header `$322,428,72,...`), which drops the spec's per-
group "Fan speed setting" string. Each group is a 3-field triplet:
(config_code, flow_l_per_s, sfp_w_per_l_per_s).

Per the spec § field 14, the 6 fan configurations are:
  1 = In-room fan, kitchen
  2 = In-room fan, other wet room
  3 = In-duct fan, kitchen
  4 = In-duct fan, other wet room
  5 = Through-wall fan, kitchen
  6 = Through-wall fan, other wet room

Some configurations may be blank per spec Note 1 — these are not
valid SAP selections and are excluded from the SFPav summation
downstream.

This slice lands the foundation only — typed parser, ETL promotion
to typed write, and a runtime lookup `decentralised_mev_record(pcdb_
id)`. No cascade integration yet → no behavioural change on any
cert; full test suite + cert 000565 expected fails unchanged.

Subsequent slices in the arc:
- S0380.99: PCDB Table 329 (In-Use Factors) ETL + lookup
- S0380.100: SAP 10.2 §2.6.4 SFPav cascade helper
- S0380.101: HP SAP code 211-227 / 521-527 → main_heating_category=4
- S0380.102: wire MEV cascade into pumps_fans

Cert 000565 lodges `MV PCDF Reference Number = 500755` (Titon
Ultimate dMEV), resolving via this lookup to:
  config 1 (in-room kitchen):     flow=13.0, SFP=0.15 W/(l/s)
  config 2 (in-room other wet):   flow=8.0,  SFP=0.15
  config 3 (in-duct kitchen):     not tested
  config 4 (in-duct other wet):   not tested
  config 5 (thru-wall kitchen):   flow=13.0, SFP=0.11
  config 6 (thru-wall other wet): flow=8.0,  SFP=0.14

48 Table 322 records ingested. Pyright net-zero per touched file.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 15:12:55 +00:00
Khalim Conn-Kowlessar
081bb8fd7e Slice S0380.28: SAP 10.2 Appendix N footnote 43 reciprocal η interpolation — closes the +0.03..+0.06 ASHP precision-floor cluster
Per SAP 10.2 Appendix N, PDF p.101 footnote 43 (line 7053):

  "For the efficiency values, the interpolated efficiency is the
  reciprocal of linear interpolation between the reciprocals of the
  efficiencies."

i.e. 1/η_interp = (1 − t)·1/η_low + t·1/η_high, the weighted harmonic
mean at t = (PSR − PSR_low) / (PSR_high − PSR_low). Cascade was using
**linear** interpolation directly on η — a +0.15..+0.25% over-estimate
in the typical PSR range (1.2..1.5) for ASHPs in the cohort.

Cohort fixture: cert 3336-2825-9400-0512-8292 (Mitsubishi PUZ-WM50VHA,
PCDB 104568). MIT/η-zone cascade matches worksheet EXACTLY (every line
86..92, every month), but η_main_heating cascade 225.443 vs worksheet
224.923 → main_heating_fuel +5.24 kWh/yr too high → ECF 1.5474 vs ws
1.5503 → SAP +0.04 vs worksheet 78.3739. Back-solving the worksheet's
η_main implies η_space_1 = 224.923 / 0.95 ≈ 236.76.

Closed form at PSR=1.40151, bracketing PCDB rows PSR 1.2
(η_space_1=253.9) and PSR 1.5 (η_space_1=229.2):
  Linear (pre-slice):     253.9 + (229.2 − 253.9) × 0.6717  = 237.31  ✗
  Reciprocal (footnote 43): 1 / ((1 − 0.6717)/253.9 + 0.6717/229.2)
                          = 1 / 0.004224                    = 236.74  ✓

The harmonic mean is curvature-aware: linear interpolation under-
penalises efficiency drops at higher PSR (η typically falls off as
PSR increases past the system's design point) by averaging on η
rather than 1/η. SAP 10.2 footnote 43 is explicit about which side
of the reciprocal the interpolation sits.

Outcome:

Cohort-2 Summary path (38 certs):
  exact (<1e-4): 23 → **33** (+10)
  ≤±0.07:        15 → **5**  (-10: HP certs close to exact)
  ±0.07..0.5:     0 → 0
  ±0.5..1:        0 → 0
  ±1+:            0 → 0
  RAISES:         0 → 0

Cohort-2 HP cluster post-slice:
  0100  +0.00003   ←  was +0.00283
  0320  -0.00001   ←  was +0.01801
  0330  -0.00004   ←  was +0.01772
  2336  +0.00003   ←  was +0.01778
  3336  +0.00001   ←  was +0.04005  (worst residual closes exact)
  4536  -0.00002   ←  was +0.01312
  9036  -0.00003   ←  was +0.02159
  9796  +0.00000   ←  was +0.00174  (post-S0380.27)
  2536  +0.00072   ←  was +0.00163
  2800  +0.00068   ←  was +0.00436
  4800  +0.00068   ←  was +0.02939
  9370  +0.00002   ←  was +0.00174
  9421  +0.00001   ←  was +0.00117

Cohort-1 ASHP cohort (7-cert cohort + new chain test certs):
  cert 0380:  +1e-6   ←  was +0.034 (Mitsubishi PUZ-WM50VHA, the
                                     canonical first-HP cohort cert)
  cert 3800:  -2e-5   ←  was +0.021
  cert 9418:  -3e-7   ←  was +0.00004
  cert 9285:  -3e-5   ←  was +0.021
  cert 2636:  -0.015  ←  was +0.003 (cantilever fixture; remaining
                                     residual is non-η in nature)

5 of 7 cohort-1 ASHP certs now hit delta < 1e-4 vs worksheet — the
+0.04 spec-precision-floor cluster diagnosed in
HANDOVER_CERT_0380_MIT_CASCADE.md is the linear-vs-reciprocal η
interpolation bug, not a spec-floor at all. The handover doc's "no
public spec or BRE data field would distinguish these" claim was
incorrect — SAP 10.2 footnote 43 is the resolution.

API path (golden fixtures): 6 ASHP cohort residuals updated to reflect
the cascade closure:
  cert 0380 PE: -14.7865 → -14.6848 kWh/m²; CO2: +0.2774 → +0.2780 t/yr
  cert 0350 PE: -7.9281  → -7.8741;        CO2: +0.1697 → +0.1701
  cert 2225 PE: -11.9175 → -11.8557;       CO2: +0.2617 → +0.2621
  cert 2636 PE: -9.7153  → -9.6692;        CO2: +0.2189 → +0.2193
  cert 3800 PE: -9.7551  → -9.6838;        CO2: +0.2598 → +0.2603
  cert 9285 PE: -8.1110  → -8.0466;        CO2: +0.1559 → +0.1564

All SAP integer residuals unchanged (cascade tracks the EPC integer
SAP at residual 0 across the cohort).

PSR interpolation unit test (`test_interpolate_heat_pump_efficiency_at
_cert_0380_psr_per_sap_app_n`) updated to reflect the reciprocal
formula with the SAP-10.2-footnote-43 spec citation and closed-form
asserts (η_space_1 ≈ 234.5235; η_water_3 ≈ 285.0861 at PSR=1.43).

Pyright net-zero (1 → 1 across touched files: pcdb/parser.py,
tests/test_pcdb_table_362_lookup.py, rdsap/tests/test_golden_fixtures.py).

Tests: 710 pass (was 710 pre-slice with linear interp + un-updated
pins; net-zero because the 6 golden pin updates + 1 interp test update
exactly offset the 6 + 1 failures the formula change introduced), 10
expected fails unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 11:44:11 +00:00
Khalim Conn-Kowlessar
4879e8c3d7 Slice S0380.20: extract PCDB keep-hot fields + strict-raise for no-keep-hot combis
Surfaces the SAP 10.2 Appendix J Table 3a sub-row dispatch gap that
masked +0.2..+0.4 SAP residuals on 11 cohort-2 PCDB-listed combi
certs. Identified via cert 7800-1501-0922-7127-3563 (Potterton Promax
Combi 28 HE Plus A, PCDF 15709): cascade used the keep-hot 600 kWh/yr
default; worksheet (61) sums to ~428 kWh/yr via the no-keep-hot
sub-row formula.

Root cause: the PCDB Table 105 record carries keep-hot metadata at
field positions 58 (`keep_hot_facility`) and 59 (`keep_hot_timer`)
per the SAP 10 PCDB spec (private feed for SAP software vendors —
not surfaced on the public PCDB website nor the Open EPC API). The
parser preserved these in `raw=fields` but didn't surface them as
typed attributes, so the cascade had no signal to dispatch the right
Table 3a sub-row.

Two-part change:

1. `domain/sap10_calculator/tables/pcdb/parser.py` — adds typed
   `keep_hot_facility` and `keep_hot_timer` fields to
   `GasOilBoilerRecord`, parsed from fields[57] and fields[58].
   Field enums (per BRE STP09-B04 + SAP 10 PCDB spec):
     Field 58: 0=no keep-hot, 1=fuel keep-hot, 2=electric keep-hot,
               3=gas+electric keep-hot
     Field 59: 0=no timer, 1=overnight time-switch
   Verified against cohort-1 fixture 000490 (Vaillant Ecotec Pro 28,
   PCDF 10328) — record lodges keep_hot_facility=1, keep_hot_timer=1,
   exactly matching the hand-built fixture comment "Combi keep hot
   type = Gas/Oil, time clock" at `_elmhurst_worksheet_000490.py:
   277-280`.

2. `domain/sap10_calculator/rdsap/cert_to_inputs.py` — adds
   `UnresolvedPcdbCombiLoss` exception. `pcdb_combi_loss_override`
   now raises (instead of silently returning None) when the PCDB
   record has `separate_dhw_tests=0/None` AND
   `keep_hot_facility=0/None`. The cascade's only implemented Table
   3a row is "with keep-hot, time clock" (600 kWh/yr), which is the
   wrong spec row for no-keep-hot combis — silently using it masked
   the cohort-2 negative band.

The ETL was re-run to refresh `pcdb_table_105_gas_oil_boilers.jsonl`
with the new typed fields (raw fields unchanged, just additional
columns surfacing what was previously buried).

Cohort distribution after slice:

  cohort-1 cert 000490 (Vaillant PCDF 10328, kh=1): NO RAISE — cascade
    keep-hot 600 default IS the spec-correct row. Tests still GREEN.
  cohort-2: 10 exact + 13 sub-±0.07 + 2 ±0.07..0.5 + 1 ±0.5..1 +
            1 ±5+ + 11 RAISES.

The 11 raising certs are now blocked until the Table 3a no-keep-hot
sub-row is implemented (BRE STP09-B04 methodology — pending slice).
Previously these certs silently produced +0.2..+0.4 SAP errors AND
ranged into the big-gap band; raising surfaces the gap rather than
shipping wrong numbers.

Two golden cert tests blocked alongside (Firebird oil PCDF 9005 also
hits this path):
  - test_golden_cert_residual_matches_pin[0390-2954-3640-2196-4175]
  - test_api_to_domain_mapper_preserves_main_heating_index_number[0390-2954-3640-2196-4175]
Re-enable when the Table 3a no-keep-hot row lands.

Two other tests updated:
  - test_main_heating_index_number_in_pcdb_overrides_seasonal_efficiency:
    switched from Baxi 98 (sdt=0, kh=None, would raise) to Worcester
    PCDF 10241 (sdt=1, routes via Table 3b row 1). Asserts 0.885 not
    0.66.
  - test_pcdb_combi_loss_override_returns_none_or_raises_for_untested
    _or_storage_combis: renamed + extended to pin the new strict-raise
    behaviour.

Pyright net-zero per file:
  - domain/sap10_calculator/rdsap/cert_to_inputs.py: 35 (baseline 35)
  - domain/sap10_calculator/tables/pcdb/parser.py: 0
  - domain/sap10_calculator/tables/pcdb/__init__.py: 0
  - domain/sap10_calculator/rdsap/tests/test_cert_to_inputs.py: 13 (baseline 13)
  - domain/sap10_calculator/rdsap/tests/test_golden_fixtures.py: 1 (was 2 — improved)

Regression baseline: 697 pass + 10 fail (= prior 699 + 10 - 2 dropped
golden parametrize entries for cert 0390-2954-3640-2196-4175).

Spec refs:
- SAP 10 PCDB spec (private SAP software vendor feed) — keep-hot
  facility / timer / electric-heater fields at positions 58 / 59 / 60.
- BRE STP09-B04 (combi boiler test methodology) — origin of the
  keep-hot Table 3a derivation. URL: https://bregroup.com/documents/d
  /bre-group/stp09-b04_combi_boiler_tests
- SAP 10.2 Appendix J Table 3a row-selection — to be implemented per
  PCDB keep-hot dispatch in a follow-up slice.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 08:10:27 +00:00
Khalim Conn-Kowlessar
7adb6c7907 Slice 102f-prep.1: PCDB Table 362 heating_duration_code field
SAP 10.2 Appendix N3.5 (PDF p.105 line 6099) — heat-pump packages
lodge a "Daily heating duration" field encoded as "24" / "16" / "9"
/ "V" (Variable). Footnote 48 (PDF p.105): "Daily heating durations
of 24, 16 and 9 hours are retained for legacy purposes" — modern
records always lodge "V".

Format-465 position 48 holds the code; cohort ground truth: "V" on
Mitsubishi PUZ-WM50VHA (104568) and Daikin EDLQ05CAV3 (102421).
The field drives Appendix N3.5 + Table N4/N5 day allocation for the
extended-heating MIT cascade (slice 102f-prep.2 onward).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 13:09:07 +00:00
Khalim Conn-Kowlessar
5b78a1e2c8 Slice 102c.2: PCDB Table 362 PSR groups + APM linear interpolation
SAP 10.2 Appendix N3.6 / N3.7(a) (PDF p.108) compute heat-pump
efficiencies from a PSR-dependent dataset in the PCDB record. Spec PDF
p.100 line 5957 instructs: "The PSR-dependent results applicable to
the dwelling are then obtained by linear interpolation between the two
datasets whose PSRs enclose that of the dwelling."

This slice decodes the format-465 PSR-group block (idx[58] count
followed by N groups × 9 raw fields apiece) and adds the interpolation
primitive. Field positions within each 9-field group reverse-engineered
against Mitsubishi PUZ-WM50VHA (104568) by back-solving cert 0380's
worksheet pin η_space=223.0480, η_water=171.0746:

  group offset 0 → PSR
  group offset 2 → η_space,1 (% gross)
  group offset 6 → η_water,3 (% gross — Appendix N3.7(a) + footnote 49,
                  PSR-dependent and calculated via the annual performance
                  method, used directly for HPs providing both space +
                  water heating)

Offsets 1 / 3 / 4 / 5 / 7 / 8 are unpopulated for record 104568 and
not yet ground-truthed. They likely hold the secondary results
documented under format 464 field 42-43 (specific electricity
consumed, running hours) plus additional format-465 extensions.

The clamping behaviour at the PSR ends is taken from SAP 10.2 PDF
p.101 lines 6007-6008: "if the PSR is greater than the largest PSR in
the database record then the heat pump space and water heating
fractions for the largest PSR should be used, and if the PSR is less
than the smallest PSR in the database record then the heat pump space
and water heating fractions for the smallest PSR should be used".

Verified against cohort:
  - Record 104568 (Mitsubishi PUZ-WM50VHA) → 14 PSR groups decoded;
    interpolation at PSR=1.43 yields η_space,1≈234.96 and η_water,3
    ≈285.09, matching back-solved worksheet values (slice 102e applies
    the N3.6 ×0.95 and N3.7 ×0.60 in-use factors to close the chain).
2026-05-27 12:01:04 +00:00
Khalim Conn-Kowlessar
70aa709c1c Slice 102c.1: typed PCDB Table 362 (heat pumps) header parser
SAP 10.2 Appendix N (N3.6 / N3.7(a)) requires PSR-interpolated values
from PCDB Table 362 for any heat-pump cert. The published PCDF Spec
Rev 6b §A.23 documents format 464 for that table; the live
pcdb10.dat (April 2026) ships format 465, which extends 464 with
additional header fields between fields 11 and 12 and a larger PSR
group set. The parser-layer test pins the format-465 offsets against
the BRE web entry for Mitsubishi Ecodan 5.0 kW PUZ-WM50VHA
(pcdb_id=104568, the cohort's dominant heat-pump model — 6 of 7 ASHP
certs use it).

This slice lands only the header fields the downstream APM cascade
needs (PSR-group decoding + linear interpolation follow in slice 102c.2):

  field                            spec ref          format-465 idx
  brand_name                       §A.23 field 7     6
  model_name                       §A.23 field 8     7
  model_qualifier                  §A.23 field 9     8
  fuel                             §A.23 field 13    16
  service_provision                §A.23 field 17    22
  hw_vessel_mode                   §A.23 field 18    23
  vessel_volume_l                  §A.23 field 19    24
  vessel_heat_loss_kwh_per_day     §A.23 field 20    25
  vessel_heat_exchanger_area_m2    §A.23 field 21    26
  max_output_kw                    §A.23 field 30    47

`max_output_kw` is the PSR-denominator per SAP 10.2 PDF p.100 line 5946
("maximum nominal output of the package … divided by the design heat
loss of the dwelling"); BRE labels it "Output power @ -4.7°C" on the
web entry.

Cohort header parse verified end-to-end against BRE web ground truth
for record 104568. Identical field positions apply to the Daikin
EDLQ05CAV3 (102421, cert 9418), confirmed by spot-checking the
populated raw indices.
2026-05-27 11:56:06 +00:00
Khalim Conn-Kowlessar
a7b08a4e8f refactor: move docs/sap-spec/ contents into domain/sap10_calculator/
Locality of reference — SAP-specific docs, specs, and runtime data
now live alongside the calculator that consumes them, mirroring the
prior packages→domain layout moves.

Move targets:

- Narrative MDs → domain/sap10_calculator/docs/
    NEXT_AGENT_PROMPT.md, HANDOVER_NEXT.md, SAP_CALCULATOR.md
- Spec PDFs → domain/sap10_calculator/docs/specs/
    RdSAP 10 Specification 10-06-2025.pdf
    PCDF_Spec_Rev-06b_12_May_2021.pdf
    sap-10-2-full-specification-2025-03-14.pdf
    sap-10-3-full-specification-2026-01-13.pdf
- PCDB runtime data → domain/sap10_calculator/tables/pcdb/data/
    pcdb10.dat (8.3MB) + 7× pcdb_table_*.jsonl (18MB total)

Path code rewrites (load-bearing):

- tables/pcdb/__init__.py: replaced parents[4]/'docs'/'sap-spec' with
  Path(__file__).resolve().parent/'data' for Table 105 JSONL loading.
- tables/pcdb/postcode_weather.py: same rebase for the pcdb10.dat path
  read by _postcode_climate_table().
- tables/pcdb/etl.py __main__: same rebase for the manual ETL invocation
  (source + output_dir both now point inside the package).
- tests/test_pcdb_etl.py: _PCDB_DAT_PATH now derives from
  parents[1]/'tables'/'pcdb'/'data' (was parents[3]/'docs'/'sap-spec').

Citation rewrites:

- 12 .py docstrings and 4 .md docs (ADRs + READMEs + narrative docs)
  had `docs/sap-spec/<file>` strings rewritten to their new locations.
- Two cases where the catch-all sed misfired (an ADR-0009 line about a
  PCDB extract; the pcdb __init__.py docstring about ETL output) were
  hand-corrected to point at tables/pcdb/data/ rather than docs/specs/.

docs/sap-spec/ is now empty (will be removed in a follow-up sweep or
left as a vestigial empty dir for future repurposing). ADRs 0009 and
0010 remain at docs/adr/ — they're part of the chronological
cross-cutting decision log, not calculator-specific narrative.

Verified:

- Calculator's 1e-4 production gate
  (test_api_001479_full_chain_sap_matches_worksheet_pdf_exactly) GREEN.
- Wider sweep (domain/sap10_calculator/ + domain/sap10_ml/): 1654
  passed / 20 failed — exact pre-move baseline. All 20 failures
  pre-existing (10 hand-built skeleton + 4 cohort chain + 6 cohort
  diff).
- Pyright net-zero on the 4 touched runtime/test files (0 errors)
  and unchanged on heat_transmission.py (13) / cert_to_inputs.py (35) /
  mapper.py (33).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:17:18 +00:00
Khalim Conn-Kowlessar
29ac35ccbe refactor: lift-and-shift packages/domain/src/domain/sap → domain/sap10_calculator
Migration of the SAP 10.2 calculator package from the uv-workspace
src-layout (`packages/domain/src/domain/sap`) to the root-level layout
(`domain/sap10_calculator`), matching the pattern already used by
`domain.addresses` / `domain.tasks` / `domain.postcode`.

Changes:

- `git mv packages/domain/src/domain/sap → domain/sap10_calculator`
  (92 files; git auto-detected all as renames so blame/history is
  preserved).
- Subpackage rename: `domain.sap` → `domain.sap10_calculator`. 48
  Python files rewritten (`from domain.sap.X` → `from domain.sap10_
  calculator.X`); zero remaining `domain.sap` refs after the sed pass.
- Path-string updates: 3 .py files (test fixtures + xlsx loader) +
  6 markdown docs (CONTEXT.md, 2 ADRs, 3 sap-spec docs, sap10_
  calculator/README.md) had hard-coded `packages/domain/src/domain/
  sap/...` paths rewritten to `domain/sap10_calculator/...`.
- `Path(__file__).parents[N]` rebasing: the old tree was 3 levels
  deeper than the new one (`packages/domain/src/`), so 4× `parents[7]`
  became `parents[4]` and 1× `parents[6]` became `parents[3]` across
  `tables/pcdb/{__init__.py, postcode_weather.py, etl.py}`,
  `worksheet/tests/_xlsx_loader.py`, and `tests/test_pcdb_etl.py`.
- PEP 420 namespace package: deleted both `domain/__init__.py`
  (root + workspace, both load-bearing only as empty/docstring) so
  Python combines `domain.sap10_calculator` (root) and `domain.ml`
  (workspace) into one namespace package. Confirmed via
  `domain.__path__ == ['/workspaces/model/domain',
  '/workspaces/model/packages/domain/src/domain']`. Without this,
  the root `domain/__init__.py` shadowed the workspace one and
  `domain.ml` was unreachable.

Verified:

- Full sweep (`backend/documents_parser/tests/test_summary_pdf_
  mapper_chain.py + domain/sap10_calculator/worksheet/tests/test_
  e2e_elmhurst_sap_score.py + domain/sap10_calculator/rdsap/tests/
  test_golden_fixtures.py`): 99 passed / 19 failed — exact same
  counts as pre-refactor. All 19 failures pre-existing (9 hand-built
  001479 + 6 cohort diff + 4 cohort chain non-spec).
- Wider sweep (all sap10_calculator + domain.ml): 1654 passed /
  20 failed (the +1 vs the focused sweep is the pre-existing
  `test_roof_insulated_assumed_with_ni_thickness_uses_50mm_per_
  section_5_11_4` which was already failing on the previous baseline).
- Pyright net-zero on the three load-bearing baselines:
  `heat_transmission.py` 13, `cert_to_inputs.py` 35, `mapper.py` 33.

Lift-and-shift only — no semantic renames (`Sap10Calculator` stays
`Sap10Calculator`), no testpaths edits in pytest.ini (sap tests
continue to be invoked by explicit pytest paths).

Note: `domain.ml` still lives at `packages/domain/src/domain/ml/`.
Migrating it would close out the dual-`domain/` layout but is
out of scope for this commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 12:22:37 +00:00
Renamed from packages/domain/src/domain/sap/tables/pcdb/parser.py (Browse further)