/** * 定义各种字段的可能关键词 * 用于更灵活地匹配不同格式的保单 */ const FIELD_KEYWORDS = { policyNumber: ['保单号', '保单号码', '保险单号', '保险凭证号', '保险合同号', '电子保单号', '保单编号', '保险单编号'], insuranceCompany: ['保险股份有限公司', '保险公司', '保险(集团)股份', '保险(集团)股份', '人寿保险', '财产保险', '人保财险'], productName: ['保险计划名称', '保险产品名称', '产品名称', '险种名称', '保险名称', '险种', '产品'], startDate: ['保险起期', '保险责任起始日', '起保日期', '生效日期', '保险生效日', '起始日期', '保障起始日'], endDate: ['保险止期', '保险责任终止日', '终保日期', '到期日期', '保险终止日', '终止日期', '保障终止日'], premiumPerPerson: ['保费/人', '每人保费', '人均保费', '单人保费', '每人保险费'], insuredCount: ['被保险人人数', '投保人数', '承保人数', '保障人数', '参保人数'], totalPremium: ['总保费', '保费合计', '总保险费', '保险费合计', '应交保险费', '应付保险费'], policyholder: ['投保人', '投保单位', '投保机构', '投保企业', '投保团体'], policyholderIdType: ['投保人证件类型', '投保人证件种类', '投保人身份证件类型', '投保人身份证明类型'], policyholderIdNumber: ['投保人证件号码', '投保人身份证号', '投保人证件号', '投保人身份证明号码'] }; /** * 标准化日期格式 * @param {string} dateStr - 日期字符串 * @returns {string} - 标准化后的日期字符串 */ function normalizeDate(dateStr) { if (!dateStr) return ''; // 移除可能的额外空格 dateStr = dateStr.trim(); // 尝试匹配常见的日期格式 const patterns = [ // YYYY-MM-DD 或 YYYY/MM/DD /(\d{4})[-\/](\d{1,2})[-\/](\d{1,2})/, // YYYY年MM月DD日 /(\d{4})年(\d{1,2})月(\d{1,2})日/, // YYYY.MM.DD /(\d{4})\.(\d{1,2})\.(\d{1,2})/, // DD-MM-YYYY 或 DD/MM/YYYY /(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})/ ]; for (const pattern of patterns) { const match = dateStr.match(pattern); if (match) { // 根据匹配的模式提取年月日 let year, month, day; if (pattern.toString().includes('\\d{4})[')) { // 年份在前的格式 [, year, month, day] = match; } else { // 年份在后的格式 [, day, month, year] = match; } // 确保月和日是两位数 month = month.padStart(2, '0'); day = day.padStart(2, '0'); return `${year}-${month}-${day}`; } } // 如果无法标准化,返回原始字符串 return dateStr; } /** * 从文本中提取特定模式的信息 * @param {string} text - 要搜索的文本 * @param {RegExp} pattern - 正则表达式模式 * @returns {string} - 提取的信息或空字符串 */ function extractPattern(text, pattern) { const match = text.match(pattern); return match && match[1] ? match[1].trim() : ''; } /** * 在OCR结果中搜索特定关键词的行 * @param {Array} wordsResult - OCR结果的words_result数组 * @param {string} keyword - 要搜索的关键词 * @returns {string|null} - 找到的行或null */ function findLineWithKeyword(wordsResult, keyword) { for (const item of wordsResult) { if (item.words.includes(keyword)) { return item.words; } } return null; } /** * 在OCR结果中搜索包含任一关键词的行 * @param {Array} wordsResult - OCR结果的words_result数组 * @param {Array} keywords - 要搜索的关键词数组 * @returns {{line: string, keyword: string}|null} - 找到的行和匹配的关键词,或null */ function findLineWithAnyKeyword(wordsResult, keywords) { for (const item of wordsResult) { for (const keyword of keywords) { if (item.words.includes(keyword)) { return { line: item.words, keyword }; } } } return null; } /** * 从文本中提取数字 * @param {string} text - 包含数字的文本 * @returns {string} - 提取的数字或空字符串 */ function extractNumber(text) { const match = text.match(/(\d+([.,]\d+)?)/); return match ? match[1] : ''; } /** * 从文本中提取金额 * @param {string} text - 包含金额的文本 * @returns {string} - 提取的金额或空字符串 */ function extractAmount(text) { // 匹配金额格式,如"¥100.00"、"RMB 100.00"、"100.00元"等 const match = text.match(/(?:¥|RMB|CNY|¥)?\s*(\d+([.,]\d+)?)\s*(?:元|圆|块钱)?/i); return match ? match[1] : extractNumber(text); } /** * 从OCR结果中提取保单信息 * @param {Object} ocrResult - 百度OCR API返回的结果 * @returns {Object} - 提取的保单信息 */ function parseInsurancePolicyOcr(ocrResult) { // 检查OCR结果是否有效 if (!ocrResult || !ocrResult.words_result || !Array.isArray(ocrResult.words_result)) { throw new Error('无效的OCR结果'); } const wordsResult = ocrResult.words_result; const extractedInfo = { policyNumber: '', // 保单号 insuranceCompany: '', // 保险公司名称 productName: '', // 保险产品名称 startDate: '', // 保险起期 endDate: '', // 保险止期 premiumPerPerson: '', // 每人保费 insuredCount: '', // 投保人数 totalPremium: '', // 总保费 policyholder: '', // 投保人 policyholderIdType: '', // 投保人证件类型 policyholderIdNumber: '', // 投保人证件号码 }; // 遍历OCR结果,提取所需字段 for (let i = 0; i < wordsResult.length; i++) { const item = wordsResult[i].words; // 提取保单号 if (item.includes('保单号')) { const match = item.match(/保单号[::]\s*(.+)/); if (match && match[1]) { extractedInfo.policyNumber = match[1].trim(); } } // 提取保险公司名称 if (item.includes('保险股份有限公司') || item.includes('保险公司')) { extractedInfo.insuranceCompany = item.trim(); } // 提取保险产品名称 if (item.includes('保险计划名称')) { const match = item.match(/保险计划名称[::]\s*(.+)/); if (match && match[1]) { extractedInfo.productName = match[1].trim(); } } // 提取保险起期 if (item.includes('保险起期')) { const match = item.match(/保险起期[((][^))]+[))][::]\s*(.+)/); if (match && match[1]) { extractedInfo.startDate = match[1].trim(); } } // 提取保险止期 if (item.includes('保险止期')) { const match = item.match(/保险止期[((][^))]+[))][::]\s*(.+)/); if (match && match[1]) { extractedInfo.endDate = match[1].trim(); } } // 提取每人保费 if (item.includes('保费/人')) { const match = item.match(/保费\/人[::]\s*(.+)/); if (match && match[1]) { extractedInfo.premiumPerPerson = match[1].trim(); } } // 提取投保人数 if (item.includes('被保险人人数')) { // 检查下一行是否包含数字(针对格式:被保险人人数:\n 91) if (i + 1 < wordsResult.length && /^\d+$/.test(wordsResult[i + 1].words.trim())) { extractedInfo.insuredCount = wordsResult[i + 1].words.trim(); } else { const match = item.match(/被保险人人数[::]\s*(\d+)/); if (match && match[1]) { extractedInfo.insuredCount = match[1].trim(); } } } // 提取总保费 if (item.includes('总保费合计')) { const match = item.match(/总保费合计\/[^::]+[::]\s*(.+)/); if (match && match[1]) { extractedInfo.totalPremium = match[1].trim(); } } // 提取投保人 if (item.includes('投保人:')) { const match = item.match(/投保人[::]\s*(.+)/); if (match && match[1]) { extractedInfo.policyholder = match[1].trim(); } } // 提取投保人证件类型 if (item.includes('投保人证件类型')) { const match = item.match(/投保人证件类型[::]\s*(.+)/); if (match && match[1]) { extractedInfo.policyholderIdType = match[1].trim(); } } // 提取投保人证件号码 if (item.includes('投保人证件号码')) { const match = item.match(/投保人证件号码[::]\s*(.+)/); if (match && match[1]) { extractedInfo.policyholderIdNumber = match[1].trim(); } } } return extractedInfo; } /** * 智能保单OCR解析函数 * 结合多种解析方法,提高提取准确率 * @param {Object} ocrResult - 百度OCR API返回的结果 * @returns {Object} - 提取的保单信息 */ function smartParseInsurancePolicyOcr(ocrResult) { // 检查OCR结果是否有效 if (!ocrResult || !ocrResult.words_result || !Array.isArray(ocrResult.words_result)) { throw new Error('无效的OCR结果'); } // 使用基础解析和通用解析获取结果 const basicResult = parseInsurancePolicyOcr(ocrResult); const universalResult = universalParseInsurancePolicyOcr(ocrResult); // 合并结果,优先使用通用解析的结果 const mergedResult = { policyNumber: universalResult.policyNumber || basicResult.policyNumber, insuranceCompany: universalResult.insuranceCompany || basicResult.insuranceCompany, productName: universalResult.productName || basicResult.productName, startDate: universalResult.startDate || basicResult.startDate, endDate: universalResult.endDate || basicResult.endDate, premiumPerPerson: universalResult.premiumPerPerson || basicResult.premiumPerPerson, insuredCount: universalResult.insuredCount || basicResult.insuredCount, totalPremium: universalResult.totalPremium || basicResult.totalPremium, policyholder: universalResult.policyholder || basicResult.policyholder, policyholderIdType: universalResult.policyholderIdType || basicResult.policyholderIdType, policyholderIdNumber: universalResult.policyholderIdNumber || basicResult.policyholderIdNumber, }; const wordsResult = ocrResult.words_result; // 进行额外的上下文分析和数据清洗 // 1. 标准化日期格式 if (mergedResult.startDate) { mergedResult.startDate = normalizeDate(mergedResult.startDate); } if (mergedResult.endDate) { mergedResult.endDate = normalizeDate(mergedResult.endDate); } // 2. 清理金额中的非数字字符 if (mergedResult.premiumPerPerson) { mergedResult.premiumPerPerson = extractAmount(mergedResult.premiumPerPerson); } if (mergedResult.totalPremium) { mergedResult.totalPremium = extractAmount(mergedResult.totalPremium); } // 3. 尝试从上下文推断缺失的字段 // 如果没有提取到产品名称,尝试查找包含特定关键词的行 if (!mergedResult.productName) { for (let i = 0; i < wordsResult.length; i++) { const line = wordsResult[i].words; if ((line.includes('保险') || line.includes('险种')) && !line.includes('公司') && !line.includes('保费') && !line.includes('保险金额') && line.length > 4 && line.length < 30) { mergedResult.productName = line.trim(); break; } } } // 如果没有提取到保险公司名称,尝试查找包含"保险"和"公司"的行 if (!mergedResult.insuranceCompany) { for (let i = 0; i < wordsResult.length; i++) { const line = wordsResult[i].words; if (line.includes('保险') && line.includes('公司') && line.length > 6) { mergedResult.insuranceCompany = line.trim(); break; } } } // 如果没有提取到总保费但有每人保费和投保人数,尝试计算 if (!mergedResult.totalPremium && mergedResult.premiumPerPerson && mergedResult.insuredCount) { const premium = parseFloat(mergedResult.premiumPerPerson); const count = parseInt(mergedResult.insuredCount, 10); if (!isNaN(premium) && !isNaN(count)) { mergedResult.totalPremium = (premium * count).toFixed(2); } } // 4. 处理特殊情况 // 如果保单号包含多余的文本,尝试提取纯数字和字母部分 if (mergedResult.policyNumber && mergedResult.policyNumber.length > 30) { const match = mergedResult.policyNumber.match(/[A-Za-z0-9-]{5,25}/); if (match) { mergedResult.policyNumber = match[0]; } } // 如果日期格式不正确,尝试修复 if (mergedResult.startDate && !mergedResult.startDate.match(/^\d{4}-\d{2}-\d{2}$/)) { // 尝试再次标准化 mergedResult.startDate = normalizeDate(mergedResult.startDate); } if (mergedResult.endDate && !mergedResult.endDate.match(/^\d{4}-\d{2}-\d{2}$/)) { // 尝试再次标准化 mergedResult.endDate = normalizeDate(mergedResult.endDate); } return mergedResult; } /** * 主要的保单OCR解析入口函数 * 根据不同的情况选择最合适的解析方法 * @param {Object} ocrResult - 百度OCR API返回的结果 * @param {Object} options - 解析选项 * @param {string} options.mode - 解析模式,可选值:'basic'、'enhanced'、'universal'、'smart',默认为'smart' * @param {boolean} options.debug - 是否返回调试信息,默认为false * @returns {Object} - 提取的保单信息 */ function parseInsurancePolicy(ocrResult, options = {}) { const { mode = 'smart', debug = false } = options; // 检查OCR结果是否有效 if (!ocrResult || !ocrResult.words_result || !Array.isArray(ocrResult.words_result)) { throw new Error('无效的OCR结果'); } let result; // 根据模式选择解析方法 switch (mode) { case 'basic': result = parseInsurancePolicyOcr(ocrResult); break; case 'enhanced': result = enhancedParseInsurancePolicyOcr(ocrResult); break; case 'universal': result = universalParseInsurancePolicyOcr(ocrResult); break; case 'smart': default: result = smartParseInsurancePolicyOcr(ocrResult); break; } // 如果需要调试信息,返回所有解析结果 if (debug) { return { result, debug: { basic: parseInsurancePolicyOcr(ocrResult), enhanced: enhancedParseInsurancePolicyOcr(ocrResult), universal: universalParseInsurancePolicyOcr(ocrResult), smart: smartParseInsurancePolicyOcr(ocrResult), } }; } return result; } /** * 验证保单解析结果是否合理 * @param {Object} result - 解析结果 * @returns {Object} - 验证结果,包含是否有效和问题列表 */ function validatePolicyResult(result) { const issues = []; // 检查必要字段是否存在 if (!result.policyNumber) { issues.push('缺少保单号'); } if (!result.insuranceCompany) { issues.push('缺少保险公司名称'); } if (!result.productName) { issues.push('缺少保险产品名称'); } // 验证日期格式 if (result.startDate) { if (!result.startDate.match(/^\d{4}-\d{2}-\d{2}$/)) { issues.push('保险起期格式不正确'); } } else { issues.push('缺少保险起期'); } if (result.endDate) { if (!result.endDate.match(/^\d{4}-\d{2}-\d{2}$/)) { issues.push('保险止期格式不正确'); } } else { issues.push('缺少保险止期'); } // 验证日期逻辑 if (result.startDate && result.endDate) { const startDate = new Date(result.startDate); const endDate = new Date(result.endDate); if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { issues.push('日期格式无效'); } else if (startDate > endDate) { issues.push('保险起期晚于止期'); } } // 验证金额和数量 if (result.premiumPerPerson) { if (isNaN(parseFloat(result.premiumPerPerson))) { issues.push('每人保费不是有效数字'); } } if (result.insuredCount) { if (isNaN(parseInt(result.insuredCount, 10))) { issues.push('投保人数不是有效数字'); } } if (result.totalPremium) { if (isNaN(parseFloat(result.totalPremium))) { issues.push('总保费不是有效数字'); } } // 验证总保费与每人保费和投保人数的关系 if (result.premiumPerPerson && result.insuredCount && result.totalPremium) { const calculatedTotal = parseFloat(result.premiumPerPerson) * parseInt(result.insuredCount, 10); const actualTotal = parseFloat(result.totalPremium); // 允许1元的误差 if (Math.abs(calculatedTotal - actualTotal) > 1) { issues.push('总保费与每人保费和投保人数不匹配'); } } return { isValid: issues.length === 0, issues }; } /** * 格式化和标准化保单解析结果 * @param {Object} result - 解析结果 * @returns {Object} - 格式化后的结果 */ function formatPolicyResult(result) { const formatted = { ...result }; // 格式化日期 if (formatted.startDate) { formatted.startDate = normalizeDate(formatted.startDate); } if (formatted.endDate) { formatted.endDate = normalizeDate(formatted.endDate); } // 格式化金额 if (formatted.premiumPerPerson) { const amount = parseFloat(formatted.premiumPerPerson); if (!isNaN(amount)) { formatted.premiumPerPerson = amount.toFixed(2); } } if (formatted.totalPremium) { const amount = parseFloat(formatted.totalPremium); if (!isNaN(amount)) { formatted.totalPremium = amount.toFixed(2); } } // 格式化投保人数 if (formatted.insuredCount) { const count = parseInt(formatted.insuredCount, 10); if (!isNaN(count)) { formatted.insuredCount = count.toString(); } } // 计算保险期限(天数) if (formatted.startDate && formatted.endDate) { const startDate = new Date(formatted.startDate); const endDate = new Date(formatted.endDate); if (!isNaN(startDate.getTime()) && !isNaN(endDate.getTime())) { const diffTime = Math.abs(endDate - startDate); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); formatted.insurancePeriod = diffDays; } } return formatted; } /** * 示例:完整的保单OCR解析流程 * @param {Object} ocrResult - 百度OCR API返回的结果 * @returns {Object} - 处理后的结果,包含解析结果、验证结果和格式化结果 */ function processInsurancePolicyOcr(ocrResult) { try { // 1. 使用智能解析获取初步结果 const parsedResult = parseInsurancePolicy(ocrResult, { mode: 'smart' }); // 2. 验证解析结果 const validationResult = validatePolicyResult(parsedResult); // 3. 格式化解析结果 const formattedResult = formatPolicyResult(parsedResult); // 4. 返回完整的处理结果 return { success: true, result: formattedResult, validation: validationResult, raw: parsedResult }; } catch (error) { return { success: false, error: error.message }; } } /** * 自动修复常见的保单解析问题 * @param {Object} result - 解析结果 * @param {Object} ocrResult - 原始OCR结果,用于重新尝试提取 * @returns {Object} - 修复后的结果 */ function autoFixPolicyResult(result, ocrResult) { const fixed = { ...result }; const wordsResult = ocrResult.words_result; const fullText = wordsResult.map(item => item.words).join(' '); // 1. 修复保单号 if (!fixed.policyNumber) { // 尝试查找包含"保单"和数字的行 for (const item of wordsResult) { if (item.words.includes('保单') && /\d/.test(item.words)) { const match = item.words.match(/[A-Za-z0-9-]{5,}/); if (match) { fixed.policyNumber = match[0]; break; } } } // 如果还是没找到,尝试查找看起来像保单号的内容 if (!fixed.policyNumber) { for (const item of wordsResult) { const match = item.words.match(/^[A-Za-z0-9-]{10,20}$/); if (match) { fixed.policyNumber = match[0]; break; } } } } // 2. 修复保险公司名称 if (!fixed.insuranceCompany) { // 尝试查找包含"保险"和"公司"的行 for (const item of wordsResult) { if (item.words.includes('保险') && item.words.includes('公司')) { fixed.insuranceCompany = item.words.trim(); break; } } } // 3. 修复产品名称 if (!fixed.productName) { // 尝试查找包含"保险"但不包含"公司"的行 for (const item of wordsResult) { if (item.words.includes('保险') && !item.words.includes('公司') && !item.words.includes('保费') && !item.words.includes('保险金额')) { fixed.productName = item.words.trim(); break; } } } // 4. 修复日期 if (!fixed.startDate || !fixed.startDate.match(/^\d{4}-\d{2}-\d{2}$/)) { // 尝试查找日期格式 for (const item of wordsResult) { const dateMatch = item.words.match(/(\d{4})[-\/年](\d{1,2})[-\/月](\d{1,2})[日]?/); if (dateMatch) { const [, year, month, day] = dateMatch; fixed.startDate = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`; break; } } } if (!fixed.endDate || !fixed.endDate.match(/^\d{4}-\d{2}-\d{2}$/)) { // 如果有起始日期但没有结束日期,尝试在起始日期后面查找另一个日期 if (fixed.startDate) { let foundStart = false; for (const item of wordsResult) { if (foundStart) { const dateMatch = item.words.match(/(\d{4})[-\/年](\d{1,2})[-\/月](\d{1,2})[日]?/); if (dateMatch) { const [, year, month, day] = dateMatch; fixed.endDate = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`; break; } } if (item.words.includes(fixed.startDate)) { foundStart = true; } } } } // 5. 修复金额 if (!fixed.totalPremium && fixed.premiumPerPerson && fixed.insuredCount) { // 如果有每人保费和投保人数,计算总保费 const premium = parseFloat(fixed.premiumPerPerson); const count = parseInt(fixed.insuredCount, 10); if (!isNaN(premium) && !isNaN(count)) { fixed.totalPremium = (premium * count).toFixed(2); } } if (!fixed.premiumPerPerson && fixed.totalPremium && fixed.insuredCount) { // 如果有总保费和投保人数,计算每人保费 const total = parseFloat(fixed.totalPremium); const count = parseInt(fixed.insuredCount, 10); if (!isNaN(total) && !isNaN(count) && count > 0) { fixed.premiumPerPerson = (total / count).toFixed(2); } } return fixed; } /** * 智能保单类型识别 * 根据OCR结果识别保单类型,以便选择最合适的解析策略 * @param {Object} ocrResult - 百度OCR API返回的结果 * @returns {string} - 保单类型:'standard'、'travel'、'health'、'property'、'unknown' */ function identifyPolicyType(ocrResult) { const wordsResult = ocrResult.words_result; const fullText = wordsResult.map(item => item.words).join(' '); // 旅游保险关键词 if (fullText.includes('旅游') || fullText.includes('旅行') || fullText.includes('境外') || fullText.includes('航空')) { return 'travel'; } // 健康保险关键词 if (fullText.includes('医疗') || fullText.includes('重疾') || fullText.includes('住院') || fullText.includes('疾病')) { return 'health'; } // 财产保险关键词 if (fullText.includes('财产') || fullText.includes('家财') || fullText.includes('车险') || fullText.includes('车辆')) { return 'property'; } // 标准保单关键词 if (fullText.includes('保单') || fullText.includes('保险单') || fullText.includes('保险合同')) { return 'standard'; } return 'unknown'; } /** * 高级保单OCR解析函数 * 根据保单类型自动选择最合适的解析策略,并进行自动修复 * @param {Object} ocrResult - 百度OCR API返回的结果 * @returns {Object} - 处理后的结果 */ function advancedParseInsurancePolicy(ocrResult) { try { // 1. 识别保单类型 const policyType = identifyPolicyType(ocrResult); // 2. 根据保单类型选择解析模式 let mode = 'smart'; switch (policyType) { case 'travel': case 'health': case 'property': mode = 'universal'; // 对于特殊类型的保单,使用通用解析 break; case 'standard': default: mode = 'smart'; // 对于标准保单,使用智能解析 break; } // 3. 解析保单 const parsedResult = parseInsurancePolicy(ocrResult, { mode }); // 4. 自动修复常见问题 const fixedResult = autoFixPolicyResult(parsedResult, ocrResult); // 5. 格式化结果 const formattedResult = formatPolicyResult(fixedResult); // 6. 验证结果 const validationResult = validatePolicyResult(formattedResult); return { success: true, result: formattedResult, validation: validationResult, policyType, raw: parsedResult }; } catch (error) { return { success: false, error: error.message }; } } /** * 增强版保单OCR解析函数 * 可以处理更复杂的格式和布局 * @param {Object} ocrResult - 百度OCR API返回的结果 * @returns {Object} - 提取的保单信息 */ function enhancedParseInsurancePolicyOcr(ocrResult) { // 基础解析 const basicInfo = parseInsurancePolicyOcr(ocrResult); // 如果基础解析没有提取到某些字段,尝试使用更复杂的方法 const wordsResult = ocrResult.words_result; // 尝试从上下文中提取更多信息 for (let i = 0; i < wordsResult.length; i++) { const currentLine = wordsResult[i].words; // 例如,如果产品名称没有提取到,尝试查找包含"计划"或"产品"的行 if (!basicInfo.productName) { if (currentLine.includes('计划') || currentLine.includes('产品')) { // 检查这是否可能是产品名称行 if (!currentLine.includes('保费') && !currentLine.includes('保险金额')) { basicInfo.productName = currentLine.trim(); } } } // 如果没有提取到投保人数,尝试在"被保险人人数"附近查找 if (!basicInfo.insuredCount && currentLine.includes('被保险人人数')) { // 检查下一行 if (i + 1 < wordsResult.length) { const nextLine = wordsResult[i + 1].words; const match = nextLine.match(/(\d+)/); if (match) { basicInfo.insuredCount = match[0]; } } } } return basicInfo; } /** * 通用保单OCR解析函数 * 适配更多不同的保单情况 * @param {Object} ocrResult - 百度OCR API返回的结果 * @returns {Object} - 提取的保单信息 */ function universalParseInsurancePolicyOcr(ocrResult) { // 检查OCR结果是否有效 if (!ocrResult || !ocrResult.words_result || !Array.isArray(ocrResult.words_result)) { throw new Error('无效的OCR结果'); } const wordsResult = ocrResult.words_result; const extractedInfo = { policyNumber: '', // 保单号 insuranceCompany: '', // 保险公司名称 productName: '', // 保险产品名称 startDate: '', // 保险起期 endDate: '', // 保险止期 premiumPerPerson: '', // 每人保费 insuredCount: '', // 投保人数 totalPremium: '', // 总保费 policyholder: '', // 投保人 policyholderIdType: '', // 投保人证件类型 policyholderIdNumber: '', // 投保人证件号码 }; // 将OCR结果转换为纯文本,用于全文搜索 const fullText = wordsResult.map(item => item.words).join(' '); // 遍历OCR结果,提取所需字段 for (let i = 0; i < wordsResult.length; i++) { const item = wordsResult[i].words; const nextItem = i + 1 < wordsResult.length ? wordsResult[i + 1].words : ''; // 提取保单号 for (const keyword of FIELD_KEYWORDS.policyNumber) { if (item.includes(keyword)) { const match = item.match(new RegExp(`${keyword}[::]*\\s*(.+)`)); if (match && match[1]) { extractedInfo.policyNumber = match[1].trim(); } else if (nextItem && /^[A-Za-z0-9-]+$/.test(nextItem.trim())) { // 如果下一行看起来像是一个单独的号码 extractedInfo.policyNumber = nextItem.trim(); } break; } } // 提取保险公司名称 for (const keyword of FIELD_KEYWORDS.insuranceCompany) { if (item.includes(keyword)) { if (keyword === '保险公司') { // 如果只是"保险公司"关键词,需要提取完整名称 const match = item.match(/(.+保险公司)/); if (match) { extractedInfo.insuranceCompany = match[1].trim(); } } else { // 对于包含"保险股份有限公司"等完整名称的情况 extractedInfo.insuranceCompany = item.trim(); } break; } } // 提取保险产品名称 for (const keyword of FIELD_KEYWORDS.productName) { if (item.includes(keyword)) { const match = item.match(new RegExp(`${keyword}[::]*\\s*(.+)`)); if (match && match[1]) { extractedInfo.productName = match[1].trim(); } else if (nextItem && !nextItem.includes(':') && !nextItem.includes(':')) { // 如果下一行看起来像是产品名称 extractedInfo.productName = nextItem.trim(); } break; } } // 提取保险起期 for (const keyword of FIELD_KEYWORDS.startDate) { if (item.includes(keyword)) { let dateStr = ''; const match = item.match(new RegExp(`${keyword}[((][^))]+[))]*[::]*\\s*(.+)`)) || item.match(new RegExp(`${keyword}[::]*\\s*(.+)`)); if (match && match[1]) { dateStr = match[1].trim(); } else if (nextItem && /\d/.test(nextItem)) { // 如果下一行包含数字,可能是日期 dateStr = nextItem.trim(); } if (dateStr) { extractedInfo.startDate = normalizeDate(dateStr); } break; } } // 提取保险止期 for (const keyword of FIELD_KEYWORDS.endDate) { if (item.includes(keyword)) { let dateStr = ''; const match = item.match(new RegExp(`${keyword}[((][^))]+[))]*[::]*\\s*(.+)`)) || item.match(new RegExp(`${keyword}[::]*\\s*(.+)`)); if (match && match[1]) { dateStr = match[1].trim(); } else if (nextItem && /\d/.test(nextItem)) { // 如果下一行包含数字,可能是日期 dateStr = nextItem.trim(); } if (dateStr) { extractedInfo.endDate = normalizeDate(dateStr); } break; } } } // 继续提取其他字段 for (let i = 0; i < wordsResult.length; i++) { const item = wordsResult[i].words; const nextItem = i + 1 < wordsResult.length ? wordsResult[i + 1].words : ''; // 提取每人保费 for (const keyword of FIELD_KEYWORDS.premiumPerPerson) { if (item.includes(keyword)) { const match = item.match(new RegExp(`${keyword}[::]*\\s*(.+)`)); if (match && match[1]) { extractedInfo.premiumPerPerson = extractAmount(match[1]); } else if (nextItem && /\d/.test(nextItem)) { // 如果下一行包含数字,可能是金额 extractedInfo.premiumPerPerson = extractAmount(nextItem); } break; } } // 提取投保人数 for (const keyword of FIELD_KEYWORDS.insuredCount) { if (item.includes(keyword)) { const match = item.match(new RegExp(`${keyword}[::]*\\s*(\\d+)`)); if (match && match[1]) { extractedInfo.insuredCount = match[1].trim(); } else if (nextItem && /^\d+$/.test(nextItem.trim())) { // 如果下一行是纯数字 extractedInfo.insuredCount = nextItem.trim(); } else { // 尝试从当前行提取任何数字 const numMatch = item.match(/(\d+)/); if (numMatch) { extractedInfo.insuredCount = numMatch[1]; } } break; } } // 提取总保费 for (const keyword of FIELD_KEYWORDS.totalPremium) { if (item.includes(keyword)) { const match = item.match(new RegExp(`${keyword}[::]*\\s*(.+)`)) || item.match(new RegExp(`${keyword}[^::]+[::]*\\s*(.+)`)); if (match && match[1]) { extractedInfo.totalPremium = extractAmount(match[1]); } else if (nextItem && /\d/.test(nextItem)) { // 如果下一行包含数字,可能是金额 extractedInfo.totalPremium = extractAmount(nextItem); } break; } } // 提取投保人 for (const keyword of FIELD_KEYWORDS.policyholder) { if (item.includes(keyword)) { const match = item.match(new RegExp(`${keyword}[::]*\\s*(.+)`)); if (match && match[1]) { extractedInfo.policyholder = match[1].trim(); } else if (nextItem && !nextItem.includes(':') && !nextItem.includes(':')) { // 如果下一行看起来像是名称 extractedInfo.policyholder = nextItem.trim(); } break; } } // 提取投保人证件类型 for (const keyword of FIELD_KEYWORDS.policyholderIdType) { if (item.includes(keyword)) { const match = item.match(new RegExp(`${keyword}[::]*\\s*(.+)`)); if (match && match[1]) { extractedInfo.policyholderIdType = match[1].trim(); } else if (nextItem && !nextItem.includes(':') && !nextItem.includes(':')) { // 如果下一行看起来像是证件类型 extractedInfo.policyholderIdType = nextItem.trim(); } break; } } // 提取投保人证件号码 for (const keyword of FIELD_KEYWORDS.policyholderIdNumber) { if (item.includes(keyword)) { const match = item.match(new RegExp(`${keyword}[::]*\\s*(.+)`)); if (match && match[1]) { extractedInfo.policyholderIdNumber = match[1].trim(); } else if (nextItem && /^[A-Za-z0-9]+$/.test(nextItem.trim())) { // 如果下一行看起来像是证件号码 extractedInfo.policyholderIdNumber = nextItem.trim(); } break; } } } return extractedInfo; } /** * @module ocrParser * @description 保单OCR解析工具模块,提供多种解析策略和辅助函数 * @author 前端团队 * @version 1.0.0 */ /** * 导出所有可用函数 */ export { // 主要解析函数 parseInsurancePolicyOcr, // 基础解析函数 enhancedParseInsurancePolicyOcr, // 增强版解析函数 universalParseInsurancePolicyOcr, // 通用解析函数 smartParseInsurancePolicyOcr, // 智能解析函数 parseInsurancePolicy, // 主要解析入口函数 advancedParseInsurancePolicy, // 高级解析函数 // 辅助函数 extractPattern, // 提取特定模式的信息 findLineWithKeyword, // 搜索特定关键词的行 findLineWithAnyKeyword, // 搜索包含任一关键词的行 // 验证和格式化函数 validatePolicyResult, // 验证解析结果 formatPolicyResult, // 格式化解析结果 autoFixPolicyResult, // 自动修复解析结果 // 工具函数 identifyPolicyType, // 识别保单类型 processInsurancePolicyOcr, // 完整的处理流程 }; /** * 单元测试示例 * 以下是一些单元测试示例,展示如何测试这些函数 * * 注意:这些示例仅供参考,实际使用时应根据项目需求进行调整 */ /* // 示例1:测试基本解析函数 test('parseInsurancePolicyOcr should extract basic policy information', () => { const mockOcrResult = { words_result: [ { words: '保单号:ABC123456789' }, { words: '某某保险股份有限公司' }, { words: '保险计划名称:综合意外保险' }, { words: '保险起期(生效日):2023-01-01' }, { words: '保险止期(到期日):2023-12-31' }, { words: '保费/人:100.00' }, { words: '被保险人人数:10' }, { words: '总保费合计/Total Premium:1000.00' }, { words: '投保人:张三' }, { words: '投保人证件类型:身份证' }, { words: '投保人证件号码:123456789012345678' } ] }; const result = parseInsurancePolicyOcr(mockOcrResult); expect(result.policyNumber).toBe('ABC123456789'); expect(result.insuranceCompany).toBe('某某保险股份有限公司'); expect(result.productName).toBe('综合意外保险'); expect(result.startDate).toBe('2023-01-01'); expect(result.endDate).toBe('2023-12-31'); expect(result.premiumPerPerson).toBe('100.00'); expect(result.insuredCount).toBe('10'); expect(result.totalPremium).toBe('1000.00'); expect(result.policyholder).toBe('张三'); expect(result.policyholderIdType).toBe('身份证'); expect(result.policyholderIdNumber).toBe('123456789012345678'); }); // 示例2:测试智能解析函数 test('smartParseInsurancePolicyOcr should handle complex policy formats', () => { const mockOcrResult = { words_result: [ { words: '保单编号' }, { words: 'XYZ987654321' }, { words: '中国人寿保险公司' }, { words: '旅游意外保险' }, { words: '起保日期' }, { words: '2023年05月01日' }, { words: '终保日期' }, { words: '2023年05月15日' }, { words: '每人保费' }, { words: '¥200元' }, { words: '参保人数' }, { words: '5' }, { words: '保费合计' }, { words: '¥1000元' } ] }; const result = smartParseInsurancePolicyOcr(mockOcrResult); expect(result.policyNumber).toBe('XYZ987654321'); expect(result.insuranceCompany).toBe('中国人寿保险公司'); expect(result.productName).toBe('旅游意外保险'); expect(result.startDate).toBe('2023-05-01'); expect(result.endDate).toBe('2023-05-15'); expect(result.premiumPerPerson).toBe('200'); expect(result.insuredCount).toBe('5'); expect(result.totalPremium).toBe('1000'); }); // 示例3:测试验证函数 test('validatePolicyResult should identify issues in policy data', () => { const invalidResult = { policyNumber: 'ABC123', insuranceCompany: '某某保险公司', productName: '意外险', startDate: '2023-01-01', endDate: '2022-12-31', // 结束日期早于开始日期 premiumPerPerson: '100', insuredCount: '10', totalPremium: '1500' // 总保费与每人保费和投保人数不匹配 }; const { isValid, issues } = validatePolicyResult(invalidResult); expect(isValid).toBe(false); expect(issues).toContain('保险起期晚于止期'); expect(issues).toContain('总保费与每人保费和投保人数不匹配'); }); // 示例4:测试格式化函数 test('formatPolicyResult should standardize policy data', () => { const rawResult = { policyNumber: 'ABC123', insuranceCompany: '某某保险公司', productName: '意外险', startDate: '2023/01/01', endDate: '2023/12/31', premiumPerPerson: '100', insuredCount: '10', totalPremium: '1000' }; const formatted = formatPolicyResult(rawResult); expect(formatted.startDate).toBe('2023-01-01'); expect(formatted.endDate).toBe('2023-12-31'); expect(formatted.premiumPerPerson).toBe('100.00'); expect(formatted.totalPremium).toBe('1000.00'); expect(formatted.insurancePeriod).toBe(364); // 计算保险期限 }); */ /** * 常见问题解决方案 */ /* * 问题1:OCR结果中的日期格式不一致 * 解决方案:使用normalizeDate函数标准化日期格式 * * 示例: * const date1 = normalizeDate('2023/01/01'); * const date2 = normalizeDate('2023年1月1日'); * const date3 = normalizeDate('01-01-2023'); * // 所有结果都会是 '2023-01-01' */ /* * 问题2:OCR识别出的金额包含货币符号和单位 * 解决方案:使用extractAmount函数提取纯数字金额 * * 示例: * const amount1 = extractAmount('¥100.00元'); * const amount2 = extractAmount('RMB 100.00'); * const amount3 = extractAmount('100.00'); * // 所有结果都会是 '100.00' */ /* * 问题3:OCR结果中的保单号格式不规范 * 解决方案:在autoFixPolicyResult函数中添加特定的修复逻辑 * * 示例: * // 如果保单号包含多余的文本,尝试提取纯数字和字母部分 * if (result.policyNumber && result.policyNumber.length > 30) { * const match = result.policyNumber.match(/[A-Za-z0-9-]{5,25}/); * if (match) { * result.policyNumber = match[0]; * } * } */ /* * 问题4:不同保险公司的保单格式差异很大 * 解决方案:使用identifyPolicyType函数识别保单类型,然后选择合适的解析策略 * * 示例: * const policyType = identifyPolicyType(ocrResult); * let parseFunction; * switch (policyType) { * case 'travel': * parseFunction = parseTravelInsurance; * break; * case 'health': * parseFunction = parseHealthInsurance; * break; * default: * parseFunction = parseStandardInsurance; * break; * } * const result = parseFunction(ocrResult); */ /* * 问题5:OCR结果中的某些字段缺失 * 解决方案:使用autoFixPolicyResult函数尝试从上下文中推断缺失的字段 * * 示例: * // 如果没有提取到总保费但有每人保费和投保人数,尝试计算 * if (!result.totalPremium && result.premiumPerPerson && result.insuredCount) { * const premium = parseFloat(result.premiumPerPerson); * const count = parseInt(result.insuredCount, 10); * if (!isNaN(premium) && !isNaN(count)) { * result.totalPremium = (premium * count).toFixed(2); * } * } */ /** * 使用说明: * * 1. 基本用法: * import { parseInsurancePolicy } from './utils/ocrParser'; * const result = parseInsurancePolicy(ocrResult); * * 2. 指定解析模式: * const result = parseInsurancePolicy(ocrResult, { mode: 'universal' }); * * 3. 获取调试信息: * const { result, debug } = parseInsurancePolicy(ocrResult, { debug: true }); * * 4. 验证解析结果: * import { validatePolicyResult } from './utils/ocrParser'; * const { isValid, issues } = validatePolicyResult(result); * * 5. 格式化解析结果: * import { formatPolicyResult } from './utils/ocrParser'; * const formattedResult = formatPolicyResult(result); * * 6. 完整的处理流程: * import { processInsurancePolicyOcr } from './utils/ocrParser'; * const { success, result, validation } = processInsurancePolicyOcr(ocrResult); */