From f86a37dd33a5f53c2a60b9aa50cd0afdfc68f30a Mon Sep 17 00:00:00 2001 From: "Sven (AAMOS AI)" Date: Tue, 11 Aug 2026 04:43:43 +0700 Subject: [PATCH] =?UTF-8?q?S5:=20onboarding=20=E2=86=92=20minne=20+=20smak?= =?UTF-8?q?signaler=20(user=5Fstated)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/lib/memoryImpact.ts | 2 + apps/api/src/lib/onboardingMemory.ts | 156 + apps/api/src/routes/me.ts | 4 + apps/api/src/routes/onboarding.ts | 11 + apps/api/src/routes/recommendations.ts | 2 + apps/api/test/onboarding-memory.test.ts | 272 + apps/mobile/src/app/onboarding.tsx | 3 + apps/mobile/src/locales/da/common.json | 1 + apps/mobile/src/locales/de/common.json | 1 + apps/mobile/src/locales/en/common.json | 1 + apps/mobile/src/locales/es/common.json | 1 + apps/mobile/src/locales/fi/common.json | 1 + apps/mobile/src/locales/fr/common.json | 1 + apps/mobile/src/locales/it/common.json | 1 + apps/mobile/src/locales/nb/common.json | 1 + apps/mobile/src/locales/nl/common.json | 1 + apps/mobile/src/locales/pl/common.json | 1 + apps/mobile/src/locales/pt/common.json | 1 + apps/mobile/src/locales/sv/common.json | 1 + .../migrations/0026_s5_onboarding_memory.sql | 10 + .../migrations/meta/0025_snapshot.json | 10306 ++++++++++++++++ infrastructure/migrations/meta/_journal.json | 9 +- packages/database/src/schema/memory.ts | 8 +- packages/memory-client/src/index.ts | 15 + packages/shared-types/src/entities.ts | 2 + packages/shared-types/src/enums.ts | 2 + 26 files changed, 10812 insertions(+), 2 deletions(-) create mode 100644 apps/api/src/lib/onboardingMemory.ts create mode 100644 apps/api/test/onboarding-memory.test.ts create mode 100644 infrastructure/migrations/0026_s5_onboarding_memory.sql create mode 100644 infrastructure/migrations/meta/0025_snapshot.json diff --git a/apps/api/src/lib/memoryImpact.ts b/apps/api/src/lib/memoryImpact.ts index ff4b2eb..bf441b3 100644 --- a/apps/api/src/lib/memoryImpact.ts +++ b/apps/api/src/lib/memoryImpact.ts @@ -281,6 +281,7 @@ export async function computeMemoryImpact(options: MemoryImpactOptions): Promise id: schema.tasteSignals.id, userId: schema.tasteSignals.userId, axis: schema.tasteSignals.axis, + target: schema.tasteSignals.target, direction: schema.tasteSignals.direction, strength: schema.tasteSignals.strength, origin: schema.tasteSignals.origin, @@ -291,6 +292,7 @@ export async function computeMemoryImpact(options: MemoryImpactOptions): Promise .where(eq(schema.tasteSignals.userId, userId)); const tasteSignals: TasteSignal[] = tasteRows.map((t) => ({ ...t, + target: t.target ?? undefined, refRecipeId: t.refRecipeId ?? undefined, createdAt: t.createdAt.toISOString(), })); diff --git a/apps/api/src/lib/onboardingMemory.ts b/apps/api/src/lib/onboardingMemory.ts new file mode 100644 index 0000000..4d5cc58 --- /dev/null +++ b/apps/api/src/lib/onboardingMemory.ts @@ -0,0 +1,156 @@ +import type { Database } from "@app/database"; +import { schema } from "@app/database"; +import type { Allergen, Cuisine, GoalType } from "@app/shared-types"; + +/** + * Spegla onboarding-angivna preferenser i användarens synliga minne och + * smaksignaler (S5). Allt skrivs som `user_stated` / `verifiedByUser=true`. + * + * - memory_items: upsert på (userId, key). + * - taste_signals: upsert på (userId, axis, target). + * + * Idempotent – upprepade anrop skapar inga dubletter. + */ +const ONBOARDING_ORIGIN = "user_stated" as const; +const ONBOARDING_CONFIDENCE = 1; +const TASTE_STRENGTH = 0.9; + +export interface OnboardingMemoryInput { + primaryGoal?: GoalType | null; + goals?: GoalType[]; + allergens?: Allergen[]; + favoriteCuisines?: Cuisine[]; + avoidIngredientIds?: string[]; + spiceLevelMax?: number | null; +} + +interface MemoryEntry { + key: string; + summarySv: string; + value: unknown; +} + +export async function syncOnboardingMemory( + db: Database, + userId: string, + prefs: OnboardingMemoryInput, +): Promise { + const memoryEntries: MemoryEntry[] = []; + + if (prefs.primaryGoal) { + memoryEntries.push({ + key: `goal:primary:${prefs.primaryGoal}`, + summarySv: `Primärt mål: ${prefs.primaryGoal}`, + value: { primaryGoal: prefs.primaryGoal }, + }); + } + + for (const goal of prefs.goals ?? []) { + memoryEntries.push({ + key: `goal:${goal}`, + summarySv: `Mål: ${goal}`, + value: { goal }, + }); + } + + for (const allergen of prefs.allergens ?? []) { + memoryEntries.push({ + key: `allergen:${allergen}`, + summarySv: `Allergi: ${allergen}`, + value: { allergen }, + }); + } + + for (const cuisine of prefs.favoriteCuisines ?? []) { + memoryEntries.push({ + key: `favorite-cuisine:${cuisine}`, + summarySv: `Favoritkök: ${cuisine}`, + value: { favoriteCuisine: cuisine }, + }); + } + + for (const ingredientId of prefs.avoidIngredientIds ?? []) { + memoryEntries.push({ + key: `avoid-ingredient:${ingredientId}`, + summarySv: `Undviker ingrediens: ${ingredientId}`, + value: { avoidIngredientId: ingredientId }, + }); + } + + if (prefs.spiceLevelMax != null) { + memoryEntries.push({ + key: "spice-level-max", + summarySv: `Max styrka: ${prefs.spiceLevelMax}`, + value: { spiceLevelMax: prefs.spiceLevelMax }, + }); + } + + for (const entry of memoryEntries) { + await db + .insert(schema.memoryItems) + .values({ + userId, + kind: "structured_fact", + key: entry.key, + summarySv: entry.summarySv, + value: entry.value, + origin: ONBOARDING_ORIGIN, + confidence: ONBOARDING_CONFIDENCE, + verifiedByUser: true, + }) + .onConflictDoUpdate({ + target: [schema.memoryItems.userId, schema.memoryItems.key], + set: { + summarySv: entry.summarySv, + value: entry.value, + origin: ONBOARDING_ORIGIN, + confidence: ONBOARDING_CONFIDENCE, + verifiedByUser: true, + updatedAt: new Date(), + }, + }); + } + + const tasteEntries: Array<{ + axis: "cuisine" | "ingredient_avoid"; + target: string; + direction: number; + }> = []; + + for (const cuisine of prefs.favoriteCuisines ?? []) { + tasteEntries.push({ axis: "cuisine", target: cuisine, direction: 1 }); + } + + for (const ingredientId of prefs.avoidIngredientIds ?? []) { + tasteEntries.push({ + axis: "ingredient_avoid", + target: ingredientId, + direction: -1, + }); + } + + for (const signal of tasteEntries) { + await db + .insert(schema.tasteSignals) + .values({ + userId, + axis: signal.axis, + target: signal.target, + direction: signal.direction, + strength: TASTE_STRENGTH, + origin: ONBOARDING_ORIGIN, + }) + .onConflictDoUpdate({ + target: [ + schema.tasteSignals.userId, + schema.tasteSignals.axis, + schema.tasteSignals.target, + ], + set: { + direction: signal.direction, + strength: TASTE_STRENGTH, + origin: ONBOARDING_ORIGIN, + }, + }); + } +} diff --git a/apps/api/src/routes/me.ts b/apps/api/src/routes/me.ts index d456233..32a8d1a 100644 --- a/apps/api/src/routes/me.ts +++ b/apps/api/src/routes/me.ts @@ -12,6 +12,7 @@ import { computeDailyTargets, DEFAULT_TARGETS } from "@app/nutrition-engine"; import { errors, parse } from "../lib/errors.js"; import { audit, generateInviteCode, getActiveHouseholdId } from "../lib/helpers.js"; import { loadEntitlementsWithToken } from "../lib/entitlements.js"; +import { syncOnboardingMemory } from "../lib/onboardingMemory.js"; /** Profil, preferenser, samtycken, dagsmål, GDPR-export/-radering (spec §6, §56). */ export async function meRoutes(app: FastifyInstance) { @@ -120,6 +121,9 @@ export async function meRoutes(app: FastifyInstance) { target: schema.userPreferences.userId, set: { ...input.preferences, updatedAt: new Date() }, }); + + // S5: spegla uttryckligen angivna preferenser i minne + smaksignaler. + await syncOnboardingMemory(app.db, req.userId, input.preferences); } let householdId: string | null = await getActiveHouseholdId(app.db, req.userId); diff --git a/apps/api/src/routes/onboarding.ts b/apps/api/src/routes/onboarding.ts index 8347965..4a4564b 100644 --- a/apps/api/src/routes/onboarding.ts +++ b/apps/api/src/routes/onboarding.ts @@ -12,6 +12,7 @@ import { import { t } from "../lib/i18n.js"; import { KNOWN_FLAGS } from "@app/feature-flags"; +import { syncOnboardingMemory } from "../lib/onboardingMemory.js"; /** * Progressive onboarding (FAS 1b): 3-layer flow. @@ -227,6 +228,16 @@ export async function onboardingRoutes(app: FastifyInstance) { }); } + // S5: spegla alla uttryckligen angivna preferenser i minne + smaksignaler. + const [completedPrefs] = await app.db + .select() + .from(schema.userPreferences) + .where(eq(schema.userPreferences.userId, req.userId)) + .limit(1); + if (completedPrefs) { + await syncOnboardingMemory(app.db, req.userId, completedPrefs); + } + const [user] = await app.db .update(schema.users) .set({ diff --git a/apps/api/src/routes/recommendations.ts b/apps/api/src/routes/recommendations.ts index 6fc6a6c..3dbb709 100644 --- a/apps/api/src/routes/recommendations.ts +++ b/apps/api/src/routes/recommendations.ts @@ -276,6 +276,7 @@ export async function recommendationRoutes(app: FastifyInstance) { id: schema.tasteSignals.id, userId: schema.tasteSignals.userId, axis: schema.tasteSignals.axis, + target: schema.tasteSignals.target, direction: schema.tasteSignals.direction, strength: schema.tasteSignals.strength, origin: schema.tasteSignals.origin, @@ -286,6 +287,7 @@ export async function recommendationRoutes(app: FastifyInstance) { .where(eq(schema.tasteSignals.userId, req.userId)); tasteSignals = tasteRows.map((t) => ({ ...t, + target: t.target ?? undefined, refRecipeId: t.refRecipeId ?? undefined, createdAt: t.createdAt.toISOString(), })); diff --git a/apps/api/test/onboarding-memory.test.ts b/apps/api/test/onboarding-memory.test.ts new file mode 100644 index 0000000..ece9cd9 --- /dev/null +++ b/apps/api/test/onboarding-memory.test.ts @@ -0,0 +1,272 @@ +import "./setup-env.js"; +import { describe, expect, it, beforeAll, afterAll } from "vitest"; +import { eq, inArray } from "drizzle-orm"; +import { buildServer } from "../src/server.js"; +import { loadConfig } from "../src/config.js"; +import { createDatabase, closeDatabase, schema } from "@app/database"; + +describe("S5 — onboarding → minne + smaksignaler", () => { + const testDb = createDatabase(process.env.TEST_DATABASE_URL!); + const config = loadConfig(); + let app: Awaited>; + + async function cleanupUser(email: string) { + const existing = await testDb.db + .select({ id: schema.users.id }) + .from(schema.users) + .where(inArray(schema.users.email, [email, `deleted-${email}`])); + for (const u of existing) { + await testDb.db.delete(schema.memoryItems).where(eq(schema.memoryItems.userId, u.id)); + await testDb.db.delete(schema.tasteSignals).where(eq(schema.tasteSignals.userId, u.id)); + await testDb.db.delete(schema.userPreferences).where(eq(schema.userPreferences.userId, u.id)); + await testDb.db.delete(schema.userHealthProfiles).where(eq(schema.userHealthProfiles.userId, u.id)); + await testDb.db.delete(schema.userCredentials).where(eq(schema.userCredentials.userId, u.id)); + await testDb.db.delete(schema.refreshTokens).where(eq(schema.refreshTokens.userId, u.id)); + await testDb.db.delete(schema.users).where(eq(schema.users.id, u.id)); + } + } + + async function registerUser(email: string) { + const res = await app.inject({ + method: "POST", + url: "/v1/auth/register", + payload: { email, password: "Password123!", displayName: "S5 Test" }, + }); + const body = JSON.parse(res.body) as { accessToken: string }; + const token = body.accessToken; + const userId = (JSON.parse(atob(token.split(".")[1]!)) as { sub: string }).sub; + return { token, userId }; + } + + beforeAll(async () => { + app = await buildServer(config); + await app.ready(); + }); + + afterAll(async () => { + await closeDatabase(); + await app.close(); + }); + + it("legacy POST /v1/me/onboarding skapar memory_items och taste_signals", async () => { + const email = "s5-legacy@example.invalid"; + await cleanupUser(email); + const { token, userId } = await registerUser(email); + + const res = await app.inject({ + method: "POST", + url: "/v1/me/onboarding", + headers: { authorization: `Bearer ${token}` }, + payload: { + precisionMode: "simple", + preferences: { + primaryGoal: "less_waste", + goals: ["less_waste", "cook_more"], + allergens: ["gluten", "milk"], + favoriteCuisines: ["italian", "thai"], + avoidIngredientIds: ["broccoli", "anchovy"], + spiceLevelMax: 2, + }, + householdChoice: { kind: "skip" }, + }, + }); + + expect(res.statusCode).toBe(200); + + const memory = await testDb.db + .select() + .from(schema.memoryItems) + .where(eq(schema.memoryItems.userId, userId)); + const memoryKeys = memory.map((m) => m.key).sort(); + expect(memoryKeys).toEqual( + [ + "goal:less_waste", + "goal:cook_more", + "goal:primary:less_waste", + "allergen:gluten", + "allergen:milk", + "favorite-cuisine:italian", + "favorite-cuisine:thai", + "avoid-ingredient:broccoli", + "avoid-ingredient:anchovy", + "spice-level-max", + ].sort(), + ); + for (const m of memory) { + expect(m.origin).toBe("user_stated"); + expect(m.verifiedByUser).toBe(true); + expect(m.confidence).toBe(1); + } + + const signals = await testDb.db + .select() + .from(schema.tasteSignals) + .where(eq(schema.tasteSignals.userId, userId)); + const cuisineSignals = signals.filter((s) => s.axis === "cuisine").sort((a, b) => (a.target ?? "").localeCompare(b.target ?? "")); + const avoidSignals = signals.filter((s) => s.axis === "ingredient_avoid").sort((a, b) => (a.target ?? "").localeCompare(b.target ?? "")); + + expect(cuisineSignals).toHaveLength(2); + expect(cuisineSignals[0]).toMatchObject({ target: "italian", direction: 1, origin: "user_stated" }); + expect(cuisineSignals[1]).toMatchObject({ target: "thai", direction: 1, origin: "user_stated" }); + + expect(avoidSignals).toHaveLength(2); + expect(avoidSignals[0]).toMatchObject({ target: "anchovy", direction: -1, origin: "user_stated" }); + expect(avoidSignals[1]).toMatchObject({ target: "broccoli", direction: -1, origin: "user_stated" }); + + await cleanupUser(email); + }); + + it("är idempotent – upprepade anrop skapar inga dubletter", async () => { + const email = "s5-idempotent@example.invalid"; + await cleanupUser(email); + const { token, userId } = await registerUser(email); + + const payload = { + precisionMode: "simple", + preferences: { + primaryGoal: "more_protein", + goals: ["more_protein", "lower_cost"], + allergens: ["peanuts"], + favoriteCuisines: ["japanese"], + avoidIngredientIds: ["liver"], + spiceLevelMax: 3, + }, + householdChoice: { kind: "skip" }, + }; + + for (let i = 0; i < 2; i++) { + const res = await app.inject({ + method: "POST", + url: "/v1/me/onboarding", + headers: { authorization: `Bearer ${token}` }, + payload, + }); + expect(res.statusCode).toBe(200); + } + + const memoryCount = await testDb.db + .select({ id: schema.memoryItems.id }) + .from(schema.memoryItems) + .where(eq(schema.memoryItems.userId, userId)); + expect(memoryCount).toHaveLength(7); + + const signalCount = await testDb.db + .select({ id: schema.tasteSignals.id }) + .from(schema.tasteSignals) + .where(eq(schema.tasteSignals.userId, userId)); + expect(signalCount).toHaveLength(2); + + await cleanupUser(email); + }); + + it("progressive onboarding complete-c skapar memory_items och taste_signals", async () => { + const email = "s5-progressive@example.invalid"; + await cleanupUser(email); + const { token, userId } = await registerUser(email); + + const stepA = await app.inject({ + method: "POST", + url: "/v1/onboarding/quick-start", + headers: { authorization: `Bearer ${token}` }, + payload: { goals: ["less_waste"], precisionMode: "simple" }, + }); + expect(stepA.statusCode).toBe(200); + + const stepB = await app.inject({ + method: "POST", + url: "/v1/onboarding/complete-b", + headers: { authorization: `Bearer ${token}` }, + payload: { + precisionMode: "simple", + preferences: { + dietPattern: "vegetarian", + allergens: ["gluten"], + }, + householdChoice: { kind: "skip" }, + }, + }); + expect(stepB.statusCode).toBe(200); + + const stepC = await app.inject({ + method: "POST", + url: "/v1/onboarding/complete-c", + headers: { authorization: `Bearer ${token}` }, + payload: { + precisionMode: "simple", + preferences: { + favoriteCuisines: ["italian"], + avoidIngredientIds: ["mushroom"], + spiceLevelMax: 2, + }, + }, + }); + expect(stepC.statusCode).toBe(200); + + const memory = await testDb.db + .select() + .from(schema.memoryItems) + .where(eq(schema.memoryItems.userId, userId)); + const keys = memory.map((m) => m.key).sort(); + expect(keys).toContain("goal:less_waste"); + expect(keys).toContain("goal:primary:less_waste"); + expect(keys).toContain("allergen:gluten"); + expect(keys).toContain("favorite-cuisine:italian"); + expect(keys).toContain("avoid-ingredient:mushroom"); + expect(keys).toContain("spice-level-max"); + + const signals = await testDb.db + .select() + .from(schema.tasteSignals) + .where(eq(schema.tasteSignals.userId, userId)); + expect(signals).toHaveLength(2); + expect(signals.some((s) => s.axis === "cuisine" && s.target === "italian" && s.direction === 1)).toBe(true); + expect(signals.some((s) => s.axis === "ingredient_avoid" && s.target === "mushroom" && s.direction === -1)).toBe(true); + + await cleanupUser(email); + }); + + it("DELETE /v1/me tömmer onboarding-skapade memory_items och taste_signals", async () => { + const email = "s5-gdpr@example.invalid"; + await cleanupUser(email); + const { token, userId } = await registerUser(email); + + await app.inject({ + method: "POST", + url: "/v1/me/onboarding", + headers: { authorization: `Bearer ${token}` }, + payload: { + precisionMode: "simple", + preferences: { + primaryGoal: "less_waste", + goals: ["less_waste"], + allergens: ["gluten"], + favoriteCuisines: ["italian"], + avoidIngredientIds: ["broccoli"], + spiceLevelMax: 2, + }, + householdChoice: { kind: "skip" }, + }, + }); + + const deleteRes = await app.inject({ + method: "DELETE", + url: "/v1/me", + headers: { authorization: `Bearer ${token}` }, + }); + expect(deleteRes.statusCode).toBe(200); + + const remainingMemory = await testDb.db + .select({ id: schema.memoryItems.id }) + .from(schema.memoryItems) + .where(eq(schema.memoryItems.userId, userId)); + expect(remainingMemory).toHaveLength(0); + + const remainingSignals = await testDb.db + .select({ id: schema.tasteSignals.id }) + .from(schema.tasteSignals) + .where(eq(schema.tasteSignals.userId, userId)); + expect(remainingSignals).toHaveLength(0); + + await cleanupUser(email); + }); +}); diff --git a/apps/mobile/src/app/onboarding.tsx b/apps/mobile/src/app/onboarding.tsx index 789f1c3..6e6f020 100644 --- a/apps/mobile/src/app/onboarding.tsx +++ b/apps/mobile/src/app/onboarding.tsx @@ -242,6 +242,7 @@ export default function OnboardingScreen() { {t("onboarding.modeExactDesc")} {t("onboarding.modesCombine")} + {t("onboarding.memoryTransparency")} {error && {error}} @@ -291,6 +292,7 @@ export default function OnboardingScreen() { ))} {t("onboarding.allergyNote")} + {t("onboarding.memoryTransparency")} {t("onboarding.household")} @@ -366,6 +368,7 @@ export default function OnboardingScreen() { onChangeText={setBirthYear} /> {t("onboarding.notMedical")} + {t("onboarding.memoryTransparency")} {error && {error}} diff --git a/apps/mobile/src/locales/da/common.json b/apps/mobile/src/locales/da/common.json index ac094b6..f226ab9 100644 --- a/apps/mobile/src/locales/da/common.json +++ b/apps/mobile/src/locales/da/common.json @@ -198,6 +198,7 @@ "onboarding.modeSimple": "Enkel tilstand", "onboarding.modeSimpleDesc": "Tag billeder af maden, accepter estimater, minimalt besvær.", "onboarding.modesCombine": "Tilstandene kan kombineres – enkelt til hverdag, præcist når du vil.", + "onboarding.memoryTransparency": "Dette gemmes i \"Hvad {brand} ved om mig\", og du kan ændre det når som helst.", "onboarding.notMedical": "{brand} giver vejledning – ikke lægelig rådgivning.", "onboarding.stepBSubtitle": "Tilpas din profil for bedre forslag.", "onboarding.stepBTitle": "Fortsæt opsætning", diff --git a/apps/mobile/src/locales/de/common.json b/apps/mobile/src/locales/de/common.json index 5fe77f6..006566d 100644 --- a/apps/mobile/src/locales/de/common.json +++ b/apps/mobile/src/locales/de/common.json @@ -198,6 +198,7 @@ "onboarding.modeSimple": "Einfacher Modus", "onboarding.modeSimpleDesc": "Essen fotografieren, Schätzungen akzeptieren, kein Aufwand.", "onboarding.modesCombine": "Die Modi lassen sich kombinieren – im Alltag einfach, exakt wenn du willst.", + "onboarding.memoryTransparency": "Dies wird unter \"Was {brand} über mich weiß\" gespeichert und du kannst es jederzeit ändern.", "onboarding.notMedical": "{brand} bietet Orientierung – keine medizinische Beratung.", "onboarding.stepBSubtitle": "Passe dein Profil für bessere Vorschläge an.", "onboarding.stepBTitle": "Einrichtung fortsetzen", diff --git a/apps/mobile/src/locales/en/common.json b/apps/mobile/src/locales/en/common.json index 6040fce..897da8a 100644 --- a/apps/mobile/src/locales/en/common.json +++ b/apps/mobile/src/locales/en/common.json @@ -198,6 +198,7 @@ "onboarding.modeSimple": "Simple mode", "onboarding.modeSimpleDesc": "Snap photos of your food, accept estimates, minimal fuss.", "onboarding.modesCombine": "You can mix modes – simple day to day, exact when you want.", + "onboarding.memoryTransparency": "This is saved in \"What {brand} knows about me\" and you can change it anytime.", "onboarding.notMedical": "{brand} provides guidance – not medical advice.", "onboarding.stepBSubtitle": "Customize your profile for better suggestions.", "onboarding.stepBTitle": "Continue setup", diff --git a/apps/mobile/src/locales/es/common.json b/apps/mobile/src/locales/es/common.json index ab0b37d..04dce85 100644 --- a/apps/mobile/src/locales/es/common.json +++ b/apps/mobile/src/locales/es/common.json @@ -198,6 +198,7 @@ "onboarding.modeSimple": "Modo sencillo", "onboarding.modeSimpleDesc": "Fotografía tu comida, acepta estimaciones, sin complicaciones.", "onboarding.modesCombine": "Puedes combinar los modos: sencillo a diario, exacto cuando quieras.", + "onboarding.memoryTransparency": "Esto se guarda en \"Lo que {brand} sabe de mí\" y puedes cambiarlo cuando quieras.", "onboarding.notMedical": "{brand} ofrece orientación, no consejo médico.", "onboarding.stepBSubtitle": "Personaliza tu perfil para mejores sugerencias.", "onboarding.stepBTitle": "Continuar configuración", diff --git a/apps/mobile/src/locales/fi/common.json b/apps/mobile/src/locales/fi/common.json index 16d61a9..01ce6da 100644 --- a/apps/mobile/src/locales/fi/common.json +++ b/apps/mobile/src/locales/fi/common.json @@ -198,6 +198,7 @@ "onboarding.modeSimple": "Helppo tila", "onboarding.modeSimpleDesc": "Kuvaa ruokasi, hyväksy arviot, mahdollisimman vaivatonta.", "onboarding.modesCombine": "Tiloja voi yhdistellä – arjessa helposti, tarkasti kun haluat.", + "onboarding.memoryTransparency": "Tämä tallennetaan kohdassa \"Mitä {brand} tietää minusta\" ja voit muuttaa sitä koska tahansa.", "onboarding.notMedical": "{brand} antaa ohjeita – ei lääketieteellisiä neuvoja.", "onboarding.stepBSubtitle": "Mukauta profiiliasi parempia ehdotuksia varten.", "onboarding.stepBTitle": "Jatka asetuksia", diff --git a/apps/mobile/src/locales/fr/common.json b/apps/mobile/src/locales/fr/common.json index 5f53af7..e2e191d 100644 --- a/apps/mobile/src/locales/fr/common.json +++ b/apps/mobile/src/locales/fr/common.json @@ -198,6 +198,7 @@ "onboarding.modeSimple": "Mode simple", "onboarding.modeSimpleDesc": "Photographiez vos aliments, acceptez les estimations, zéro prise de tête.", "onboarding.modesCombine": "Les modes se combinent : simple au quotidien, précis quand vous voulez.", + "onboarding.memoryTransparency": "Ceci est enregistré dans \"Ce que {brand} sait de moi\" et vous pouvez le modifier à tout moment.", "onboarding.notMedical": "{brand} fournit des repères, pas un avis médical.", "onboarding.stepBSubtitle": "Personnalisez votre profil pour de meilleures suggestions.", "onboarding.stepBTitle": "Continuer la configuration", diff --git a/apps/mobile/src/locales/it/common.json b/apps/mobile/src/locales/it/common.json index 446f5ef..9cadeb9 100644 --- a/apps/mobile/src/locales/it/common.json +++ b/apps/mobile/src/locales/it/common.json @@ -198,6 +198,7 @@ "onboarding.modeSimple": "Modalità semplice", "onboarding.modeSimpleDesc": "Fotografa il cibo, accetta le stime, zero complicazioni.", "onboarding.modesCombine": "Le modalità si possono combinare: semplice ogni giorno, esatta quando vuoi.", + "onboarding.memoryTransparency": "Questo viene salvato in \"Cosa sa {brand} di me\" e puoi modificarlo in qualsiasi momento.", "onboarding.notMedical": "{brand} offre indicazioni, non consigli medici.", "onboarding.stepBSubtitle": "Personalizza il profilo per suggerimenti migliori.", "onboarding.stepBTitle": "Continua configurazione", diff --git a/apps/mobile/src/locales/nb/common.json b/apps/mobile/src/locales/nb/common.json index fe561a0..c237651 100644 --- a/apps/mobile/src/locales/nb/common.json +++ b/apps/mobile/src/locales/nb/common.json @@ -198,6 +198,7 @@ "onboarding.modeSimple": "Enkel modus", "onboarding.modeSimpleDesc": "Ta bilde av maten, godta estimater, minimalt styr.", "onboarding.modesCombine": "Modusene kan kombineres – enkelt til hverdags, nøyaktig når du vil.", + "onboarding.memoryTransparency": "Dette lagres i \"Hva {brand} vet om meg\", og du kan endre det når som helst.", "onboarding.notMedical": "{brand} gir veiledning – ikke medisinske råd.", "onboarding.stepBSubtitle": "Tilpass profilen din for bedre forslag.", "onboarding.stepBTitle": "Fortsett oppsett", diff --git a/apps/mobile/src/locales/nl/common.json b/apps/mobile/src/locales/nl/common.json index 6dd59dc..896e4d7 100644 --- a/apps/mobile/src/locales/nl/common.json +++ b/apps/mobile/src/locales/nl/common.json @@ -198,6 +198,7 @@ "onboarding.modeSimple": "Simpele modus", "onboarding.modeSimpleDesc": "Fotografeer je eten, accepteer schattingen, minimaal gedoe.", "onboarding.modesCombine": "De standen zijn te combineren – simpel in het dagelijks leven, exact wanneer je wilt.", + "onboarding.memoryTransparency": "Dit wordt opgeslagen in \"Wat {brand} over mij weet\" en je kunt het altijd wijzigen.", "onboarding.notMedical": "{brand} geeft richtlijnen – geen medisch advies.", "onboarding.stepBSubtitle": "Pas je profiel aan voor betere suggesties.", "onboarding.stepBTitle": "Doorgaan met instellen", diff --git a/apps/mobile/src/locales/pl/common.json b/apps/mobile/src/locales/pl/common.json index 452fc6e..e5c307c 100644 --- a/apps/mobile/src/locales/pl/common.json +++ b/apps/mobile/src/locales/pl/common.json @@ -204,6 +204,7 @@ "onboarding.modeSimple": "Tryb prosty", "onboarding.modeSimpleDesc": "Fotografuj jedzenie, akceptuj szacunki, minimum zachodu.", "onboarding.modesCombine": "Tryby można łączyć – na co dzień prosto, dokładnie, gdy chcesz.", + "onboarding.memoryTransparency": "To jest zapisywane w \"Co {brand} wie o mnie\" i możesz to zmienić w każdej chwili.", "onboarding.notMedical": "{brand} to wskazówki – nie porada medyczna.", "onboarding.stepBSubtitle": "Dostosuj swój profil, aby uzyskać lepsze sugestie.", "onboarding.stepBTitle": "Kontynuuj konfigurację", diff --git a/apps/mobile/src/locales/pt/common.json b/apps/mobile/src/locales/pt/common.json index af13b65..91c12a6 100644 --- a/apps/mobile/src/locales/pt/common.json +++ b/apps/mobile/src/locales/pt/common.json @@ -198,6 +198,7 @@ "onboarding.modeSimple": "Modo simples", "onboarding.modeSimpleDesc": "Fotografe a comida, aceite estimativas, zero complicações.", "onboarding.modesCombine": "Os modos podem combinar-se: simples no dia a dia, exato quando quiser.", + "onboarding.memoryTransparency": "Isto é guardado em \"O que {brand} sabe sobre mim\" e podes alterá-lo a qualquer momento.", "onboarding.notMedical": "{brand} dá orientações – não aconselhamento médico.", "onboarding.stepBSubtitle": "Personaliza o teu perfil para melhores sugestões.", "onboarding.stepBTitle": "Continuar configuração", diff --git a/apps/mobile/src/locales/sv/common.json b/apps/mobile/src/locales/sv/common.json index 85d8abe..72b9a22 100644 --- a/apps/mobile/src/locales/sv/common.json +++ b/apps/mobile/src/locales/sv/common.json @@ -198,6 +198,7 @@ "onboarding.modeSimple": "Enkelt läge", "onboarding.modeSimpleDesc": "Fota maten, acceptera uppskattningar, minimalt pyssel.", "onboarding.modesCombine": "Lägena kan kombineras – enkelt till vardags, exakt när du vill.", + "onboarding.memoryTransparency": "Detta sparas i \"Vad {brand} vet om mig\" och du kan ändra det när som helst.", "onboarding.notMedical": "{brand} ger vägledning – inte medicinsk rådgivning.", "onboarding.stepBSubtitle": "Anpassa din profil för bättre förslag.", "onboarding.stepBTitle": "Fortsätt konfiguration", diff --git a/infrastructure/migrations/0026_s5_onboarding_memory.sql b/infrastructure/migrations/0026_s5_onboarding_memory.sql new file mode 100644 index 0000000..cb925c4 --- /dev/null +++ b/infrastructure/migrations/0026_s5_onboarding_memory.sql @@ -0,0 +1,10 @@ +-- S5: onboarding → minne + smaksignaler +-- Användarens onboarding-svar speglas som user_stated memory_items och taste_signals. + +ALTER TYPE "public"."taste_axis" ADD VALUE IF NOT EXISTS 'cuisine'; +ALTER TYPE "public"."taste_axis" ADD VALUE IF NOT EXISTS 'ingredient_avoid'; + +ALTER TABLE "public"."taste_signals" ADD COLUMN IF NOT EXISTS "target" text; + +CREATE UNIQUE INDEX IF NOT EXISTS "memory_items_user_key_unique" ON "public"."memory_items" USING btree ("user_id","key"); +CREATE UNIQUE INDEX IF NOT EXISTS "taste_signals_user_axis_target_unique" ON "public"."taste_signals" USING btree ("user_id","axis","target"); diff --git a/infrastructure/migrations/meta/0025_snapshot.json b/infrastructure/migrations/meta/0025_snapshot.json new file mode 100644 index 0000000..26c9770 --- /dev/null +++ b/infrastructure/migrations/meta/0025_snapshot.json @@ -0,0 +1,10306 @@ +{ + "id": "c55e8b9c-e57f-4c35-941d-83b257afa455", + "prevId": "c5ed7b86-b55f-4aa1-8287-634b85de56a8", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.admin_totp": { + "name": "admin_totp", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "secret_base32": { + "name": "secret_base32", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled_at": { + "name": "enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_step": { + "name": "last_used_step", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "admin_totp_user_id_users_id_fk": { + "name": "admin_totp_user_id_users_id_fk", + "tableFrom": "admin_totp", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_verification_tokens": { + "name": "email_verification_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_verification_tokens_user_idx": { + "name": "email_verification_tokens_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_verification_tokens_hash_unique": { + "name": "email_verification_tokens_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_verification_tokens_user_id_users_id_fk": { + "name": "email_verification_tokens_user_id_users_id_fk", + "tableFrom": "email_verification_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "password_reset_tokens_user_idx": { + "name": "password_reset_tokens_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "password_reset_tokens_hash_unique": { + "name": "password_reset_tokens_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.refresh_tokens": { + "name": "refresh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "family_id": { + "name": "family_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "replaced_by_token_id": { + "name": "replaced_by_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "refresh_tokens_user_idx": { + "name": "refresh_tokens_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "refresh_tokens_hash_unique": { + "name": "refresh_tokens_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "refresh_tokens_user_id_users_id_fk": { + "name": "refresh_tokens_user_id_users_id_fk", + "tableFrom": "refresh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_consents": { + "name": "user_consents", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "consent_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "consent_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "granted_at": { + "name": "granted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_consents_user_id_users_id_fk": { + "name": "user_consents_user_id_users_id_fk", + "tableFrom": "user_consents", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_consents_user_id_kind_pk": { + "name": "user_consents_user_id_kind_pk", + "columns": [ + "user_id", + "kind" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_credentials": { + "name": "user_credentials", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_updated_at": { + "name": "password_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_credentials_user_id_users_id_fk": { + "name": "user_credentials_user_id_users_id_fk", + "tableFrom": "user_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_health_profiles": { + "name": "user_health_profiles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "birth_year": { + "name": "birth_year", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sex": { + "name": "sex", + "type": "sex", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "height_cm": { + "name": "height_cm", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "weight_kg": { + "name": "weight_kg", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "target_weight_kg": { + "name": "target_weight_kg", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "activity_level": { + "name": "activity_level", + "type": "activity_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'moderate'" + }, + "training_sessions_per_week": { + "name": "training_sessions_per_week", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "training_types": { + "name": "training_types", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_health_profiles_user_id_users_id_fk": { + "name": "user_health_profiles_user_id_users_id_fk", + "tableFrom": "user_health_profiles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "primary_goal": { + "name": "primary_goal", + "type": "goal_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "goals": { + "name": "goals", + "type": "goal_type[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "diet_pattern": { + "name": "diet_pattern", + "type": "diet_pattern", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'omnivore'" + }, + "religious_rule": { + "name": "religious_rule", + "type": "religious_rule", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "allergens": { + "name": "allergens", + "type": "allergen[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "intolerances": { + "name": "intolerances", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "avoid_ingredient_ids": { + "name": "avoid_ingredient_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "favorite_cuisines": { + "name": "favorite_cuisines", + "type": "cuisine[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "disliked_dishes": { + "name": "disliked_dishes", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "spice_level_max": { + "name": "spice_level_max", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "weekly_budget_minor": { + "name": "weekly_budget_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_cooking_minutes_weekday": { + "name": "max_cooking_minutes_weekday", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "equipment": { + "name": "equipment", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "default_portions": { + "name": "default_portions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified_at": { + "name": "email_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "user_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sv-SE'" + }, + "precision_mode": { + "name": "precision_mode", + "type": "precision_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "onboarding_completed": { + "name": "onboarding_completed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "onboarding_step": { + "name": "onboarding_step", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'a'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gdpr_retention_pseudonyms": { + "name": "gdpr_retention_pseudonyms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pseudonym": { + "name": "pseudonym", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "retention_until": { + "name": "retention_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "gdpr_retention_pseudonyms_user_id_users_id_fk": { + "name": "gdpr_retention_pseudonyms_user_id_users_id_fk", + "tableFrom": "gdpr_retention_pseudonyms", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "gdpr_retention_pseudonyms_user_id_unique": { + "name": "gdpr_retention_pseudonyms_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + }, + "gdpr_retention_pseudonyms_pseudonym_unique": { + "name": "gdpr_retention_pseudonyms_pseudonym_unique", + "nullsNotDistinct": false, + "columns": [ + "pseudonym" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_locale_preferences": { + "name": "user_locale_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "language_tag": { + "name": "language_tag", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sv-SE'" + }, + "region_code": { + "name": "region_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'SE'" + }, + "time_zone": { + "name": "time_zone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Europe/Stockholm'" + }, + "measurement_system": { + "name": "measurement_system", + "type": "measurement_system", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'METRIC'" + }, + "temperature_unit": { + "name": "temperature_unit", + "type": "temperature_unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'CELSIUS'" + }, + "currency_code": { + "name": "currency_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'SEK'" + }, + "first_day_of_week": { + "name": "first_day_of_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "use_24_hour_time": { + "name": "use_24_hour_time", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_locale_preferences_user_id_users_id_fk": { + "name": "user_locale_preferences_user_id_users_id_fk", + "tableFrom": "user_locale_preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.household_members": { + "name": "household_members", + "schema": "", + "columns": { + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "household_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "portion_factor": { + "name": "portion_factor", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "household_members_user_idx": { + "name": "household_members_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "household_members_household_id_households_id_fk": { + "name": "household_members_household_id_households_id_fk", + "tableFrom": "household_members", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "household_members_user_id_users_id_fk": { + "name": "household_members_user_id_users_id_fk", + "tableFrom": "household_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "household_members_household_id_user_id_pk": { + "name": "household_members_household_id_user_id_pk", + "columns": [ + "household_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.households": { + "name": "households", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invite_code": { + "name": "invite_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "weekly_budget_minor": { + "name": "weekly_budget_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "first_scan_completed_at": { + "name": "first_scan_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "fifth_item_confirmed_at": { + "name": "fifth_item_confirmed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "first_recipe_recommendation_viewed_at": { + "name": "first_recipe_recommendation_viewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "first_recipe_saved_or_started_at": { + "name": "first_recipe_saved_or_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "activated_at": { + "name": "activated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "first_cooking_session_completed_at": { + "name": "first_cooking_session_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "inventory_updated_after_cooking_at": { + "name": "inventory_updated_after_cooking_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "value_confirmed_at": { + "name": "value_confirmed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "currency_code": { + "name": "currency_code", + "type": "varchar(3)", + "primaryKey": false, + "notNull": true, + "default": "'SEK'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "households_invite_code_unique": { + "name": "households_invite_code_unique", + "columns": [ + { + "expression": "invite_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.storage_locations": { + "name": "storage_locations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "storage_location_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sublocations": { + "name": "sublocations", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "storage_locations_household_idx": { + "name": "storage_locations_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "storage_locations_household_id_households_id_fk": { + "name": "storage_locations_household_id_households_id_fk", + "tableFrom": "storage_locations", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.canonical_ingredients": { + "name": "canonical_ingredients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name_sv": { + "name": "name_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name_en": { + "name": "name_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aliases": { + "name": "aliases", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_unit": { + "name": "default_unit", + "type": "unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "density_g_per_ml": { + "name": "density_g_per_ml", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "grams_per_piece": { + "name": "grams_per_piece", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "allergens": { + "name": "allergens", + "type": "allergen[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "is_vegan": { + "name": "is_vegan", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_vegetarian": { + "name": "is_vegetarian", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "contains_gluten": { + "name": "contains_gluten", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "contains_lactose": { + "name": "contains_lactose", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_pork": { + "name": "is_pork", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_beef": { + "name": "is_beef", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_alcohol": { + "name": "is_alcohol", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "nutrition_per_100": { + "name": "nutrition_per_100", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "nutrition_provenance": { + "name": "nutrition_provenance", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "peak_seasons": { + "name": "peak_seasons", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "shelf_life_guidance": { + "name": "shelf_life_guidance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "default_price_minor_per_kg": { + "name": "default_price_minor_per_kg", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "canonical_ingredients_category_idx": { + "name": "canonical_ingredients_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.substitutions": { + "name": "substitutions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "from_ingredient_id": { + "name": "from_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_ingredient_id": { + "name": "to_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ratio": { + "name": "ratio", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "instructions_sv": { + "name": "instructions_sv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "best_for": { + "name": "best_for", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "not_recommended_for": { + "name": "not_recommended_for", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "flavor_impact_sv": { + "name": "flavor_impact_sv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "texture_impact_sv": { + "name": "texture_impact_sv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "substitutions_from_idx": { + "name": "substitutions_from_idx", + "columns": [ + { + "expression": "from_ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "substitutions_pair_unique": { + "name": "substitutions_pair_unique", + "columns": [ + { + "expression": "from_ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "substitutions_from_ingredient_id_canonical_ingredients_id_fk": { + "name": "substitutions_from_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "substitutions", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "from_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "substitutions_to_ingredient_id_canonical_ingredients_id_fk": { + "name": "substitutions_to_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "substitutions", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "to_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.products": { + "name": "products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "gtin": { + "name": "gtin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "brand": { + "name": "brand", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_ingredient_id": { + "name": "canonical_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_size_value": { + "name": "package_size_value", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "package_size_unit": { + "name": "package_size_unit", + "type": "unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "ingredients_text": { + "name": "ingredients_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allergens": { + "name": "allergens", + "type": "allergen[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "may_contain_allergens": { + "name": "may_contain_allergens", + "type": "allergen[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "nutrition": { + "name": "nutrition", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "image_urls": { + "name": "image_urls", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sv'" + }, + "market": { + "name": "market", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'SE'" + }, + "data_source": { + "name": "data_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verification_status": { + "name": "verification_status", + "type": "verification_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unverified'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "valid_from": { + "name": "valid_from", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "valid_to": { + "name": "valid_to", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "products_gtin_version_unique": { + "name": "products_gtin_version_unique", + "columns": [ + { + "expression": "gtin", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"products\".\"gtin\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "products_gtin_current_idx": { + "name": "products_gtin_current_idx", + "columns": [ + { + "expression": "gtin", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"products\".\"valid_to\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "products_canonical_idx": { + "name": "products_canonical_idx", + "columns": [ + { + "expression": "canonical_ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "products_canonical_ingredient_id_canonical_ingredients_id_fk": { + "name": "products_canonical_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "products", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "canonical_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inventory_conflicts": { + "name": "inventory_conflicts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "inventory_item_id": { + "name": "inventory_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scan_job_id": { + "name": "scan_job_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conflict_type": { + "name": "conflict_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_a": { + "name": "source_a", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_a_priority": { + "name": "source_a_priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_b": { + "name": "source_b", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_b_priority": { + "name": "source_b_priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "payload_a": { + "name": "payload_a", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "payload_b": { + "name": "payload_b", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "proposed_resolution": { + "name": "proposed_resolution", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolution": { + "name": "resolution", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "model_version": { + "name": "model_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt_version": { + "name": "prompt_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inventory_conflicts_household_idx": { + "name": "inventory_conflicts_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inventory_conflicts_item_idx": { + "name": "inventory_conflicts_item_idx", + "columns": [ + { + "expression": "inventory_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inventory_conflicts_status_idx": { + "name": "inventory_conflicts_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inventory_conflicts_household_id_households_id_fk": { + "name": "inventory_conflicts_household_id_households_id_fk", + "tableFrom": "inventory_conflicts", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inventory_conflicts_inventory_item_id_inventory_items_id_fk": { + "name": "inventory_conflicts_inventory_item_id_inventory_items_id_fk", + "tableFrom": "inventory_conflicts", + "tableTo": "inventory_items", + "columnsFrom": [ + "inventory_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inventory_conflicts_resolved_by_user_id_users_id_fk": { + "name": "inventory_conflicts_resolved_by_user_id_users_id_fk", + "tableFrom": "inventory_conflicts", + "tableTo": "users", + "columnsFrom": [ + "resolved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inventory_items": { + "name": "inventory_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "canonical_ingredient_id": { + "name": "canonical_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "brand": { + "name": "brand", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "unit": { + "name": "unit", + "type": "unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "storage_location_id": { + "name": "storage_location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sublocation": { + "name": "sublocation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "purchased_at": { + "name": "purchased_at", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "opened_at": { + "name": "opened_at", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "best_before_date": { + "name": "best_before_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "use_by_date": { + "name": "use_by_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "date_kind": { + "name": "date_kind", + "type": "date_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "frozen_at": { + "name": "frozen_at", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "thawed_at": { + "name": "thawed_at", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "price_minor": { + "name": "price_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "nutrition_per_100": { + "name": "nutrition_per_100", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "inventory_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "confidence": { + "name": "confidence", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "verified_by_user": { + "name": "verified_by_user", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_verified_at": { + "name": "last_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expiry_status": { + "name": "expiry_status", + "type": "expiry_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "trust_state": { + "name": "trust_state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'unverified'" + }, + "depleted_at": { + "name": "depleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "model_version": { + "name": "model_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt_version": { + "name": "prompt_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inventory_items_household_idx": { + "name": "inventory_items_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inventory_items_household_bb_idx": { + "name": "inventory_items_household_bb_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "best_before_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inventory_items_location_idx": { + "name": "inventory_items_location_idx", + "columns": [ + { + "expression": "storage_location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inventory_items_canonical_idx": { + "name": "inventory_items_canonical_idx", + "columns": [ + { + "expression": "canonical_ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inventory_items_household_id_households_id_fk": { + "name": "inventory_items_household_id_households_id_fk", + "tableFrom": "inventory_items", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inventory_items_canonical_ingredient_id_canonical_ingredients_id_fk": { + "name": "inventory_items_canonical_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "inventory_items", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "canonical_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inventory_items_product_id_products_id_fk": { + "name": "inventory_items_product_id_products_id_fk", + "tableFrom": "inventory_items", + "tableTo": "products", + "columnsFrom": [ + "product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inventory_items_storage_location_id_storage_locations_id_fk": { + "name": "inventory_items_storage_location_id_storage_locations_id_fk", + "tableFrom": "inventory_items", + "tableTo": "storage_locations", + "columnsFrom": [ + "storage_location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inventory_transactions": { + "name": "inventory_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "inventory_item_id": { + "name": "inventory_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "inventory_transaction_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "quantity_delta": { + "name": "quantity_delta", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "unit": { + "name": "unit", + "type": "unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "ref_type": { + "name": "ref_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ref_id": { + "name": "ref_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cooking_session_id": { + "name": "cooking_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value_minor": { + "name": "value_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inventory_tx_item_idx": { + "name": "inventory_tx_item_idx", + "columns": [ + { + "expression": "inventory_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inventory_tx_household_time_idx": { + "name": "inventory_tx_household_time_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inventory_tx_type_idx": { + "name": "inventory_tx_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inventory_transactions_household_id_households_id_fk": { + "name": "inventory_transactions_household_id_households_id_fk", + "tableFrom": "inventory_transactions", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inventory_transactions_inventory_item_id_inventory_items_id_fk": { + "name": "inventory_transactions_inventory_item_id_inventory_items_id_fk", + "tableFrom": "inventory_transactions", + "tableTo": "inventory_items", + "columnsFrom": [ + "inventory_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inventory_transactions_cooking_session_id_cooking_sessions_id_fk": { + "name": "inventory_transactions_cooking_session_id_cooking_sessions_id_fk", + "tableFrom": "inventory_transactions", + "tableTo": "cooking_sessions", + "columnsFrom": [ + "cooking_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "inventory_transactions_actor_user_id_users_id_fk": { + "name": "inventory_transactions_actor_user_id_users_id_fk", + "tableFrom": "inventory_transactions", + "tableTo": "users", + "columnsFrom": [ + "actor_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inventory_decay_profiles": { + "name": "inventory_decay_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "half_life_days": { + "name": "half_life_days", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 7 + }, + "stale_after_days": { + "name": "stale_after_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "inventory_decay_profiles_name_unique": { + "name": "inventory_decay_profiles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inventory_item_decay_profile": { + "name": "inventory_item_decay_profile", + "schema": "", + "columns": { + "inventory_item_id": { + "name": "inventory_item_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "decay_profile_id": { + "name": "decay_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "inventory_item_decay_profile_inventory_item_id_inventory_items_id_fk": { + "name": "inventory_item_decay_profile_inventory_item_id_inventory_items_id_fk", + "tableFrom": "inventory_item_decay_profile", + "tableTo": "inventory_items", + "columnsFrom": [ + "inventory_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inventory_item_decay_profile_decay_profile_id_inventory_decay_profiles_id_fk": { + "name": "inventory_item_decay_profile_decay_profile_id_inventory_decay_profiles_id_fk", + "tableFrom": "inventory_item_decay_profile", + "tableTo": "inventory_decay_profiles", + "columnsFrom": [ + "decay_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cooking_assumption_profiles": { + "name": "cooking_assumption_profiles", + "schema": "", + "columns": { + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "canonical_ingredient_id": { + "name": "canonical_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "average_eaten_portions": { + "name": "average_eaten_portions", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "average_leftover_portions": { + "name": "average_leftover_portions", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "observation_count": { + "name": "observation_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_session_answers": { + "name": "last_session_answers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cooking_assumption_profiles_household_idx": { + "name": "cooking_assumption_profiles_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cooking_assumption_profiles_household_id_households_id_fk": { + "name": "cooking_assumption_profiles_household_id_households_id_fk", + "tableFrom": "cooking_assumption_profiles", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cooking_assumption_profiles_canonical_ingredient_id_canonical_ingredients_id_fk": { + "name": "cooking_assumption_profiles_canonical_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "cooking_assumption_profiles", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "canonical_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cooking_assumption_profiles_household_id_canonical_ingredient_id_pk": { + "name": "cooking_assumption_profiles_household_id_canonical_ingredient_id_pk", + "columns": [ + "household_id", + "canonical_ingredient_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cooking_sessions": { + "name": "cooking_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "planned_portions": { + "name": "planned_portions", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "planned_meal_type": { + "name": "planned_meal_type", + "type": "meal_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'dinner'" + }, + "actual_portions_eaten": { + "name": "actual_portions_eaten", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "leftover_estimate_portions": { + "name": "leftover_estimate_portions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "meal_date": { + "name": "meal_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "meal_box_mutations": { + "name": "meal_box_mutations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "leftover_note": { + "name": "leftover_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "planned_deductions": { + "name": "planned_deductions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancel_reason": { + "name": "cancel_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cooking_sessions_household_status_idx": { + "name": "cooking_sessions_household_status_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cooking_sessions_recipe_idx": { + "name": "cooking_sessions_recipe_idx", + "columns": [ + { + "expression": "recipe_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cooking_sessions_timeout_idx": { + "name": "cooking_sessions_timeout_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cooking_sessions_recipe_id_recipes_id_fk": { + "name": "cooking_sessions_recipe_id_recipes_id_fk", + "tableFrom": "cooking_sessions", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cooking_sessions_household_id_households_id_fk": { + "name": "cooking_sessions_household_id_households_id_fk", + "tableFrom": "cooking_sessions", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cooking_sessions_started_by_user_id_users_id_fk": { + "name": "cooking_sessions_started_by_user_id_users_id_fk", + "tableFrom": "cooking_sessions", + "tableTo": "users", + "columnsFrom": [ + "started_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recipe_cooks": { + "name": "recipe_cooks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cooking_session_id": { + "name": "cooking_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "portions_cooked": { + "name": "portions_cooked", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cooked_at": { + "name": "cooked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "recipe_cooks_recipe_idx": { + "name": "recipe_cooks_recipe_idx", + "columns": [ + { + "expression": "recipe_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recipe_cooks_user_idx": { + "name": "recipe_cooks_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recipe_cooks_session_idx": { + "name": "recipe_cooks_session_idx", + "columns": [ + { + "expression": "cooking_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recipe_cooks_recipe_id_recipes_id_fk": { + "name": "recipe_cooks_recipe_id_recipes_id_fk", + "tableFrom": "recipe_cooks", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recipe_cooks_user_id_users_id_fk": { + "name": "recipe_cooks_user_id_users_id_fk", + "tableFrom": "recipe_cooks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recipe_cooks_cooking_session_id_cooking_sessions_id_fk": { + "name": "recipe_cooks_cooking_session_id_cooking_sessions_id_fk", + "tableFrom": "recipe_cooks", + "tableTo": "cooking_sessions", + "columnsFrom": [ + "cooking_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recipe_favorites": { + "name": "recipe_favorites", + "schema": "", + "columns": { + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "recipe_favorites_user_idx": { + "name": "recipe_favorites_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recipe_favorites_recipe_id_recipes_id_fk": { + "name": "recipe_favorites_recipe_id_recipes_id_fk", + "tableFrom": "recipe_favorites", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recipe_favorites_user_id_users_id_fk": { + "name": "recipe_favorites_user_id_users_id_fk", + "tableFrom": "recipe_favorites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "recipe_favorites_recipe_id_user_id_pk": { + "name": "recipe_favorites_recipe_id_user_id_pk", + "columns": [ + "recipe_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recipe_ingredients": { + "name": "recipe_ingredients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "canonical_ingredient_id": { + "name": "canonical_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name_sv": { + "name": "display_name_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "unit": { + "name": "unit", + "type": "unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "optional": { + "name": "optional", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "recipe_ingredients_recipe_idx": { + "name": "recipe_ingredients_recipe_idx", + "columns": [ + { + "expression": "recipe_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recipe_ingredients_canonical_idx": { + "name": "recipe_ingredients_canonical_idx", + "columns": [ + { + "expression": "canonical_ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recipe_ingredients_recipe_id_recipes_id_fk": { + "name": "recipe_ingredients_recipe_id_recipes_id_fk", + "tableFrom": "recipe_ingredients", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recipe_ingredients_canonical_ingredient_id_canonical_ingredients_id_fk": { + "name": "recipe_ingredients_canonical_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "recipe_ingredients", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "canonical_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recipe_ratings": { + "name": "recipe_ratings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stars": { + "name": "stars", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "feedback_tags": { + "name": "feedback_tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "recipe_ratings_user_recipe_unique": { + "name": "recipe_ratings_user_recipe_unique", + "columns": [ + { + "expression": "recipe_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recipe_ratings_recipe_idx": { + "name": "recipe_ratings_recipe_idx", + "columns": [ + { + "expression": "recipe_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recipe_ratings_recipe_id_recipes_id_fk": { + "name": "recipe_ratings_recipe_id_recipes_id_fk", + "tableFrom": "recipe_ratings", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recipe_ratings_user_id_users_id_fk": { + "name": "recipe_ratings_user_id_users_id_fk", + "tableFrom": "recipe_ratings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recipe_similarities": { + "name": "recipe_similarities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "recipe_a_id": { + "name": "recipe_a_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recipe_b_id": { + "name": "recipe_b_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "similarity_score": { + "name": "similarity_score", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "classification": { + "name": "classification", + "type": "recipe_similarity_class", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "recipe_similarities_pair_unique": { + "name": "recipe_similarities_pair_unique", + "columns": [ + { + "expression": "recipe_a_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recipe_b_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recipe_similarities_recipe_a_id_recipes_id_fk": { + "name": "recipe_similarities_recipe_a_id_recipes_id_fk", + "tableFrom": "recipe_similarities", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_a_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "recipe_similarities_recipe_b_id_recipes_id_fk": { + "name": "recipe_similarities_recipe_b_id_recipes_id_fk", + "tableFrom": "recipe_similarities", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_b_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recipe_source_registry": { + "name": "recipe_source_registry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source_name": { + "name": "source_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "license": { + "name": "license", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "right_to_store": { + "name": "right_to_store", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "right_to_modify": { + "name": "right_to_modify", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "right_to_display": { + "name": "right_to_display", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "attribution_required": { + "name": "attribution_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "attribution_text": { + "name": "attribution_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "commercial_use": { + "name": "commercial_use", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "valid_from": { + "name": "valid_from", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "valid_to": { + "name": "valid_to", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recipe_steps": { + "name": "recipe_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "step_number": { + "name": "step_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "instruction_sv": { + "name": "instruction_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timer_seconds": { + "name": "timer_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "temperature_c": { + "name": "temperature_c", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tip": { + "name": "tip", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "recipe_steps_recipe_idx": { + "name": "recipe_steps_recipe_idx", + "columns": [ + { + "expression": "recipe_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recipe_steps_recipe_step_unique": { + "name": "recipe_steps_recipe_step_unique", + "columns": [ + { + "expression": "recipe_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "step_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recipe_steps_recipe_id_recipes_id_fk": { + "name": "recipe_steps_recipe_id_recipes_id_fk", + "tableFrom": "recipe_steps", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recipes": { + "name": "recipes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title_sv": { + "name": "title_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_sv": { + "name": "description_sv", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cuisine": { + "name": "cuisine", + "type": "cuisine", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "meal_types": { + "name": "meal_types", + "type": "meal_type[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "methods": { + "name": "methods", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "equipment": { + "name": "equipment", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "difficulty": { + "name": "difficulty", + "type": "recipe_difficulty", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'easy'" + }, + "prep_time_minutes": { + "name": "prep_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cook_time_minutes": { + "name": "cook_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_time_minutes": { + "name": "total_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "portions": { + "name": "portions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 4 + }, + "nutrition_per_portion": { + "name": "nutrition_per_portion", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "allergens": { + "name": "allergens", + "type": "allergen[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "spice_level": { + "name": "spice_level", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "estimated_cost_minor_per_portion": { + "name": "estimated_cost_minor_per_portion", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "storage_guidance_sv": { + "name": "storage_guidance_sv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "meal_prep_friendly": { + "name": "meal_prep_friendly", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "freezer_friendly": { + "name": "freezer_friendly", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "peak_seasons": { + "name": "peak_seasons", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "holiday_tags": { + "name": "holiday_tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "dna": { + "name": "dna", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "variant_type": { + "name": "variant_type", + "type": "recipe_variant_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "variant_of_recipe_id": { + "name": "variant_of_recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "forked_from_recipe_id": { + "name": "forked_from_recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "recipe_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "verification_status": { + "name": "verification_status", + "type": "recipe_verification_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unverified'" + }, + "source_type": { + "name": "source_type", + "type": "recipe_source_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source_registry_id": { + "name": "source_registry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_user_id": { + "name": "creator_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_display_name": { + "name": "creator_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_urls": { + "name": "image_urls", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "rating_average": { + "name": "rating_average", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "rating_count": { + "name": "rating_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cook_count": { + "name": "cook_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "favorite_count": { + "name": "favorite_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "moderation_note": { + "name": "moderation_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "recipes_slug_unique": { + "name": "recipes_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recipes_status_idx": { + "name": "recipes_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recipes_cuisine_idx": { + "name": "recipes_cuisine_idx", + "columns": [ + { + "expression": "cuisine", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recipes_variant_of_idx": { + "name": "recipes_variant_of_idx", + "columns": [ + { + "expression": "variant_of_recipe_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recipes_creator_idx": { + "name": "recipes_creator_idx", + "columns": [ + { + "expression": "creator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recipes_total_time_idx": { + "name": "recipes_total_time_idx", + "columns": [ + { + "expression": "total_time_minutes", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recipes_source_registry_id_recipe_source_registry_id_fk": { + "name": "recipes_source_registry_id_recipe_source_registry_id_fk", + "tableFrom": "recipes", + "tableTo": "recipe_source_registry", + "columnsFrom": [ + "source_registry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "recipes_creator_user_id_users_id_fk": { + "name": "recipes_creator_user_id_users_id_fk", + "tableFrom": "recipes", + "tableTo": "users", + "columnsFrom": [ + "creator_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ingredient_translations": { + "name": "ingredient_translations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ingredient_id": { + "name": "ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "language_tag": { + "name": "language_tag", + "type": "varchar(35)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aliases": { + "name": "aliases", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "source": { + "name": "source", + "type": "translation_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'seed'" + }, + "status": { + "name": "status", + "type": "translation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'published'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ingredient_translations_unique": { + "name": "ingredient_translations_unique", + "columns": [ + { + "expression": "ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "language_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ingredient_translations_lang_idx": { + "name": "ingredient_translations_lang_idx", + "columns": [ + { + "expression": "language_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ingredient_translations_ingredient_id_canonical_ingredients_id_fk": { + "name": "ingredient_translations_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "ingredient_translations", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recipe_step_translations": { + "name": "recipe_step_translations", + "schema": "", + "columns": { + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "language_tag": { + "name": "language_tag", + "type": "varchar(35)", + "primaryKey": false, + "notNull": true + }, + "step_number": { + "name": "step_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "instruction": { + "name": "instruction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tip": { + "name": "tip", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "recipe_step_translations_recipe_id_recipes_id_fk": { + "name": "recipe_step_translations_recipe_id_recipes_id_fk", + "tableFrom": "recipe_step_translations", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "recipe_step_translations_recipe_id_language_tag_step_number_pk": { + "name": "recipe_step_translations_recipe_id_language_tag_step_number_pk", + "columns": [ + "recipe_id", + "language_tag", + "step_number" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.recipe_translations": { + "name": "recipe_translations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "language_tag": { + "name": "language_tag", + "type": "varchar(35)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_guidance": { + "name": "storage_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "translation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'draft_ai'" + }, + "source": { + "name": "source", + "type": "translation_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ai'" + }, + "verification": { + "name": "verification", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "recipe_translations_unique": { + "name": "recipe_translations_unique", + "columns": [ + { + "expression": "recipe_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "language_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "recipe_translations_lang_idx": { + "name": "recipe_translations_lang_idx", + "columns": [ + { + "expression": "language_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "recipe_translations_recipe_id_recipes_id_fk": { + "name": "recipe_translations_recipe_id_recipes_id_fk", + "tableFrom": "recipe_translations", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.unit_translations": { + "name": "unit_translations", + "schema": "", + "columns": { + "unit_code": { + "name": "unit_code", + "type": "unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "language_tag": { + "name": "language_tag", + "type": "varchar(35)", + "primaryKey": false, + "notNull": true + }, + "abbreviation": { + "name": "abbreviation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "unit_translations_unit_code_language_tag_pk": { + "name": "unit_translations_unit_code_language_tag_pk", + "columns": [ + "unit_code", + "language_tag" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.allergen_market_rules": { + "name": "allergen_market_rules", + "schema": "", + "columns": { + "region_code": { + "name": "region_code", + "type": "varchar(2)", + "primaryKey": false, + "notNull": true + }, + "allergen": { + "name": "allergen", + "type": "allergen", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "must_highlight": { + "name": "must_highlight", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "allergen_market_rules_region_code_allergen_pk": { + "name": "allergen_market_rules_region_code_allergen_pk", + "columns": [ + "region_code", + "allergen" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.nutrition_display_profiles": { + "name": "nutrition_display_profiles", + "schema": "", + "columns": { + "region_code": { + "name": "region_code", + "type": "varchar(2)", + "primaryKey": true, + "notNull": true + }, + "energy_display": { + "name": "energy_display", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "salt_display": { + "name": "salt_display", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "energy_label_key": { + "name": "energy_label_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.meal_boxes": { + "name": "meal_boxes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cooking_session_id": { + "name": "cooking_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title_sv": { + "name": "title_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "portions": { + "name": "portions", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "portions_remaining": { + "name": "portions_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "nutrition_per_portion": { + "name": "nutrition_per_portion", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cooked_at": { + "name": "cooked_at", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "storage_location_id": { + "name": "storage_location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "frozen": { + "name": "frozen", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "recommended_use_by": { + "name": "recommended_use_by", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "leftover_source": { + "name": "leftover_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reserved_for_user_id": { + "name": "reserved_for_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "meal_boxes_household_idx": { + "name": "meal_boxes_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "meal_boxes_use_by_idx": { + "name": "meal_boxes_use_by_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recommended_use_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "meal_boxes_household_id_households_id_fk": { + "name": "meal_boxes_household_id_households_id_fk", + "tableFrom": "meal_boxes", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "meal_boxes_recipe_id_recipes_id_fk": { + "name": "meal_boxes_recipe_id_recipes_id_fk", + "tableFrom": "meal_boxes", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "meal_boxes_cooking_session_id_cooking_sessions_id_fk": { + "name": "meal_boxes_cooking_session_id_cooking_sessions_id_fk", + "tableFrom": "meal_boxes", + "tableTo": "cooking_sessions", + "columnsFrom": [ + "cooking_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "meal_boxes_storage_location_id_storage_locations_id_fk": { + "name": "meal_boxes_storage_location_id_storage_locations_id_fk", + "tableFrom": "meal_boxes", + "tableTo": "storage_locations", + "columnsFrom": [ + "storage_location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "meal_boxes_reserved_for_user_id_users_id_fk": { + "name": "meal_boxes_reserved_for_user_id_users_id_fk", + "tableFrom": "meal_boxes", + "tableTo": "users", + "columnsFrom": [ + "reserved_for_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.meals": { + "name": "meals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "meal_type": { + "name": "meal_type", + "type": "meal_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "meal_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cooking_session_id": { + "name": "cooking_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title_sv": { + "name": "title_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "portion_fraction": { + "name": "portion_fraction", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "nutrition": { + "name": "nutrition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "nutrition_is_estimate": { + "name": "nutrition_is_estimate", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "estimate_min_kcal": { + "name": "estimate_min_kcal", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "estimate_max_kcal": { + "name": "estimate_max_kcal", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "items": { + "name": "items", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "photo_url": { + "name": "photo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scan_job_id": { + "name": "scan_job_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "logged_at": { + "name": "logged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "meals_user_date_idx": { + "name": "meals_user_date_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "meals_household_idx": { + "name": "meals_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "meals_user_id_users_id_fk": { + "name": "meals_user_id_users_id_fk", + "tableFrom": "meals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "meals_household_id_households_id_fk": { + "name": "meals_household_id_households_id_fk", + "tableFrom": "meals", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "meals_recipe_id_recipes_id_fk": { + "name": "meals_recipe_id_recipes_id_fk", + "tableFrom": "meals", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "meals_cooking_session_id_cooking_sessions_id_fk": { + "name": "meals_cooking_session_id_cooking_sessions_id_fk", + "tableFrom": "meals", + "tableTo": "cooking_sessions", + "columnsFrom": [ + "cooking_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shopping_list_items": { + "name": "shopping_list_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "shopping_list_id": { + "name": "shopping_list_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "canonical_ingredient_id": { + "name": "canonical_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "unit": { + "name": "unit", + "type": "unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'COUNT'" + }, + "store_section": { + "name": "store_section", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'hygien_ovrigt'" + }, + "suggested_package_size": { + "name": "suggested_package_size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_price_minor": { + "name": "estimated_price_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "checked": { + "name": "checked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "added_by_user_id": { + "name": "added_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "shopping_list_items_list_idx": { + "name": "shopping_list_items_list_idx", + "columns": [ + { + "expression": "shopping_list_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shopping_list_items_shopping_list_id_shopping_lists_id_fk": { + "name": "shopping_list_items_shopping_list_id_shopping_lists_id_fk", + "tableFrom": "shopping_list_items", + "tableTo": "shopping_lists", + "columnsFrom": [ + "shopping_list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shopping_list_items_canonical_ingredient_id_canonical_ingredients_id_fk": { + "name": "shopping_list_items_canonical_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "shopping_list_items", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "canonical_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "shopping_list_items_added_by_user_id_users_id_fk": { + "name": "shopping_list_items_added_by_user_id_users_id_fk", + "tableFrom": "shopping_list_items", + "tableTo": "users", + "columnsFrom": [ + "added_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shopping_lists": { + "name": "shopping_lists", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Inköpslista'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "week_plan_id": { + "name": "week_plan_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "shopping_lists_household_idx": { + "name": "shopping_lists_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shopping_lists_household_id_households_id_fk": { + "name": "shopping_lists_household_id_households_id_fk", + "tableFrom": "shopping_lists", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shopping_lists_week_plan_id_week_plans_id_fk": { + "name": "shopping_lists_week_plan_id_week_plans_id_fk", + "tableFrom": "shopping_lists", + "tableTo": "week_plans", + "columnsFrom": [ + "week_plan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.week_plan_entries": { + "name": "week_plan_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "week_plan_id": { + "name": "week_plan_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "meal_type": { + "name": "meal_type", + "type": "meal_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "meal_box_id": { + "name": "meal_box_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title_sv": { + "name": "title_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "portions": { + "name": "portions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "reschedule_reason_sv": { + "name": "reschedule_reason_sv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "week_plan_entries_plan_idx": { + "name": "week_plan_entries_plan_idx", + "columns": [ + { + "expression": "week_plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "week_plan_entries_week_plan_id_week_plans_id_fk": { + "name": "week_plan_entries_week_plan_id_week_plans_id_fk", + "tableFrom": "week_plan_entries", + "tableTo": "week_plans", + "columnsFrom": [ + "week_plan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "week_plan_entries_recipe_id_recipes_id_fk": { + "name": "week_plan_entries_recipe_id_recipes_id_fk", + "tableFrom": "week_plan_entries", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "week_plan_entries_meal_box_id_meal_boxes_id_fk": { + "name": "week_plan_entries_meal_box_id_meal_boxes_id_fk", + "tableFrom": "week_plan_entries", + "tableTo": "meal_boxes", + "columnsFrom": [ + "meal_box_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.week_plans": { + "name": "week_plans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "week_start_date": { + "name": "week_start_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "generated_by": { + "name": "generated_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "week_plans_household_week_idx": { + "name": "week_plans_household_week_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_start_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "week_plans_household_id_households_id_fk": { + "name": "week_plans_household_id_households_id_fk", + "tableFrom": "week_plans", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.price_observations": { + "name": "price_observations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "canonical_ingredient_id": { + "name": "canonical_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "store_name": { + "name": "store_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "price_minor": { + "name": "price_minor", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "price_per_kg_minor": { + "name": "price_per_kg_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "observed_at": { + "name": "observed_at", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'receipt'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "price_observations_ingredient_idx": { + "name": "price_observations_ingredient_idx", + "columns": [ + { + "expression": "canonical_ingredient_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "observed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "price_observations_canonical_ingredient_id_canonical_ingredients_id_fk": { + "name": "price_observations_canonical_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "price_observations", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "canonical_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "price_observations_product_id_products_id_fk": { + "name": "price_observations_product_id_products_id_fk", + "tableFrom": "price_observations", + "tableTo": "products", + "columnsFrom": [ + "product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.receipt_lines": { + "name": "receipt_lines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "receipt_id": { + "name": "receipt_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "raw_text": { + "name": "raw_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_name": { + "name": "normalized_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_ingredient_id": { + "name": "canonical_ingredient_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "unit", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "unit_price_minor": { + "name": "unit_price_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_price_minor": { + "name": "total_price_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "verified_by_user": { + "name": "verified_by_user", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "added_to_inventory": { + "name": "added_to_inventory", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "receipt_lines_receipt_idx": { + "name": "receipt_lines_receipt_idx", + "columns": [ + { + "expression": "receipt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "receipt_lines_receipt_id_receipts_id_fk": { + "name": "receipt_lines_receipt_id_receipts_id_fk", + "tableFrom": "receipt_lines", + "tableTo": "receipts", + "columnsFrom": [ + "receipt_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "receipt_lines_canonical_ingredient_id_canonical_ingredients_id_fk": { + "name": "receipt_lines_canonical_ingredient_id_canonical_ingredients_id_fk", + "tableFrom": "receipt_lines", + "tableTo": "canonical_ingredients", + "columnsFrom": [ + "canonical_ingredient_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "receipt_lines_product_id_products_id_fk": { + "name": "receipt_lines_product_id_products_id_fk", + "tableFrom": "receipt_lines", + "tableTo": "products", + "columnsFrom": [ + "product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.receipts": { + "name": "receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "store_name": { + "name": "store_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "purchase_date": { + "name": "purchase_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "total_minor": { + "name": "total_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "discount_minor": { + "name": "discount_minor", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scan_job_id": { + "name": "scan_job_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "receipts_household_idx": { + "name": "receipts_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "purchase_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "receipts_household_id_households_id_fk": { + "name": "receipts_household_id_households_id_fk", + "tableFrom": "receipts", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_corrections": { + "name": "ai_corrections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scan_job_id": { + "name": "scan_job_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_type": { + "name": "task_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ai_output": { + "name": "ai_output", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "user_correction": { + "name": "user_correction", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "proposal": { + "name": "proposal", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "image_s3_key": { + "name": "image_s3_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_version": { + "name": "model_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt_version": { + "name": "prompt_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_snapshot": { + "name": "consent_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "exported_to_training": { + "name": "exported_to_training", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "training_batch_id": { + "name": "training_batch_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_corrections_task_idx": { + "name": "ai_corrections_task_idx", + "columns": [ + { + "expression": "task_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_corrections_scan_job_id_scan_jobs_id_fk": { + "name": "ai_corrections_scan_job_id_scan_jobs_id_fk", + "tableFrom": "ai_corrections", + "tableTo": "scan_jobs", + "columnsFrom": [ + "scan_job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ai_corrections_user_id_users_id_fk": { + "name": "ai_corrections_user_id_users_id_fk", + "tableFrom": "ai_corrections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_training_bank": { + "name": "ai_training_bank", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "correction_id": { + "name": "correction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scan_job_id": { + "name": "scan_job_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'v1'" + }, + "task_type": { + "name": "task_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image_s3_key": { + "name": "image_s3_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposal": { + "name": "proposal", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "corrected": { + "name": "corrected", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "model_version": { + "name": "model_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt_version": { + "name": "prompt_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_snapshot": { + "name": "consent_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_training_bank_correction_unique": { + "name": "ai_training_bank_correction_unique", + "columns": [ + { + "expression": "correction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_training_bank_version_idx": { + "name": "ai_training_bank_version_idx", + "columns": [ + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_training_bank_scan_job_idx": { + "name": "ai_training_bank_scan_job_idx", + "columns": [ + { + "expression": "scan_job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_training_bank_created_at_idx": { + "name": "ai_training_bank_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_training_bank_correction_id_ai_corrections_id_fk": { + "name": "ai_training_bank_correction_id_ai_corrections_id_fk", + "tableFrom": "ai_training_bank", + "tableTo": "ai_corrections", + "columnsFrom": [ + "correction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_training_bank_scan_job_id_scan_jobs_id_fk": { + "name": "ai_training_bank_scan_job_id_scan_jobs_id_fk", + "tableFrom": "ai_training_bank", + "tableTo": "scan_jobs", + "columnsFrom": [ + "scan_job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scan_jobs": { + "name": "scan_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scan_type": { + "name": "scan_type", + "type": "scan_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "job_type": { + "name": "job_type", + "type": "job_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "job_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "s3_keys": { + "name": "s3_keys", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_version": { + "name": "model_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt_version": { + "name": "prompt_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scan_jobs_user_idx": { + "name": "scan_jobs_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scan_jobs_status_idx": { + "name": "scan_jobs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scan_jobs_user_id_users_id_fk": { + "name": "scan_jobs_user_id_users_id_fk", + "tableFrom": "scan_jobs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scan_jobs_household_id_households_id_fk": { + "name": "scan_jobs_household_id_households_id_fk", + "tableFrom": "scan_jobs", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.food_memories": { + "name": "food_memories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "recipe_id": { + "name": "recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title_sv": { + "name": "title_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary_sv": { + "name": "summary_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "holiday_tag": { + "name": "holiday_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo_url": { + "name": "photo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stars": { + "name": "stars", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "food_memories_household_idx": { + "name": "food_memories_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "food_memories_holiday_idx": { + "name": "food_memories_holiday_idx", + "columns": [ + { + "expression": "holiday_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "food_memories_user_id_users_id_fk": { + "name": "food_memories_user_id_users_id_fk", + "tableFrom": "food_memories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "food_memories_household_id_households_id_fk": { + "name": "food_memories_household_id_households_id_fk", + "tableFrom": "food_memories", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "food_memories_recipe_id_recipes_id_fk": { + "name": "food_memories_recipe_id_recipes_id_fk", + "tableFrom": "food_memories", + "tableTo": "recipes", + "columnsFrom": [ + "recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_items": { + "name": "memory_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "memory_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary_sv": { + "name": "summary_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "signal_origin", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "confidence": { + "name": "confidence", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0.5 + }, + "verified_by_user": { + "name": "verified_by_user", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "paused": { + "name": "paused", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "memory_items_user_idx": { + "name": "memory_items_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_items_household_idx": { + "name": "memory_items_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_items_key_idx": { + "name": "memory_items_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_items_user_key_unique": { + "name": "memory_items_user_key_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_items_user_id_users_id_fk": { + "name": "memory_items_user_id_users_id_fk", + "tableFrom": "memory_items", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "memory_items_household_id_households_id_fk": { + "name": "memory_items_household_id_households_id_fk", + "tableFrom": "memory_items", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.taste_signals": { + "name": "taste_signals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "axis": { + "name": "axis", + "type": "taste_axis", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "strength": { + "name": "strength", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0.5 + }, + "origin": { + "name": "origin", + "type": "signal_origin", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "ref_recipe_id": { + "name": "ref_recipe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "taste_signals_user_idx": { + "name": "taste_signals_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "axis", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "taste_signals_user_axis_target_unique": { + "name": "taste_signals_user_axis_target_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "axis", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "taste_signals_user_id_users_id_fk": { + "name": "taste_signals_user_id_users_id_fk", + "tableFrom": "taste_signals", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "taste_signals_ref_recipe_id_recipes_id_fk": { + "name": "taste_signals_ref_recipe_id_recipes_id_fk", + "tableFrom": "taste_signals", + "tableTo": "recipes", + "columnsFrom": [ + "ref_recipe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_events": { + "name": "season_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name_sv": { + "name": "name_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "market": { + "name": "market", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'SE'" + }, + "date_rule": { + "name": "date_rule", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "lead_days": { + "name": "lead_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 7 + }, + "food_tags": { + "name": "food_tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "recipe_slugs": { + "name": "recipe_slugs", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "season_events_slug_market_unique": { + "name": "season_events_slug_market_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "market", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_usage_counters": { + "name": "ai_usage_counters", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "month": { + "name": "month", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ai_scans": { + "name": "ai_scans", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "ai_tokens_in": { + "name": "ai_tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "ai_tokens_out": { + "name": "ai_tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "ai_cost_usd_microcents": { + "name": "ai_cost_usd_microcents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_usage_user_month_unique": { + "name": "ai_usage_user_month_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_usage_counters_user_id_users_id_fk": { + "name": "ai_usage_counters_user_id_users_id_fk", + "tableFrom": "ai_usage_counters", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.store_notifications": { + "name": "store_notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "subscription_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "raw_payload": { + "name": "raw_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "signature_verified": { + "name": "signature_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "processed": { + "name": "processed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "store_notifications_pending_idx": { + "name": "store_notifications_pending_idx", + "columns": [ + { + "expression": "processed", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.store_transactions": { + "name": "store_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "subscription_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "transaction_id": { + "name": "transaction_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_transaction_id": { + "name": "original_transaction_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pseudonym": { + "name": "pseudonym", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "raw_payload": { + "name": "raw_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "store_transactions_unique": { + "name": "store_transactions_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "store_transactions_pseudonym_idx": { + "name": "store_transactions_pseudonym_idx", + "columns": [ + { + "expression": "pseudonym", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "store_transactions_user_id_users_id_fk": { + "name": "store_transactions_user_id_users_id_fk", + "tableFrom": "store_transactions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "store_transactions_pseudonym_gdpr_retention_pseudonyms_pseudonym_fk": { + "name": "store_transactions_pseudonym_gdpr_retention_pseudonyms_pseudonym_fk", + "tableFrom": "store_transactions", + "tableTo": "gdpr_retention_pseudonyms", + "columnsFrom": [ + "pseudonym" + ], + "columnsTo": [ + "pseudonym" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription_events": { + "name": "subscription_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pseudonym": { + "name": "pseudonym", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "subscription_events_sub_idx": { + "name": "subscription_events_sub_idx", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscription_events_pseudonym_idx": { + "name": "subscription_events_pseudonym_idx", + "columns": [ + { + "expression": "pseudonym", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "subscription_events_subscription_id_subscriptions_id_fk": { + "name": "subscription_events_subscription_id_subscriptions_id_fk", + "tableFrom": "subscription_events", + "tableTo": "subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "subscription_events_user_id_users_id_fk": { + "name": "subscription_events_user_id_users_id_fk", + "tableFrom": "subscription_events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "subscription_events_pseudonym_gdpr_retention_pseudonyms_pseudonym_fk": { + "name": "subscription_events_pseudonym_gdpr_retention_pseudonyms_pseudonym_fk", + "tableFrom": "subscription_events", + "tableTo": "gdpr_retention_pseudonyms", + "columnsFrom": [ + "pseudonym" + ], + "columnsTo": [ + "pseudonym" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pseudonym": { + "name": "pseudonym", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "subscription_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "subscription_plan", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "original_transaction_id": { + "name": "original_transaction_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "subscription_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "purchased_at": { + "name": "purchased_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "grace_period_expires_at": { + "name": "grace_period_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_verified_at": { + "name": "last_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "subscriptions_user_idx": { + "name": "subscriptions_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscriptions_pseudonym_idx": { + "name": "subscriptions_pseudonym_idx", + "columns": [ + { + "expression": "pseudonym", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscriptions_original_tx_unique": { + "name": "subscriptions_original_tx_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "original_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"subscriptions\".\"original_transaction_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "subscriptions_user_id_users_id_fk": { + "name": "subscriptions_user_id_users_id_fk", + "tableFrom": "subscriptions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "subscriptions_pseudonym_gdpr_retention_pseudonyms_pseudonym_fk": { + "name": "subscriptions_pseudonym_gdpr_retention_pseudonyms_pseudonym_fk", + "tableFrom": "subscriptions", + "tableTo": "gdpr_retention_pseudonyms", + "columnsFrom": [ + "pseudonym" + ], + "columnsTo": [ + "pseudonym" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "subscriptions_household_id_households_id_fk": { + "name": "subscriptions_household_id_households_id_fk", + "tableFrom": "subscriptions", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.trials": { + "name": "trials", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "trials_user_id_users_id_fk": { + "name": "trials_user_id_users_id_fk", + "tableFrom": "trials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.analytics_daily_snapshots": { + "name": "analytics_daily_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "date": { + "name": "date", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "event_name": { + "name": "event_name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "unique_sessions": { + "name": "unique_sessions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "properties_fingerprint": { + "name": "properties_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "pa_daily_household_date_idx": { + "name": "pa_daily_household_date_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pa_daily_date_event_idx": { + "name": "pa_daily_date_event_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "analytics_daily_snapshots_household_id_households_id_fk": { + "name": "analytics_daily_snapshots_household_id_households_id_fk", + "tableFrom": "analytics_daily_snapshots", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.product_analytics_events": { + "name": "product_analytics_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "event_name": { + "name": "event_name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "anonymous_id": { + "name": "anonymous_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "app_version": { + "name": "app_version", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "locale": { + "name": "locale", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "experiment_variant": { + "name": "experiment_variant", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "pa_events_name_occurred_idx": { + "name": "pa_events_name_occurred_idx", + "columns": [ + { + "expression": "event_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pa_events_household_occurred_idx": { + "name": "pa_events_household_occurred_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pa_events_user_occurred_idx": { + "name": "pa_events_user_occurred_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pa_events_session_idx": { + "name": "pa_events_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pa_events_received_idx": { + "name": "pa_events_received_idx", + "columns": [ + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "product_analytics_events_user_id_users_id_fk": { + "name": "product_analytics_events_user_id_users_id_fk", + "tableFrom": "product_analytics_events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "product_analytics_events_household_id_households_id_fk": { + "name": "product_analytics_events_household_id_households_id_fk", + "tableFrom": "product_analytics_events", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback": { + "name": "feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "typ": { + "name": "typ", + "type": "feedback_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "rubrik": { + "name": "rubrik", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "feedback_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'oppen'" + }, + "plattform": { + "name": "plattform", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "app_version": { + "name": "app_version", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_status_created_idx": { + "name": "feedback_status_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_user_idx": { + "name": "feedback_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_user_id_users_id_fk": { + "name": "feedback_user_id_users_id_fk", + "tableFrom": "feedback", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ops_safety_canary": { + "name": "ops_safety_canary", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "allergen_invariant_brott": { + "name": "allergen_invariant_brott", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "overifierade_visade": { + "name": "overifierade_visade", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "food_safety_lint_avvisade_7d": { + "name": "food_safety_lint_avvisade_7d", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "computed_at": { + "name": "computed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_eval_cases": { + "name": "ai_eval_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_type": { + "name": "task_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name_sv": { + "name": "name_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_ref": { + "name": "input_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expected_output": { + "name": "expected_output", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_eval_runs": { + "name": "ai_eval_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_type": { + "name": "task_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_version": { + "name": "model_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt_version": { + "name": "prompt_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metrics": { + "name": "metrics", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "passed": { + "name": "passed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_eval_runs_task_idx": { + "name": "ai_eval_runs_task_idx", + "columns": [ + { + "expression": "task_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_logs_actor_idx": { + "name": "audit_logs_actor_idx", + "columns": [ + { + "expression": "actor_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_logs_action_idx": { + "name": "audit_logs_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.creator_follows": { + "name": "creator_follows", + "schema": "", + "columns": { + "follower_user_id": { + "name": "follower_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "creator_user_id": { + "name": "creator_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "creator_follows_follower_user_id_users_id_fk": { + "name": "creator_follows_follower_user_id_users_id_fk", + "tableFrom": "creator_follows", + "tableTo": "users", + "columnsFrom": [ + "follower_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "creator_follows_creator_user_id_users_id_fk": { + "name": "creator_follows_creator_user_id_users_id_fk", + "tableFrom": "creator_follows", + "tableTo": "users", + "columnsFrom": [ + "creator_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "creator_follows_follower_user_id_creator_user_id_pk": { + "name": "creator_follows_follower_user_id_creator_user_id_pk", + "columns": [ + "follower_user_id", + "creator_user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.creator_stats": { + "name": "creator_stats", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "profile_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, + "level": { + "name": "level", + "type": "creator_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'beginner'" + }, + "published_recipes": { + "name": "published_recipes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "followers": { + "name": "followers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cooks": { + "name": "total_cooks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_favorites": { + "name": "total_favorites", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "average_rating": { + "name": "average_rating", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "verified_recipes": { + "name": "verified_recipes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "badges": { + "name": "badges", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "creator_stats_user_id_users_id_fk": { + "name": "creator_stats_user_id_users_id_fk", + "tableFrom": "creator_stats", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.domain_events": { + "name": "domain_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "type": { + "name": "type", + "type": "event_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "domain_events_unpublished_idx": { + "name": "domain_events_unpublished_idx", + "columns": [ + { + "expression": "published_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "domain_events_type_idx": { + "name": "domain_events_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "domain_events_household_idx": { + "name": "domain_events_household_idx", + "columns": [ + { + "expression": "household_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feature_flags": { + "name": "feature_flags", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "description_sv": { + "name": "description_sv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rollout_percent": { + "name": "rollout_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_keys": { + "name": "idempotency_keys", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "response_status": { + "name": "response_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_body": { + "name": "response_body", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "idempotency_keys_user_id_users_id_fk": { + "name": "idempotency_keys_user_id_users_id_fk", + "tableFrom": "idempotency_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "idempotency_keys_user_id_key_endpoint_pk": { + "name": "idempotency_keys_user_id_key_endpoint_pk", + "columns": [ + "user_id", + "key", + "endpoint" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "title_sv": { + "name": "title_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_sv": { + "name": "body_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "template_key": { + "name": "template_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scheduled_for": { + "name": "scheduled_for", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "read_at": { + "name": "read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "notifications_user_idx": { + "name": "notifications_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.push_tokens": { + "name": "push_tokens", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "push_tokens_user_id_users_id_fk": { + "name": "push_tokens_user_id_users_id_fk", + "tableFrom": "push_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "push_tokens_user_id_token_pk": { + "name": "push_tokens_user_id_token_pk", + "columns": [ + "user_id", + "token" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waste_summaries": { + "name": "waste_summaries", + "schema": "", + "columns": { + "household_id": { + "name": "household_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "week_start_date": { + "name": "week_start_date", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "discarded_count": { + "name": "discarded_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "discarded_value_minor": { + "name": "discarded_value_minor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "waste_summaries_household_id_households_id_fk": { + "name": "waste_summaries_household_id_households_id_fk", + "tableFrom": "waste_summaries", + "tableTo": "households", + "columnsFrom": [ + "household_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "waste_summaries_household_id_week_start_date_pk": { + "name": "waste_summaries_household_id_week_start_date_pk", + "columns": [ + "household_id", + "week_start_date" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.release_gates": { + "name": "release_gates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "gate_key": { + "name": "gate_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "release_gate_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name_sv": { + "name": "name_sv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name_en": { + "name": "name_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_sv": { + "name": "description_sv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description_en": { + "name": "description_en", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_value": { + "name": "target_value", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "comparison": { + "name": "comparison", + "type": "release_gate_comparison", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "evaluation_window_days": { + "name": "evaluation_window_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 7 + }, + "last_value": { + "name": "last_value", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "last_evaluated_at": { + "name": "last_evaluated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "release_gate_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "blocking": { + "name": "blocking", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "release_gates_category_idx": { + "name": "release_gates_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "release_gates_status_idx": { + "name": "release_gates_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "release_gates_gate_key_unique": { + "name": "release_gates_gate_key_unique", + "nullsNotDistinct": false, + "columns": [ + "gate_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.activity_level": { + "name": "activity_level", + "schema": "public", + "values": [ + "sedentary", + "light", + "moderate", + "active", + "very_active" + ] + }, + "public.allergen": { + "name": "allergen", + "schema": "public", + "values": [ + "gluten", + "crustaceans", + "eggs", + "fish", + "peanuts", + "soy", + "milk", + "tree_nuts", + "celery", + "mustard", + "sesame", + "sulphites", + "lupin", + "molluscs" + ] + }, + "public.consent_kind": { + "name": "consent_kind", + "schema": "public", + "values": [ + "personalization", + "anonymized_improvement", + "image_training", + "health_integration", + "location_weather", + "push_notifications", + "notifications", + "product_analytics" + ] + }, + "public.consent_status": { + "name": "consent_status", + "schema": "public", + "values": [ + "granted", + "denied", + "revoked" + ] + }, + "public.cooking_method": { + "name": "cooking_method", + "schema": "public", + "values": [ + "stovetop", + "oven", + "grill", + "airfryer", + "wok", + "slow_cooker", + "sous_vide", + "microwave", + "no_cook", + "pressure_cooker", + "deep_fry", + "steam" + ] + }, + "public.creator_level": { + "name": "creator_level", + "schema": "public", + "values": [ + "beginner", + "sous_chef", + "chef", + "master_chef", + "legend" + ] + }, + "public.cuisine": { + "name": "cuisine", + "schema": "public", + "values": [ + "swedish", + "nordic", + "italian", + "french", + "spanish", + "greek", + "thai", + "chinese", + "japanese", + "korean", + "vietnamese", + "indian", + "mexican", + "american", + "turkish", + "lebanese", + "moroccan", + "middle_eastern", + "international" + ] + }, + "public.date_kind": { + "name": "date_kind", + "schema": "public", + "values": [ + "best_before", + "use_by" + ] + }, + "public.diet_pattern": { + "name": "diet_pattern", + "schema": "public", + "values": [ + "omnivore", + "flexitarian", + "pescatarian", + "vegetarian", + "vegan", + "low_carb", + "keto", + "high_protein", + "mediterranean" + ] + }, + "public.event_type": { + "name": "event_type", + "schema": "public", + "values": [ + "PRODUCT_ADDED", + "PRODUCT_UPDATED", + "PRODUCT_CONSUMED", + "PRODUCT_DISCARDED", + "RECIPE_COOKED", + "RECIPE_RATED", + "RECIPE_CREATED", + "RECIPE_FORKED", + "MEAL_LOGGED", + "MEAL_PHOTO_ANALYZED", + "HOUSEHOLD_MEMBER_ADDED", + "SUBSCRIPTION_STARTED", + "SUBSCRIPTION_CHANGED", + "AI_CORRECTED", + "MEMORY_UPDATED", + "SHOPPING_COMPLETED", + "WEEK_PLAN_UPDATED", + "MEAL_BOX_CREATED", + "MEAL_BOX_UPDATED", + "MEAL_BOX_CONSUMED", + "MEAL_BOX_DISCARDED", + "MEAL_REMOVED", + "COOKING_SESSION_UNDONE" + ] + }, + "public.expiry_status": { + "name": "expiry_status", + "schema": "public", + "values": [ + "fresh", + "use_soon", + "expiring", + "expired", + "unknown" + ] + }, + "public.goal_type": { + "name": "goal_type", + "schema": "public", + "values": [ + "lose_weight", + "gain_weight", + "build_muscle", + "maintain_weight", + "more_protein", + "less_fat", + "more_fiber", + "more_variety", + "less_waste", + "lower_cost", + "cook_more" + ] + }, + "public.household_role": { + "name": "household_role", + "schema": "public", + "values": [ + "owner", + "adult", + "member", + "child" + ] + }, + "public.inventory_source": { + "name": "inventory_source", + "schema": "public", + "values": [ + "fridge_photo", + "freezer_photo", + "pantry_photo", + "ingredient_photo", + "barcode", + "receipt", + "digital_receipt", + "label_photo", + "manual_search", + "free_text", + "voice", + "cooked_recipe", + "connector", + "seed" + ] + }, + "public.inventory_transaction_type": { + "name": "inventory_transaction_type", + "schema": "public", + "values": [ + "purchase", + "consume", + "discard", + "adjust", + "cook_use", + "leftover_created", + "leftover_consumed", + "correction", + "freeze", + "thaw", + "move" + ] + }, + "public.job_status": { + "name": "job_status", + "schema": "public", + "values": [ + "queued", + "running", + "awaiting_confirmation", + "completed", + "failed", + "canceled" + ] + }, + "public.job_type": { + "name": "job_type", + "schema": "public", + "values": [ + "ANALYZE_FRIDGE_IMAGE", + "ANALYZE_PANTRY_IMAGE", + "ANALYZE_MEAL_IMAGE", + "READ_RECEIPT", + "READ_NUTRITION_LABEL", + "READ_EXPIRY_DATE", + "NORMALIZE_PRODUCTS", + "DEDUPLICATE_INVENTORY", + "CALCULATE_NUTRITION", + "GENERATE_RECIPE_OPTIONS", + "RANK_RECIPES", + "UPDATE_USER_MEMORY", + "GENERATE_WEEK_PLAN", + "SEND_EXPIRY_NOTIFICATION", + "VERIFY_SUBSCRIPTION", + "PROCESS_STORE_NOTIFICATION", + "BUILD_TRAINING_SAMPLE", + "RUN_AI_EVALUATION" + ] + }, + "public.meal_log_source": { + "name": "meal_log_source", + "schema": "public", + "values": [ + "cooked_recipe", + "plate_photo", + "barcode", + "product", + "free_text", + "voice", + "previous_meal", + "meal_box", + "manual" + ] + }, + "public.meal_type": { + "name": "meal_type", + "schema": "public", + "values": [ + "breakfast", + "lunch", + "dinner", + "snack", + "dessert", + "starter", + "buffet", + "party" + ] + }, + "public.memory_kind": { + "name": "memory_kind", + "schema": "public", + "values": [ + "structured_fact", + "event", + "semantic", + "profile_summary", + "recipe_memory" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "expiry_warning", + "meal_box_reminder", + "quick_dinner_suggestion", + "holiday_upcoming", + "creator_new_recipe", + "week_plan_change", + "pantry_forecast", + "subscription_status", + "proactive_tip" + ] + }, + "public.precision_mode": { + "name": "precision_mode", + "schema": "public", + "values": [ + "simple", + "exact" + ] + }, + "public.profile_visibility": { + "name": "profile_visibility", + "schema": "public", + "values": [ + "private", + "friends", + "national", + "global" + ] + }, + "public.recipe_difficulty": { + "name": "recipe_difficulty", + "schema": "public", + "values": [ + "beginner", + "easy", + "medium", + "advanced", + "expert" + ] + }, + "public.recipe_similarity_class": { + "name": "recipe_similarity_class", + "schema": "public", + "values": [ + "duplicate", + "variant", + "inspired", + "independent" + ] + }, + "public.recipe_source_type": { + "name": "recipe_source_type", + "schema": "public", + "values": [ + "own_editorial", + "ai_assisted_reviewed", + "open_license", + "public_domain", + "licensed_database", + "creator_agreement", + "user_generated" + ] + }, + "public.recipe_status": { + "name": "recipe_status", + "schema": "public", + "values": [ + "draft", + "submitted", + "ai_checked", + "in_moderation", + "published", + "rejected", + "archived" + ] + }, + "public.recipe_variant_type": { + "name": "recipe_variant_type", + "schema": "public", + "values": [ + "standard", + "high_protein", + "low_calorie", + "low_fat", + "vegetarian", + "vegan", + "gluten_free", + "lactose_free", + "kid_friendly", + "airfryer", + "budget" + ] + }, + "public.recipe_verification_status": { + "name": "recipe_verification_status", + "schema": "public", + "values": [ + "unverified", + "community", + "verified", + "editorial", + "rejected" + ] + }, + "public.religious_rule": { + "name": "religious_rule", + "schema": "public", + "values": [ + "none", + "halal", + "kosher", + "hindu_no_beef", + "buddhist_vegetarian" + ] + }, + "public.scan_type": { + "name": "scan_type", + "schema": "public", + "values": [ + "fridge", + "freezer", + "pantry", + "ingredients", + "plate", + "receipt", + "barcode", + "expiry_date", + "nutrition_label", + "product_package" + ] + }, + "public.sex": { + "name": "sex", + "schema": "public", + "values": [ + "female", + "male", + "unspecified" + ] + }, + "public.signal_origin": { + "name": "signal_origin", + "schema": "public", + "values": [ + "user_stated", + "observed", + "ai_inferred" + ] + }, + "public.storage_location_type": { + "name": "storage_location_type", + "schema": "public", + "values": [ + "fridge", + "freezer", + "pantry", + "garage_freezer", + "wine_fridge", + "cellar", + "meal_box", + "custom" + ] + }, + "public.subscription_plan": { + "name": "subscription_plan", + "schema": "public", + "values": [ + "free", + "household", + "family", + "large_household" + ] + }, + "public.subscription_provider": { + "name": "subscription_provider", + "schema": "public", + "values": [ + "apple", + "google", + "promo", + "none" + ] + }, + "public.subscription_status": { + "name": "subscription_status", + "schema": "public", + "values": [ + "trial", + "active", + "in_grace", + "on_hold", + "paused", + "canceled", + "expired" + ] + }, + "public.taste_axis": { + "name": "taste_axis", + "schema": "public", + "values": [ + "spice", + "salt", + "acid", + "creaminess", + "garlic", + "sweetness", + "herbs", + "umami", + "cuisine", + "ingredient_avoid" + ] + }, + "public.unit": { + "name": "unit", + "schema": "public", + "values": [ + "GRAM", + "KILOGRAM", + "MILLILITER", + "DECILITER", + "LITER", + "TEASPOON", + "TABLESPOON", + "CUP_US", + "FLUID_OUNCE_US", + "OUNCE", + "POUND", + "COUNT", + "PORTION", + "PINCH", + "SLICE", + "CLOVE", + "CAN", + "PACKAGE" + ] + }, + "public.user_role": { + "name": "user_role", + "schema": "public", + "values": [ + "user", + "moderator", + "admin" + ] + }, + "public.verification_status": { + "name": "verification_status", + "schema": "public", + "values": [ + "unverified", + "user_verified", + "editorially_verified" + ] + }, + "public.measurement_system": { + "name": "measurement_system", + "schema": "public", + "values": [ + "METRIC", + "US_CUSTOMARY", + "MIXED" + ] + }, + "public.temperature_unit": { + "name": "temperature_unit", + "schema": "public", + "values": [ + "CELSIUS", + "FAHRENHEIT" + ] + }, + "public.translation_source": { + "name": "translation_source", + "schema": "public", + "values": [ + "seed", + "ai", + "human" + ] + }, + "public.translation_status": { + "name": "translation_status", + "schema": "public", + "values": [ + "draft_ai", + "in_review", + "published" + ] + }, + "public.feedback_status": { + "name": "feedback_status", + "schema": "public", + "values": [ + "oppen", + "pagar", + "stangd" + ] + }, + "public.feedback_type": { + "name": "feedback_type", + "schema": "public", + "values": [ + "bug", + "onskemal" + ] + }, + "public.release_gate_category": { + "name": "release_gate_category", + "schema": "public", + "values": [ + "product", + "quality", + "retention", + "economy" + ] + }, + "public.release_gate_comparison": { + "name": "release_gate_comparison", + "schema": "public", + "values": [ + "gte", + "lte", + "eq" + ] + }, + "public.release_gate_status": { + "name": "release_gate_status", + "schema": "public", + "values": [ + "pending", + "passed", + "failed", + "blocked", + "not_measurable" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/infrastructure/migrations/meta/_journal.json b/infrastructure/migrations/meta/_journal.json index 93c371e..3414c02 100644 --- a/infrastructure/migrations/meta/_journal.json +++ b/infrastructure/migrations/meta/_journal.json @@ -176,6 +176,13 @@ "when": 1786306800000, "tag": "0025_recipe_status_rejected", "breakpoints": true + }, + { + "idx": 25, + "version": "7", + "when": 1786397281333, + "tag": "0026_s5_onboarding_memory", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/packages/database/src/schema/memory.ts b/packages/database/src/schema/memory.ts index c0bea68..ade29a8 100644 --- a/packages/database/src/schema/memory.ts +++ b/packages/database/src/schema/memory.ts @@ -6,6 +6,7 @@ import { pgTable, text, timestamp, + uniqueIndex, uuid, } from "drizzle-orm/pg-core"; import { @@ -47,6 +48,7 @@ export const memoryItems = pgTable( index("memory_items_user_idx").on(t.userId, t.kind), index("memory_items_household_idx").on(t.householdId, t.kind), index("memory_items_key_idx").on(t.key), + uniqueIndex("memory_items_user_key_unique").on(t.userId, t.key), ], ); @@ -59,13 +61,17 @@ export const tasteSignals = pgTable( .notNull() .references(() => users.id, { onDelete: "cascade" }), axis: tasteAxisEnum("axis").notNull(), + target: text("target"), direction: doublePrecision("direction").notNull(), strength: doublePrecision("strength").notNull().default(0.5), origin: signalOriginEnum("origin").notNull(), refRecipeId: uuid("ref_recipe_id").references(() => recipes.id, { onDelete: "set null" }), createdAt: createdAt(), }, - (t) => [index("taste_signals_user_idx").on(t.userId, t.axis)], + (t) => [ + index("taste_signals_user_idx").on(t.userId, t.axis), + uniqueIndex("taste_signals_user_axis_target_unique").on(t.userId, t.axis, t.target), + ], ); /** Food Memory (spec §39): långsiktiga matminnen kopplade till högtider och betyg. */ diff --git a/packages/memory-client/src/index.ts b/packages/memory-client/src/index.ts index 991113c..0f61bb6 100644 --- a/packages/memory-client/src/index.ts +++ b/packages/memory-client/src/index.ts @@ -320,6 +320,21 @@ export function renderMemorySummary( if (typeof v.spicePreference === "string") { return `Spice preference: ${String(v.spicePreference)}`; } + if (typeof v.goal === "string") { + return `Goal: ${String(v.goal)}`; + } + if (typeof v.primaryGoal === "string") { + return `Primary goal: ${String(v.primaryGoal)}`; + } + if (typeof v.allergen === "string") { + return `Allergy: ${String(v.allergen)}`; + } + if (typeof v.avoidIngredientId === "string") { + return `Avoids ingredient: ${String(v.avoidIngredientId)}`; + } + if (typeof v.spiceLevelMax === "number") { + return `Max spice level: ${v.spiceLevelMax}`; + } } return item.summarySv; } diff --git a/packages/shared-types/src/entities.ts b/packages/shared-types/src/entities.ts index 696cc5f..5550feb 100644 --- a/packages/shared-types/src/entities.ts +++ b/packages/shared-types/src/entities.ts @@ -599,6 +599,8 @@ export interface TasteSignal { direction: number; strength: number; origin: SignalOrigin; + /** Axis-specific target, e.g. a cuisine slug or canonical ingredient id. */ + target?: string; refRecipeId?: string; createdAt: string; } diff --git a/packages/shared-types/src/enums.ts b/packages/shared-types/src/enums.ts index f532640..fc357c3 100644 --- a/packages/shared-types/src/enums.ts +++ b/packages/shared-types/src/enums.ts @@ -477,6 +477,8 @@ export const TASTE_AXES = [ "sweetness", "herbs", "umami", + "cuisine", + "ingredient_avoid", ] as const; export type TasteAxis = (typeof TASTE_AXES)[number];