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();
|
||||
|
||||
Reference in New Issue
Block a user