diff --git a/backend/src/receipt-parsing/receipt-parsing.service.ts b/backend/src/receipt-parsing/receipt-parsing.service.ts index 5eb1df2..e1e9fe7 100644 --- a/backend/src/receipt-parsing/receipt-parsing.service.ts +++ b/backend/src/receipt-parsing/receipt-parsing.service.ts @@ -258,6 +258,17 @@ function ruleBasedParseLine(line: string): ParsedReceiptItemRaw | null { return null; } +/** + * Avgör om en rad troligen är en produktrad (har namnliknande text OCH ett + * numeriskt värde som tyder på pris eller kvantitet). Rader utan siffror är + * troligen header/footer i PDF:en och behöver inte AI-tolkning. + */ +function looksLikeReceiptProductLine(line: string): boolean { + if (!isLikelyNameLikeText(line)) return false; + // Måste innehålla minst ett tal (pris, vikt, antal) + return /\d/.test(line); +} + @Injectable() export class ReceiptParsingService { private readonly logger = new Logger(ReceiptParsingService.name); @@ -332,10 +343,18 @@ export class ReceiptParsingService { } } - this.logger.log(`PDF: ${resolved.length} rader lösta regelbaserat, ${needsAI.length} skickas till AI`); + // Filtrera bort PDF-skräp (butiksnamn, datum, header/footer) innan AI-anrop. + // En rad behöver AI bara om den ser ut som en produktrad (namntext + siffra). + const productLinesForAI = needsAI.filter(looksLikeReceiptProductLine); + const discarded = needsAI.length - productLinesForAI.length; - if (needsAI.length > 0) { - const aiItems = await this.callMistralText(needsAI, apiKey); + this.logger.log( + `PDF: ${resolved.length} regelbaserade, ${productLinesForAI.length} till AI` + + (discarded > 0 ? `, ${discarded} rader kasserade (ej produktrader)` : ''), + ); + + if (productLinesForAI.length > 0) { + const aiItems = await this.callMistralText(productLinesForAI, apiKey); resolved.push(...aiItems); }