feat(receipt-import): enhance bread category detection and improve session management

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Nils-Johan Gynther
2026-05-03 16:34:15 +02:00
parent a1c4a2f24d
commit fa7f225ee5
7 changed files with 299 additions and 12 deletions
@@ -60,6 +60,22 @@ function hasPorkLikeSignal(normalized: string): boolean {
);
}
function hasBreadLikeSignal(normalized: string): boolean {
return (
/\brostbrod\b/.test(normalized) ||
/\brost\s*n\s*toast\b/.test(normalized) ||
/\broast\s*n\s*toast\b/.test(normalized) ||
/\btoastbrod\b/.test(normalized) ||
/\bformbrod\b/.test(normalized) ||
/\blantbrod\b/.test(normalized) ||
/\bfullkornsbrod\b/.test(normalized) ||
/\bfranska\b/.test(normalized) ||
/\blimpa\b/.test(normalized) ||
/\bbrod\b/.test(normalized) ||
/\btoast\b/.test(normalized)
);
}
function inferPackageDebugFromRawName(rawName: string): {
packageCount: number;
packQuantity: number | null;
@@ -465,6 +481,24 @@ export class ReceiptImportService {
);
}
private resolveBreadCategory(
categories: Awaited<ReturnType<CategoriesService['findFlattened']>>,
) {
return (
categories.find(
(c) =>
c.name.toLowerCase() === 'rostbröd' &&
c.path.toLowerCase().startsWith('bröd & kakor > bröd > '),
) ||
categories.find(
(c) =>
c.name.toLowerCase() === 'bröd' &&
c.path.toLowerCase() === 'bröd & kakor > bröd',
) ||
categories.find((c) => c.path.toLowerCase() === 'bröd & kakor')
);
}
private applyHardCategoryOverrides(
signalText: string,
suggestion: CategorySuggestion,
@@ -531,6 +565,14 @@ export class ReceiptImportService {
// ── Regel: Kött/chark (bacon/fläsk m.m.) ────────────────────────────
const hasPorkSignal = hasPorkLikeSignal(normalized);
const hasToastBreadSignal = hasBreadLikeSignal(normalized);
if (hasToastBreadSignal) {
const bread = this.resolveBreadCategory(categories);
const hit = toSuggestion(bread, 'high');
if (hit) return hit;
}
if (hasPorkSignal) {
const l3Pork = this.resolvePorkCategory(categories);
const hit = toSuggestion(l3Pork, 'high');
@@ -975,6 +1017,28 @@ export class ReceiptImportService {
};
}
const hasToastBreadSignal = hasBreadLikeSignal(normalized);
if (hasToastBreadSignal) {
const aiPath = suggestion.path.toLowerCase();
const isOutsideBread = !aiPath.startsWith('bröd & kakor > bröd');
if (!isOutsideBread) return suggestion;
const bread = this.resolveBreadCategory(categories);
if (!bread) return suggestion;
this.logger.log(
`AI contradiction-guard: "${rawName}" remappas från "${suggestion.path}" till "${bread.path}"`,
);
return {
categoryId: bread.id,
categoryName: bread.name,
path: bread.path,
confidence: 'high',
usedFallback: true,
};
}
return suggestion;
}
}