feat: kitchen screen, scan dedup, gemini-3.5-flash-lite, seed grapes/beetroot/pea shoots
This commit is contained in:
@@ -48,7 +48,7 @@ const configSchema = z.object({
|
||||
AAMOS_TIMEOUT_MS: z.coerce.number().int().default(60_000),
|
||||
|
||||
GEMINI_API_KEY: z.string().optional(),
|
||||
GEMINI_MODEL: z.string().default("gemini-2.5-flash"),
|
||||
GEMINI_MODEL: z.string().default("gemini-3.5-flash-lite"),
|
||||
GEMINI_TIMEOUT_MS: z.coerce.number().int().default(60_000),
|
||||
GEMINI_DAILY_BUDGET_USD: z.coerce.number().default(0),
|
||||
|
||||
@@ -104,8 +104,13 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
|
||||
console.error("SÄKERHETSSTOPP: AAMOS_MODE=mock är inte tillåtet i produktion.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (cfg.AAMOS_MODE === "gemini" && !cfg.GEMINI_API_KEY) {
|
||||
console.error("SÄKERHETSSTOPP: AAMOS_MODE=gemini kräver GEMINI_API_KEY i produktion.");
|
||||
if (
|
||||
cfg.AAMOS_MODE === "gemini" &&
|
||||
(!cfg.GEMINI_API_KEY || cfg.GEMINI_API_KEY.startsWith("ROTATE"))
|
||||
) {
|
||||
console.error(
|
||||
"SÄKERHETSSTOPP: AAMOS_MODE=gemini kräver en riktig GEMINI_API_KEY (hämtas från SSM /cibello/prod/gemini-api-key), inte tom eller placeholder.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (cfg.AAMOS_MODE === "http" && (!cfg.AAMOS_API_URL || !cfg.AAMOS_API_KEY)) {
|
||||
|
||||
@@ -150,6 +150,43 @@ export async function scanRoutes(app: FastifyInstance) {
|
||||
if (!locationId)
|
||||
throw errors.badRequest("storageLocationId saknas och ingen standardplats finns.");
|
||||
|
||||
// Dedup (#1): finns varan redan aktiv i hushållet? Uppdatera + hoppa över,
|
||||
// så överlappande foton / omfotografering inte skapar dubletter.
|
||||
const dedupCond = item.canonicalIngredientId
|
||||
? and(
|
||||
eq(schema.inventoryItems.householdId, householdId),
|
||||
eq(schema.inventoryItems.canonicalIngredientId, item.canonicalIngredientId),
|
||||
gt(schema.inventoryItems.quantity, 0),
|
||||
)
|
||||
: and(
|
||||
eq(schema.inventoryItems.householdId, householdId),
|
||||
isNull(schema.inventoryItems.canonicalIngredientId),
|
||||
eq(schema.inventoryItems.displayName, item.displayName),
|
||||
gt(schema.inventoryItems.quantity, 0),
|
||||
);
|
||||
const [dupe] = await app.db
|
||||
.select({ id: schema.inventoryItems.id })
|
||||
.from(schema.inventoryItems)
|
||||
.where(dedupCond)
|
||||
.limit(1);
|
||||
if (dupe) {
|
||||
await app.db
|
||||
.update(schema.inventoryItems)
|
||||
.set({
|
||||
quantity: item.quantity,
|
||||
unit: item.unit,
|
||||
brand: item.brand ?? null,
|
||||
bestBeforeDate: item.bestBeforeDate ?? null,
|
||||
useByDate: item.useByDate ?? null,
|
||||
lastVerifiedAt: new Date(),
|
||||
verifiedByUser: true,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(schema.inventoryItems.id, dupe.id));
|
||||
created.push(dupe.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const [inv] = await app.db
|
||||
.insert(schema.inventoryItems)
|
||||
.values({
|
||||
|
||||
Reference in New Issue
Block a user