import type { JsonValue, SourcePosition, ToolIdentity } from './canonical-model'; export type EnvironmentCollectionState = | 'observed' | 'explicitly-absent' | 'empty' | 'not-assessed' | 'access-denied' | 'license-restricted' | 'unsupported' | 'parse-failed' | 'unknown'; export type EnvironmentValueType = 'string' | 'number' | 'boolean' | 'datetime' | 'duration' | 'enum' | 'object'; export interface ScannerReportTable { name: string; sourceTableIndex?: number; headers: string[]; rows: JsonValue[][]; } export interface ScannerReportSection { version: 1; id: string; scanRunId: string; importFileId: string; tool: ToolIdentity; sectionKey: string; parentSectionKey?: string; label: string; ordinal: number; state: EnvironmentCollectionState; prose?: string; tables: ScannerReportTable[]; source: SourcePosition; extractorVersion: string; attributes: Record; } export interface EnvironmentSubject { type: string; identity?: string; label?: string; } export interface EnvironmentObservation { version: 1; id: string; scanRunId: string; importFileId: string; tool: ToolIdentity; subject: EnvironmentSubject; property: string; label: string; originalValue: JsonValue; normalizedValue?: JsonValue; valueType: EnvironmentValueType; state: EnvironmentCollectionState; observedAt?: string; sectionKey?: string; source: SourcePosition; mappingVersion: string; attributes: Record; } export interface EnvironmentCoverageSummary { observed: number; explicitlyAbsent: number; empty: number; restricted: number; incomplete: number; } export interface EnvironmentSnapshot { version: 1; projectId: string; assessmentId?: string; scanRunIds: string[]; generatedAt: string; sections: ScannerReportSection[]; observations: EnvironmentObservation[]; coverage: EnvironmentCoverageSummary; } export interface EnvironmentSnapshotQuery { projectId: string; assessmentId?: string; scanRunIds?: string[]; } export function environmentCoverage( sections: ScannerReportSection[], observations: EnvironmentObservation[] ): EnvironmentCoverageSummary { const states = [...sections.map(item => item.state), ...observations.map(item => item.state)]; return { observed: states.filter(state => state === 'observed').length, explicitlyAbsent: states.filter(state => state === 'explicitly-absent').length, empty: states.filter(state => state === 'empty').length, restricted: states.filter(state => state === 'license-restricted' || state === 'access-denied').length, incomplete: states.filter(state => ['not-assessed', 'unsupported', 'parse-failed', 'unknown'].includes(state)).length }; }