594 lines
23 KiB
JavaScript
Raw Permalink Normal View History

2026-05-20 21:39:12 +08:00
const fs = require('node:fs');
function valueOf(source, paths) {
for (const path of paths) {
const value = path.split('.').reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), source);
if (value !== undefined && value !== null && String(value).trim() !== '') return value;
}
return '';
}
function normalizeToken(value) {
return String(value || '').normalize('NFKC').replace(/[^A-Za-z0-9]/g, '').toUpperCase();
}
function normalizeName(value) {
return String(value || '').normalize('NFKC').replace(/\s+/g, '').trim();
}
function normalizePlanNo(value) {
return String(value || '').trim().toLowerCase();
}
function getProductType(planNo) {
const code = normalizePlanNo(planNo);
if (code.startsWith('cxwypro')) return 'CXWY_PRO';
if (code.startsWith('cxwy')) return 'CXWY';
if (code.startsWith('pqxf')) return 'PQXF';
if (code.startsWith('ltwy')) return 'LTWY';
if (code.startsWith('yb')) return 'YB';
return 'UNKNOWN';
}
function needCommercialPolicy(productType) {
return ['CXWY', 'CXWY_PRO', 'PQXF'].includes(productType);
}
function hasValue(value) {
if (Array.isArray(value)) return value.some(hasValue);
return value !== undefined && value !== null && String(value).trim() !== '';
}
function normalizeDateString(value) {
const text = String(value || '').normalize('NFKC').trim();
if (!text) return '';
if (/^\d{4}-\d{2}-\d{2}$/.test(text)) return text;
const direct = text.match(/(\d{4})[年./\-\s](\d{1,2})[月./\-\s](\d{1,2})日?/);
if (direct) {
return [
String(direct[1]).padStart(4, '0'),
String(direct[2]).padStart(2, '0'),
String(direct[3]).padStart(2, '0'),
].join('-');
}
const compact = text.match(/^(\d{4})(\d{2})(\d{2})$/);
if (compact) {
return `${compact[1]}-${compact[2]}-${compact[3]}`;
}
const timestamp = Number(text);
if (Number.isFinite(timestamp) && String(Math.trunc(timestamp)).length >= 10) {
const date = new Date(String(Math.trunc(timestamp)).length === 10 ? timestamp * 1000 : timestamp);
if (!Number.isNaN(date.getTime())) {
const year = String(date.getFullYear()).padStart(4, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
}
return '';
}
function normalizeUsageNature(value) {
const text = String(value || '').normalize('NFKC').replace(/\s+/g, '');
if (!text) return '';
if (/家庭自用/.test(text)) return '家庭自用汽车';
if (/非营运/.test(text)) return '非营运';
if (/非营业/.test(text)) return '非营业';
if (/营业/.test(text)) return '营业';
if (/营运/.test(text)) return '营运';
if (/预约出租/.test(text)) return '预约出租客运';
if (/网约/.test(text)) return '网约车';
if (/出租/.test(text)) return '出租';
if (/租赁/.test(text)) return '租赁';
if (/客运/.test(text)) return '客运';
if (/货运/.test(text)) return '货运';
return text;
}
function isOperatingUsageNature(value) {
const text = String(value || '').normalize('NFKC').replace(/\s+/g, '');
if (!text) return false;
if (/家庭自用/.test(text) || /非营运/.test(text) || /非营业/.test(text)) return false;
return /营运|出租|租赁|网约|预约出租|客运|货运|营业/.test(text);
}
function isFamilySelfUseUsageNature(value) {
const text = String(value || '').normalize('NFKC').replace(/\s+/g, '');
return /家庭自用|非营运|非营业/.test(text);
}
function mismatchCount(a, b) {
if (!a || !b || a.length !== b.length) return Number.POSITIVE_INFINITY;
let count = 0;
for (let i = 0; i < a.length; i += 1) {
if (a[i] !== b[i]) count += 1;
}
return count;
}
function isLikelyVinOcrError(expectedVin, actualVin) {
if (!expectedVin || !actualVin || expectedVin.length !== 17 || actualVin.length !== 17) {
return false;
}
const diff = mismatchCount(expectedVin, actualVin);
if (diff <= 1) return true;
if (diff === 2) {
const positions = [];
for (let i = 0; i < expectedVin.length; i += 1) {
if (expectedVin[i] !== actualVin[i]) positions.push(i);
}
return (
positions[1] === positions[0] + 1 &&
expectedVin[positions[0]] === actualVin[positions[1]] &&
expectedVin[positions[1]] === actualVin[positions[0]]
);
}
return false;
}
function isKnownAvatrTa60OcrSwap(expectedVin, actualVin) {
if (!expectedVin || !actualVin || expectedVin.length !== 17 || actualVin.length !== 17) {
return false;
}
if (!expectedVin.includes('TA60') || !actualVin.includes('TA06')) {
return false;
}
return actualVin.replace('TA06', 'TA60') === expectedVin;
}
function canCorrectByConfusion(expected, actual, groups) {
if (!expected || !actual || expected.length !== actual.length) {
return false;
}
let hasDiff = false;
for (let i = 0; i < expected.length; i += 1) {
if (expected[i] === actual[i]) continue;
hasDiff = true;
const inSameGroup = groups.some(group => group.includes(expected[i]) && group.includes(actual[i]));
if (!inSameGroup) return false;
}
return hasDiff;
}
function isPolicyNoOq0Confusion(expectedPolicyNo, actualPolicyNo) {
return canCorrectByConfusion(
normalizeToken(expectedPolicyNo),
normalizeToken(actualPolicyNo),
[['O', 'Q', '0']]
);
}
function isDateWithinRange(date, start, end) {
if (!date || !start || !end) return false;
return date >= start && date <= end;
}
function isRangeWithinRange(innerStart, innerEnd, outerStart, outerEnd) {
if (!innerStart && !innerEnd) return false;
if (!outerStart || !outerEnd) return false;
if (innerStart && innerStart < outerStart) return false;
if (innerEnd && innerEnd > outerEnd) return false;
return true;
}
function collectFirstValue(source, paths) {
for (const path of paths) {
const raw = valueOf(source, [path]);
const normalized = normalizeDateString(raw);
if (normalized) return normalized;
}
return '';
}
function getOrderPurchaseWindow(order) {
const singleDate = collectFirstValue({ order }, [
'order.purchaseTime',
'order.purchaseDate',
'order.buyTime',
'order.buyDate',
'order.commercialInsuranceDate',
'order.orderTime',
'order.createTime',
]);
const purchaseStartDate = collectFirstValue({ order }, [
'order.purchaseStartDate',
'order.purchaseStartTime',
'order.buyStartDate',
'order.buyStartTime',
'order.commercialInsuranceStartDate',
'order.commercialInsuranceStartTime',
'order.purchaseRange.startDate',
'order.purchaseRange.startTime',
]);
const purchaseEndDate = collectFirstValue({ order }, [
'order.purchaseEndDate',
'order.purchaseEndTime',
'order.buyEndDate',
'order.buyEndTime',
'order.commercialInsuranceEndDate',
'order.commercialInsuranceEndTime',
'order.purchaseRange.endDate',
'order.purchaseRange.endTime',
]);
if (purchaseStartDate || purchaseEndDate) {
return {
purchaseDate: singleDate,
purchaseStartDate: purchaseStartDate || singleDate,
purchaseEndDate: purchaseEndDate || singleDate,
purchaseLabel: purchaseStartDate || purchaseEndDate ? '购买时间范围' : '购买时间',
};
}
return {
purchaseDate: singleDate,
purchaseStartDate: singleDate,
purchaseEndDate: singleDate,
purchaseLabel: '购买时间',
};
}
function normalizeOcr(input) {
const ocr = input.commercialPolicyOcr || {};
const nestedPolicy = ocr.policy || {};
const nestedVehicle = ocr.vehicle || {};
const nestedIdentity = ocr.identity || {};
const nameCandidates = [];
if (Array.isArray(ocr.nameCandidates)) {
for (const item of ocr.nameCandidates) {
if (item && typeof item === 'object' && item.name) {
nameCandidates.push({ label: String(item.label || '姓名候选'), name: String(item.name) });
} else if (typeof item === 'string') {
nameCandidates.push({ label: '姓名候选', name: item });
}
}
}
for (const [label, value] of [
['被保险人', nestedIdentity.insuredName],
['车主', nestedIdentity.ownerName],
['投保人', nestedIdentity.applicantName],
['姓名', ocr.name],
]) {
if (hasValue(value)) nameCandidates.push({ label, name: String(value) });
}
return {
policyNo: String(valueOf({ ocr, nestedPolicy }, ['ocr.policyNo', 'nestedPolicy.policyNo']) || ''),
vin: String(valueOf({ ocr, nestedVehicle }, ['ocr.vin', 'nestedVehicle.vin']) || ''),
nameCandidates,
certificateNo: String(valueOf({ ocr, nestedIdentity }, ['ocr.certificateNo', 'nestedIdentity.certificateNo']) || ''),
certificateNoMasked: Boolean(ocr.certificateNoMasked || nestedIdentity.certificateNoMasked),
usageNature: normalizeUsageNature(valueOf({ ocr, nestedPolicy, nestedVehicle }, [
'ocr.usageNature',
'ocr.useNature',
'ocr.vehicleUsageNature',
'ocr.usageNatureText',
'nestedPolicy.usageNature',
'nestedPolicy.useNature',
'nestedVehicle.usageNature',
'nestedVehicle.useNature',
])),
startDate: normalizeDateString(valueOf({ ocr, nestedPolicy }, [
'ocr.startDate',
'ocr.insuranceStartDate',
'ocr.policyStartDate',
'nestedPolicy.startDate',
'nestedPolicy.insuranceStartDate',
'nestedPolicy.policyStartDate',
])),
endDate: normalizeDateString(valueOf({ ocr, nestedPolicy }, [
'ocr.endDate',
'ocr.insuranceEndDate',
'ocr.policyEndDate',
'nestedPolicy.endDate',
'nestedPolicy.insuranceEndDate',
'nestedPolicy.policyEndDate',
])),
hasVehicleDamageCoverage: valueOf({ ocr, nestedPolicy }, ['ocr.hasVehicleDamageCoverage', 'nestedPolicy.hasVehicleDamageCoverage']),
confidence: typeof ocr.confidence === 'number' ? ocr.confidence : undefined,
warnings: Array.isArray(ocr.warnings) ? ocr.warnings : [],
};
}
function isCompleteIdCard(value) {
return /^\d{17}[\dX]$/i.test(String(value || '').trim());
}
function maskedIdMatches(masked, actual) {
const mask = String(masked || '').toUpperCase();
const id = String(actual || '').toUpperCase();
if (!mask || !id) return false;
if (isCompleteIdCard(mask)) return mask === id;
let idIndex = 0;
for (const char of mask) {
if (char === '*' || char === 'X') {
idIndex += 1;
continue;
}
while (idIndex < id.length && id[idIndex] !== char) idIndex += 1;
if (idIndex >= id.length) return false;
idIndex += 1;
}
return true;
}
function createAudit(input) {
const order = input.order || {};
const attachmentsProvided = input.attachments && typeof input.attachments === 'object';
const attachments = input.attachments || {};
const ocrProvided = input.commercialPolicyOcr && typeof input.commercialPolicyOcr === 'object';
const ocr = normalizeOcr(input);
const productType = getProductType(order.planNo);
const purchaseWindow = getOrderPurchaseWindow(order);
const policyStartDate = normalizeDateString(ocr.startDate);
const policyEndDate = normalizeDateString(ocr.endDate);
const normalizedUsageNature = normalizeUsageNature(ocr.usageNature);
const fieldChecks = [];
const reasons = [];
const riskFlags = [];
let reject = false;
let manual = false;
function addCheck(field, expected, actual, result, message, extra = {}) {
fieldChecks.push({
field,
expected: expected || '',
actual: actual || '',
result,
message,
...extra,
});
if (result === 'REJECT') reject = true;
if (result === 'REVIEW') manual = true;
}
function addReason(result, code, message) {
reasons.push({ code, message });
if (result === 'REJECT') reject = true;
if (result === 'REVIEW') manual = true;
}
for (const field of ['businessNo', 'orderNum', 'planNo', 'vin', 'ownerName']) {
if (!hasValue(order[field])) {
addCheck(field, '必填', '', 'REVIEW', `订单缺少${field},需要人工确认`);
}
}
if (hasValue(order.status) && order.status !== 'CONFIRMED') {
addCheck('status', 'CONFIRMED', order.status, 'REVIEW', '阿维塔子单状态不是CONFIRMED');
} else if (hasValue(order.status)) {
addCheck('status', 'CONFIRMED', order.status, 'PASS', '阿维塔子单状态正常');
}
if (hasValue(order.mainStatus) && order.mainStatus !== 'WAITEXAMINE') {
addCheck('mainStatus', 'WAITEXAMINE', order.mainStatus, 'REVIEW', '主订单状态不是WAITEXAMINE');
} else if (hasValue(order.mainStatus)) {
addCheck('mainStatus', 'WAITEXAMINE', order.mainStatus, 'PASS', '主订单状态正常');
}
if (needCommercialPolicy(productType)) {
if (attachmentsProvided) {
if (hasValue(attachments.commercialInsuranceUrl)) {
addCheck('commercialInsuranceUrl', '必传', '已传', 'PASS', '商业险保单附件已上传');
} else {
addCheck('commercialInsuranceUrl', '必传', '', 'REJECT', '产品要求商业险保单附件,但未上传');
}
} else {
addCheck('commercialInsuranceUrl', '必传', '', 'REVIEW', '未提供附件字段,无法确认商业险保单附件');
}
if (!ocrProvided) {
addReason('REVIEW', 'MISSING_POLICY_OCR', '缺少商业险/车损险保单OCR结构化结果');
}
}
if (productType === 'LTWY' && attachmentsProvided) {
for (const field of ['leftFrontDot', 'rightFrontDot', 'leftRearDot', 'rightRearDot']) {
addCheck(field, '必传', hasValue(attachments[field]) ? '已传' : '', hasValue(attachments[field]) ? 'PASS' : 'REJECT', `${field}轮胎DOT附件校验`);
}
}
if (productType === 'YB') {
addCheck('warrantyPeriod', '必填', order.warrantyPeriod || '', hasValue(order.warrantyPeriod) ? 'PASS' : 'REJECT', '整车延保产品需填写原厂整车质保期');
}
if (needCommercialPolicy(productType) || ocrProvided) {
if (!hasValue(ocr.policyNo)) {
addCheck('policyNo', 'OCR应识别', '', 'REVIEW', '未识别到商业险保单号', {
needsMultimodalReview: true,
multimodalReason: 'policyNo_missing',
});
} else if (hasValue(order.commercialInsuranceNo)) {
const expectedPolicyNo = normalizeToken(order.commercialInsuranceNo);
const actualPolicyNo = normalizeToken(ocr.policyNo);
const matched = expectedPolicyNo === actualPolicyNo;
if (matched) {
addCheck('policyNo', order.commercialInsuranceNo, ocr.policyNo, 'PASS', '保单号一致');
} else if (isPolicyNoOq0Confusion(expectedPolicyNo, actualPolicyNo)) {
addCheck('policyNo', order.commercialInsuranceNo, ocr.policyNo, 'PASS', '保单号存在O/Q/0常见OCR混淆按订单商业险保单号校正后一致', {
correctedValue: order.commercialInsuranceNo,
correctionRule: 'POLICY_O_Q_0_CONFUSION',
});
riskFlags.push({ level: 'LOW', code: 'POLICY_O_Q_0_OCR_CORRECTED', message: '保单号O/Q/0 OCR误识别已按订单保单号校正' });
} else {
addCheck('policyNo', order.commercialInsuranceNo, ocr.policyNo, 'REVIEW', 'OCR保单号与订单商业险保单号不一致需多模态复核保单原图', {
needsMultimodalReview: true,
multimodalReason: 'policyNo_mismatch',
});
riskFlags.push({ level: 'MEDIUM', code: 'POLICY_NO_MISMATCH_NEEDS_MULTIMODAL', message: 'OCR保单号与订单商业险保单号不一致需多模态复核' });
}
} else {
addCheck('policyNo', '订单可为空', ocr.policyNo, 'PASS', 'OCR已识别保单号可用于回填');
}
if (!hasValue(ocr.vin)) {
addCheck('vin', order.vin || '订单VIN', '', 'REVIEW', '未识别到保单VIN', {
needsMultimodalReview: true,
multimodalReason: 'vin_missing',
});
} else {
const expectedVin = normalizeToken(order.vin);
const actualVin = normalizeToken(ocr.vin);
if (expectedVin && actualVin && expectedVin !== actualVin) {
if (isKnownAvatrTa60OcrSwap(expectedVin, actualVin)) {
addCheck('vin', order.vin, ocr.vin, 'PASS', '阿维塔车型VIN存在TA60被OCR识别为TA06的已知误识别按订单VIN校正后一致', {
correctedValue: order.vin,
correctionRule: 'AVATR_TA06_TO_TA60',
});
riskFlags.push({ level: 'LOW', code: 'VIN_TA60_TA06_OCR_CORRECTED', message: '阿维塔VIN TA60/TA06 OCR误识别已按订单VIN校正' });
} else if (isLikelyVinOcrError(expectedVin, actualVin)) {
addCheck('vin', order.vin, ocr.vin, 'REVIEW', '保单VIN与订单VIN存在轻微差异疑似OCR识别错误需多模态复核保单原图', {
needsMultimodalReview: true,
multimodalReason: 'vin_suspected_ocr_error',
});
riskFlags.push({ level: 'MEDIUM', code: 'VIN_OCR_SUSPECT_NEEDS_MULTIMODAL', message: '保单VIN与订单VIN存在轻微差异需多模态复核' });
} else {
addCheck('vin', order.vin, ocr.vin, 'REJECT', '保单VIN与订单VIN不一致');
riskFlags.push({ level: 'HIGH', code: 'VIN_MISMATCH', message: '保单VIN与订单VIN不一致' });
}
} else {
addCheck('vin', order.vin, ocr.vin, 'PASS', 'VIN一致');
}
}
const ownerName = normalizeName(order.ownerName);
const matchedName = ocr.nameCandidates.find(item => normalizeName(item.name) === ownerName);
if (!ownerName) {
addCheck('ownerName', '订单车主姓名', '', 'REVIEW', '订单车主姓名缺失');
} else if (matchedName) {
addCheck('ownerName', order.ownerName, `${matchedName.label}:${matchedName.name}`, 'PASS', '保单姓名候选与订单车主一致');
} else if (ocr.nameCandidates.length > 0) {
addCheck('ownerName', order.ownerName, ocr.nameCandidates.map(item => `${item.label}:${item.name}`).join(', '), 'REVIEW', '保单姓名候选未匹配订单车主');
riskFlags.push({ level: 'MEDIUM', code: 'NAME_NOT_MATCHED', message: '保单姓名候选未匹配订单车主' });
} else {
addCheck('ownerName', order.ownerName, '', 'REVIEW', '未识别到被保险人/车主/投保人姓名');
}
if (hasValue(ocr.certificateNo) && hasValue(order.idCard)) {
if (ocr.certificateNoMasked) {
const matched = maskedIdMatches(ocr.certificateNo, order.idCard);
addCheck('idCard', order.idCard, ocr.certificateNo, matched ? 'PASS' : 'REVIEW', matched ? '脱敏证件号可见位匹配' : '脱敏证件号无法确认匹配');
} else if (isCompleteIdCard(ocr.certificateNo)) {
const matched = normalizeToken(ocr.certificateNo) === normalizeToken(order.idCard);
addCheck('idCard', order.idCard, ocr.certificateNo, matched ? 'PASS' : 'REJECT', matched ? '完整证件号一致' : '完整证件号与订单身份证号不一致');
}
} else {
addCheck('idCard', order.idCard || '可选', ocr.certificateNo || '', 'SKIP', '保单证件号不是必填项,缺失不影响核心审核');
}
if (!hasValue(ocr.usageNature)) {
addCheck('usageNature', '家庭自用汽车', '', 'REVIEW', '未识别到保单使用性质,需多模态复核保单原图', {
needsMultimodalReview: true,
multimodalReason: 'usageNature_missing',
});
} else if (isFamilySelfUseUsageNature(ocr.usageNature)) {
addCheck('usageNature', '家庭自用汽车/非营运/非营业', ocr.usageNature, 'PASS', '保单使用性质为家庭自用或非营运/非营业,符合承保要求');
} else if (isOperatingUsageNature(ocr.usageNature)) {
addCheck('usageNature', '家庭自用汽车', ocr.usageNature, 'REJECT', '保单使用性质为营运/营业类,不符合承保要求');
riskFlags.push({ level: 'HIGH', code: 'OPERATING_USAGE_NATURE', message: '保单使用性质不是家庭自用汽车' });
} else {
addCheck('usageNature', '家庭自用汽车', ocr.usageNature, 'REJECT', '保单使用性质不是家庭自用汽车,不符合承保要求');
riskFlags.push({ level: 'HIGH', code: 'NON_FAMILY_USAGE_NATURE', message: '保单使用性质不是家庭自用汽车' });
}
if (!policyStartDate || !policyEndDate) {
addCheck('insurancePeriod', '应识别保险起止日期', `${ocr.startDate || ''} - ${ocr.endDate || ''}`, 'REVIEW', '未完整识别保单保险期间,需多模态复核保单原图', {
needsMultimodalReview: true,
multimodalReason: 'insurancePeriod_missing',
});
} else if (!purchaseWindow.purchaseStartDate && !purchaseWindow.purchaseEndDate) {
addCheck('insurancePeriod', '购买时间应在保险期间内', `${policyStartDate} - ${policyEndDate}`, 'REVIEW', '订单缺少购买时间,无法自动校验保险期间');
} else if (purchaseWindow.purchaseStartDate === purchaseWindow.purchaseEndDate) {
const purchaseDate = purchaseWindow.purchaseStartDate;
const matched = isDateWithinRange(purchaseDate, policyStartDate, policyEndDate);
addCheck(
'insurancePeriod',
`应覆盖购买时间 ${purchaseDate}`,
`${policyStartDate} - ${policyEndDate}`,
matched ? 'PASS' : 'REJECT',
matched ? '购买时间在保单保险期间内' : '购买时间不在保单保险期间内'
);
if (!matched) {
riskFlags.push({ level: 'HIGH', code: 'PURCHASE_TIME_OUT_OF_POLICY_PERIOD', message: '购买时间不在保单保险期间内' });
}
} else {
const matched = isRangeWithinRange(purchaseWindow.purchaseStartDate, purchaseWindow.purchaseEndDate, policyStartDate, policyEndDate);
addCheck(
'insurancePeriod',
`应覆盖${purchaseWindow.purchaseLabel} ${purchaseWindow.purchaseStartDate || ''} - ${purchaseWindow.purchaseEndDate || ''}`,
`${policyStartDate} - ${policyEndDate}`,
matched ? 'PASS' : 'REJECT',
matched ? '购买时间范围在保单保险期间内' : '购买时间范围不在保单保险期间内'
);
if (!matched) {
riskFlags.push({ level: 'HIGH', code: 'PURCHASE_RANGE_OUT_OF_POLICY_PERIOD', message: '购买时间范围不在保单保险期间内' });
}
}
if (ocr.hasVehicleDamageCoverage === true) {
addCheck('vehicleDamageCoverage', '应包含车损险', '已识别', 'PASS', '识别到车损险险种证据');
} else {
addCheck('vehicleDamageCoverage', '应包含车损险', '未确认', 'REVIEW', '未识别到机动车损失保险/车辆损失保险/车损险证据');
}
}
const decision = reject ? 'REJECT' : manual ? 'MANUAL_REVIEW' : 'PASS';
const suggestedAction = decision === 'PASS'
? '提交人保投保'
: decision === 'REJECT'
? '驳回并要求重新核对或上传正确商业险/车损险保单'
: '转人工复核保单原件和OCR结果';
return {
success: true,
decision,
reasons,
fieldChecks,
riskFlags,
suggestedAction,
summary: {
productType,
policyNo: ocr.policyNo,
vin: ocr.vin,
nameCandidates: ocr.nameCandidates,
usageNature: ocr.usageNature,
normalizedUsageNature,
policyStartDate,
policyEndDate,
purchaseDate: purchaseWindow.purchaseDate,
purchaseStartDate: purchaseWindow.purchaseStartDate,
purchaseEndDate: purchaseWindow.purchaseEndDate,
ocrConfidence: ocr.confidence,
ocrWarnings: ocr.warnings,
},
};
}
async function main() {
try {
const stdin = fs.readFileSync(0, 'utf8');
const input = JSON.parse(stdin || '{}');
process.stdout.write(JSON.stringify(createAudit(input)));
} catch (error) {
process.stdout.write(JSON.stringify({ success: false, error: error.message }));
process.exitCode = 0;
}
}
if (require.main === module) {
main();
}
module.exports = {
createAudit,
normalizeOcr,
getProductType,
};