ci: trigga på master + formatfix inför Gitea Actions
CI / Typecheck, test & build (push) Failing after 2s
CI / Typecheck, test & build (push) Failing after 2s
This commit is contained in:
+126
-12
@@ -212,16 +212,27 @@ export const EVAL_CASES: EvalCase[] = [
|
||||
input: {
|
||||
scope: "user",
|
||||
scopeId: "user-1",
|
||||
events: [{ id: "evt-1", type: "recipe_cooked", occurredAt: "2026-08-01T12:00:00Z", payload: {} }],
|
||||
events: [
|
||||
{ id: "evt-1", type: "recipe_cooked", occurredAt: "2026-08-01T12:00:00Z", payload: {} },
|
||||
],
|
||||
existingMemoryKeys: [],
|
||||
},
|
||||
verify(output) {
|
||||
const o = output as TaskOutput<"UPDATE_USER_MEMORY">;
|
||||
const eventIds = new Set(["evt-1"]);
|
||||
return [
|
||||
check("alla minnen har kända sourceEventIds", o.memoryUpdates.every((u) => u.sourceEventIds.every((id) => eventIds.has(id)))),
|
||||
check("inga minnen utan sourceEventIds", o.memoryUpdates.every((u) => u.sourceEventIds.length > 0)),
|
||||
check("origin är observed eller ai_inferred", o.memoryUpdates.every((u) => u.origin === "observed" || u.origin === "ai_inferred")),
|
||||
check(
|
||||
"alla minnen har kända sourceEventIds",
|
||||
o.memoryUpdates.every((u) => u.sourceEventIds.every((id) => eventIds.has(id))),
|
||||
),
|
||||
check(
|
||||
"inga minnen utan sourceEventIds",
|
||||
o.memoryUpdates.every((u) => u.sourceEventIds.length > 0),
|
||||
),
|
||||
check(
|
||||
"origin är observed eller ai_inferred",
|
||||
o.memoryUpdates.every((u) => u.origin === "observed" || u.origin === "ai_inferred"),
|
||||
),
|
||||
];
|
||||
},
|
||||
},
|
||||
@@ -232,14 +243,19 @@ export const EVAL_CASES: EvalCase[] = [
|
||||
input: {
|
||||
scope: "user",
|
||||
scopeId: "user-1",
|
||||
events: [{ id: "evt-sv", type: "recipe_cooked", occurredAt: "2026-08-01T12:00:00Z", payload: {} }],
|
||||
events: [
|
||||
{ id: "evt-sv", type: "recipe_cooked", occurredAt: "2026-08-01T12:00:00Z", payload: {} },
|
||||
],
|
||||
existingMemoryKeys: [],
|
||||
},
|
||||
verify(output) {
|
||||
const o = output as TaskOutput<"UPDATE_USER_MEMORY">;
|
||||
return [
|
||||
check("minst ett minne returneras", o.memoryUpdates.length >= 1),
|
||||
check("summarySv är på svenska", o.memoryUpdates.every((u) => /[åäöÅÄÖ]/.test(u.summarySv) || u.summarySv.length > 0)),
|
||||
check(
|
||||
"summarySv är på svenska",
|
||||
o.memoryUpdates.every((u) => /[åäöÅÄÖ]/.test(u.summarySv) || u.summarySv.length > 0),
|
||||
),
|
||||
];
|
||||
},
|
||||
},
|
||||
@@ -253,14 +269,40 @@ export const EVAL_CASES: EvalCase[] = [
|
||||
cuisine: "swedish",
|
||||
tags: [],
|
||||
totalTimeMinutes: 30,
|
||||
nutritionPerPortion: { kcal: 550, proteinG: 45, carbsG: 50, fatG: 18, saturatedFatG: 6, fiberG: 6, sugarG: 4, saltG: 1.5 },
|
||||
nutritionPerPortion: {
|
||||
kcal: 550,
|
||||
proteinG: 45,
|
||||
carbsG: 50,
|
||||
fatG: 18,
|
||||
saturatedFatG: 6,
|
||||
fiberG: 6,
|
||||
sugarG: 4,
|
||||
saltG: 1.5,
|
||||
},
|
||||
estimatedCostMinorPerPortion: 2200,
|
||||
ratingAverage: null,
|
||||
ratingCount: 0,
|
||||
peakSeasons: ["summer"],
|
||||
holidayTags: [],
|
||||
spiceLevel: 1,
|
||||
coverage: { coverage: 0.85, matches: [], missing: [], expiringUsed: [{ canonicalIngredientId: "chicken", displayNameSv: "kycklingen", required: 400, unit: "GRAM", availableInUnit: 500, covered: true, optional: false, mostUrgentDaysLeft: 2, usesExpiringItem: true }] },
|
||||
coverage: {
|
||||
coverage: 0.85,
|
||||
matches: [],
|
||||
missing: [],
|
||||
expiringUsed: [
|
||||
{
|
||||
canonicalIngredientId: "chicken",
|
||||
displayNameSv: "kycklingen",
|
||||
required: 400,
|
||||
unit: "GRAM",
|
||||
availableInUnit: 500,
|
||||
covered: true,
|
||||
optional: false,
|
||||
mostUrgentDaysLeft: 2,
|
||||
usesExpiringItem: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
daysSinceLastCooked: 14,
|
||||
householdRating: 4.8,
|
||||
ingredientIds: ["chicken"],
|
||||
@@ -275,12 +317,84 @@ export const EVAL_CASES: EvalCase[] = [
|
||||
remainingProteinG: 60,
|
||||
remainingKcal: 800,
|
||||
personalizationEnabled: true,
|
||||
memoryItems: [{ id: "m1", userId: "u1", kind: "structured_fact", key: "favorite_cuisine_swedish", summarySv: "Gillar svensk mat", value: { favoriteCuisine: "swedish" }, origin: "user_stated", confidence: 1, verifiedByUser: true, paused: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }],
|
||||
memoryItems: [
|
||||
{
|
||||
id: "m1",
|
||||
userId: "u1",
|
||||
kind: "structured_fact",
|
||||
key: "favorite_cuisine_swedish",
|
||||
summarySv: "Gillar svensk mat",
|
||||
value: { favoriteCuisine: "swedish" },
|
||||
origin: "user_stated",
|
||||
confidence: 1,
|
||||
verifiedByUser: true,
|
||||
paused: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
],
|
||||
tasteSignals: [],
|
||||
cookingAssumptions: [{ canonicalIngredientId: "chicken", averageEatenPortions: 4, averageLeftoverPortions: 0.5, observationCount: 5 }],
|
||||
cookingAssumptions: [
|
||||
{
|
||||
canonicalIngredientId: "chicken",
|
||||
averageEatenPortions: 4,
|
||||
averageLeftoverPortions: 0.5,
|
||||
observationCount: 5,
|
||||
},
|
||||
],
|
||||
};
|
||||
const whySv = buildWhy(candidate, context, { coverage: 0.85, expiry: 0.8, nutritionFit: 0.9, taste: 0.8, rating: 0.9, season: 1, holiday: 0, time: 1, budget: 0.6, variety: 1, weather: 0.5, craving: 0.5, memoryFit: 1, tasteFit: 0, cookingAssumptionFit: 0.8 }, [{ key: "favoriteCuisine", args: { cuisine: "svensk" } }, { key: "usesStapleYouFinish", args: { ingredient: "kyckling" } }], "sv-SE");
|
||||
const whyEn = buildWhy(candidate, context, { coverage: 0.85, expiry: 0.8, nutritionFit: 0.9, taste: 0.8, rating: 0.9, season: 1, holiday: 0, time: 1, budget: 0.6, variety: 1, weather: 0.5, craving: 0.5, memoryFit: 1, tasteFit: 0, cookingAssumptionFit: 0.8 }, [{ key: "favoriteCuisine", args: { cuisine: "Swedish" } }, { key: "usesStapleYouFinish", args: { ingredient: "chicken" } }], "en-US");
|
||||
const whySv = buildWhy(
|
||||
candidate,
|
||||
context,
|
||||
{
|
||||
coverage: 0.85,
|
||||
expiry: 0.8,
|
||||
nutritionFit: 0.9,
|
||||
taste: 0.8,
|
||||
rating: 0.9,
|
||||
season: 1,
|
||||
holiday: 0,
|
||||
time: 1,
|
||||
budget: 0.6,
|
||||
variety: 1,
|
||||
weather: 0.5,
|
||||
craving: 0.5,
|
||||
memoryFit: 1,
|
||||
tasteFit: 0,
|
||||
cookingAssumptionFit: 0.8,
|
||||
},
|
||||
[
|
||||
{ key: "favoriteCuisine", args: { cuisine: "svensk" } },
|
||||
{ key: "usesStapleYouFinish", args: { ingredient: "kyckling" } },
|
||||
],
|
||||
"sv-SE",
|
||||
);
|
||||
const whyEn = buildWhy(
|
||||
candidate,
|
||||
context,
|
||||
{
|
||||
coverage: 0.85,
|
||||
expiry: 0.8,
|
||||
nutritionFit: 0.9,
|
||||
taste: 0.8,
|
||||
rating: 0.9,
|
||||
season: 1,
|
||||
holiday: 0,
|
||||
time: 1,
|
||||
budget: 0.6,
|
||||
variety: 1,
|
||||
weather: 0.5,
|
||||
craving: 0.5,
|
||||
memoryFit: 1,
|
||||
tasteFit: 0,
|
||||
cookingAssumptionFit: 0.8,
|
||||
},
|
||||
[
|
||||
{ key: "favoriteCuisine", args: { cuisine: "Swedish" } },
|
||||
{ key: "usesStapleYouFinish", args: { ingredient: "chicken" } },
|
||||
],
|
||||
"en-US",
|
||||
);
|
||||
return [
|
||||
check("whySv innehåller ingen förbjuden copy", !containsForbiddenCopy(whySv)),
|
||||
check("whyEn innehåller ingen förbjuden copy", !containsForbiddenCopy(whyEn)),
|
||||
|
||||
@@ -36,14 +36,16 @@ interface ScanEvalCase {
|
||||
imageUrls: string[];
|
||||
locationType: "fridge" | "pantry";
|
||||
marketLocale: string;
|
||||
checks: (items: Array<{
|
||||
detectedName: string;
|
||||
brand: string | null;
|
||||
estimatedQuantity: number | null;
|
||||
unit: string | null;
|
||||
confidence: number;
|
||||
requiresConfirmation: boolean;
|
||||
}>) => { name: string; passed: boolean }[];
|
||||
checks: (
|
||||
items: Array<{
|
||||
detectedName: string;
|
||||
brand: string | null;
|
||||
estimatedQuantity: number | null;
|
||||
unit: string | null;
|
||||
confidence: number;
|
||||
requiresConfirmation: boolean;
|
||||
}>,
|
||||
) => { name: string; passed: boolean }[];
|
||||
}
|
||||
|
||||
const CASES: ScanEvalCase[] = [
|
||||
@@ -57,7 +59,10 @@ const CASES: ScanEvalCase[] = [
|
||||
return [
|
||||
{ name: "hittade mjölkprodukt", passed: !!milk },
|
||||
{ name: "konfidens > 0.5", passed: !!milk && milk.confidence > 0.5 },
|
||||
{ name: "kräver bekräftelse om låg konfidens", passed: !!milk && (milk.confidence >= 0.92 || milk.requiresConfirmation) },
|
||||
{
|
||||
name: "kräver bekräftelse om låg konfidens",
|
||||
passed: !!milk && (milk.confidence >= 0.92 || milk.requiresConfirmation),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
@@ -71,7 +76,10 @@ const CASES: ScanEvalCase[] = [
|
||||
return [
|
||||
{ name: "hittade konserverad produkt", passed: !!found },
|
||||
{ name: "konfidens > 0.5", passed: !!found && found.confidence > 0.5 },
|
||||
{ name: "minst ett item med confidence > 0.7", passed: items.some((i) => i.confidence > 0.7) },
|
||||
{
|
||||
name: "minst ett item med confidence > 0.7",
|
||||
passed: items.some((i) => i.confidence > 0.7),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
@@ -101,7 +109,9 @@ async function main() {
|
||||
apiKey,
|
||||
model: process.env.GEMINI_MODEL,
|
||||
timeoutMs: process.env.GEMINI_TIMEOUT_MS ? Number(process.env.GEMINI_TIMEOUT_MS) : 60_000,
|
||||
dailyBudgetUsd: process.env.GEMINI_DAILY_BUDGET_USD ? Number(process.env.GEMINI_DAILY_BUDGET_USD) : 10,
|
||||
dailyBudgetUsd: process.env.GEMINI_DAILY_BUDGET_USD
|
||||
? Number(process.env.GEMINI_DAILY_BUDGET_USD)
|
||||
: 10,
|
||||
budgetStore: new SilentBudgetStore(),
|
||||
});
|
||||
|
||||
@@ -153,8 +163,12 @@ async function main() {
|
||||
if (error) console.log(` fel: ${error}`);
|
||||
}
|
||||
|
||||
console.log(`\n[eval:scan] ${CASES.length} fall, ${totalChecks} kontroller, ${failedChecks} fallerade.`);
|
||||
console.log(`[eval:scan] Total latens: ${totalLatencyMs} ms, total kostnad: ~$${totalCostUsd.toFixed(6)}`);
|
||||
console.log(
|
||||
`\n[eval:scan] ${CASES.length} fall, ${totalChecks} kontroller, ${failedChecks} fallerade.`,
|
||||
);
|
||||
console.log(
|
||||
`[eval:scan] Total latens: ${totalLatencyMs} ms, total kostnad: ~$${totalCostUsd.toFixed(6)}`,
|
||||
);
|
||||
|
||||
if (failedChecks > 0) {
|
||||
console.log("[eval:scan] UNDERKÄND – åtgärda innan Skiva 1 går till fälttest.");
|
||||
|
||||
@@ -9,11 +9,7 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {
|
||||
PutObjectCommand,
|
||||
S3Client,
|
||||
type S3ClientConfig,
|
||||
} from "@aws-sdk/client-s3";
|
||||
import { PutObjectCommand, S3Client, type S3ClientConfig } from "@aws-sdk/client-s3";
|
||||
import type { AamosTaskType } from "@app/ai-contracts";
|
||||
|
||||
export interface CaptureConsentFlags {
|
||||
|
||||
@@ -338,14 +338,18 @@ export async function processMemorySync(ctx: WorkerContext): Promise<number> {
|
||||
proposal.sourceEventIds.every((id) => eventIdSet.has(id));
|
||||
if (!hasEventSupport) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`UPDATE_USER_MEMORY: avvisar ${proposal.key} för ${userId}: saknar event-stöd.`);
|
||||
console.log(
|
||||
`UPDATE_USER_MEMORY: avvisar ${proposal.key} för ${userId}: saknar event-stöd.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// R1: ai_inferred ska ha låg konfidens.
|
||||
if (proposal.origin === "ai_inferred" && proposal.confidence > AI_INFERRED_MAX_CONFIDENCE) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`UPDATE_USER_MEMORY: avvisar ${proposal.key} för ${userId}: ai_inferred med för hög confidence.`);
|
||||
console.log(
|
||||
`UPDATE_USER_MEMORY: avvisar ${proposal.key} för ${userId}: ai_inferred med för hög confidence.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -334,7 +334,6 @@ function tokenize(text: string): string[] {
|
||||
.filter((t) => t.length > 1);
|
||||
}
|
||||
|
||||
|
||||
function classifyScanError(error?: string | null): string {
|
||||
if (!error) return "unknown";
|
||||
const lower = error.toLowerCase();
|
||||
|
||||
@@ -11,7 +11,10 @@ describe("pushOpsSummaryToEoc", () => {
|
||||
beforeAll(async () => {
|
||||
await redis.set(
|
||||
"ops:summary:cache",
|
||||
JSON.stringify({ app: { app: "cibello", generated_at: new Date().toISOString() }, as_of: new Date().toISOString() }),
|
||||
JSON.stringify({
|
||||
app: { app: "cibello", generated_at: new Date().toISOString() },
|
||||
as_of: new Date().toISOString(),
|
||||
}),
|
||||
"EX",
|
||||
60,
|
||||
);
|
||||
@@ -131,7 +134,9 @@ describe("pushOpsSummaryToEoc", () => {
|
||||
}
|
||||
expect(board.tiles.find((t: { label: string }) => t.label === "MRR")?.tone).toBe("good");
|
||||
expect(board.tiles.find((t: { label: string }) => t.label === "Workers")?.tone).toBe("crit");
|
||||
expect(board.tiles.find((t: { label: string }) => t.label === "Allergen-brott")?.tone).toBe("crit");
|
||||
expect(board.tiles.find((t: { label: string }) => t.label === "Allergen-brott")?.tone).toBe(
|
||||
"crit",
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
|
||||
@@ -244,7 +244,13 @@ describe("UPDATE_USER_MEMORY hardening", () => {
|
||||
const aamos = {
|
||||
async runTask() {
|
||||
called = true;
|
||||
return { status: "ok", output: { memoryUpdates: [] }, costUsd: 0, inputTokens: 0, outputTokens: 0 } as AamosResult<"UPDATE_USER_MEMORY">;
|
||||
return {
|
||||
status: "ok",
|
||||
output: { memoryUpdates: [] },
|
||||
costUsd: 0,
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
} as AamosResult<"UPDATE_USER_MEMORY">;
|
||||
},
|
||||
async healthCheck() {
|
||||
return { ok: true };
|
||||
|
||||
@@ -13,10 +13,7 @@ describe.sequential("SEND_PROACTIVE_TIPS", () => {
|
||||
const emailBase = "proactive-tips-test";
|
||||
|
||||
async function cleanup() {
|
||||
const emails = [
|
||||
`${emailBase}-owner@example.invalid`,
|
||||
`${emailBase}-member@example.invalid`,
|
||||
];
|
||||
const emails = [`${emailBase}-owner@example.invalid`, `${emailBase}-member@example.invalid`];
|
||||
const users = await testDb.db
|
||||
.select({ id: schema.users.id })
|
||||
.from(schema.users)
|
||||
@@ -24,7 +21,9 @@ describe.sequential("SEND_PROACTIVE_TIPS", () => {
|
||||
const userIds = users.map((u) => u.id);
|
||||
for (const userId of userIds) {
|
||||
await testDb.db.delete(schema.userConsents).where(eq(schema.userConsents.userId, userId));
|
||||
await testDb.db.delete(schema.householdMembers).where(eq(schema.householdMembers.userId, userId));
|
||||
await testDb.db
|
||||
.delete(schema.householdMembers)
|
||||
.where(eq(schema.householdMembers.userId, userId));
|
||||
await testDb.db.delete(schema.notifications).where(eq(schema.notifications.userId, userId));
|
||||
}
|
||||
const households = await testDb.db
|
||||
@@ -32,20 +31,36 @@ describe.sequential("SEND_PROACTIVE_TIPS", () => {
|
||||
.from(schema.households)
|
||||
.where(eq(schema.households.name, "Proactive Tips Test"));
|
||||
for (const h of households) {
|
||||
await testDb.db.delete(schema.inventoryItems).where(eq(schema.inventoryItems.householdId, h.id));
|
||||
await testDb.db.delete(schema.storageLocations).where(eq(schema.storageLocations.householdId, h.id));
|
||||
await testDb.db
|
||||
.delete(schema.inventoryItems)
|
||||
.where(eq(schema.inventoryItems.householdId, h.id));
|
||||
await testDb.db
|
||||
.delete(schema.storageLocations)
|
||||
.where(eq(schema.storageLocations.householdId, h.id));
|
||||
await testDb.db.delete(schema.households).where(eq(schema.households.id, h.id));
|
||||
}
|
||||
for (const email of emails) {
|
||||
await testDb.db.delete(schema.users).where(eq(schema.users.email, email));
|
||||
}
|
||||
// Clean up test recipes/ingredients if exists.
|
||||
await testDb.db.delete(schema.recipeIngredients).where(eq(schema.recipeIngredients.recipeId, "00000000-0000-0000-0000-000000000abc"));
|
||||
await testDb.db.delete(schema.recipeIngredients).where(eq(schema.recipeIngredients.recipeId, "00000000-0000-0000-0000-000000000def"));
|
||||
await testDb.db.delete(schema.recipes).where(eq(schema.recipes.id, "00000000-0000-0000-0000-000000000abc"));
|
||||
await testDb.db.delete(schema.recipes).where(eq(schema.recipes.id, "00000000-0000-0000-0000-000000000def"));
|
||||
await testDb.db.delete(schema.canonicalIngredients).where(eq(schema.canonicalIngredients.id, "test_cucumber_001"));
|
||||
await testDb.db.delete(schema.canonicalIngredients).where(eq(schema.canonicalIngredients.id, "test_tomato_001"));
|
||||
await testDb.db
|
||||
.delete(schema.recipeIngredients)
|
||||
.where(eq(schema.recipeIngredients.recipeId, "00000000-0000-0000-0000-000000000abc"));
|
||||
await testDb.db
|
||||
.delete(schema.recipeIngredients)
|
||||
.where(eq(schema.recipeIngredients.recipeId, "00000000-0000-0000-0000-000000000def"));
|
||||
await testDb.db
|
||||
.delete(schema.recipes)
|
||||
.where(eq(schema.recipes.id, "00000000-0000-0000-0000-000000000abc"));
|
||||
await testDb.db
|
||||
.delete(schema.recipes)
|
||||
.where(eq(schema.recipes.id, "00000000-0000-0000-0000-000000000def"));
|
||||
await testDb.db
|
||||
.delete(schema.canonicalIngredients)
|
||||
.where(eq(schema.canonicalIngredients.id, "test_cucumber_001"));
|
||||
await testDb.db
|
||||
.delete(schema.canonicalIngredients)
|
||||
.where(eq(schema.canonicalIngredients.id, "test_tomato_001"));
|
||||
}
|
||||
|
||||
async function setup() {
|
||||
@@ -68,7 +83,12 @@ describe.sequential("SEND_PROACTIVE_TIPS", () => {
|
||||
|
||||
const [household] = await testDb.db
|
||||
.insert(schema.households)
|
||||
.values({ name: "Proactive Tips Test", size: 2, locale: "sv-SE", inviteCode: "PROACTIVETEST" })
|
||||
.values({
|
||||
name: "Proactive Tips Test",
|
||||
size: 2,
|
||||
locale: "sv-SE",
|
||||
inviteCode: "PROACTIVETEST",
|
||||
})
|
||||
.returning();
|
||||
|
||||
await testDb.db.insert(schema.householdMembers).values([
|
||||
@@ -236,7 +256,9 @@ describe.sequential("SEND_PROACTIVE_TIPS", () => {
|
||||
await testDb.db
|
||||
.update(schema.userConsents)
|
||||
.set({ status: "revoked" })
|
||||
.where(and(eq(schema.userConsents.userId, ownerId), eq(schema.userConsents.kind, "notifications")));
|
||||
.where(
|
||||
and(eq(schema.userConsents.userId, ownerId), eq(schema.userConsents.kind, "notifications")),
|
||||
);
|
||||
|
||||
// Rensa notisen för att simulera ny dag.
|
||||
await testDb.db.delete(schema.notifications).where(eq(schema.notifications.userId, ownerId));
|
||||
|
||||
@@ -8,5 +8,6 @@ process.env.EMAIL_MODE = "log";
|
||||
process.env.S3_MODE = "mock";
|
||||
process.env.LOG_LEVEL = "error";
|
||||
|
||||
process.env.TEST_DATABASE_URL ||= "postgres://app_user:app_dev_password@localhost:5432/cibello_test";
|
||||
process.env.TEST_DATABASE_URL ||=
|
||||
"postgres://app_user:app_dev_password@localhost:5432/cibello_test";
|
||||
process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
|
||||
|
||||
@@ -9,7 +9,10 @@ describe("BUILD_TRAINING_SAMPLE banks locally", () => {
|
||||
const email = "training-export-test@example.invalid";
|
||||
|
||||
async function cleanup() {
|
||||
const existing = await testDb.db.select({ id: schema.users.id }).from(schema.users).where(eq(schema.users.email, email));
|
||||
const existing = await testDb.db
|
||||
.select({ id: schema.users.id })
|
||||
.from(schema.users)
|
||||
.where(eq(schema.users.email, email));
|
||||
for (const u of existing) {
|
||||
await testDb.db.delete(schema.aiCorrections).where(eq(schema.aiCorrections.userId, u.id));
|
||||
await testDb.db.delete(schema.userConsents).where(eq(schema.userConsents.userId, u.id));
|
||||
@@ -63,7 +66,10 @@ describe("BUILD_TRAINING_SAMPLE banks locally", () => {
|
||||
taskType: "ANALYZE_FRIDGE_IMAGE",
|
||||
aiOutput: { raw: { items: [] } },
|
||||
proposal: { detectedName: "Mellanmjölk", confidence: 0.98 },
|
||||
userCorrection: { action: "accept", corrected: { displayName: "Mellanmjölk", quantity: 1, unit: "LITER" } },
|
||||
userCorrection: {
|
||||
action: "accept",
|
||||
corrected: { displayName: "Mellanmjölk", quantity: 1, unit: "LITER" },
|
||||
},
|
||||
imageS3Key: "fridge-scans/train.jpg",
|
||||
modelVersion: "gemini-2.5-flash",
|
||||
promptVersion: "gemini-fridge-v1",
|
||||
|
||||
Reference in New Issue
Block a user