import type { FindingRecord } from '../../domain/finding'; import type { ImportProfile } from '../../domain/import-profile'; export type DashboardResultState = 'passed' | 'deviation' | 'not-configured' | 'conflicts' | 'duplicates' | 'not-run' | 'not-applicable' | 'error' | 'unknown' | 'review-required' | 'reevaluation-required'; const recordStateCache = new WeakMap(); function fieldValue(record: FindingRecord, pattern: RegExp): string | undefined { return record.fields.find(field => pattern.test(field.label.trim()))?.value; } function matchesStatus(record: FindingRecord, profile: ImportProfile, status: string): boolean { const supported = status === 'deviation' || ['not-configured', 'conflicts', 'duplicates'].includes(status) || profile.presentation.statusFilters.some(filter => filter.value === status); return supported && profile.matchesStatus(record, status); } function resultStateOf(record: FindingRecord, profile: ImportProfile): DashboardResultState { if (fieldValue(record, /^status$/i)?.trim().toLowerCase() === 'review required') { return fieldValue(record, /^evaluation readiness$/i)?.trim() ? 'reevaluation-required' : 'review-required'; } // The authoritative verdict wins. Conflict and duplicate are independent // GPO diagnostics and are counted from their dedicated fields below. if (matchesStatus(record, profile, 'not-configured')) return 'not-configured'; if (matchesStatus(record, profile, 'passed')) return 'passed'; if (matchesStatus(record, profile, 'deviation')) return 'deviation'; if (matchesStatus(record, profile, 'conflicts')) return 'conflicts'; if (matchesStatus(record, profile, 'duplicates')) return 'duplicates'; if (matchesStatus(record, profile, 'not-applicable') || matchesStatus(record, profile, 'not-relevant')) return 'not-applicable'; if (matchesStatus(record, profile, 'not-run') || matchesStatus(record, profile, 'not-selected') || matchesStatus(record, profile, 'canceled') || matchesStatus(record, profile, 'skipped') || matchesStatus(record, profile, 'manual')) return 'not-run'; if (matchesStatus(record, profile, 'error') || matchesStatus(record, profile, 'failed-to-run')) return 'error'; return 'unknown'; } export function dashboardResultState(record: FindingRecord, profile: ImportProfile): DashboardResultState { const cached = recordStateCache.get(record); if (cached && cached.profile === profile) return cached.state; const state = resultStateOf(record, profile); recordStateCache.set(record, { profile, state }); return state; } export function dashboardScoreFromStates(states: DashboardResultState[], records: FindingRecord[] = []) { let passed = 0; let deviations = 0; let notConfigured = 0; let conflicts = 0; let duplicates = 0; let reviewRequired = 0; let reevaluationRequired = 0; let missingEvidence = 0; let eligible = 0; states.forEach((state, index) => { if (state === 'passed') passed += 1; else if (state === 'deviation') deviations += 1; else if (state === 'not-configured') notConfigured += 1; else if (state === 'conflicts') conflicts += 1; else if (state === 'duplicates') duplicates += 1; else if (state === 'review-required') reviewRequired += 1; else if (state === 'reevaluation-required') reevaluationRequired += 1; else if (state === 'unknown' || state === 'not-run' || state === 'error') missingEvidence += 1; if (state !== 'not-applicable') eligible += 1; const record = records[index]; const diagnosticPresent = (value: string | undefined) => value !== undefined && !['', 'no', 'none', 'false', '0'].includes(value.trim().toLowerCase()); if (record && state !== 'conflicts' && diagnosticPresent(fieldValue(record, /^conflict$/i))) conflicts += 1; if (record && state !== 'duplicates' && diagnosticPresent(fieldValue(record, /^duplicates$/i))) duplicates += 1; }); const assessed = passed + deviations; return { passed, deviations, notConfigured, conflicts, duplicates, reviewRequired, reevaluationRequired, missingEvidence, assessed, eligible, unassessed: Math.max(0, eligible - assessed), score: assessed > 0 ? (passed / assessed) * 100 : undefined, coverage: eligible > 0 ? Math.round((assessed / eligible) * 100) : undefined }; }