mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-27 22:45:03 +00:00
The modelling_e2e Lambda reads the active catalogue once per invocation (SELECT * FROM material WHERE is_active ORDER BY id). Without an index this was a full seq scan every time (~23ms), paid by all 32 concurrent Lambdas at startup. Partial index on id WHERE is_active returns the active rows already id-ordered — no sort, no filter. Verified on dev: seq scan 23.2ms -> index scan 4.2ms. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
137 lines
4.1 KiB
TypeScript
137 lines
4.1 KiB
TypeScript
import { InferModel, sql } from "drizzle-orm";
|
||
import {
|
||
bigserial,
|
||
text,
|
||
timestamp,
|
||
pgTable,
|
||
real,
|
||
pgEnum,
|
||
json,
|
||
boolean,
|
||
index,
|
||
} from "drizzle-orm/pg-core";
|
||
|
||
export const MaterialType: [string, ...string[]] = [
|
||
"suspended_floor_insulation",
|
||
"solid_floor_insulation",
|
||
"external_wall_insulation",
|
||
"internal_wall_insulation",
|
||
"cavity_wall_insulation",
|
||
"mechanical_ventilation",
|
||
"loft_insulation",
|
||
"exposed_floor_insulation",
|
||
"flat_roof_insulation",
|
||
"room_roof_insulation",
|
||
"cavity_wall_extraction",
|
||
// Non insulation tasks
|
||
"iwi_wall_demolition",
|
||
"iwi_vapour_barrier",
|
||
"iwi_redecoration",
|
||
"suspended_floor_demolition",
|
||
"suspended_floor_redecoration",
|
||
"suspended_floor_vapour_barrier",
|
||
"solid_floor_demolition",
|
||
"solid_floor_preparation",
|
||
"solid_floor_vapour_barrier",
|
||
"solid_floor_redecoration",
|
||
"ewi_wall_demolition",
|
||
"ewi_wall_preparation",
|
||
"ewi_wall_redecoration",
|
||
"low_energy_lighting_installation",
|
||
"flat_roof_preparation",
|
||
"flat_roof_vapour_barrier",
|
||
"flat_roof_waterproofing",
|
||
// Windows
|
||
"windows_glazing",
|
||
"secondary_glazing",
|
||
"double_glazing",
|
||
// vents
|
||
"trickle_vent",
|
||
"door_undercut",
|
||
// solar
|
||
"solar_pv",
|
||
"solar_battery",
|
||
// scaffolding
|
||
"scaffolding",
|
||
// heating systems
|
||
"high_heat_retention_storage_heaters",
|
||
"air_source_heat_pump",
|
||
// Upgrade an exiting heating system
|
||
"boiler_upgrade",
|
||
// heating controls
|
||
"roomstat_programmer_trvs",
|
||
"time_temperature_zone_control",
|
||
// other
|
||
"sealing_fireplace",
|
||
];
|
||
export const materialTypeEnum = pgEnum("type", MaterialType);
|
||
|
||
export const DepthUnit: [string, ...string[]] = ["mm"];
|
||
export const depthUnitEnum = pgEnum("depth_unit", DepthUnit);
|
||
|
||
export const SizeUnit: [string, ...string[]] = ["kWp", "kW", "watt", "storey"];
|
||
export const sizeUnitEnum = pgEnum("size_unit", SizeUnit);
|
||
|
||
export const CostUnit: [string, ...string[]] = [
|
||
"gbp_sq_meter",
|
||
"gbp_per_unit",
|
||
"gbp_per_m2",
|
||
"gbp_per_m",
|
||
];
|
||
export const costUnitEnum = pgEnum("cost_unit", CostUnit);
|
||
|
||
export const RValueUnit: [string, ...string[]] = [
|
||
"square_meter_kelvin_per_watt",
|
||
];
|
||
export const rValueUnitEnum = pgEnum("r_value_unit", RValueUnit);
|
||
|
||
export const ThermalConductivityUnit: [string, ...string[]] = [
|
||
"watt_per_meter_kelvin",
|
||
];
|
||
export const thermalConductivityUnitEnum = pgEnum(
|
||
"thermal_conductivity_unit",
|
||
ThermalConductivityUnit
|
||
);
|
||
|
||
export const material = pgTable("material", {
|
||
id: bigserial("id", { mode: "bigint" }).primaryKey(),
|
||
type: materialTypeEnum("type").notNull(),
|
||
description: text("description").notNull(),
|
||
depth: json("depth").$type<number[]>(),
|
||
depth_unit: depthUnitEnum("depth_unit"),
|
||
|
||
costUnit: costUnitEnum("cost_unit"),
|
||
rValuePerMm: real("r_value_per_mm"),
|
||
rValueUnit: rValueUnitEnum("r_value_unit"),
|
||
thermalConductivity: real("thermal_conductivity"),
|
||
thermalConductivityUnit: thermalConductivityUnitEnum(
|
||
"thermal_conductivity_unit"
|
||
),
|
||
link: text("link"),
|
||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||
isActive: boolean("is_active").notNull().default(true),
|
||
prime_material_cost: real("prime_material_cost"),
|
||
material_cost: real("material_cost"),
|
||
labour_cost: real("labour_cost"),
|
||
labour_hours_per_unit: real("labour_hours_per_unit"),
|
||
plant_cost: real("plant_cost"),
|
||
total_cost: real("total_cost"),
|
||
cost: json("cost").$type<number[]>(),
|
||
notes: text("notes"),
|
||
isInstallerQuote: boolean("is_installer_quote").default(false),
|
||
innovationRate: real("innovation_rate").default(0),
|
||
size: real("size"),
|
||
// Can be null
|
||
sizeUnit: sizeUnitEnum("size_unit"),
|
||
includesScaffolding: boolean("includes_scaffolding").default(false),
|
||
includesBattery: boolean("includes_battery").default(false),
|
||
batterySize: real("battery_size"),
|
||
}, (table) => [
|
||
// The modelling_e2e Lambda reads the active catalogue once per invocation
|
||
// (WHERE is_active ORDER BY id). Without this index that is a full seq scan
|
||
// every time (~23ms, ×32 concurrent at startup). Partial on id so the active
|
||
// rows come back already id-ordered — no sort, no filter.
|
||
index("idx_material_active").on(table.id).where(sql`${table.isActive}`),
|
||
]);
|
||
|
||
export type Material = InferModel<typeof material, "select">;
|