import type { AssessmentContextResponse } from '../../api/assessor-api-contract'; import type { FindingRecord } from '../../domain/finding'; import type { DynamicFacet } from '../search-filter/dynamic-facets'; import { publishContextReport } from '../../assessment-context/context-report-publication'; /** * Work facet of the context layer (Phase 13E, decision (c)). It exists only while the layer is on and * its context is loaded; switched off, the facet is absent and Work filters exactly as without it. */ export const CONTEXT_CLUSTER_FACET_ID = 'context:cluster'; const SOURCE_SCAN_FACET_ID = 'provenance:source-scan'; export interface ContextClusterIndex { publication: ReturnType; facet: DynamicFacet; /** The loaded clusters with their state, for the Pathfinder details (Phase 13E). */ clusters: AssessmentContextResponse['clusters']; } const field = (record: FindingRecord, label: string) => record.fields.find(item => item.label.toLocaleLowerCase('en-US') === label.toLocaleLowerCase('en-US'))?.value?.trim() ?? ''; const indexes = new WeakMap(); /** One shared index per loaded context object, so every Work consumer uses identical cache keys. */ export function contextClusterIndex(context: AssessmentContextResponse): ContextClusterIndex { const existing = indexes.get(context); if (existing) return existing; const byOccurrence = new Map(context.findings.map(item => [item.occurrenceId, item.clusterIds])); const bySetting = new Map(context.settings.map(item => [`${item.scope}\u0000${item.identityKey}`, item.clusterIds])); // A record belongs to a cluster when it provides evidence for one of its conditions. const valuesFor = (record: FindingRecord): string[] => { const scanner = record.findingOccurrenceId ? byOccurrence.get(record.findingOccurrenceId) : undefined; if (scanner) return scanner; const identity = field(record, 'Technical Identity'); return identity ? bySetting.get(`${field(record, 'Scope')}\u0000${identity}`) ?? [] : []; }; const index: ContextClusterIndex = { publication: publishContextReport(context), clusters: context.clusters, facet: { id: CONTEXT_CLUSTER_FACET_ID, label: 'Context Cluster', source: 'derived', kind: 'category', derivedKind: 'context-cluster', preserveEmptyValues: true, description: `Derived context layer (catalog ${context.catalog.version}): findings and GPO settings that provide evidence for a cluster. A second reading; it does not change results.`, values: context.clusters.map(cluster => ({ value: cluster.clusterId, label: cluster.label.en, count: 0 })), valuesFor } }; indexes.set(context, index); return index; } /** Adds the context facet directly before the permanent final `Source: Scan` control. */ export function withContextClusterFacet(facets: DynamicFacet[], index: ContextClusterIndex | undefined): DynamicFacet[] { if (!index) return facets; const sourceScan = facets.findIndex(facet => facet.id === SOURCE_SCAN_FACET_ID); return sourceScan < 0 ? [...facets, index.facet] : [...facets.slice(0, sourceScan), index.facet, ...facets.slice(sourceScan)]; }