refactor(ai): enhance AI trace integration and OCR normalization
Test Suite / backend-pr-quick (push) Has been skipped
Test Suite / quick-import-pr-quick (push) Has been skipped
Test Suite / backend-full (push) Successful in 3m54s
Test Suite / flutter-quality (push) Failing after 1m29s

- Add FlyerTraceSupplement type for AI trace metadata
- Implement getFlyerTraceSupplements method to fetch trace supplements
- Update AiTraceService to include prompt/rawOutput and counters in flyer traces
- Add persistFlyerTrace method to FlyerImportService for trace persistence
- Enhance AiFlyerParserService to return structured trace data with prompts and retries
- Update FlyerNormalizerService with OCR typo fixes for cheese variants and spröd bakad firre
- Improve Flutter admin panel with selectable text, warnings display, and tooltips
- Add comprehensive tests for AI trace supplements and normalization rules
This commit is contained in:
Nils-Johan Gynther
2026-05-21 19:11:54 +02:00
parent 67a7590525
commit 026323b72a
9 changed files with 681 additions and 67 deletions
+55
View File
@@ -90,4 +90,59 @@ describe('AiTraceService receipt masking', () => {
}),
);
});
it('returns flyer prompt/rawOutput and trace counters from aiTrace supplement', async () => {
prismaMock.flyerSession.findUnique.mockResolvedValue({
id: 101,
userId: 7,
createdAt: new Date('2026-05-21T12:00:00.000Z'),
sourceFileName: 'willys.pdf',
sourceMimeType: 'application/pdf',
sourceFileSize: 12345,
user: { username: 'admin', email: 'admin@example.com' },
items: [
{
id: 1,
rawName: 'Tomat',
normalizedName: 'tomat',
brand: null,
categoryHint: 'Grönsaker',
categoryId: null,
price: null,
priceUnit: null,
comparisonPrice: null,
comparisonUnit: null,
weight: null,
bundleWeight: null,
isBundle: false,
bundleItems: [],
offerText: null,
parseConfidence: 0.9,
parseReasons: ['low_confidence'],
matchedProductId: null,
matchedProductName: null,
matchedVia: 'none',
matchConfidence: null,
matchReasons: [],
},
],
});
prismaMock.aiTrace.findMany.mockResolvedValue([
{
sessionId: 101,
prompt: 'Flyer prompt med email kund@example.com',
rawOutput: '{"ok":true}',
normalizedOutput: { retryCount: 2, chunkCount: 4 },
},
]);
const result = await service.getTraceById('flyer-101');
expect(result.prompt).toContain('[MASKED]');
expect(result.rawOutput).toContain('{"ok":true}');
expect(result.retryCount).toBe(2);
expect(result.chunkCount).toBe(4);
expect(result.warnings).toContain('parse:low_confidence');
});
});