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

73 lines
2.9 KiB
TypeScript

import type {
UnifiedResultProjection,
UnifiedResultQuery,
UnifiedResultQueryResult
} from '../domain/unified-result';
export const RESULT_DB_JSONL_FORMAT = 'assessor-result-db-jsonl';
export const RESULT_DB_JSONL_SCHEMA_VERSION = '1.0.0';
export const RESULT_DB_EXPORT_PAGE_SIZE = 1000;
type ResultPageReader = (query: UnifiedResultQuery) => Promise<UnifiedResultQueryResult>;
function definedQueryEntries(query: UnifiedResultQuery): Array<[string, unknown]> {
return Object.entries(query).filter(([, value]) => value !== undefined);
}
function exportScope(query: UnifiedResultQuery): Record<string, unknown> {
const scopeKeys = new Set(['projectId', 'assessmentId', 'scanRunId', 'scanRunIds', 'evaluationRunId']);
return Object.fromEntries(definedQueryEntries(query).filter(([key]) => scopeKeys.has(key)));
}
function exportFilters(query: UnifiedResultQuery): Record<string, unknown> {
const excluded = new Set(['projectId', 'assessmentId', 'scanRunId', 'scanRunIds', 'evaluationRunId', 'offset', 'limit']);
return Object.fromEntries(definedQueryEntries(query).filter(([key]) => !excluded.has(key)));
}
export function buildUnifiedResultJsonl(
query: UnifiedResultQuery,
results: UnifiedResultProjection[],
generatedAt: string
): string {
const lines = [{
recordType: 'manifest',
format: RESULT_DB_JSONL_FORMAT,
schemaVersion: RESULT_DB_JSONL_SCHEMA_VERSION,
generatedAt,
resultCount: results.length,
scope: exportScope(query),
filters: exportFilters(query)
}, ...results.map((result, index) => ({
recordType: 'result',
position: index + 1,
result
}))];
return `${lines.map(line => JSON.stringify(line)).join('\n')}\n`;
}
export function unifiedResultJsonlFileName(generatedAt: string): string {
const timestamp = generatedAt.replace(/\.\d{3}Z$/, 'Z').replaceAll(':', '-');
return `assessor-result-db-filtered-${timestamp}.jsonl`;
}
export async function queryAllUnifiedResults(
readPage: ResultPageReader,
query: UnifiedResultQuery,
pageSize = RESULT_DB_EXPORT_PAGE_SIZE
): Promise<UnifiedResultProjection[]> {
const baseQuery = { ...query, offset: undefined, limit: undefined };
const results: UnifiedResultProjection[] = [];
let expectedTotal: number | undefined;
while (expectedTotal === undefined || results.length < expectedTotal) {
const page = await readPage({ ...baseQuery, offset: results.length, limit: pageSize });
if (expectedTotal === undefined) expectedTotal = page.total;
if (page.total !== expectedTotal) throw new Error('The filtered result set changed during export. Please retry.');
if (!page.items.length && results.length < expectedTotal) throw new Error('The filtered result export ended before all rows were returned.');
results.push(...page.items);
}
if (results.length !== expectedTotal) throw new Error('The filtered result export returned an inconsistent row count.');
return results;
}