# -*- coding: utf-8 -*- """ 票据理赔自动化最小化实现 功能清单 https://liubiren.feishu.cn/docx/WFjTdBpzroUjQvxxrNIcKvGnneh?from=from_copylink """ from datetime import datetime from pathlib import Path from typing import Any, Dict, List import pandas from jinja2 import Environment, FileSystemLoader from common import dossier, rule_engine from image import image_classify from image import image_recognize if __name__ == "__main__": # 初始化工作目录路径 workplace_path = Path("directory") workplace_path.mkdir(parents=True, exist_ok=True) # 若工作目录不存在则创建 # 实例化JINJA2环境 environment = Environment(loader=FileSystemLoader(".")) # 添加DATE过滤器 environment.filters["date"] = lambda date: ( date.strftime("%Y-%m-%d") if date else "长期" ) # 加载赔案档案模版 template = environment.get_template("template.html") # ------------------------- # 自定义方法 # ------------------------- # noinspection PyShadowingNames def case_adjust() -> None: """ 理算赔案并整合至赔案档案 :return: 无 """ def receipt_adjust(row: pandas.Series) -> List[Dict[str, Any]]: """ 票据理算 :param row: 票据 :return: 理算记录 """ date = row["date"] current_type = row["就诊类型"] current_amount = row["合理金额"] remaining_claim = current_amount claim_details = [] if current_amount <= 0: return [] # 筛选有效保单并排序 valid_rules = sorted( [ r for r in policy_rules if current_type in r["就诊类型"] and r["生效日期"] <= current_date <= r["失效日期"] and r["剩余额度"] > 0.0 ], key=lambda x: x["剩余额度"], reverse=True, ) # 循环分摊赔付,生成分明细列表 for rule in valid_rules: if remaining_claim <= 0.0: break pay_ratio = rule["赔付比例"] rule_name = rule["责任名称"] remaining_quota = rule["剩余额度"] max_payable = remaining_claim * pay_ratio actual_pay = min(remaining_quota, max_payable) if actual_pay > 0.0: corresponding_actual_amount = actual_pay / pay_ratio # 构建明细字典(字段与后续DataFrame列对应) detail = { "就诊类型": current_type, "就诊合理金额": current_amount, "保单责任名称": rule_name, "保单赔付比例": pay_ratio, "保单本次赔付金额": round(actual_pay, 2), "本次对应合理金额部分": round(corresponding_actual_amount, 2), "保单赔付后剩余额度": round(remaining_quota - actual_pay, 2), } claim_details.append(detail) # 更新保单额度和剩余待赔付金额 rule["剩余额度"] -= actual_pay remaining_claim -= corresponding_actual_amount return claim_details # 基于据拒付规则评估 if not (result := rule_engine.evaluate(decision="拒付", inputs=dossier)): # TODO: 若评估结果为空值(保险分公司未配置拒付规则)则流转至人工处理 raise dossier["adjustment_layer"].update( { "conclusion": result["conclusion"], # 理赔结论 "explanation": result["explanation"], # 结论说明 } ) if result["conclusion"] == "拒付": return adjustments = ( pandas.DataFrame(dossier["receipts_layer"]).assing( adjustments=lambda dataframe: dataframe.apply( receipt_adjust, axis="columns" ) ) ).explode("adjustments", ignore_index=True) print(adjustments) # 遍历工作目录中赔案目录并创建赔案档案(模拟自动化域就待自动化任务创建理赔档案) for case_path in [x for x in workplace_path.iterdir() if x.is_dir()]: # 初始化赔案档案(保险公司将提供投保公司、保险分公司和报案时间等,TPA作业系统签收后生成赔案号) dossier["report_layer"].update( { "report_time": datetime(2025, 7, 25, 12, 0, 0), # 指定报案时间 "case_number": case_path.stem, # 设定:赔案目录名称为赔案号 "insurer_company": ( insurer_company := "中银保险有限公司苏州分公司" ), # 指定保险分公司 } ) # 遍历赔案目录中影像件 for image_index, image_path in enumerate( sorted( [ x for x in case_path.glob(pattern="*") if x.is_file() and x.suffix.lower() in [".jpg", ".jpeg", ".png"] ], # 实际作业亦仅支持JPG、JPEG或PNG key=lambda x: x.stat().st_ctime, # 根据影像件创建时间顺序排序 ), 1, ): # 分类影像件并旋正(较初审自动化无使能检查) image_classify(image_index, image_path) # 就影像件层按照影像件类型指定排序 dossier["images_layer"].sort( key=lambda x: [ "居民户口簿", "居民身份证(国徽面)", "居民身份证(头像面)", "居民身份证(国徽、头像面)", "中国港澳台地区及境外护照", "理赔申请书", "增值税发票", "医疗门诊收费票据", "医疗住院收费票据", "医疗费用清单", "银行卡", "其它", ].index(x["image_type"]) ) # 遍历影像件层中影像件 for image in dossier["images_layer"]: # 识别影像件并整合至赔案档案 image_recognize( image, insurer_company, ) # 就票据层按照开票日期和票据号顺序排序 dossier["receipts_layer"].sort(key=lambda x: (x["date"], x["number"])) print(dossier["insured_persons_layer"]) exit() # 理算 case_adjust() print(dossier["adjustment_layer"]) for receipt in dossier["receipts_layer"]: print(receipt) print(dossier["report_layer"]) print(dossier["insured_person_layer"]) print(dossier["insured_persons_layer"]) dossier.pop("images_layer") dossier.pop("receipts_layer")