feat(ai): enhance AI trace warnings and reason codes system
- Added structured warning system with `AdminAiWarning` type in backend and Flutter - Implemented detailed reason descriptors with `FlyerReasonDescriptor` for parse and match operations - Added `legacyWarnings` field to maintain backward compatibility - Enhanced AI trace service to collect and format warnings with item-level context - Updated flyer import services to include detailed reason descriptions in responses - Added Swedish diacritic preservation for cheese variants (Prästost, Herrgårdsost, Grevéost) - Implemented UTF-8 content validation for AI responses - Added new reason code definitions in `reason-codes.ts` - Updated Flutter UI to display structured warnings with severity indicators - Added error report generation and copy functionality in admin panel - Added comprehensive test coverage for new warning system and cheese normalization BREAKING CHANGE: AI trace warnings are now structured objects instead of simple strings
This commit is contained in:
@@ -9,14 +9,15 @@ import {
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { normalizeName } from '../common/utils/normalize-name';
|
||||
import {
|
||||
FlyerImportItem,
|
||||
FlyerImportMatchVia,
|
||||
FlyerImportResponse,
|
||||
} from './dto/flyer-import.response';
|
||||
import {
|
||||
FlyerImportItem,
|
||||
FlyerImportMatchVia,
|
||||
FlyerImportResponse,
|
||||
} from './dto/flyer-import.response';
|
||||
import { TextExtractorService } from './services/text-extractor.service';
|
||||
import { AiFlyerParserService } from './services/ai-flyer-parser.service';
|
||||
import { FlyerNormalizerService } from './services/flyer-normalizer.service';
|
||||
import { FlyerNormalizerService } from './services/flyer-normalizer.service';
|
||||
import { describeMatchReason, describeParseReason } from './services/reason-codes';
|
||||
|
||||
type FlyerParseItem = {
|
||||
rawName: string;
|
||||
@@ -135,13 +136,15 @@ export class FlyerImportService {
|
||||
offerLimitText,
|
||||
parseConfidence: item.confidence,
|
||||
parseReasons: item.reasonCodes,
|
||||
matchedProductId: match.product?.id ?? null,
|
||||
matchedProductName: match.product?.name ?? null,
|
||||
matchedVia: match.via,
|
||||
matchConfidence: match.confidence,
|
||||
matchReasons: match.reasons,
|
||||
};
|
||||
});
|
||||
parseReasonsDetailed: this.describeParseReasons(item.reasonCodes),
|
||||
matchedProductId: match.product?.id ?? null,
|
||||
matchedProductName: match.product?.name ?? null,
|
||||
matchedVia: match.via,
|
||||
matchConfidence: match.confidence,
|
||||
matchReasons: match.reasons,
|
||||
matchReasonsDetailed: this.describeMatchReasons(match.reasons),
|
||||
};
|
||||
});
|
||||
|
||||
const persistedItems = await this.persistSessionWithItems(userId, parsed.retailer, items, file);
|
||||
|
||||
@@ -790,14 +793,24 @@ export class FlyerImportService {
|
||||
offerLimitText,
|
||||
parseConfidence: item.parseConfidence,
|
||||
parseReasons: toStringArray(item.parseReasons),
|
||||
parseReasonsDetailed: this.describeParseReasons(toStringArray(item.parseReasons)),
|
||||
matchedProductId: item.matchedProductId,
|
||||
matchedProductName: item.matchedProductName,
|
||||
matchedVia: normalizedMatchVia,
|
||||
matchConfidence: item.matchConfidence ?? 0,
|
||||
matchReasons: toStringArray(item.matchReasons),
|
||||
matchReasonsDetailed: this.describeMatchReasons(toStringArray(item.matchReasons)),
|
||||
};
|
||||
}
|
||||
|
||||
private describeParseReasons(codes: string[]) {
|
||||
return codes.map((code) => describeParseReason(code));
|
||||
}
|
||||
|
||||
private describeMatchReasons(codes: string[]) {
|
||||
return codes.map((code) => describeMatchReason(code));
|
||||
}
|
||||
|
||||
private buildCategoryPath(categoryRef?: {
|
||||
name: string;
|
||||
parent?: {
|
||||
|
||||
Reference in New Issue
Block a user