Merge pull request #307 from Hestia-Homes/feature/recommendations-migrations
Some checks failed
Test Suite / unit-tests (push) Has been cancelled

Recommendations migrations #1: add plan_id to recommendation table and backfill
This commit is contained in:
Daniel Roth 2026-06-05 11:19:36 +01:00 committed by GitHub
commit 899ac432ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 43076 additions and 3 deletions

View file

@ -0,0 +1,3 @@
ALTER TABLE "recommendation" ADD COLUMN "plan_id" bigint;--> statement-breakpoint
ALTER TABLE "recommendation" ADD CONSTRAINT "recommendation_plan_id_plan_id_fk" FOREIGN KEY ("plan_id") REFERENCES "public"."plan"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "idx_recommendation_plan_id" ON "recommendation" USING btree ("plan_id");

View file

@ -0,0 +1,23 @@
-- Guard: fail if any recommendation is linked to more than one plan.
-- The spec asserts each recommendation belongs to exactly one plan; if real
-- data violates that the backfill cannot pick deterministically and must not
-- proceed silently.
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM plan_recommendations
GROUP BY recommendation_id
HAVING count(*) > 1
) THEN
RAISE EXCEPTION
'plan_id backfill aborted: one or more recommendations appear in multiple '
'plan_recommendations rows. Resolve cardinality before re-running.';
END IF;
END $$;
UPDATE recommendation r
SET plan_id = pr.plan_id
FROM plan_recommendations pr
WHERE pr.recommendation_id = r.id
AND r.plan_id IS NULL;

View file

@ -0,0 +1,6 @@
ALTER TABLE "recommendation" ADD COLUMN "material_id" bigint;--> statement-breakpoint
ALTER TABLE "recommendation" ADD COLUMN "material_quantity" real;--> statement-breakpoint
ALTER TABLE "recommendation" ADD COLUMN "material_quantity_unit" "unit_quantity";--> statement-breakpoint
ALTER TABLE "recommendation" ADD COLUMN "material_depth" real;--> statement-breakpoint
ALTER TABLE "recommendation" ADD CONSTRAINT "recommendation_material_id_material_id_fk" FOREIGN KEY ("material_id") REFERENCES "public"."material"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "idx_recommendation_material_id" ON "recommendation" USING btree ("material_id");

View file

@ -0,0 +1,26 @@
-- Guard: fail if any recommendation maps to more than one material row.
-- The modelled fabric measures never produce this; if real data violates it
-- the backfill cannot pick deterministically and must not proceed silently.
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM recommendation_materials
GROUP BY recommendation_id
HAVING count(*) > 1
) THEN
RAISE EXCEPTION
'recommendation_material_id backfill aborted: one or more recommendations '
'have multiple recommendation_materials rows. Resolve cardinality before re-running.';
END IF;
END $$;
-- Backfill all four columns in a single pass.
UPDATE recommendation r
SET material_id = rm.material_id,
material_quantity = rm.quantity,
material_quantity_unit = rm.quantity_unit,
material_depth = rm.depth
FROM recommendation_materials rm
WHERE rm.recommendation_id = r.id
AND r.material_id IS NULL;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1548,6 +1548,34 @@
"when": 1780566543108,
"tag": "0221_nice_sumo",
"breakpoints": true
},
{
"idx": 222,
"version": "7",
"when": 1780647165601,
"tag": "0222_nifty_hellcat",
"breakpoints": true
},
{
"idx": 223,
"version": "7",
"when": 1780647248894,
"tag": "0223_recommendation_plan_id_backfill",
"breakpoints": true
},
{
"idx": 224,
"version": "7",
"when": 1780653770494,
"tag": "0224_busy_nitro",
"breakpoints": true
},
{
"idx": 225,
"version": "7",
"when": 1780654800000,
"tag": "0225_recommendation_material_id_backfill",
"breakpoints": true
}
]
}

View file

@ -60,6 +60,9 @@ export const measureTypeEnum = pgEnum("measure_type", [
"sealing_open_fireplace",
]);
export const unitQuantity: [string, ...string[]] = ["m2", "part", "kwp"];
export const unitQuantityEnum = pgEnum("unit_quantity", unitQuantity);
export const recommendation = pgTable(
"recommendation",
{
@ -67,6 +70,16 @@ export const recommendation = pgTable(
propertyId: bigint("property_id", { mode: "bigint" })
.notNull()
.references(() => property.id),
planId: bigint("plan_id", { mode: "bigint" }).references(() => plan.id, {
onDelete: "cascade",
}),
materialId: bigint("material_id", { mode: "bigint" }).references(
() => material.id,
{ onDelete: "set null" }
),
materialQuantity: real("material_quantity"),
materialQuantityUnit: unitQuantityEnum("material_quantity_unit"),
materialDepth: real("material_depth"),
createdAt: timestamp("created_at").notNull().defaultNow(),
type: text("type").notNull(),
measureType: text("measure_type"),
@ -92,6 +105,7 @@ export const recommendation = pgTable(
},
(table) => [
index("recommendation_property_id_idx").on(table.propertyId),
index("idx_recommendation_plan_id").on(table.planId),
index("idx_recommendation_active_defaults")
.on(table.id)
.where(
@ -103,12 +117,11 @@ export const recommendation = pgTable(
.where(
sql`${table.default} = true AND ${table.alreadyInstalled} = false`,
),
index("idx_recommendation_material_id").on(table.materialId),
],
);
export const unitQuantity: [string, ...string[]] = ["m2", "part", "kwp"];
export const unitQuantityEnum = pgEnum("unit_quantity", unitQuantity);
export const recommendationMaterials = pgTable(
"recommendation_materials",
{