2026-05-20 21:39:12 +08:00

594 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};