feat(deploy): Gemini som committat prod-läge + hermetiska eval-fixtures
- config.ts: tillåt AAMOS_MODE=http|gemini i prod; mock förbjudet - first-deploy.sh: Grind 1 accepterar gemini (kräv GEMINI_API_KEY); Grind 5 kör eval:scan mot gemini; Grind 8 behandlar gemini som riktig AI - gemini.ts: stöd för data-URLer (inline base64-bilder) i fetchOneImage - eval-fixtures: 4 lokala bilder (fridge-1, fridge-2, label-1, receipt-1) - scan-eval.ts: läser lokala fixtures, inga externa hämtningar - capture-eval.ts: använder lokal fixture som data-URL
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { config as loadDotenv } from "dotenv";
|
||||
import { existsSync } from "node:fs";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
for (const candidate of [".env", "../.env", "../../.env"]) {
|
||||
const p = path.resolve(process.cwd(), candidate);
|
||||
@@ -9,6 +9,7 @@ for (const candidate of [".env", "../.env", "../../.env"]) {
|
||||
}
|
||||
}
|
||||
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { GeminiAamosClient, type BudgetStore } from "@app/ai-contracts";
|
||||
|
||||
/**
|
||||
@@ -16,12 +17,20 @@ import { GeminiAamosClient, type BudgetStore } from "@app/ai-contracts";
|
||||
*
|
||||
* pnpm --filter @app/worker eval:scan
|
||||
*
|
||||
* Kräver AAMOS_MODE=gemini + GEMINI_API_KEY. Använder ett litet guldset av
|
||||
* publikt tillgängliga bild-URL:er (eller lokala filer). Rapporterar
|
||||
* träffbild, kvantitet, enhet, kostnad och latens. Avslutar med exitkod 1
|
||||
* om någon obligatorisk kontroll fallerar.
|
||||
* Kräver AAMOS_MODE=gemini + GEMINI_API_KEY. Använder lokala fixture-bilder
|
||||
* (committade i repot) – inga externa hämtningar, inga 429.
|
||||
*/
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const FIXTURES = path.resolve(path.dirname(__filename), "fixtures");
|
||||
|
||||
function fileToDataUrl(filePath: string): string {
|
||||
const buf = readFileSync(filePath);
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const mime = ext === ".png" ? "image/png" : ext === ".webp" ? "image/webp" : "image/jpeg";
|
||||
return `data:${mime};base64,${buf.toString("base64")}`;
|
||||
}
|
||||
|
||||
interface ScanEvalCase {
|
||||
id: string;
|
||||
imageUrls: string[];
|
||||
@@ -39,31 +48,30 @@ interface ScanEvalCase {
|
||||
|
||||
const CASES: ScanEvalCase[] = [
|
||||
{
|
||||
id: "arla-milk-fridge",
|
||||
imageUrls: ["https://commons.wikimedia.org/wiki/Special:FilePath/Arla_Ko_Mellanmj%C3%B6lk_1L.jpg?width=800"],
|
||||
id: "fridge-1",
|
||||
imageUrls: [fileToDataUrl(path.join(FIXTURES, "fridge-1.jpg"))],
|
||||
locationType: "fridge",
|
||||
marketLocale: "sv-SE",
|
||||
checks: (items) => {
|
||||
const milk = items.find((i) =>
|
||||
/mjölk|milk/i.test(i.detectedName) || /arla/i.test(i.brand ?? ""),
|
||||
);
|
||||
const milk = items.find((i) => /mjölk|milk|grädde|cream/i.test(i.detectedName));
|
||||
return [
|
||||
{ name: "hittade mjölkprodukt", passed: !!milk },
|
||||
{ name: "konfidens > 0.7", passed: !!milk && milk.confidence > 0.7 },
|
||||
{ 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) },
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "swedish-butter-fridge",
|
||||
imageUrls: ["https://commons.wikimedia.org/wiki/Special:FilePath/Svenskt_Sm%C3%B6r_Normalsaltat_80-25_500g.jpg?width=800"],
|
||||
id: "fridge-2",
|
||||
imageUrls: [fileToDataUrl(path.join(FIXTURES, "fridge-2.jpg"))],
|
||||
locationType: "fridge",
|
||||
marketLocale: "sv-SE",
|
||||
checks: (items) => {
|
||||
const butter = items.find((i) => /smör|butter/i.test(i.detectedName));
|
||||
const found = items.find((i) => /gurka|inlagd|konserverad|burk|pickle/i.test(i.detectedName));
|
||||
return [
|
||||
{ name: "hittade smör", passed: !!butter },
|
||||
{ name: "konfidens > 0.7", passed: !!butter && butter.confidence > 0.7 },
|
||||
{ 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) },
|
||||
];
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user