feat(ai): enhance AI trace warnings with product context
- 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:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user