200 lines
7.1 KiB
Python
200 lines
7.1 KiB
Python
# -*- 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")
|