第2245篇:保险理赔AI——理赔文件自动审核的工程实现
2026/4/30大约 6 分钟
第2245篇:保险理赔AI——理赔文件自动审核的工程实现
适读人群:保险科技工程师、Java开发者、AI产品经理 | 阅读时长:约15分钟 | 核心价值:从保险理赔的真实业务流程出发,实现文件识别、信息抽取、反欺诈检测的完整工程方案
保险理赔大概是最让普通用户抓狂的体验之一——提交一堆文件,等着审核,不知道什么时候能出结果,有时候还要反复补材料。
从保险公司的角度来说,理赔审核也很头疼:理赔文件种类繁多(病历、发票、诊断书、事故报告、车损照片……),格式千差万别,审核员要逐一核对,效率极低。更重要的是,医疗理赔欺诈是个行业痼疾,虚假票据、重复理赔、夸大损失的情况并不少见。
我参与过一家财险公司的理赔自动化项目。项目开始时,理赔员平均每天处理20-30个案子,60%的时间花在文件核对和信息录入上。系统上线后,常规案子可以做到秒级自动审核,理赔员只需要处理复杂案例和系统标记的疑似欺诈案例。
理赔审核系统架构
文件OCR与版面分析
理赔文件的OCR不是简单的文字识别,还需要理解文件的版面结构:
@Service
public class ClaimDocumentProcessor {
@Autowired
private OCRService ocrService;
@Autowired
private DocumentLayoutAnalyzer layoutAnalyzer;
@Autowired
private LLMClient llmClient;
/**
* 处理理赔文件,提取结构化信息
*/
public ClaimDocumentInfo process(ClaimDocument document) {
// 1. OCR识别
OCRResult ocrResult = ocrService.recognize(document.getImageData());
// 2. 文件类型识别
DocumentType docType = classifyDocumentType(ocrResult, document);
// 3. 根据文件类型抽取关键信息
return switch (docType) {
case MEDICAL_INVOICE -> extractMedicalInvoiceInfo(ocrResult);
case HOSPITAL_DISCHARGE_SUMMARY -> extractDischargeSummaryInfo(ocrResult);
case DIAGNOSIS_CERTIFICATE -> extractDiagnosisCertificateInfo(ocrResult);
case ACCIDENT_REPORT -> extractAccidentReportInfo(ocrResult);
case VEHICLE_REPAIR_INVOICE -> extractVehicleRepairInfo(ocrResult);
default -> extractGenericInfo(ocrResult, docType);
};
}
/**
* 医疗发票信息抽取
* 关键字段:医院名称、就诊日期、项目明细、费用金额、发票号
*/
private ClaimDocumentInfo extractMedicalInvoiceInfo(OCRResult ocrResult) {
String rawText = ocrResult.getFullText();
// 用LLM做结构化抽取(比规则更鲁棒)
String prompt = String.format("""
从以下医疗发票OCR文本中提取关键信息,以JSON格式输出:
OCR文本:
%s
需要提取的字段:
- hospital_name: 医院名称
- patient_name: 患者姓名
- invoice_date: 发票日期(YYYY-MM-DD格式)
- invoice_number: 发票号码
- total_amount: 总金额(数字,单位元)
- medical_insurance_amount: 医保支付金额(如有)
- self_pay_amount: 自费金额
- diagnosis: 诊断名称(如有)
- item_details: 费用明细列表,每项包含name和amount
注意:如果某字段无法识别,返回null。金额统一返回数字,不含单位。
""", rawText);
LLMResponse response = llmClient.complete(
"你是医疗文件信息抽取专家。",
prompt,
LLMConfig.builder().temperature(0.0).responseFormat(ResponseFormat.JSON).build()
);
MedicalInvoiceInfo info = parseJsonResponse(response.getContent(),
MedicalInvoiceInfo.class);
// 数据校验
validateMedicalInvoice(info, ocrResult);
return ClaimDocumentInfo.fromMedicalInvoice(info);
}
/**
* 发票校验:金额一致性、格式合规性
*/
private void validateMedicalInvoice(MedicalInvoiceInfo info, OCRResult ocrResult) {
// 校验明细金额之和是否等于总金额
if (info.getItemDetails() != null && !info.getItemDetails().isEmpty()) {
double detailSum = info.getItemDetails().stream()
.mapToDouble(MedicalItem::getAmount).sum();
if (info.getTotalAmount() != null &&
Math.abs(detailSum - info.getTotalAmount()) > 0.01) {
info.addValidationWarning(String.format(
"明细金额合计%.2f与票面总金额%.2f不一致",
detailSum, info.getTotalAmount()));
}
}
// 发票号码格式校验(不同省份格式不同)
if (info.getInvoiceNumber() != null) {
if (!isValidInvoiceNumber(info.getInvoiceNumber())) {
info.addValidationWarning("发票号码格式疑似异常: " + info.getInvoiceNumber());
}
}
}
}反欺诈检测:多维度异常识别
@Service
public class ClaimFraudDetector {
@Autowired
private ClaimHistoryRepository claimHistoryRepo;
@Autowired
private MedicalKnowledgeBase medicalKB;
/**
* 综合欺诈检测
*/
public FraudDetectionResult detect(ClaimApplication claim) {
List<FraudSignal> signals = new ArrayList<>();
// 1. 重复理赔检测:同一份发票是否已经被理赔过
signals.addAll(detectDuplicateClaims(claim));
// 2. 频率异常:近期理赔次数异常多
signals.addAll(detectClaimFrequencyAnomaly(claim.getPolicyholderId()));
// 3. 金额异常:与历史同类理赔相比,金额明显偏高
signals.addAll(detectAmountAnomaly(claim));
// 4. 医疗逻辑一致性:诊断与治疗项目是否匹配
signals.addAll(detectMedicalLogicInconsistency(claim));
// 5. 时间线异常:就诊时间、出院时间是否合理
signals.addAll(detectTimelineAnomaly(claim));
// 综合评分
double fraudScore = calculateFraudScore(signals);
RiskLevel riskLevel = determineRiskLevel(fraudScore, signals);
return FraudDetectionResult.builder()
.fraudScore(fraudScore)
.riskLevel(riskLevel)
.signals(signals)
.recommendation(buildRecommendation(riskLevel, signals))
.build();
}
/**
* 重复理赔检测
* 通过发票号、金额、医院、日期的组合来识别重复提交
*/
private List<FraudSignal> detectDuplicateClaims(ClaimApplication claim) {
List<FraudSignal> signals = new ArrayList<>();
for (ClaimDocument doc : claim.getDocuments()) {
if (doc.getDocumentInfo() instanceof MedicalInvoiceInfo invoice) {
// 查找相同发票号的历史理赔
List<ClaimHistory> duplicates = claimHistoryRepo.findByInvoiceNumber(
invoice.getInvoiceNumber());
// 排除当前申请本身
duplicates = duplicates.stream()
.filter(h -> !h.getClaimId().equals(claim.getClaimId()))
.collect(Collectors.toList());
if (!duplicates.isEmpty()) {
signals.add(FraudSignal.critical(
FraudType.DUPLICATE_CLAIM,
String.format("发票号%s已在%s申请理赔(案件ID:%s)",
invoice.getInvoiceNumber(),
duplicates.get(0).getClaimDate(),
duplicates.get(0).getClaimId())
));
}
}
}
return signals;
}
/**
* 医疗逻辑一致性检测
* 例如:感冒诊断但有骨科手术费用,心内科就诊但有眼科药品费用
*/
private List<FraudSignal> detectMedicalLogicInconsistency(ClaimApplication claim) {
List<FraudSignal> signals = new ArrayList<>();
// 提取所有医疗文件的诊断和费用项目
List<String> diagnoses = extractAllDiagnoses(claim);
List<MedicalItem> allItems = extractAllMedicalItems(claim);
if (diagnoses.isEmpty() || allItems.isEmpty()) return signals;
// 检查每个费用项目是否与诊断相关
for (MedicalItem item : allItems) {
if (item.getAmount() > 500) { // 只检查大额项目
boolean isRelated = medicalKB.isItemRelatedToDiagnoses(
item.getName(), diagnoses);
if (!isRelated) {
signals.add(FraudSignal.warning(
FraudType.MEDICAL_LOGIC_INCONSISTENCY,
String.format("费用项目[%s](%.0f元)与诊断[%s]相关性存疑",
item.getName(), item.getAmount(),
String.join(", ", diagnoses))
));
}
}
}
return signals;
}
}保单条款自动匹配
理赔前需要验证是否在保险范围内:
@Service
public class PolicyCoverageChecker {
@Autowired
private PolicyRepository policyRepo;
@Autowired
private LLMClient llmClient;
/**
* 用LLM理解保单条款,判断理赔是否在保险范围内
* 保险条款往往很复杂,规则引擎很难覆盖所有情况
*/
public CoverageCheckResult checkCoverage(ClaimApplication claim) {
Policy policy = policyRepo.findById(claim.getPolicyId())
.orElseThrow(() -> new PolicyNotFoundException(claim.getPolicyId()));
// 提取理赔摘要
String claimSummary = buildClaimSummary(claim);
// 找到相关条款
String relevantClauses = findRelevantClauses(policy, claim);
String prompt = String.format("""
你是一名保险理赔核损专家,请根据以下保险条款判断本次理赔是否应予赔付。
理赔情况:
%s
相关保险条款:
%s
请分析:
1. 本次理赔是否在保险责任范围内?
2. 是否存在除外责任(如预先存在的疾病、不在等待期等)?
3. 可赔付金额上限(根据条款的赔付比例和免赔额计算)
4. 如有不确定性,说明需要核实的信息
输出格式:
- 结论:赔付/拒赔/部分赔付/需核实
- 依据:具体条款引用
- 可赔金额:(数字)
- 补充说明:
""",
claimSummary,
relevantClauses
);
LLMResponse response = llmClient.complete(
"你是保险理赔专家,精通各类险种条款解读。",
prompt,
LLMConfig.builder().model("deepseek-v3").temperature(0.1).build()
);
return parseCoverageCheckResult(response.getContent());
}
}自动化审核的边界
这套系统上线后,我们制定了一个分级自动化策略:
全自动通过(无需人工):
- 无欺诈信号
- 金额在5000元以下
- 文件完整,信息一致
- 客户历史理赔记录良好
系统建议+人工确认:
- 金额5000-5万元
- 有轻微欺诈信号但证据不足
- 部分文件信息有疑问
人工调查:
- 发现重复理赔信号
- 金额超过5万元
- 医疗逻辑明显不合理
- 客户有历史欺诈记录
上线6个月的数据:自动化案件占比从0提升到67%,平均理赔时效从5.3天降到1.2天,欺诈拦截率提升了40%。但最重要的成果是,理赔员从重复性的文件核对工作中解放出来,可以专注于真正需要判断力的复杂案例。
