From b0c47432bab938dbf4fa0f5e91d893b294d8fda3 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 14 Jul 2026 15:02:28 +0000 Subject: [PATCH] fix(condition): match Calico roof covering (element_type "roof") Verifying against portfolio 824 revealed the Calico condition upload stores roof covering as the generic `roof` element (renewal on its `material` aspect), not the enum's pitched/flat_roof_covering variants (which have zero rows). Add "roof" to the solar-warning roof set so the warning actually fires against real data; add a regression test and note the data shape in CONTEXT.md. Co-Authored-By: Claude Opus 4.8 (1M context) --- CONTEXT.md | 2 +- src/lib/condition/solarWarning.test.ts | 9 +++++++++ src/lib/condition/solarWarning.ts | 10 ++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 2d739506..5adc4930 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -143,7 +143,7 @@ A dated batch of condition data for one property from a single **source** (e.g. _Avoid_: survey (unqualified — collides with retrofit/EPC surveys), condition report, upload, batch **Element**: -One physically distinct part of a home whose condition is surveyed — e.g. roof covering, external wall, boiler. The canonical domain and code term, backed by the `element_type` enum (roof covering is `pitched_roof_covering` / `flat_roof_covering`). An Element belongs to one Condition survey and may recur within it (`element_instance` distinguishes two roofs). +One physically distinct part of a home whose condition is surveyed — e.g. roof covering, external wall, boiler. The canonical domain and code term, backed by the `element_type` enum. (The enum has `pitched_roof_covering` / `flat_roof_covering`, but the Calico data models roof covering as the generic `roof` element, with its renewal on the `material` Aspect — match on the value the data actually uses, not the enum catalogue.) An Element belongs to one Condition survey and may recur within it (`element_instance` distinguishes two roofs). _Avoid_: component (in code/domain — see **Component**) **Component**: diff --git a/src/lib/condition/solarWarning.test.ts b/src/lib/condition/solarWarning.test.ts index c5157a6e..ac37c1cb 100644 --- a/src/lib/condition/solarWarning.test.ts +++ b/src/lib/condition/solarWarning.test.ts @@ -26,6 +26,15 @@ describe("evaluateRoofCoveringSolarWarning", () => { expect(result).toEqual({ renewalYear: 2027, isOverdue: false }); }); + it("treats the generic `roof` element as roof covering (Calico data shape)", () => { + const result = evaluateRoofCoveringSolarWarning({ + recommendations: [{ type: "solar_pv" }], + elements: [elem({ elementType: "roof", renewalYear: 2018 })], + currentYear: 2026, + }); + expect(result).toEqual({ renewalYear: 2018, isOverdue: true }); + }); + it("uses the earliest renewal across multiple roof-covering elements (pitched + flat)", () => { const result = evaluateRoofCoveringSolarWarning({ recommendations: [{ type: "solar_pv" }], diff --git a/src/lib/condition/solarWarning.ts b/src/lib/condition/solarWarning.ts index 79d1b7c0..61aa7dbe 100644 --- a/src/lib/condition/solarWarning.ts +++ b/src/lib/condition/solarWarning.ts @@ -1,7 +1,13 @@ import type { ResolvedElement } from "./model"; -// Roof covering is an Element; both pitched and flat count for the solar rule. -const ROOF_COVERING_TYPES = new Set(["pitched_roof_covering", "flat_roof_covering"]); +// Roof covering as an Element. The Calico condition data models it as the +// generic `roof` element (renewal carried on its `material` aspect); the enum +// also has pitched/flat variants, included for future data sources. +const ROOF_COVERING_TYPES = new Set([ + "roof", + "pitched_roof_covering", + "flat_roof_covering", +]); export type RoofCoveringSolarWarning = { renewalYear: number;