94 lines
4.3 KiB
TypeScript
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
|
|
};
|
|
}
|