const db = require('../db'); const { getLastIngestionBySource } = require('../state'); let statusCache = null; let statusCacheAt = 0; const STATUS_CACHE_TTL_MS = 30 * 1000; async function statusRoutes(fastify) { fastify.get('/status', async (request) => { const deep = String(request.query?.deep || '').toLowerCase() === 'true'; if (!deep) { const sequence = db.prepare("SELECT seq FROM sqlite_sequence WHERE name = 'articles'").get(); return { total: sequence ? sequence.seq : 0, usable: null, lastIngestionBySource: getLastIngestionBySource(), bySource: null, embeddingModels: null, mode: 'lightweight', deep_status_url: '/status?deep=true', }; } const now = Date.now(); if (statusCache && now - statusCacheAt < STATUS_CACHE_TTL_MS) { return statusCache; } const bySourceRows = db.prepare(` SELECT source, COUNT(*) AS total, SUM(CASE WHEN content IS NOT NULL AND content != '' AND is_index_page = 0 AND has_embedding = 1 THEN 1 ELSE 0 END) AS usable FROM articles GROUP BY source ORDER BY source `).all(); const totals = bySourceRows.reduce( (acc, row) => { acc.total += row.total; acc.usable += row.usable; return acc; }, { total: 0, usable: 0 } ); const embeddingModelRows = db.prepare(` SELECT model, COUNT(*) AS article_count, (SELECT LENGTH(embedding) FROM article_embedding_store s2 WHERE s2.model = s.model LIMIT 1) AS sample_bytes FROM article_embedding_store s GROUP BY model ORDER BY article_count DESC `).all(); const result = { total: totals.total, usable: totals.usable, lastIngestionBySource: getLastIngestionBySource(), bySource: Object.fromEntries( bySourceRows.map((row) => [row.source, { total: row.total, usable: row.usable }]) ), embeddingModels: embeddingModelRows.map((row) => ({ model: row.model, articles: row.article_count, dimensions: row.sample_bytes ? row.sample_bytes / 4 : null, })), }; statusCache = result; statusCacheAt = now; return result; }); } module.exports = statusRoutes;