Files
citation_sentinel/server/src/routes/documents.ts

84 lines
2.7 KiB
TypeScript

'use strict';
import { Router, type Request, type Response, type NextFunction } from 'express';
import logger from '../logger.js';
import * as sourceCorpusStore from '../stores/sourceCorpusStore.js';
import type { TextChunk, Source, SourceGroup } from '../stores/sourceCorpusStore.js';
import * as documentCacheStore from '../stores/documentCacheStore.js';
import type { CacheEntry } from '../stores/documentCacheStore.js';
import {
generateStudyGuide,
generateFaq,
generateExecutiveBrief,
type StudyGuide,
type Faq,
type ExecutiveBrief,
} from '../services/documentService.js';
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 router = Router();
interface GenerateBody {
sourceCorpusId?: string;
type?: string;
}
router.post('/generate', async (req: Request<unknown, unknown, GenerateBody>, res: Response, next: NextFunction) => {
try {
const { sourceCorpusId, type } = req.body;
if (!sourceCorpusId || !type) {
res.status(400).json({ error: 'sourceCorpusId and type are required' });
return;
}
const generator = GENERATORS[type as DocumentType];
if (!generator) {
res.status(400).json({
error: `Invalid type. Must be one of: ${Object.keys(GENERATORS).join(', ')}`,
});
return;
}
const chunks: TextChunk[] = sourceCorpusStore.getChunksForSourceCorpus(sourceCorpusId);
if (chunks.length === 0) {
res.status(422).json({ error: 'No source material available in this source corpus' });
return;
}
const sources: Source[] = sourceCorpusStore.getSources(sourceCorpusId);
const cached: CacheEntry | null = documentCacheStore.getCachedDocument(sourceCorpusId, type);
if (cached && documentCacheStore.isFresh(sourceCorpusId, type, sources)) {
logger.info({ sourceCorpusId, type }, 'serving cached document');
res.json({ type, document: cached.document });
return;
}
const sourceGroups: SourceGroup[] = sourceCorpusStore.buildSourceGroups(sourceCorpusId, chunks);
logger.info(
{ sourceCorpusId, type, sourceCount: sourceGroups.length, chunkCount: chunks.length },
'document generation started'
);
const document: StudyGuide | Faq | ExecutiveBrief = await generator(sourceGroups);
documentCacheStore.setCachedDocument(sourceCorpusId, type, document, sources);
logger.info({ sourceCorpusId, type }, 'document generation complete');
res.json({ type, document });
} catch (err) {
next(err);
}
});
export default router;