#!/usr/bin/env tsx /** * Priority1 全矩阵 canonical 路径批量询价(24 条) * * npm run benchmark:priority1-canonical-matrix * $env:PRIORITY1_MATRIX_FILTER="F04"; npm run benchmark:priority1-canonical-matrix * $env:PRIORITY1_MATRIX_LIMIT="3"; npm run benchmark:priority1-canonical-matrix */ import { randomUUID } from "node:crypto"; import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { formatPriority1InputSummary, PRIORITY1_CANONICAL_PATHS, } from "@/workers/rpa/priority1/demo-input"; import { validatePriority1FormConstraints } from "@/workers/rpa/priority1/form-constraints"; import { formatPriority1QuoteOutcomeMessage, formatPriority1QuotesTable, type Priority1QuoteOutcome, } from "@/workers/rpa/priority1/quote-extract"; import { runPriority1VisualChain } from "./visual-priority1-full-chain"; function resolveMatrixBatchStatus(r: { ok: boolean; quoteCount: number; quoteOutcome?: Priority1QuoteOutcome; allObservedOk: boolean; }): string { if (r.quoteCount > 0) return "quote_ok"; if (r.quoteOutcome === "manual_followup") return "manual_followup"; if (r.allObservedOk) return "form_only"; return "fail"; } function formatMatrixCaseLine(r: { caseId: string; ok: boolean; quoteCount: number; quoteOutcome?: Priority1QuoteOutcome; message?: string; }): string { if (r.quoteCount > 0) return `✓ ${r.caseId} quotes=${r.quoteCount}`; if (r.quoteOutcome === "manual_followup") { return `✓ ${r.caseId} manual_followup — ${r.message ?? formatPriority1QuoteOutcomeMessage("manual_followup")}`; } return `✗ ${r.caseId} quotes=0`; } process.env.RPA_HEADLESS = process.env.RPA_HEADLESS ?? "true"; process.env.RPA_KEEP_BROWSER_OPEN = "false"; process.env.RPA_SLOW_MO_MS = process.env.RPA_SLOW_MO_MS ?? "0"; process.env.RPA_DWELL_MS = process.env.RPA_DWELL_MS ?? "0"; process.env.RPA_QUOTE_WAIT_MS = process.env.RPA_QUOTE_WAIT_MS ?? "120000"; const FILTER = process.env.PRIORITY1_MATRIX_FILTER?.trim(); const LIMIT = Number(process.env.PRIORITY1_MATRIX_LIMIT ?? 0); const START = Number(process.env.PRIORITY1_MATRIX_START ?? 0); function filterPaths() { let paths = [...PRIORITY1_CANONICAL_PATHS]; if (FILTER) { const re = new RegExp(FILTER, "i"); paths = paths.filter((p) => re.test(p.id) || re.test(p.title)); } if (START > 0) paths = paths.slice(START); if (LIMIT > 0) paths = paths.slice(0, LIMIT); return paths; } async function main(): Promise { const runId = randomUUID().slice(0, 8); const batchDir = join(process.cwd(), ".rpa", "visual-priority1-matrix", runId); mkdirSync(batchDir, { recursive: true }); const paths = filterPaths(); if (paths.length === 0) { console.error("[matrix] 无匹配路径"); process.exit(1); } console.log( `\n[matrix] Priority1 canonical 全矩阵 batch=${runId} cases=${paths.length}/${PRIORITY1_CANONICAL_PATHS.length}`, ); writeFileSync( join(batchDir, "cases-input.json"), `${JSON.stringify({ runId, filter: FILTER ?? null, paths: paths.map((p) => ({ id: p.id, title: p.title, verified: p.verified, input: p.input })) }, null, 2)}\n`, "utf8", ); const results: Awaited>[] = []; for (let i = 0; i < paths.length; i += 1) { const path = paths[i]!; console.log(`\n[matrix] ▶ ${i + 1}/${paths.length} ${path.id} — ${path.title}`); console.log(formatPriority1InputSummary(path.input)); const constraintErrors = validatePriority1FormConstraints(path.input).filter( (v) => v.severity === "error", ); if (constraintErrors.length) { console.error(`[matrix] ✗ ${path.id} 违反填写规则:${constraintErrors.map((e) => e.message).join(";")}`); results.push({ runId, caseId: path.id, outDir: join(batchDir, path.id), demo: path.input, finishedAt: new Date().toISOString(), durationMs: 0, finalUrl: "", observations: [], quotes: [], quoteCount: 0, quoteOutcome: "no_result", allObservedOk: false, ok: false, error: constraintErrors.map((e) => e.message).join(";"), }); continue; } const result = await runPriority1VisualChain({ demo: path.input, caseId: path.id, runId, outDir: join(batchDir, path.id), keepBrowserOpen: false, }); results.push(result); console.log(`\n${formatMatrixCaseLine({ caseId: path.id, ...result })}`); if (result.quotes.length) console.log(formatPriority1QuotesTable(result.quotes)); if (result.quoteOutcome === "manual_followup" && result.message) { console.log(result.message); } if (result.error) console.log(`错误: ${result.error}`); } const instantPass = results.filter((r) => r.quoteCount > 0).length; const manualPass = results.filter((r) => r.quoteOutcome === "manual_followup").length; const pass = results.filter((r) => r.ok).length; const formOk = results.filter((r) => r.allObservedOk).length; const summary = { runId, total: paths.length, canonicalTotal: PRIORITY1_CANONICAL_PATHS.length, pass, instantQuote: instantPass, manualFollowup: manualPass, fail: paths.length - pass, formObservationsOk: formOk, finishedAt: new Date().toISOString(), cases: results.map((r, idx) => ({ caseId: r.caseId, title: paths[idx]?.title, persona: paths[idx]?.persona, priorVerified: paths[idx]?.verified, batchStatus: resolveMatrixBatchStatus(r), ok: r.ok, quoteCount: r.quoteCount, quoteOutcome: r.quoteOutcome ?? null, message: r.message ?? null, allObservedOk: r.allObservedOk, error: r.error, outDir: r.outDir, quotes: r.quotes, })), }; const summaryPath = join(batchDir, "batch-summary.json"); writeFileSync(summaryPath, `${JSON.stringify(summary, null, 2)}\n`, "utf8"); const md: string[] = [ `# Priority1 Canonical 全矩阵报告`, ``, `- 批次: \`${runId}\``, `- 执行: ${paths.length} / ${PRIORITY1_CANONICAL_PATHS.length} 条`, `- 即时报价: **${instantPass}/${paths.length}**`, `- 人工跟进: **${manualPass}/${paths.length}**`, `- 成功合计: **${pass}/${paths.length}**`, `- 表单观测全绿: ${formOk}/${paths.length}`, ``, `| ID | 路径 | 原状态 | 批量结果 | 报价数 |`, `|----|------|--------|----------|--------|`, ]; for (let i = 0; i < results.length; i += 1) { const r = results[i]!; const p = paths[i]!; const status = r.quoteCount > 0 ? "✓ 即时报价" : r.quoteOutcome === "manual_followup" ? "✓ 人工跟进" : r.allObservedOk ? "△ 仅填表" : "✗ 失败"; md.push( `| ${r.caseId} | ${p.title} | ${p.verified === "recorded" ? "已录制" : "待录制"} | ${status} | ${r.quoteCount} |`, ); } md.push("", "## 未成功详情", ""); for (let i = 0; i < results.length; i += 1) { const r = results[i]!; if (r.ok) continue; md.push(`### ${r.caseId}`); md.push(r.error ?? r.message ?? "(无报价)"); md.push(""); } const mdPath = join(batchDir, "batch-summary.md"); writeFileSync(mdPath, `${md.join("\n")}\n`, "utf8"); console.log( `\n[matrix] 完成 成功 ${pass}/${paths.length}(即时 ${instantPass} + 人工跟进 ${manualPass})`, ); console.log(`[matrix] JSON: ${summaryPath}`); console.log(`[matrix] MD: ${mdPath}`); process.exit(pass === paths.length ? 0 : 1); } main().catch((e) => { console.error(e); process.exit(1); });