Model/domain
Khalim Conn-Kowlessar 1b3bbbf783 Slice S0380.88: full Table 4e dispatch + strict-raise on unmapped control codes
SAP 10.2 Table 4e (PDF p.171-174) "Heating system controls" — 8 groups
covering boiler / HP / heat-network / electric-storage / warm-air /
room-heater / other systems, ~40 codes total. Pre-S0380.88 the cascade
dispatch dict had spotty coverage:

  - Group 1 (BOILER): partial (12 of 13 codes)
  - Group 2 (HEAT PUMP): added in S0380.87 (10 codes)
  - Groups 3, 4, 5, 6, 7: completely missing

Codes from missing groups silently defaulted to type 2 — the same
failure mode that hid the cert 000565 2207→type-3 bug for ~22 slices
until S0380.87 surfaced it. Per the user-requested strict-raise
philosophy ("we keep debugging silent fallbacks"), this slice
forecloses the pattern at the dispatch boundary.

Corpus audit (full JSON sweep) at HEAD c0328f4e:

    2103, 2104, 2106, 2110, 2113  (Group 1 — covered)
    2206, 2207                    (Group 2 — covered after S0380.87)
    2307                          (Group 3 — silently mis-classified)
    2401                          (Group 4 — silently mis-classified)
    2603                          (Group 6 — silently mis-classified)

Three corpus codes (2307, 2401, 2603) were silently routed to type 2
when their spec types are 2 / 3 / 3 respectively.

Fix:

  - `_CONTROL_TYPE_BY_CODE` extended to full Table 4e coverage
    (Groups 0-7), with per-group spec citation in comments
  - New `UnmappedSapCode(ValueError)` exception class mirroring the
    `UnmappedApiCode` / `UnmappedElmhurstLabel` mapper-side pattern
    per [[reference-unmapped-api-code]]
  - `_control_type` flipped to strict-raise: lodging absent (None /
    0 / "") returns modal type 2 default; lodging present but
    unmapped raises `UnmappedSapCode("main_heating_control", code)`

The strict / not-strict distinction is principled: cascade-helper
value defaults (u_wall, u_floor, ...) stay total per RdSAP §6.2.3
"assume as-built if no evidence". Code-dispatch sites strict-raise
because an unmapped code means the spec table coverage is incomplete
— a forcing function for spec-completion slices rather than a
silent miscalculation.

Tests (3 new, AAA-structure):

  - `test_main_heating_control_code_table_4e_full_coverage_groups_0_through_7`
    pins ~20 codes across Groups 3-7 to their spec-correct control
    types (Table 4e PDF p.171-174 verbatim)
  - `test_cert_to_inputs_raises_unmapped_sap_code_on_unknown_main_heating_control`
    pins the strict-raise contract: lodging present but unmapped
    (e.g. test code 2998) raises `UnmappedSapCode` with the field
    name + value attached
  - `test_cert_to_inputs_does_not_raise_when_main_heating_control_is_missing`
    pins the absent-lodging contract: None / "" / 0 returns modal
    type 2 default — same behaviour as pre-S0380.88 for legitimately
    missing data

Test baseline: 564 pass (was 561 + 3 new) + 9 expected
`test_sap_result_pin[000565-*]` fails unchanged. Cohort + golden +
cert 9501 unaffected (their codes were all already covered or
silently routed to type 2 which is now explicit).

Pyright net-zero per touched file. The new `not code` absent-
lodging check replaces the original `code is None or code == "" or
code == 0` triple-check (pyright flagged `is None` as redundant given
`main_heating_control: Union[int, str]` annotation; runtime data
exhibits None / "" on Main 2 records that lack space-heating
controls — cert 000565 Main 2 is one such case).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 09:12:25 +00:00
..
addresses get rid of comments 2026-05-20 13:21:11 +00:00
sap10_calculator Slice S0380.88: full Table 4e dispatch + strict-raise on unmapped control codes 2026-05-30 09:12:25 +00:00
sap10_ml Slice S0380.86: §5.6 thin-wall stone + §5.8 dry-line closes BP[0] alt1 cascade gap 2026-05-30 08:37:46 +00:00
tasks added postcode splitter rewrite to ddd 2026-05-19 16:35:09 +00:00
postcode.py get rid of comments 2026-05-20 13:21:11 +00:00