Assessor_2.0/server/reference-data/reference-recognition-servi...

67 lines
4.6 KiB
TypeScript

import type { CanonicalAssessmentModel } from '../../src/domain/canonical-model';
import { createReferenceRecognizer, latestReferenceRuns, REFERENCE_RECOGNIZER_VERSION, type ReferenceCatalogSnapshot, type ReferenceRecognitionRun } from '../../src/domain/reference-recognition';
import type { AssessorRepository } from '../../src/persistence/assessor-repository';
import { RepositoryNotFoundError } from '../../src/persistence/repository-errors';
import { bundledReferenceCatalogs, referenceHash } from './reference-catalogs';
export function deriveReferenceRun(model: CanonicalAssessmentModel, scanRunId: string, catalogs: ReferenceCatalogSnapshot[], createdAt: string): ReferenceRecognitionRun {
const occurrences = model.findingOccurrences.filter(item => item.scanRunId === scanRunId).sort((a, b) => a.id.localeCompare(b.id));
const files = new Map(model.importFiles.map(file => [file.id, file]));
const inputSha256 = referenceHash(occurrences.map(item => ({ id: item.id, importFileId: item.importFileId,
sourceFileSha256: files.get(item.importFileId)?.sha256, sourcePosition: item.sourcePosition, fields: item.originalFields })));
const catalogHashes = catalogs.map(catalog => catalog.sha256).sort();
const id = 'references-' + referenceHash([model.project.id, model.assessment.id, scanRunId, REFERENCE_RECOGNIZER_VERSION, inputSha256, catalogHashes]);
const recognize = createReferenceRecognizer(catalogs);
return { schemaVersion: 1, id, projectId: model.project.id, assessmentId: model.assessment.id, scanRunId,
recognizerVersion: REFERENCE_RECOGNIZER_VERSION, inputSha256, catalogHashes, createdAt,
results: occurrences.map(item => ({ occurrenceId: item.id, importFileId: item.importFileId,
sourceFileSha256: files.get(item.importFileId)?.sha256, sourcePosition: item.sourcePosition, references: recognize(item.originalFields) })) };
}
export class ReferenceRecognitionService {
private active = new Map<string, Promise<{ processedScans: number; unchangedScans: number; references: number }>>();
constructor(private readonly repository: AssessorRepository,
readonly catalogs: () => Promise<ReferenceCatalogSnapshot[]> = async () => bundledReferenceCatalogs) {}
async status(projectId: string) {
if (!await this.repository.getProject(projectId)) throw new RepositoryNotFoundError('Client nicht vorhanden.');
const scans = (await this.repository.listProjectScanRuns(projectId)).filter(scan => !scan.supersededByScanRunId);
const runs = latestReferenceRuns(await this.repository.listReferenceRuns(projectId));
const catalogs = await this.catalogs();
const hashes = catalogs.map(catalog => catalog.sha256).sort().join(':');
const indexed = scans.filter(scan => runs.some(run => run.scanRunId === scan.id));
return { totalScans: scans.length, indexedScans: indexed.length,
pendingScans: scans.filter(scan => !runs.some(run => run.scanRunId === scan.id && run.recognizerVersion === REFERENCE_RECOGNIZER_VERSION && [...run.catalogHashes].sort().join(':') === hashes)).length,
catalogs: catalogs.map(({ framework, version, sha256 }) => ({ framework, version, sha256 })) };
}
reprocess(projectId: string) {
let task = this.active.get(projectId);
if (!task) {
task = this.performReprocess(projectId).finally(() => this.active.delete(projectId));
this.active.set(projectId, task);
}
return task;
}
private async performReprocess(projectId: string) {
if (!await this.repository.getProject(projectId)) throw new RepositoryNotFoundError('Client nicht vorhanden.');
const catalogs = await this.catalogs();
const existing = new Map((await this.repository.listReferenceRuns(projectId)).map(run => [run.id, run]));
let processedScans = 0, unchangedScans = 0, references = 0;
for (const assessment of await this.repository.listAssessments(projectId)) {
const model = await this.repository.loadAssessmentModel(assessment.id);
if (!model) continue;
for (const scan of model.scanRuns.filter(item => !item.supersededByScanRunId)) {
const latestTimestamp = Math.max(0, ...[...existing.values()].filter(run => run.scanRunId === scan.id).map(run => Date.parse(run.createdAt)));
const run = deriveReferenceRun(model, scan.id, catalogs, new Date(Math.max(Date.now(), latestTimestamp + 1)).toISOString());
references += run.results.reduce((sum, item) => sum + item.references.length, 0);
if (existing.has(run.id)) { unchangedScans++; continue; }
await this.repository.saveReferenceRun(run, catalogs);
processedScans++;
}
}
return { processedScans, unchangedScans, references };
}
}