Model/packages/domain
Khalim Conn-Kowlessar aff331ff34 Slice 87: implement RdSAP 10 §5 (12) spec rule for suspended timber floor
Replace the empirical `_elmhurst_has_suspended_timber_floor` heuristic
(which keyed on Room-in-Roof < Main ground area) with the mechanical
RdSAP 10 Specification §5 rule (page 29):

  - Age band A-E: U-value < 0.5 → sealed (0.1); retro insulation + no
    U → sealed (0.1); otherwise unsealed (0.2)
  - Age band F-M: sealed (0.1)
  - Park home: unsealed (0.2)
  - Only applies when Main bp's lowest floor is a "Ground floor" with
    "Suspended timber" construction

The spec rule is derived in `_has_suspended_timber_floor_per_spec`
(cert_to_inputs.py) and applied in `ventilation_from_cert` whenever
the lodged `epc.sap_ventilation.has_suspended_timber_floor` is None.
Explicit lodged values (cohort hand-built fixtures) take precedence.

Impact on cert 001479 (the load-bearing API↔Elmhurst parity-test
fixture; previously the RR-based heuristic returned False for this
no-RR semi-detached, dropping (12) entirely):

  Mapper → cascade → SAP delta vs worksheet 69.0094:
    BEFORE: +1.1903 (mapper extracted False; cascade applied (12)=0)
    AFTER : +0.2290 (mapper extracts None; spec derives True/unsealed;
                     cascade applies (12)=0.2 → matches worksheet)

  Cohort cascade pins remain GREEN (66 of 66) — cohort hand-built
  fixtures retain their explicit `has_suspended_timber_floor` values
  which override the spec derivation.

Expected cohort regressions to triage in the next slice:
  - 4 cohort chain tests RED (000474, 000480, 000487, 000490) — their
    Elmhurst worksheets enter non-spec (12) values (0.0 or 0.2 when
    spec predicts the opposite) so the mapper-path cascade now
    diverges from the worksheet PDF at 1e-4.
  - 6 cohort diff tests RED — mapper now produces
    has_suspended_timber_floor=None while the cohort hand-builts
    retain explicit True/False overrides, producing a 1-field
    divergence per cohort cert.

Pyright net-zero (mapper 35→35; cert_to_inputs 35→35) — dead
`_elmhurst_has_suspended_timber_floor` removed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 20:29:54 +00:00
..
src/domain Slice 87: implement RdSAP 10 §5 (12) spec rule for suspended timber floor 2026-05-25 20:29:54 +00:00
pyproject.toml slice 13: to_rows(properties) returns pd.DataFrame 2026-05-16 16:43:28 +00:00
README.md added potential file scaffolding: 2026-05-15 10:56:53 +00:00

domna-domain

Shared domain types — Property, Properties, BaselinePerformance, Plan, PlanPhase, Scenario, ScenarioPhase, ScenarioSnapshot, Recommendation, OptimisedPackage, EpcPropertyData, etc.

Boundary: types only. No persistence, no IO, no business logic. Other packages and services depend on domna-domain; this package depends on nothing internal.

Domain definitions live in ../../CONTEXT.md. New types added here must match the glossary terms.

Layout

src/domain/
├── __init__.py
├── property.py             # Property, Properties, PropertyIdentity
├── site_notes.py
├── landlord_overrides.py
├── baseline_performance.py # lodged + effective pair (ADR-0004)
├── plan.py                 # Plan, PlanPhase, OptimisedPackage
├── scenario.py             # Scenario, ScenarioPhase, ScenarioSnapshot (ADR-0005)
├── recommendation.py
├── geospatial.py
├── solar.py
├── anomaly_flags.py
└── ml/
    ├── __init__.py
    ├── transform.py        # EpcMlTransform (versioned per §8.3)
    └── schema.py

When datatypes/epc/domain/ folds in, the EPC schema types move under src/domain/epc/.