390 lines
15 KiB
JavaScript
Raw Normal View History

2026-05-20 21:39:12 +08:00
#!/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, '<br>');
}
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('<br>') || '-';
}
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
? `<p align="center"><img src="${logo}" alt="${companyName}" height="48"></p>\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),
}));
}
})();