de279a7c01
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.
68 lines
1.8 KiB
TypeScript
68 lines
1.8 KiB
TypeScript
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);
|
||
});
|
||
});
|