#!/usr/bin/env node 'use strict'; // KB 质量审计:数量/父子结构、空答案、重复问题、按来源统计。 const fs = require('fs'); const path = require('path'); const { Pool } = require('../bridge/node_modules/pg'); const ROOT = path.resolve(__dirname, '..'); const env = {}; for (const line of fs.readFileSync(path.join(ROOT, '.env'), 'utf8').split('\n')) { if (!line.includes('=') || line.trim().startsWith('#')) continue; const i = line.indexOf('='); env[line.slice(0, i).trim()] = line.slice(i + 1).trim(); } const url = process.env.RAG_PG_URL || env.RAG_PG_URL; if (!url) throw new Error('RAG_PG_URL is required'); (async () => { const pool = new Pool({ connectionString: url, max: 1 }); try { const summary = await pool.query(` SELECT count(*)::int AS total, count(*) FILTER (WHERE chunk_type='parent')::int AS parents, count(*) FILTER (WHERE chunk_type='child')::int AS children, count(*) FILTER (WHERE answer IS NULL OR answer='')::int AS empty_answers, count(*) FILTER (WHERE text_tsv IS NULL)::int AS missing_tsv FROM kb_docs`); const dups = await pool.query(` SELECT lower(regexp_replace(question, '[^a-zA-Z0-9\\u4e00-\\u9fff]+', ' ', 'g')) AS q, count(*)::int AS n FROM kb_docs WHERE chunk_type='parent' GROUP BY 1 HAVING count(*) > 1 ORDER BY n DESC LIMIT 10`); const sources = await pool.query(` SELECT source_file, count(*)::int AS n FROM kb_docs WHERE chunk_type='parent' GROUP BY source_file ORDER BY n DESC`); console.log(JSON.stringify({ summary: summary.rows[0], duplicates: dups.rows, sources: sources.rows }, null, 2)); } finally { await pool.end(); } })().catch((err) => { console.error('[kb-audit]', err.message); process.exit(1); });