Assessor_2.0/src/exports/unified-result-csv.ts

25 lines
1.9 KiB
TypeScript

import type { UnifiedResultProjection, UnifiedResultQuery } from '../domain/unified-result';
import { buildUnifiedResultJsonl } from './unified-result-jsonl';
import { rowsToSpreadsheetSafeCsv } from './csv-export';
export function unifiedResultCsvFileName(generatedAt: string): string {
return `assessor-result-db-filtered-${generatedAt.slice(0, 10)}.csv`;
}
/** Use the same disclosed projection as JSONL; nested values remain complete JSON text. */
export function buildUnifiedResultCsv(query: UnifiedResultQuery, results: UnifiedResultProjection[], generatedAt: string): string {
const [manifest, ...entries] = buildUnifiedResultJsonl(query, results, generatedAt).trimEnd().split('\n')
.map(line => JSON.parse(line) as Record<string, unknown>);
const records = entries.map(entry => entry.result as Record<string, unknown>);
const preferred = ['title', 'sourceRuleId', 'sourceTool', 'severityNormalized', 'outcomeNormalized', 'resultType', 'isIoe', 'isDeviation', 'description', 'recommendation'];
const fields = [...new Set(records.flatMap(record => Object.keys(record)))];
const rank = (field: string) => preferred.includes(field) ? preferred.indexOf(field) : preferred.length;
fields.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
const handling = manifest.handling as { classification: string; notice: string };
const metadata = [manifest.generatedOn, manifest.disclosurePolicyVersion, handling.classification, handling.notice];
const headers = ['Position', ...fields, 'exportGeneratedOn', 'exportDisclosurePolicyVersion', 'exportClassification', 'exportNotice'];
const value = (cell: unknown) => cell !== null && typeof cell === 'object' ? JSON.stringify(cell) : cell;
const rows = [headers, ...records.map((record, index) => [index + 1, ...fields.map(field => value(record[field])), ...metadata])];
return '\uFEFF' + rowsToSpreadsheetSafeCsv(rows, ';') + '\n';
}