feat(ai): enhance AI trace warnings with product context
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 2m31s
Test Suite / flutter-quality (push) Failing after 1m12s

- Added `productName` field to `AdminAiWarning` to include product context in warnings
- Updated `collectWarnings` to extract and include `rawName` as `productName` in AI trace warnings
- Added `signals` field to `FlyerParseItem` type for detailed product signals
- Enhanced Flutter admin panel to display product names in AI trace warnings
- Added new `AdminAiTraceResponse` DTO for AI trace data structure
This commit is contained in:
Nils-Johan Gynther
2026-05-24 20:55:14 +02:00
parent 7713eb2fa7
commit ca1eed5061
5 changed files with 117 additions and 93 deletions
+6 -3
View File
@@ -457,7 +457,7 @@ export class AiTraceService {
return `user:${userId}`;
}
private collectWarnings(items: Array<{ parseReasons: unknown; matchReasons: unknown; itemIndex?: number }>): {
private collectWarnings(items: Array<{ parseReasons: unknown; matchReasons: unknown; itemIndex?: number; rawName?: string }>): {
warnings: AdminAiWarning[];
legacyWarnings: string[];
} {
@@ -467,6 +467,7 @@ export class AiTraceService {
for (const item of items) {
const itemIndex = item.itemIndex != null ? item.itemIndex + 1 : undefined;
const productName = item.rawName?.trim() || 'okänt';
if (Array.isArray(item.parseReasons)) {
for (const reason of item.parseReasons) {
@@ -475,7 +476,8 @@ export class AiTraceService {
const warning: AdminAiWarning = {
...describeParseReason(text),
itemIndex,
};
productName,
} as AdminAiWarning;
const key = `${warning.kind}:${text}:${warning.itemIndex ?? 0}`;
if (dedupe.has(key)) continue;
dedupe.add(key);
@@ -491,7 +493,8 @@ export class AiTraceService {
const warning: AdminAiWarning = {
...describeMatchReason(text),
itemIndex,
};
productName,
} as AdminAiWarning;
const key = `${warning.kind}:${text}:${warning.itemIndex ?? 0}`;
if (dedupe.has(key)) continue;
dedupe.add(key);
@@ -0,0 +1,6 @@
import { FlyerReasonDescriptor } from '../../flyer-import/services/reason-codes';
export type AdminAiWarning = FlyerReasonDescriptor & {
itemIndex?: number;
productName?: string;
};
@@ -40,6 +40,7 @@ type FlyerParseItem = {
offerText: string | null;
confidence: number;
reasonCodes: string[];
signals?: ImportedItemSignals | null;
};
type FlyerParseResponse = {
@@ -149,11 +150,14 @@ export class FlyerImportService {
matchConfidence: match.confidence,
});
const origin = item.signals?.originCountries?.[0] || null;
const brand = item.brand && item.brand.trim() !== origin ? item.brand : null;
return {
flyerItemId: null,
rawName: item.rawName,
normalizedName: signalData.normalizedMatchName || item.normalizedName,
brand: item.brand,
brand,
category: item.category,
categoryId,
price,
@@ -733,6 +737,7 @@ export class FlyerImportService {
offerText: item.offerText,
confidence: item.parseConfidence,
reasonCodes: item.parseReasons,
signals: null,
}));
const warnings: string[] = [];
@@ -8,6 +8,7 @@ class AdminAiWarning {
final String severity;
final String? location;
final int? itemIndex;
final String? productName;
const AdminAiWarning({
required this.code,
@@ -17,6 +18,7 @@ class AdminAiWarning {
required this.severity,
required this.location,
required this.itemIndex,
required this.productName,
});
factory AdminAiWarning.fromJson(Map<String, dynamic> json) {
@@ -28,6 +30,7 @@ class AdminAiWarning {
severity: (json['severity'] ?? '').toString(),
location: json['location']?.toString(),
itemIndex: (json['itemIndex'] as num?)?.toInt(),
productName: json['productName']?.toString(),
);
}
@@ -44,6 +47,7 @@ class AdminAiWarning {
severity: 'warning',
location: null,
itemIndex: null,
productName: null,
);
}
}
@@ -139,7 +139,8 @@ class _AdminAiPanelState extends ConsumerState<AdminAiPanel> {
String _formatWarningLine(AdminAiWarning warning) {
final rowSuffix = warning.itemIndex == null ? '' : ' (rad ${warning.itemIndex})';
return '[${warning.severity}] ${warning.title}$rowSuffix: ${warning.message}';
final productSuffix = warning.productName != null ? ' (${warning.productName})' : '';
return '[${warning.severity}] ${warning.title}$rowSuffix$productSuffix: ${warning.message}';
}
String _buildErrorReport({
@@ -690,6 +691,11 @@ class _WarningsCard extends StatelessWidget {
'Rad: ${warning.itemIndex}',
style: theme.textTheme.bodySmall,
),
if (warning.productName != null)
Text(
'Produkt: ${warning.productName}',
style: theme.textTheme.bodySmall,
),
],
),
trailing: IconButton(