#!/usr/bin/env node 'use strict'; const fs = require('fs'); function emit(stage, message, status = 'running', extra = {}) { process.stderr.write(JSON.stringify({ type: 'process_event', stage, message, status, timestamp: new Date().toISOString(), ...extra, }) + '\n'); } function readInput() { const raw = String(process.argv[2] || process.env.SKILL_INPUT || process.env.AIFLOW_SKILL_INPUT || fs.readFileSync(0, 'utf8')).trim(); return raw ? JSON.parse(raw) : {}; } function str(value) { if (value === undefined || value === null || value === '') return '-'; return String(value).replace(/\|/g, '\\|').replace(/\r?\n/g, '
'); } function raw(value) { if (value === undefined || value === null || value === '') return ''; return String(value); } function hasText(value) { return value !== undefined && value !== null && String(value).trim() !== '' && String(value).trim() !== 'null'; } function normalizeStatus(status) { const text = raw(status).trim().replace(/[-\s]/g, '_').toLowerCase(); if (['pass', 'passed', 'approved', 'approve', 'success', 'ok', '审核通过', '通过'].includes(text)) return 'PASS'; if (['reject', 'rejected', 'deny', 'denied', 'refuse', 'refused', '驳回', '拒绝', '不通过'].includes(text)) return 'REJECT'; if (['manual_review', 'manual', 'review', '人工', '转人工', '人工复核', '人工复审'].includes(text)) return 'MANUAL_REVIEW'; if (['failed', 'fail', 'failure', 'error', '异常', '失败'].includes(text)) return 'MANUAL_REVIEW'; return ''; } function inferStatus(audit) { const normalized = normalizeStatus(audit.status || audit.decision || audit.auditDecision || audit.result); if (normalized) return normalized; const rules = Array.isArray(audit.rules) ? audit.rules : []; const fieldChecks = Array.isArray(audit.fieldChecks) ? audit.fieldChecks : []; if (rules.some(r => normalizeStatus(r.status) === 'REJECT')) return 'REJECT'; if (rules.some(r => normalizeStatus(r.status) === 'MANUAL_REVIEW')) return 'MANUAL_REVIEW'; if (rules.length && rules.every(r => normalizeStatus(r.status) === 'PASS')) return 'PASS'; if (fieldChecks.some(r => normalizeRuleStatusForReport(r.result) === 'REJECT')) return 'REJECT'; if (fieldChecks.some(r => { const result = normalizeRuleStatusForReport(r.result); return result === 'MANUAL_REVIEW' || result === 'REVIEW'; })) return 'MANUAL_REVIEW'; if (fieldChecks.length && fieldChecks.every(r => { const result = normalizeRuleStatusForReport(r.result); return result === 'PASS' || result === 'SKIP'; })) return 'PASS'; const reason = raw(audit.reason || audit.summary); if (/审核通过|建议通过|通过/.test(reason)) return 'PASS'; if (/驳回|拒绝|不通过/.test(reason)) return 'REJECT'; if (/转人工|人工复核|人工复审/.test(reason)) return 'MANUAL_REVIEW'; return 'MANUAL_REVIEW'; } function formatLocalGeneratedAt(value) { const explicit = raw(value); if (explicit) return explicit.replace(/T(\d{2}:\d{2}:\d{2})(?:\.\d{3})?Z$/, ' $1'); const date = new Date(); const parts = new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, }).formatToParts(date).reduce((acc, part) => { acc[part.type] = part.value; return acc; }, {}); return `${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute}:${parts.second}`; } function businessText(value) { return raw(value).replace(/(公户车检查|购买方名称比对|购车时间超期|身份证比对|发票比对|车辆类型判断|PlusC车龄限制):/g, '$1:'); } function statusLabel(status) { status = normalizeStatus(status) || status; if (status === 'PASS') return '通过'; if (status === 'REJECT') return '驳回'; if (status === 'MANUAL_REVIEW') return '转人工'; return status || '未知'; } function statusIcon(status) { status = normalizeStatus(status) || status; if (status === 'PASS') return 'PASS'; if (status === 'REJECT') return 'REJECT'; if (status === 'MANUAL_REVIEW') return 'MANUAL'; return 'MANUAL'; } function ruleStatus(status) { status = normalizeStatus(status) || status; if (status === 'PASS') return '通过'; if (status === 'REJECT') return '驳回'; if (status === 'MANUAL_REVIEW') return '转人工'; return str(status); } function firstValue(obj, keys) { if (!obj || typeof obj !== 'object') return ''; for (const key of keys) { const value = obj[key]; if (hasText(value)) return value; } return ''; } const EVIDENCE_LABELS = { carHost: '车主类型', buyerName: '发票购买方', companyNames: '疑似企业名称', orderName: '订单客户姓名', invoiceBuyerName: '发票购买方', invoiceIssueDate: '发票开票日期', orderPurchaseDate: '订单购车日期', referenceDate: '审核基准日期', maxPurchaseAgeDays: '允许最长间隔', ageDays: '实际间隔天数', productName: '产品名称', packageType: '权益套餐类型', isPlusC: '是否PlusC/代步权益', vehicleAgeStartSource: '车龄起算字段', vehicleAgeStartDate: '车龄起算日期', plusCMaxVehicleAgeYears: 'PlusC允许最长车龄', vehicleAgeDays: '车辆实际车龄天数', vehicleAgeMonths: '车辆实际车龄月数', vehicleAgeYears: '车辆实际车龄年数', ocrName: '身份证OCR姓名', orderIdNumber: '订单身份证号', ocrIdNumber: '身份证OCR号码', orderVin: '订单VIN/车架号', invoiceVin: '发票VIN/车架号', orderEngineNo: '订单发动机号/电机号', invoiceEngineNo: '发票发动机号/电机号', orderModel: '订单车型/产品', invoiceBrandModel: '发票厂牌型号', sellerName: '销货单位', vehicleType: '发票车辆类型', brandModel: '发票厂牌型号', invoiceTotalAmount: '发票价税合计', orderVehiclePrice: '订单车辆价格', }; function evidenceLabel(key) { return EVIDENCE_LABELS[key] || key; } function evidenceValue(key, value) { if (value === undefined || value === null || value === '') return ''; if (Array.isArray(value)) return value.filter(v => v !== undefined && v !== null && String(v) !== '').join('、'); if (key === 'carHost') { const text = String(value).trim(); if (text === '0' || text === '个人' || text.toLowerCase() === 'false') return '个人'; if (text === '1') return '企业/公户'; return text; } if (key === 'maxPurchaseAgeDays') return `${value}天`; if (key === 'ageDays') return `${value}天`; if (key === 'isPlusC') return value ? '是' : '否'; if (key === 'packageType') return String(value) === '3' ? 'PlusC/代步权益' : value; if (key === 'plusCMaxVehicleAgeYears') return `${value}年`; if (key === 'vehicleAgeDays') return `${value}天`; if (key === 'vehicleAgeMonths') return `${value}个月`; if (key === 'vehicleAgeYears') return `${value}年`; if (key === 'vehicleAgeStartSource') { const labels = { firstRegistrationDate: '首次注册日期', orderPurchaseDate: '订单购车日期', invoiceIssueDate: '发票开票日期', }; return labels[value] || value; } return value; } function renderEvidence(evidence) { if (!evidence || typeof evidence !== 'object' || !Object.keys(evidence).length) return '-'; return Object.entries(evidence) .map(([key, value]) => [evidenceLabel(key), evidenceValue(key, value)]) .filter(([, value]) => value !== undefined && value !== null && String(value) !== '') .map(([label, value]) => `${label}:${Array.isArray(value) ? value.join('、') : value}`) .join('
') || '-'; } function renderOrderTable(audit, order) { const n = audit.normalized || {}; const rows = [ ['订单号', firstValue(order, ['orderNo', 'orderNum']) || n.orderNo], ['业务单号', firstValue(order, ['businessNo', 'orderBusinessNo'])], ['客户姓名', n.orderName || firstValue(order, ['cardName', 'ownerName', 'customerName'])], ['身份证号', n.orderIdNumber || firstValue(order, ['cardNumber', 'idNumber'])], ['VIN/车架号', n.orderVin || firstValue(order, ['carFrame', 'vin'])], ['车辆类型', n.vehicleType || firstValue(order, ['vehicleType', 'carType'])], ['厂牌型号', n.brandModel || firstValue(order, ['modelName', 'carModel'])], ['计划号', firstValue(order, ['productNo', 'planNo', 'seresPlanNo'])], ['产品名称', firstValue(order, ['productName', 'goodsName'])], ['门店名称', firstValue(order, ['factroyName', 'factoryName', 'dealerName'])], ['车牌号', firstValue(order, ['carPlate', 'plateNo'])], ['购车日期', firstValue(order, ['carPurchaseTime', 'purchaseTime', 'purchaseDate'])], ['里程', firstValue(order, ['mileage', 'kilometers'])], ['车辆价格', firstValue(order, ['vehiclePrice'])], ['车主类型', firstValue(order, ['carHost'])], ['保单号', firstValue(order, ['policyNo', 'policyno', 'commercialInsuranceNo', 'seresInsuranceNo'])], ['保险公司', firstValue(order, ['insuranceCompany'])], ['商业险日期', firstValue(order, ['commercialInsuranceDate', 'effectiveDate', 'seresEffectiveDate'])], ['商业险URL', firstValue(order, ['commercialInsuranceUrl', 'pingAnUrl'])], ['附件模式', firstValue(order, ['seresAttachmentMode', 'attachmentExpectation'])], ['发票URL', firstValue(order, ['billUrl', 'carBill'])], ['身份证URL', firstValue(order, ['cardUrl'])], ['发票日期', n.invoiceIssueDate || firstValue(order, ['invoiceIssueDate', 'seresInvoiceDate'])], ].filter(([, v]) => hasText(v)); return [ '| 项目 | 内容 |', '| --- | --- |', ...rows.map(([k, v]) => `| ${str(k)} | ${str(v)} |`), ].join('\n'); } function normalizeRuleStatusForReport(status) { const normalized = normalizeStatus(status); if (normalized === 'PASS') return 'PASS'; if (normalized === 'REJECT') return 'REJECT'; if (normalized === 'MANUAL_REVIEW') return 'MANUAL_REVIEW'; const text = raw(status).trim().toUpperCase(); if (['SKIP', 'IGNORED', 'N_A', 'NA', 'NOT_APPLICABLE'].includes(text)) return 'SKIP'; return text || ''; } function renderRulesTable(rules, fieldChecks) { if (Array.isArray(rules) && rules.length > 0) { return [ '| 序号 | 规则 | 结论 | 说明 | 证据 |', '| --- | --- | --- | --- | --- |', ...rules.map((r, idx) => `| ${idx + 1} | ${str(r.name || r.id)} | ${ruleStatus(r.status)} | ${str(businessText(r.reason))} | ${renderEvidence(r.evidence)} |`), ].join('\n'); } if (Array.isArray(fieldChecks) && fieldChecks.length > 0) { return [ '| 序号 | 字段 | 期望 | 实际 | 结论 | 说明 |', '| --- | --- | --- | --- | --- | --- |', ...fieldChecks.map((check, idx) => { const result = normalizeRuleStatusForReport(check.result); const statusText = result === 'PASS' ? '通过' : result === 'REJECT' ? '驳回' : result === 'MANUAL_REVIEW' ? '转人工' : result === 'SKIP' ? '不适用' : str(check.result); return `| ${idx + 1} | ${str(check.field)} | ${str(check.expected)} | ${str(check.actual)} | ${statusText} | ${str(check.message)} |`; }), ].join('\n'); } return [ '暂无独立规则明细;本次报告按结构化订单数据生成综合结论。', ].join('\n'); } function findRule(rules, id) { return (rules || []).find(r => r.id === id) || {}; } function renderCompareSection(audit) { const rules = audit.rules || []; const idRule = findRule(rules, 'R4_ID_CARD'); const invoiceRule = findRule(rules, 'R5_INVOICE'); const id = idRule.evidence || {}; const inv = invoiceRule.evidence || {}; const sections = []; if ([id.orderName, id.ocrName, id.orderIdNumber, id.ocrIdNumber].some(hasText)) { sections.push([ '## 身份证比对详情', '', '| 字段 | 订单录入 | OCR识别 |', '| --- | --- | --- |', `| 姓名 | ${str(id.orderName)} | ${str(id.ocrName)} |`, `| 身份证号 | ${str(id.orderIdNumber)} | ${str(id.ocrIdNumber)} |`, ].join('\n')); } if ([inv.orderVin, inv.invoiceVin, inv.orderEngineNo, inv.invoiceEngineNo, inv.orderModel, inv.invoiceBrandModel, inv.sellerName].some(hasText)) { sections.push([ '## 发票比对详情', '', '| 字段 | 订单录入 | 发票OCR |', '| --- | --- | --- |', `| VIN/车架号 | ${str(inv.orderVin)} | ${str(inv.invoiceVin)} |`, `| 发动机号 | ${str(inv.orderEngineNo)} | ${str(inv.invoiceEngineNo)} |`, `| 车型/产品 | ${str(inv.orderModel)} | ${str(inv.invoiceBrandModel)} |`, `| 销货单位 | - | ${str(inv.sellerName)} |`, ].join('\n')); } return sections.join('\n\n'); } function renderMarkdown(input) { const audit = input.auditResult; if (!audit || typeof audit !== 'object') throw new Error('auditResult is required'); const companyName = raw(input.companyName) || '车主权益管理系统'; const generatedAt = formatLocalGeneratedAt(input.generatedAt); const logo = raw(input.logoUrl); const status = inferStatus(audit); const score = audit.score ?? '-'; const order = input.order || {}; const compareSection = renderCompareSection(audit); const summaryText = raw(audit.summary || audit.reason || '').trim(); const rules = Array.isArray(audit.rules) ? audit.rules : []; const fieldChecks = Array.isArray(audit.fieldChecks) ? audit.fieldChecks : []; const failedCount = rules.length ? rules.filter(r => normalizeRuleStatusForReport(r.status) !== 'PASS').length : fieldChecks.filter(r => { const result = normalizeRuleStatusForReport(r.result); return result === 'REJECT' || result === 'MANUAL_REVIEW'; }).length; const totalCount = rules.length || fieldChecks.length; const title = logo ? `

${companyName}

\n\n# ${companyName}投保审核报告` : `# ${companyName}投保审核报告`; const summary = summaryText || (failedCount > 0 ? `${failedCount}项规则未通过,存在需驳回或人工复核事项。` : `${totalCount}项规则均通过,未发现需驳回或人工复核事项。`); return [ title, '', `> 生成时间:${generatedAt}`, '', '## 审核结论', '', `| 结论 | 评分 | 结论码 | 主要原因 |`, `| --- | ---: | --- | --- |`, `| ${statusLabel(status)} | ${score} | ${statusIcon(status)} | ${str(businessText(audit.reason) || summary)} |`, '', '## 订单信息', '', renderOrderTable(audit, order), '', '## 规则校验', '', renderRulesTable(rules, fieldChecks), '', compareSection, compareSection ? '' : null, '## 综合摘要', '', `${str(businessText(summary))}`, '', '> 本报告由AI审核流程自动生成,结论基于订单录入数据及已提供的结构化/OCR结果。转人工项需以业务人员复核结果为准。', '', ].filter(line => line !== null).join('\n'); } (async () => { try { const input = readInput(); emit('render', '生成Markdown审核报告'); const reportMarkdown = renderMarkdown(input); const audit = input.auditResult || {}; emit('complete', '审核报告生成完成', 'completed'); process.stdout.write(JSON.stringify({ success: true, reportMarkdown, summary: businessText(audit.reason || audit.summary || ''), status: inferStatus(audit), score: audit.score, })); } catch (err) { process.stdout.write(JSON.stringify({ success: false, error: err && err.message ? err.message : String(err), })); } })();