Files
citation_sentinel/server/src/services/preGenerationService.ts

106 lines
3.5 KiB
TypeScript

'use strict';
import logger from '../logger.js';
import * as sourceCorpusStore from '../stores/sourceCorpusStore.js';
import * as documentCacheStore from '../stores/documentCacheStore.js';
import {
generateStudyGuide,
generateFaq,
generateExecutiveBrief,
type SourceGroup,
type StudyGuide,
type Faq,
type ExecutiveBrief,
} from './documentService.js';
const MIN_SOURCES = 2;
type DocumentType = 'study-guide' | 'faq' | 'executive-brief';
type GeneratorFn = (sourceGroups: SourceGroup[]) => Promise<StudyGuide | Faq | ExecutiveBrief>;
const GENERATORS: Record<DocumentType, GeneratorFn> = {
'study-guide': generateStudyGuide,
'faq': generateFaq,
'executive-brief': generateExecutiveBrief,
};
const DEBOUNCE_MS = 5000;
const pendingReGen = new Set<string>();
const debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();
export function triggerPreGeneration(sourceCorpusId: string): void {
const sourceCorpus = sourceCorpusStore.getSourceCorpus(sourceCorpusId);
if (!sourceCorpus) return;
const sources = sourceCorpusStore.getSources(sourceCorpusId);
if (sources.length < MIN_SOURCES) return;
if (documentCacheStore.isGenerating(sourceCorpusId)) {
pendingReGen.add(sourceCorpusId);
documentCacheStore.invalidate(sourceCorpusId);
logger.debug({ sourceCorpusId }, 'pre-generation in progress, queued re-generation');
return;
}
const existingTimer = debounceTimers.get(sourceCorpusId);
if (existingTimer !== undefined) {
clearTimeout(existingTimer);
}
debounceTimers.set(
sourceCorpusId,
setTimeout(() => {
debounceTimers.delete(sourceCorpusId);
runPreGeneration(sourceCorpusId);
}, DEBOUNCE_MS)
);
logger.debug({ sourceCorpusId, debounceMs: DEBOUNCE_MS }, 'pre-generation debounced');
}
function runPreGeneration(sourceCorpusId: string): void {
try {
const sources = sourceCorpusStore.getSources(sourceCorpusId);
const chunks = sourceCorpusStore.getChunksForSourceCorpus(sourceCorpusId);
const sourceGroups = sourceCorpusStore.buildSourceGroups(sourceCorpusId, chunks) as SourceGroup[];
documentCacheStore.invalidate(sourceCorpusId);
documentCacheStore.markGenerating(sourceCorpusId);
logger.info(
{ sourceCorpusId, sourceCount: sources.length, chunkCount: chunks.length },
'background pre-generation started'
);
const jobs = (Object.entries(GENERATORS) as Array<[DocumentType, GeneratorFn]>).map(
async ([type, generator]) => {
try {
const document = await generator(sourceGroups);
documentCacheStore.setCachedDocument(sourceCorpusId, type, document, sources);
logger.info({ sourceCorpusId, type }, 'background pre-generation complete for type');
} catch (err) {
const error = err as Error;
logger.error({ sourceCorpusId, type, err: error.message }, 'background pre-generation failed for type');
}
}
);
Promise.all(jobs)
.then(() => {
logger.info({ sourceCorpusId }, 'all background pre-generation complete');
})
.finally(() => {
documentCacheStore.clearGenerating(sourceCorpusId);
if (pendingReGen.has(sourceCorpusId)) {
pendingReGen.delete(sourceCorpusId);
logger.info({ sourceCorpusId }, 're-triggering pre-generation for updated sources');
runPreGeneration(sourceCorpusId);
}
});
} catch (err) {
const error = err as Error;
logger.error({ sourceCorpusId, err: error.message }, 'pre-generation setup failed');
documentCacheStore.clearGenerating(sourceCorpusId);
}
}