Files
Cibello-app/packages/ai-contracts/test/receipt-units.test.ts
T
Claude de279a7c01 fix(skan+sok): robust kvitto-enheter, titel-/fuzzy-sok, hogtidsfilter
Kvitto-skanning (rapporterad krasch 'svar matchar inte schema … invalid value'):
- AI-skan-enheter (kvitto/kyl/maltid) normaliseras nu fore validering:
  'st'->COUNT, 'kg'->KILOGRAM, 'forp'/'pase'->PACKAGE osv. Okand enhet -> null
  i stallet for att falla hela svaret. Skarpt kvitto-prompt med exakt enhetslista.
  17 nya tester bevisar normaliseringen.

Sok pa 'Vad ska vi ata':
- Endast TITELN (inte beskrivningen) -> 'pannkaka' ger inte langre appelkompott
  (som bara namnde pannkakor i beskrivningen).
- Stavningstolerant via trigram-similarity: 'pankakor' hittar pannkakor.

Hogtidsknapparna (kraftskiva/grill/jul ...):
- Filtrerar nu pa recept-holidayTag (slug) i stallet for en titelsok som gav
  tomt. Har event inga recept an visas ett arligt tomt-lage.

Full typecheck gron, ai-contracts 24 tester grona.
2026-08-19 14:09:45 +00:00

68 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from "vitest";
import { readReceiptOutput } from "../src/tasks.js";
/**
* Regression: kvitto-skanning kraschade på "svar matchar inte schema … invalid
* value" när Gemini läste kvitto-enheter som "st", "kg", "förp". Enheten ska nu
* normaliseras till en giltig kod eller null aldrig fälla hela svaret.
*/
function receiptWith(unit: unknown) {
return {
storeName: "ICA",
purchaseDate: "2026-08-19",
lines: [
{
rawText: "Rad",
normalizedName: "Vara",
canonicalIngredientId: null,
quantity: 1,
unit,
unitPriceMinor: null,
totalPriceMinor: 1500,
isDiscount: false,
confidence: 0.8,
},
],
totalMinor: 1500,
discountTotalMinor: null,
confidence: 0.8,
};
}
describe("kvitto: robust enhetstolkning", () => {
const cases: Array<[unknown, string | null]> = [
["st", "COUNT"],
["styck", "COUNT"],
["kg", "KILOGRAM"],
["g", "GRAM"],
["GRAM", "GRAM"],
["dl", "DECILITER"],
["ml", "MILLILITER"],
["l", "LITER"],
["förp", "PACKAGE"],
["paket", "PACKAGE"],
["påse", "PACKAGE"],
["burk", "CAN"],
["kg.", "KILOGRAM"], // med skiljetecken
[" St ", "COUNT"], // whitespace + versal
["xyz", null], // okänd → null (kraschar inte)
[null, null],
];
for (const [input, expected] of cases) {
it(`"${String(input)}" → ${expected}`, () => {
const result = readReceiptOutput.safeParse(receiptWith(input));
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.lines[0]!.unit).toBe(expected);
}
});
}
it("ett okänt enhetsvärde fäller inte hela kvittot", () => {
const result = readReceiptOutput.safeParse(receiptWith("2-pack"));
expect(result.success).toBe(true);
});
});