ktx/packages/context/src/ingest/reports.ts
2026-05-11 22:52:47 +02:00

145 lines
4.6 KiB
TypeScript

import type { MemoryAction } from '../memory/index.js';
import type { TouchedSlSource } from '../tools/index.js';
import type { MemoryFlowReplayInput } from './memory-flow/types.js';
import type { IngestProvenanceInsert } from './ports.js';
import type {
ArtifactResolutionRecord,
ConflictResolvedRecord,
EvictionAppliedRecord,
StageIndex,
UnmappedFallbackRecord,
} from './stages/stage-index.types.js';
import type { IngestDiffSummary, SourceFetchReport, UnresolvedCardInfo } from './types.js';
export interface IngestReportWorkUnit {
unitKey: string;
rawFiles: string[];
status: 'success' | 'failed';
reason?: string;
actions: MemoryAction[];
touchedSlSources: TouchedSlSource[];
slDisallowed?: boolean;
slDisallowedReason?: 'lookml_connection_mismatch';
}
export interface IngestReportProvenanceDetail {
rawPath: string;
artifactKind: 'sl' | 'wiki' | null;
artifactKey: string | null;
targetConnectionId?: string | null;
actionType: IngestProvenanceInsert['actionType'];
}
export interface IngestReportToolTranscriptSummary {
unitKey: string;
path: string;
toolCallCount: number;
errorCount: number;
toolNames: string[];
}
export interface IngestReportPostProcessorOutcome {
sourceKey: string;
status: 'success' | 'failed';
result?: unknown;
errors: string[];
warnings: string[];
touchedSources: TouchedSlSource[];
}
export interface IngestReportBody {
syncId: string;
diffSummary: IngestDiffSummary;
fetch?: SourceFetchReport;
commitSha: string | null;
workUnits: IngestReportWorkUnit[];
failedWorkUnits: string[];
reconciliationSkipped: boolean;
conflictsResolved: ConflictResolvedRecord[];
evictionsApplied: EvictionAppliedRecord[];
unmappedFallbacks: UnmappedFallbackRecord[];
artifactResolutions?: ArtifactResolutionRecord[];
evictionInputs: string[];
unresolvedCards: UnresolvedCardInfo[];
supersededBy: string | null;
overrideOf: string | null;
provenanceRows: IngestReportProvenanceDetail[];
toolTranscripts: IngestReportToolTranscriptSummary[];
postProcessor?: IngestReportPostProcessorOutcome;
memoryFlow?: MemoryFlowReplayInput;
}
export interface IngestReportSnapshot {
id: string;
runId: string;
jobId: string;
connectionId: string;
sourceKey: string;
body: IngestReportBody;
createdAt: string;
}
export interface IngestSavedMemoryCounts {
wikiCount: number;
slCount: number;
}
function numericResultField(result: Record<string, unknown>, field: string): number {
const value = result[field];
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0;
}
export function postProcessorSavedMemoryCounts(
postProcessor: IngestReportPostProcessorOutcome | undefined,
): IngestSavedMemoryCounts {
if (!postProcessor || postProcessor.sourceKey !== 'historic-sql') {
return { wikiCount: 0, slCount: 0 };
}
const result = postProcessor.result;
if (!result || typeof result !== 'object' || Array.isArray(result)) {
return { wikiCount: 0, slCount: 0 };
}
const record = result as Record<string, unknown>;
return {
wikiCount:
numericResultField(record, 'patternPagesWritten') +
numericResultField(record, 'stalePatternPagesMarked') +
numericResultField(record, 'archivedPatternPages') +
numericResultField(record, 'legacyPagesDeleted'),
slCount: numericResultField(record, 'tableUsageMerged') + numericResultField(record, 'staleTablesMarked'),
};
}
export function savedMemoryCountsForReport(report: IngestReportSnapshot): IngestSavedMemoryCounts {
const actions = report.body.workUnits.flatMap((workUnit) => workUnit.actions);
const directCounts = {
wikiCount: actions.filter((action) => action.target === 'wiki').length,
slCount: actions.filter((action) => action.target === 'sl').length,
};
const postProcessorCounts = postProcessorSavedMemoryCounts(report.body.postProcessor);
return {
wikiCount: directCounts.wikiCount + postProcessorCounts.wikiCount,
slCount: directCounts.slCount + postProcessorCounts.slCount,
};
}
export function buildStageIndexFromReportBody(jobId: string, connectionId: string, body: IngestReportBody): StageIndex {
return {
jobId,
connectionId,
workUnits: body.workUnits.map((wu) => ({
unitKey: wu.unitKey,
rawFiles: wu.rawFiles,
status: wu.status,
reason: wu.reason,
actions: wu.actions,
touchedSlSources: wu.touchedSlSources,
slDisallowed: wu.slDisallowed,
slDisallowedReason: wu.slDisallowedReason,
})),
conflictsResolved: [],
evictionsApplied: [],
unmappedFallbacks: [],
artifactResolutions: body.artifactResolutions ?? [],
};
}