Assessor_2.0/src/features/dashboard/dashboard-result-state.ts

94 lines
4.3 KiB
TypeScript

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<FindingRecord, { profile: ImportProfile; state: DashboardResultState }>();
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
};
}