#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..'); const DEFAULT_INPUT = path.join(ROOT, 'docs', 'ai-optimization-proof-gap-software-table.md'); const DEFAULT_OUTPUT = path.join(ROOT, 'outputs', 'tihao-proof-gap-software-form-latest'); const SOFTWARE_HEADER = [ 'brief编号', '策略', '排名', '平台', '博主名称', '综合分', 'brief匹配分', '参考风格分', '主页证据分', '视觉质感分', '调性一致分', '证据加分', '证据风险扣分', '推荐理由', '风险提示', '主页链接', '人工复核标签' ]; function main() { const args = parseArgs(process.argv.slice(2)); const input = path.resolve(args.input || DEFAULT_INPUT); const outputDir = path.resolve(args.output || DEFAULT_OUTPUT); const result = buildProofGapSoftwareForm({ input, outputDir }); fs.mkdirSync(outputDir, { recursive: true }); fs.writeFileSync(result.files.csv, withBom(renderCsv([SOFTWARE_HEADER, ...result.rows])), 'utf8'); fs.writeFileSync(result.files.markdown, withBom(renderMarkdown(result)), 'utf8'); fs.writeFileSync(result.files.summary, JSON.stringify(result.summary, null, 2), 'utf8'); console.log(JSON.stringify({ outputDir, csv: result.files.csv, markdown: result.files.markdown, summary: result.files.summary, rowCount: result.summary.rowCount, duplicateIdCount: result.summary.duplicateIdCount, headerMatches: result.summary.headerMatches }, null, 2)); if (args.strict && !result.summary.passed) process.exitCode = 1; } function buildProofGapSoftwareForm({ input, outputDir }) { const text = fs.readFileSync(input, 'utf8').replace(/^\uFEFF/, ''); const rows = text.split(/\r?\n/).map(splitMarkdownRow).filter(cells => cells.length); const header = rows.find(cells => cells[0] === 'brief编号') || []; const dataRows = rows.filter(cells => /^GAP-\d{3}$/.test(cells[0] || '')); const ids = dataRows.map(cells => cells[0]); const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index); const headerMatches = JSON.stringify(header) === JSON.stringify(SOFTWARE_HEADER); const expectedTitles = [ '真实历史 Brief 数据集', '真实视频资源表', '真实视频证据 A/B', '真实 live/provider 长跑证明', '商务复核和客户效果' ]; const actualTitles = dataRows.map(cells => cells[4]); const missingTitles = expectedTitles.filter(title => !actualTitles.includes(title)); const rowWidthOk = dataRows.every(cells => cells.length === SOFTWARE_HEADER.length); const summary = { generatedAt: new Date().toISOString(), input: path.relative(ROOT, input).replace(/\\/g, '/'), outputDir: path.relative(ROOT, outputDir).replace(/\\/g, '/'), headerMatches, rowWidthOk, rowCount: dataRows.length, duplicateIdCount: duplicateIds.length, duplicateIds: Array.from(new Set(duplicateIds)), expectedTitles, actualTitles, missingTitles, passed: headerMatches && rowWidthOk && dataRows.length === 5 && duplicateIds.length === 0 && missingTitles.length === 0, guardrails: [ '本表单用于安排补证任务,不证明客户效果完成。', '不能把 sample、smoke、模板、provider fallback 或接口 200 当成业务证明。', '表单不得包含 sessionToken、Authorization、模型 token 或 npm token。' ] }; return { rows: dataRows, summary, files: { csv: path.join(outputDir, 'tihao-proof-gap-software-form.csv'), markdown: path.join(outputDir, 'tihao-proof-gap-software-form.md'), summary: path.join(outputDir, 'tihao-proof-gap-software-form-summary.json') } }; } function renderMarkdown(result) { const lines = [ '# 最新提号补证表单(软件端格式)', '', `- 生成时间:${result.summary.generatedAt}`, `- 行数:${result.summary.rowCount}`, `- 表头匹配:${result.summary.headerMatches ? '是' : '否'}`, `- 是否通过:${result.summary.passed ? '是' : '否'}`, '', '| ' + SOFTWARE_HEADER.join(' | ') + ' |', '| ' + SOFTWARE_HEADER.map(() => '---').join(' | ') + ' |', ...result.rows.map(row => '| ' + row.map(escapeMarkdownCell).join(' | ') + ' |'), '', '## 边界', '', ...result.summary.guardrails.map(item => `- ${item}`) ]; return lines.join('\n'); } function renderCsv(rows) { return rows.map(row => row.map(csvCell).join(',')).join('\n'); } function splitMarkdownRow(line) { if (!line || !line.trim().startsWith('|')) return []; return line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(cell => cell.trim()); } function parseArgs(argv) { const args = {}; for (let index = 0; index < argv.length; index += 1) { const raw = argv[index]; if (!raw.startsWith('--')) continue; const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase()); const next = argv[index + 1]; if (!next || next.startsWith('--')) args[key] = true; else { args[key] = next; index += 1; } } return args; } function csvCell(value) { const text = String(value ?? ''); return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text; } function escapeMarkdownCell(value) { return String(value ?? '').replace(/\|/g, '/').replace(/\r?\n/g, ' '); } function withBom(text) { return `\uFEFF${text}`; } if (require.main === module) main(); module.exports = { SOFTWARE_HEADER, buildProofGapSoftwareForm };