/** Champ requis configuré pour le calcul du score de qualité d’une facture. */ export type QualityFieldConfig = { fieldName: string; isRequired: number; }; /** Référentiel utilisé tant qu’un compte ne possède pas encore sa configuration en base. */ export const DEFAULT_REQUIRED_INVOICE_FIELDS = [ "supplierName", "invoiceNumber", "invoiceDate", "totalAmount", ] as const; function hasValue(value: unknown): boolean { if (value === null || value === undefined) return false; if (typeof value === "string") return value.trim().length > 0; if (value instanceof Date) return Number.isFinite(value.getTime()); return true; } /** * Recalcule le score selon les champs obligatoires configurés pour le compte. * Les champs optionnels n'affectent jamais le résultat. Le calcul est purement * déterministe afin d'être identique après import et après une correction manuelle. */ export function calculateInvoiceQualityScore( invoice: Record, fieldsConfiguration: readonly QualityFieldConfig[], ): number { const configuredRequiredFields = fieldsConfiguration .filter((field) => field.isRequired === 1) .map((field) => field.fieldName); const requiredFields = fieldsConfiguration.length > 0 ? configuredRequiredFields : DEFAULT_REQUIRED_INVOICE_FIELDS; if (requiredFields.length === 0) return 100; const completedFields = requiredFields.filter((fieldName) => hasValue(invoice[fieldName])).length; return Math.round((completedFields / requiredFields.length) * 100); } /** * Prépare une extraction IA pour la persistance. Le score brut du modèle est * volontairement remplacé par le calcul déterministe commun à tous les flux. */ export function withCalculatedInvoiceQuality( invoice: T, fieldsConfiguration: readonly QualityFieldConfig[], ): T & { qualityScore: number } { return { ...invoice, qualityScore: calculateInvoiceQualityScore( invoice as Record, fieldsConfiguration, ), }; }