diff --git a/CONTEXT.md b/CONTEXT.md index 7a79d1c..4bdd7ba 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -74,6 +74,20 @@ See [ADR-0001](./docs/adr/0001-bulk-upload-state-machine.md) for the deliberate - A **BulkUpload** has at most one **Task** (the orchestration handle for the FastAPI pipeline run); a Task has many **SubTasks** (one per pipeline stage: address matching, combiner). - A **Portfolio** has many **VocabularyMappings** — one row per `(category, description)` it has ever encountered across all its BulkUploads. See [ADR-0002](./docs/adr/0002-landlord-override-vocabulary.md). +### Baseline performance + +**Lodged performance**: +The SAP score, EPC band, CO₂ emissions, and primary energy intensity as submitted to the government EPC register. Ground truth from the register; never modified. +_Avoid_: original performance, registered performance + +**Effective performance**: +The SAP score (and associated metrics) that the modelling engine actually uses as its baseline. Usually equals Lodged performance, but differs when a Landlord override or data-quality issue makes the lodged certificate unreliable — triggering a Rebaseline. +_Avoid_: current performance, adjusted performance + +**Rebaseline**: +The act of substituting a corrected set of performance metrics in place of the Lodged values. Recorded on `property_baseline_performance` with a `rebaseline_reason` enum value: `none`, `pre_sap10`, `physical_state_changed`, or `both`. +_Avoid_: override, adjustment, correction + ## Example dialogue > **Dev:** "If the **Combiner** finishes but the user hasn't clicked Finalise, what does the user see?" diff --git a/backlog/drizzle-schema-handoff.md b/backlog/drizzle-schema-handoff.md index 4308465..0cbf28c 100644 --- a/backlog/drizzle-schema-handoff.md +++ b/backlog/drizzle-schema-handoff.md @@ -1,6 +1,6 @@ # Drizzle schema handoff — pending EPC migrations -**Task:** Update Drizzle table definitions in `src/app/schema/property.ts` to match the +**Task:** Update Drizzle table definitions in `src/app/db/schema/property.ts` to match the Python SQLModel definitions. Do **not** run `drizzle-kit generate` or any migration commands — the developer will run generation manually after your changes. @@ -9,6 +9,27 @@ Two sets of changes are covered here: 1. EPC property round-trip fidelity gaps (fixes an active production error) 2. New `property_baseline_performance` table +### Before starting: update the import line + +`jsonb` is not currently imported. Add it (and `pgEnum` is already present): + +```typescript +import { + bigserial, + text, + timestamp, + pgTable, + real, + pgEnum, + integer, + boolean, + smallint, + bigint, + uniqueIndex, + jsonb, // ← add this +} from "drizzle-orm/pg-core"; +``` + --- ## 1. `epc_property` — new columns @@ -122,12 +143,28 @@ curtainWallAge: text("curtain_wall_age"), > **Note on `draughtProofed` and `permanentShuttersPresent`:** these are `boolean` in the > current TypeScript schema but `Union[bool, str]` JSONB in the Python model. Change them > to `jsonb(...).notNull()` — the TypeScript boolean type was incorrect. +> +> These two columns also require **boolean-specific** USING clauses in the generated migration +> (PostgreSQL will not implicitly cast `boolean` to `jsonb`): +> +> ```sql +> ALTER TABLE "epc_window" ALTER COLUMN "draught_proofed" +> SET DATA TYPE jsonb +> USING to_json("draught_proofed")::jsonb; +> +> ALTER TABLE "epc_window" ALTER COLUMN "permanent_shutters_present" +> SET DATA TYPE jsonb +> USING to_json("permanent_shutters_present")::jsonb; +> ``` +> +> `to_json` converts `true`/`false` to JSON booleans (not quoted strings), which is correct +> for the `Union[bool, str]` Python type. --- ## 6. New table: `epc_renewable_heat_incentive` -Add this table to `src/app/schema/property.ts`: +Add this table to `src/app/db/schema/property.ts`: ```typescript export const epcRenewableHeatIncentive = pgTable( @@ -151,7 +188,18 @@ export const epcRenewableHeatIncentive = pgTable( ## 7. New table: `property_baseline_performance` -Add this table to `src/app/schema/property.ts`: +First, add the enum (before the table definition): + +```typescript +export const rebaselineReasonEnum = pgEnum("rebaseline_reason", [ + "none", + "pre_sap10", + "physical_state_changed", + "both", +]); +``` + +Then add the table to `src/app/db/schema/property.ts`: ```typescript export const propertyBaselinePerformance = pgTable( @@ -165,7 +213,7 @@ export const propertyBaselinePerformance = pgTable( // Lodged performance (from gov EPC register) lodgedSapScore: integer("lodged_sap_score").notNull(), - lodgedEpcBand: text("lodged_epc_band").notNull(), + lodgedEpcBand: epcEnum("lodged_epc_band").notNull(), lodgedCo2EmissionsTPerYr: real("lodged_co2_emissions_t_per_yr").notNull(), lodgedPrimaryEnergyIntensityKwhPerM2Yr: integer( "lodged_primary_energy_intensity_kwh_per_m2_yr", @@ -173,7 +221,7 @@ export const propertyBaselinePerformance = pgTable( // Effective performance (what modelling scored against) effectiveSapScore: integer("effective_sap_score").notNull(), - effectiveEpcBand: text("effective_epc_band").notNull(), + effectiveEpcBand: epcEnum("effective_epc_band").notNull(), effectiveCo2EmissionsTPerYr: real( "effective_co2_emissions_t_per_yr", ).notNull(), @@ -181,7 +229,7 @@ export const propertyBaselinePerformance = pgTable( "effective_primary_energy_intensity_kwh_per_m2_yr", ).notNull(), - rebaselineReason: text("rebaseline_reason").notNull(), + rebaselineReason: rebaselineReasonEnum("rebaseline_reason").notNull(), // Interim energy demand (from EPC RHI data; superseded by bill block below once populated) spaceHeatingKwh: real("space_heating_kwh").notNull(),