Python/票据理赔自动化/case.py

247 lines
9.5 KiB
Python
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.

# -*- coding: utf-8 -*-
"""通用模块"""
from decimal import Decimal, ROUND_HALF_UP
from pathlib import Path
from typing import Any, Dict, List
from datetime import datetime
from jinja2 import Environment, FileSystemLoader
import pandas
from common import masterdata, rules_engine
def case_adjust(dossier: Dict[str, Any]) -> None:
"""
理算赔案并整合至赔案档案
:param dossier: 赔案档案
:return: 无
"""
# 基于拒付决策规则评估
if not (result := rules_engine.evaluate(decision="拒付", inputs=dossier)):
raise RuntimeError("该保险分公司未配置拒付规则")
dossier["adjustment_layer"].update(
{
"conclusion": (conclusion := result["conclusion"]), # 理赔结论
"explanation": result["explanation"], # 结论说明
}
)
if conclusion == "拒付":
return
# 赔案理算记录
receipts_adjustments = (
(
pandas.DataFrame(data=dossier["receipts_layer"]).assign(
receipt_adjustments=lambda dataframe: dataframe.apply(
lambda row: receipt_adjust(
row=row, liabilities=dossier["liabilities_layer"]
),
axis="columns",
) # 票据理算
)
)
.explode(column="receipt_adjustments", ignore_index=True)
.pipe(
lambda dataframe: pandas.concat(
[
dataframe.drop(
[
"receipt_adjustments",
],
axis="columns",
),
pandas.json_normalize(dataframe["receipt_adjustments"]),
],
axis="columns",
)
)
)
dossier["receipts_layer"] = receipts_adjustments.to_dict(orient="records")
print(dossier["receipts_layer"])
# 赔案理算金额
dossier["adjustment_layer"].update(
{
"adjustment_amount": (
receipts_adjustments["adjustment_amount"]
.sum()
.quantize(
Decimal("0.00"),
rounding=ROUND_HALF_UP,
)
), # 理算金额
}
)
# 实例化JINJA2
environment = Environment(
loader=FileSystemLoader(file_path := Path(__file__).parent)
)
# 添加过滤器
environment.filters["DateTime"] = lambda i: (
i.strftime("%Y-%m-%d") if i != datetime(9999, 12, 31) else "长期"
)
# 加载赔案档案模版
template = environment.get_template("template.html")
with open(
file_path / f"dossiers/{dossier["report_layer"]["case_number"]}.html",
"w",
encoding="utf-8",
) as file:
file.write(
template.render(
{
"dossier": dossier,
}
)
)
def receipt_adjust(
row: pandas.Series, liabilities: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""
理算票据
:param row: 票据数据
:param liabilities: 理算责任
:return: 理算记录
"""
# 初始化票据理算记录
receipt_adjustments = []
# 初始化票据剩余可理算金额
remaining_adjustable_amount = (
row["personal_self_payment"]
+ row["non_medical_payment"]
+ row["reasonable_amount"]
).quantize( # type: ignore[reportAttributeAccessIssue]
Decimal("0.00"),
rounding=ROUND_HALF_UP,
)
# 遍历所有理赔责任,根据出险人、出险事故、查验状态和出险日期匹配理赔责任
for liability in liabilities:
if (
row["payer"] in [liability["insured_person"] for liability in liabilities]
and row["accident"] == liability["accident"]
and row["verification"] in ["真票", "无法查验"]
and liability["commencement_date"]
<= row["date"]
<= liability["termination_date"]
):
# 个单余额
remaining_amount = masterdata.query_remaining_amount(
person_policy_guid=liability["person_policy_guid"],
)
# 个人自费可理算金额
personal_self_adjustable_amount = (
row["personal_self_payment"]
* liability["personal_self_ratio"]
* Decimal("0.01")
)
# 个人自付可理算金额
non_medical_adjustable_amount = (
row["non_medical_payment"]
* liability["non_medical_ratio"]
* Decimal("0.01")
)
# 合理可理算金额
reasonable_adjustable_amount = (
row["reasonable_amount"]
* liability["reasonable_ratio"]
* Decimal("0.01")
)
# 理算金额
adjustment_amount = max(
Decimal("0.00"),
min(
remaining_adjustable_amount,
remaining_amount,
adjustable_amount := (
(
personal_self_adjustable_amount
+ non_medical_adjustable_amount
+ reasonable_adjustable_amount
).quantize(
Decimal("0.00"),
rounding=ROUND_HALF_UP,
)
), # 可理算金额
),
)
# 若理算金额大于0则新增保额扣减记录
if adjustment_amount > Decimal("0.00"):
masterdata.add_coverage_change(
person_policy_guid=liability["person_policy_guid"],
before_change_amount=remaining_amount,
change_amount=adjustment_amount,
)
receipt_adjustments.append(
{
"person_policy": liability["person_policy"], # 个单号
"liability": liability["liability"], # 理赔责任名称
"personal_self_payment": row[
"personal_self_payment"
], # 个人自费金额
"personal_self_ratio": liability[
"personal_self_ratio"
], # 个人自费比例
"personal_self_adjustable_amount": personal_self_adjustable_amount, # 个人自费可理算金额
"non_medical_payment": row["non_medical_payment"], # 个人自付金额
"non_medical_ratio": liability["non_medical_ratio"], # 个人自付比例
"non_medical_adjustable_amount": non_medical_adjustable_amount, # 个人自付可理算金额
"reasonable_amount": row["reasonable_amount"], # 合理可理算金额
"reasonable_ratio": liability["reasonable_ratio"], # 合理部分比例
"reasonable_adjustable_amount": reasonable_adjustable_amount, # 合理可理算金额
"adjustment_amount": adjustment_amount, # 理算金额
"adjustment_explanation": f"""
1、应理算金额{remaining_adjustable_amount:.2f}
2、个单余额{remaining_amount:.2f}
3、可理算金额{adjustable_amount:.2f},其中:
1个人自费可理算金额{personal_self_adjustable_amount:.2f}={row['personal_self_payment']:.2f}*{liability['personal_self_ratio']:.2f}%
2个人自付可理算金额{non_medical_adjustable_amount:.2f}={row['non_medical_payment']:.2f}*{liability['non_medical_ratio']:.2f}%
3合理部分可理算金额{reasonable_adjustable_amount:.2f}={row['reasonable_amount']:.2f}*{liability['reasonable_ratio']:.2f}%
4、理算金额{adjustment_amount:.2f},即上述应理算金额、个人余额和可理算金额的最小值。
""".replace(
"\n", ""
).replace(
" ", ""
), # 理算说明
}
)
remaining_adjustable_amount -= adjustment_amount
# 若剩余可理算金额小于等于0则跳出循环
if remaining_adjustable_amount <= Decimal("0.00"):
break
if not receipt_adjustments:
receipt_adjustments.append(
{
"person_policy": None, # 个单号
"liability": None, # 理赔责任名称
"personal_self_payment": None, # 个人自费金额
"personal_self_ratio": None, # 个人自费比例
"personal_self_adjustable_amount": None, # 个人自费可理算金额
"non_medical_payment": None, # 个人自付金额
"non_medical_ratio": None, # 个人自付比例
"non_medical_adjustable_amount": None, # 个人自付可理算金额
"reasonable_amount": None, # 合理可理算金额
"reasonable_ratio": None, # 合理部分比例
"reasonable_adjustable_amount": None, # 合理可理算金额
"adjustment_amount": Decimal("0.00"), # 理赔责任理算金额
"adjustment_explanation": "票据不予理算", # 理算说明
}
)
return receipt_adjustments