diff --git a/utils/html_render.py b/utils/html_render.py
new file mode 100644
index 0000000..abe3b9b
--- /dev/null
+++ b/utils/html_render.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+"""
+HTML渲染器
+"""
+
+from datetime import datetime
+from pathlib import Path
+from typing import Any, Dict
+
+from jinja2 import Environment, FileSystemLoader
+
+
+def datetime_to_str(field):
+ """
+ 渲染模板时,若字段为datetime对象则转为字符串
+ :param field: 字段
+ :return: 字符串
+ """
+ if isinstance(field, datetime):
+ if field == datetime(9999, 12, 31):
+ return "长期"
+ if field.hour == 0 and field.minute == 0 and field.second == 0:
+ return field.strftime("%Y-%m-%d")
+ return field.strftime("%Y-%m-%d %H:%M:%S")
+ else:
+ return field
+
+
+def str_to_str(field):
+ """
+ 渲染模板时,若字段为字符串则转空字符串
+ :param field: 字段
+ :return: 字符串
+ """
+ if isinstance(field, str):
+ return field
+ return ""
+
+
+class HTMLRenderer:
+ """
+ HTML渲染器,支持:
+ 基于模板,根据数据字典渲染HTML文档
+ """
+
+ def __init__(self, template_path: Path):
+ """
+ 初始化HTML渲染器
+ :param template_path: 模板路径
+ """
+ # 实例化jinja2环境
+ self.environment = Environment(
+ loader=FileSystemLoader(searchpath=template_path.parent)
+ )
+ # 设置过滤器
+ self.environment.filters.update(
+ {
+ "datetime_to_str": datetime_to_str,
+ "str_to_str": str_to_str,
+ }
+ )
+ # 加载指定模板
+ self.template = self.environment.get_template(template_path.name)
+
+ def render(self, obj: Dict[str, Any], output_path: Path) -> None:
+ """
+ 根据数据字典渲染HTML文档
+ :param obj: 数据字典
+ :param output_path: HTML文档输出路径
+ :return: 无
+ """
+ try:
+ with open(
+ file=output_path,
+ mode="w",
+ encoding="utf-8",
+ ) as file:
+ file.write(self.template.render(obj=obj)) # 在模板中需以obj获取键值
+
+ except Exception as exception:
+ print(f"根据数据字典渲染HTML文档发生异常:{str(exception)}")
diff --git a/票据理赔自动化/case.py b/票据理赔自动化/case.py
index 62ba2d9..4d229ea 100644
--- a/票据理赔自动化/case.py
+++ b/票据理赔自动化/case.py
@@ -3,13 +3,14 @@
from decimal import Decimal, ROUND_HALF_UP
from pathlib import Path
+import sys
from typing import Any, Dict, List
-from datetime import datetime
-from jinja2 import Environment, FileSystemLoader
-import pandas
from common import masterdata, rules_engine
+sys.path.append(Path(__file__).parent.parent.as_posix())
+from utils.html_render import HTMLRenderer
+
def case_adjust(dossier: Dict[str, Any]) -> None:
"""
@@ -30,139 +31,134 @@ def case_adjust(dossier: Dict[str, Any]) -> None:
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",
- ) # 票据理算
- )
+ # 就票据层按照开票日期和票据号顺序排序
+ dossier["receipts_layer"].sort(key=lambda x: (x["date"], x["number"]))
+
+ # 遍历票据层内所有票据,票据理算并添加理算记录
+ for idx, receipt in enumerate(dossier["receipts_layer"]):
+ # 添加理算记录
+ dossier["receipts_layer"][idx]["adjustments"] = receipt_adjust(
+ receipt=receipt, liabilities=dossier["liabilities_layer"]
)
- .explode(column="receipt_adjustments", ignore_index=True)
- .pipe(
- lambda dataframe: pandas.concat(
+ # 票据理算金额
+ dossier["receipts_layer"][idx]["adjustment_amount"] = Decimal(
+ sum(
[
- dataframe.drop(
- [
- "receipt_adjustments",
- ],
- axis="columns",
- ),
- pandas.json_normalize(dataframe["receipt_adjustments"]),
- ],
- axis="columns",
+ adjustment["adjustment_amount"]
+ for adjustment in dossier["receipts_layer"][idx]["adjustments"]
+ ]
)
+ ).quantize(
+ exp=Decimal("0.00"),
+ rounding=ROUND_HALF_UP,
)
- )
-
- 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"),
+ dossier["adjustment_layer"]["adjustment_amount"] = Decimal(
+ sum(
+ [
+ adjustment["adjustment_amount"]
+ for receipt in dossier["receipts_layer"]
+ for adjustment in receipt["adjustments"]
+ ]
+ )
+ ).quantize(
+ exp=Decimal("0.00"),
rounding=ROUND_HALF_UP,
)
- # 遍历所有理赔责任,根据出险人、出险事故、查验状态和出险日期匹配理赔责任
+ # 实例化HTML渲染器
+ html_renderer = HTMLRenderer(template_path=Path(__file__).parent / "template.html")
+ # 根据赔案档案渲染HTML文档
+ html_renderer.render(
+ obj=dossier,
+ output_path=Path(__file__).parent
+ / "dossiers"
+ / f"{dossier["report_layer"]["case_number"]}.html",
+ )
+
+
+def receipt_adjust(
+ receipt: Dict[str, Any], liabilities: List[Dict[str, Any]]
+) -> List[Dict[str, Any]]:
+ """
+ 理算票据
+ :param receipt: 票据数据字典
+ :param liabilities: 理算责任
+ :return: 理算记录
+ """
+ # 初始化理算记录
+ adjustments = []
+
+ # 初始化票据剩余理算金额
+ remaining_adjustment_amount = masterdata.query_remaining_adjustment_amount(
+ receipt_number=receipt["number"],
+ )
+ if remaining_adjustment_amount is None:
+ remaining_adjustment_amount = (
+ receipt["personal_self_payment"] # 个人自费金额
+ + receipt["non_medical_payment"] # 个人自付金额
+ + receipt["reasonable_amount"] # 合理金额
+ ).quantize(
+ exp=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 ["真票", "无法查验"]
+ receipt["payer"]
+ in [liability["insured_person"] for liability in liabilities]
+ and receipt["accident"] == liability["accident"]
+ and receipt["verification"] in ["真票", "无法查验"]
and liability["commencement_date"]
- <= row["date"]
+ <= receipt["date"]
<= liability["termination_date"]
):
- # 个单余额
- remaining_amount = masterdata.query_remaining_amount(
+ # 个单剩余保额
+ remaining_coverage_amount = masterdata.query_after_change_amount(
person_policy_guid=liability["person_policy_guid"],
)
# 个人自费可理算金额
personal_self_adjustable_amount = (
- row["personal_self_payment"]
- * liability["personal_self_ratio"]
+ receipt["personal_self_payment"] # 个人自费金额
+ * liability["personal_self_ratio"] # 个人自费理算比例
* Decimal("0.01")
+ ).quantize(
+ exp=Decimal("0.00"),
+ rounding=ROUND_HALF_UP,
)
# 个人自付可理算金额
non_medical_adjustable_amount = (
- row["non_medical_payment"]
- * liability["non_medical_ratio"]
+ receipt["non_medical_payment"] # 个人自付金额
+ * liability["non_medical_ratio"] # 个人自付理算比例
* Decimal("0.01")
+ ).quantize(
+ exp=Decimal("0.00"),
+ rounding=ROUND_HALF_UP,
)
# 合理可理算金额
reasonable_adjustable_amount = (
- row["reasonable_amount"]
- * liability["reasonable_ratio"]
+ receipt["reasonable_amount"] # 合理金额
+ * liability["reasonable_ratio"] # 合理理算比例
* Decimal("0.01")
+ ).quantize(
+ exp=Decimal("0.00"),
+ rounding=ROUND_HALF_UP,
)
# 理算金额
adjustment_amount = max(
Decimal("0.00"),
min(
- remaining_adjustable_amount,
- remaining_amount,
+ remaining_adjustment_amount, # 剩余理算金额
+ remaining_coverage_amount, # 个单剩余保额
adjustable_amount := (
(
- personal_self_adjustable_amount
- + non_medical_adjustable_amount
- + reasonable_adjustable_amount
+ personal_self_adjustable_amount # 个人自费可理算金额
+ + non_medical_adjustable_amount # 个人自付可理算金额
+ + reasonable_adjustable_amount # 合理可理算金额
).quantize(
Decimal("0.00"),
rounding=ROUND_HALF_UP,
@@ -175,35 +171,44 @@ def receipt_adjust(
if adjustment_amount > Decimal("0.00"):
masterdata.add_coverage_change(
person_policy_guid=liability["person_policy_guid"],
- before_change_amount=remaining_amount,
+ before_change_amount=remaining_coverage_amount,
change_amount=adjustment_amount,
)
- receipt_adjustments.append(
+ adjustments.append(
{
+ "group_policy": liability["group_policy"], # 团单号
"person_policy": liability["person_policy"], # 个单号
"liability": liability["liability"], # 理赔责任名称
- "personal_self_payment": row[
+ "accident": liability["accident"], # 理赔类型
+ "personal_self_payment": receipt[
"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_payment": receipt[
+ "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_amount": receipt["reasonable_amount"], # 合理金额
+ "reasonable_ratio": liability["reasonable_ratio"], # 合理理算比例
"reasonable_adjustable_amount": reasonable_adjustable_amount, # 合理可理算金额
+ "remaining_adjustment_amount": remaining_adjustment_amount, # 剩余理算金额
+ "remaining_coverage_amount": remaining_coverage_amount, # 个单剩余保额
+ "adjustable_amount": adjustable_amount, # 可理算金额
"adjustment_amount": adjustment_amount, # 理算金额
"adjustment_explanation": f"""
- 1、应理算金额:{remaining_adjustable_amount:.2f};
- 2、个单余额:{remaining_amount:.2f};
+ 1、剩余理算金额:{remaining_adjustment_amount:.2f};
+ 2、个单剩余保额:{remaining_coverage_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}%;
+ 1)个人自费可理算金额:{personal_self_adjustable_amount:.2f}={receipt['personal_self_payment']:.2f}*{liability['personal_self_ratio']:.2f}%;
+ 2)个人自付可理算金额:{non_medical_adjustable_amount:.2f}={receipt['non_medical_payment']:.2f}*{liability['non_medical_ratio']:.2f}%;
+ 3)合理部分可理算金额:{reasonable_adjustable_amount:.2f}={receipt['reasonable_amount']:.2f}*{liability['reasonable_ratio']:.2f}%;
4、理算金额:{adjustment_amount:.2f},即上述应理算金额、个人余额和可理算金额的最小值。
""".replace(
"\n", ""
@@ -213,28 +218,41 @@ def receipt_adjust(
}
)
- remaining_adjustable_amount -= adjustment_amount
- # 若剩余可理算金额小于等于0则跳出循环
- if remaining_adjustable_amount <= Decimal("0.00"):
+ remaining_adjustment_amount -= adjustment_amount
+ # 若剩余理算金额小于等于0则跳出循环
+ if remaining_adjustment_amount <= Decimal("0.00"):
break
- if not receipt_adjustments:
- receipt_adjustments.append(
+ if not adjustments:
+ adjustments.append(
{
+ "group_policy": None, # 团单号
"person_policy": None, # 个单号
"liability": None, # 理赔责任名称
- "personal_self_payment": None, # 个人自费金额
- "personal_self_ratio": None, # 个人自费比例
+ "accident": None, # 理赔类型
+ "personal_self_payment": receipt[
+ "personal_self_payment"
+ ], # 个人自费金额
+ "personal_self_ratio": None, # 个人自费理算比例
"personal_self_adjustable_amount": None, # 个人自费可理算金额
- "non_medical_payment": None, # 个人自付金额
- "non_medical_ratio": None, # 个人自付比例
+ "non_medical_payment": receipt["non_medical_payment"], # 个人自付金额
+ "non_medical_ratio": None, # 个人自付理算比例
"non_medical_adjustable_amount": None, # 个人自付可理算金额
- "reasonable_amount": None, # 合理可理算金额
- "reasonable_ratio": None, # 合理部分比例
+ "reasonable_amount": receipt["reasonable_amount"], # 合理金额
+ "reasonable_ratio": None, # 合理理算比例
"reasonable_adjustable_amount": None, # 合理可理算金额
- "adjustment_amount": Decimal("0.00"), # 理赔责任理算金额
+ "remaining_adjustment_amount": remaining_adjustment_amount, # 剩余理算金额
+ "remaining_coverage_amount": None, # 个单剩余保额
+ "adjustable_amount": None, # 可理算金额
+ "adjustment_amount": Decimal("0.00"), # 理算金额
"adjustment_explanation": "票据不予理算", # 理算说明
}
)
- return receipt_adjustments
+ # 新增理算记录
+ masterdata.add_ajustment(
+ receipt_number=receipt["number"],
+ remaining_adjustment_amount=remaining_adjustment_amount,
+ )
+
+ return adjustments
diff --git a/票据理赔自动化/database.db b/票据理赔自动化/database.db
index 8f3acbf..719b104 100644
Binary files a/票据理赔自动化/database.db and b/票据理赔自动化/database.db differ
diff --git a/票据理赔自动化/dossiers/254728869001.html b/票据理赔自动化/dossiers/254728869001.html
index e69de29..25720aa 100644
--- a/票据理赔自动化/dossiers/254728869001.html
+++ b/票据理赔自动化/dossiers/254728869001.html
@@ -0,0 +1,2406 @@
+
+
+
+
+
+ 票据理赔自动化报告
+
+
+
+
+
+
+
+
+
影像件层
+
+ 签收影像件15张,已分类15张,已识别14张:
+
+
+
+
+ | 影像件编号 |
+ 影像件路径 |
+ 影像件类型 |
+ 已识别 |
+
+
+
+
+
+ | 03 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/insurant_1.jpg |
+ 居民身份证(国徽、头像面) |
+ 是 |
+
+
+
+ | 02 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/beneficiary_1.jpg |
+ 银行卡 |
+ 否,无需识别 |
+
+
+
+ | 01 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/application_1.jpg |
+ 理赔申请书 |
+ 是 |
+
+
+
+ | 04 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_1.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+ | 05 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_10.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+ | 06 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_12.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+ | 07 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_2.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+ | 08 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_3.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+ | 09 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_4.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+ | 10 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_5.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+ | 11 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_6.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+ | 12 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_7.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+ | 13 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_8.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+ | 14 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_9.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+ | 15 |
+ c:/Users/admin/Documents/trae_projects/python/票据理赔自动化/directory/254728869001/invoice_11.jpg |
+ 增值税发票 |
+ 是 |
+
+
+
+
+
+
+
+
赔案层
+
+
+
出险人(被保险人)信息
+
+
+
+
出生
+
+ 1974-01-31 | 51岁
+
+
+
+
+
+
证件号码
+
+ 320602197401310029
+
+
+
+
证件有效期
+
+ 2016-06-28 至 2036-06-28
+
+
+
+
手机号
+
+ 13962635271
+
+
+
+
住址
+
+ 江苏省 苏州市 昆山市
+
+
+ 玉山镇许文塘新村45幢301室
+
+
+
+
+
+
领款信息
+
+
+
开户银行
+
+ 中国银行昆山市分行营业部
+
+
+
+
+
户号
+
+ 6217566101001926753
+
+
+
+
+
+
在保个单
+
+
+
+ | 团单号 |
+ 主被保险人 |
+ 被保险人 |
+ 与主被保险人关系 |
+ 保险期 |
+ 理赔责任 |
+
+
+
+
+
+ | 3291120253201000000001 |
+ 唐敏华 |
+ 唐敏华 |
+ 本人 |
+
+ 2025-01-01 至 2025-12-31
+ |
+ 药店购药补充 |
+
+
+
+ | 3291120253201000000001 |
+ 唐敏华 |
+ 唐敏华 |
+ 本人 |
+
+ 2025-01-01 至 2025-12-31
+ |
+ 门诊就医补充 |
+
+
+
+
+
+
+
+
+
+
票据层
+
+
+
+
+
+
+
票据代码
+
+
032002200307
+
+
+
校验码
+
44501452541545875292
+
+
+
开票日期
+
+ 2024-10-16
+
+
+
+
+
购药及就医机构
+
+ 药店 | 苏州雷允上国药连锁总店有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 化学药品制剂 | 塞来昔布胶囊 |
+ 2.00 |
+ 53.88元 |
+ 0.00元 |
+ 0.00元 |
+ 53.88元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 53.88 |
+ None |
+ None |
+ None |
+ None |
+ None |
+ None |
+ None |
+ None |
+ 0.00 |
+
+
+
+
+
+ 理算金额合计: 0.00元
+
+
+
+
+
+
+
+
+
+
校验码
+
24412000000199673775
+
+
+
开票日期
+
+ 2024-11-11
+
+
+
+
+
购药及就医机构
+
+ 药店 | 荥阳市药之佳缘大药房有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 中成药 | 香菊感冒颗粒 |
+ 2.00 |
+ 35.00元 |
+ 0.00元 |
+ 0.00元 |
+ 35.00元 |
+
+
+
+ | 化学药品制剂 | 感冒灵颗粒 |
+ 1.00 |
+ 15.80元 |
+ 0.00元 |
+ 0.00元 |
+ 15.80元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 50.80 |
+ None |
+ None |
+ None |
+ None |
+ None |
+ None |
+ None |
+ None |
+ 0.00 |
+
+
+
+
+
+ 理算金额合计: 0.00元
+
+
+
+
+
+
+
+
+
+
校验码
+
24127200000191481229
+
+
+
开票日期
+
+ 2024-12-30
+
+
+
+
+
购药及就医机构
+
+ 药店 | 天津美团大药房有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 化学药品制剂 | 塞来昔布胶囊 |
+ 1.00 |
+ 69.00元 |
+ 0.00元 |
+ 0.00元 |
+ 69.00元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 69.00 |
+ None |
+ None |
+ None |
+ None |
+ None |
+ None |
+ None |
+ None |
+ 0.00 |
+
+
+
+
+
+ 理算金额合计: 0.00元
+
+
+
+
+
+
+
+
+
+
校验码
+
25447200000004924953
+
+
+
开票日期
+
+ 2025-01-11
+
+
+
+
+
购药及就医机构
+
+ 药店 | 广州美团大药房有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 化学药品制剂 | 吗替麦考酚酯胶囊 |
+ 10.00 |
+ 607.00元 |
+ 0.00元 |
+ 0.00元 |
+ 607.00元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 607.00 |
+ 3291120253201000000001 |
+ 320602197401310029 |
+ 5000.00 |
+ 药店购药补充 |
+ 0.000000 |
+ 0.000000 |
+ 607.000000 |
+ 607.00 |
+ 607.00 |
+
+
+
+
+
+ 理算金额合计: 607.00元
+
+
+
+
+
+
+
+
+
+
校验码
+
25447200000004924954
+
+
+
开票日期
+
+ 2025-01-11
+
+
+
+
+
购药及就医机构
+
+ 药店 | 广州美团大药房有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 化学药品制剂 | 吗替麦考酚酯胶囊 |
+ 10.00 |
+ 607.00元 |
+ 0.00元 |
+ 0.00元 |
+ 607.00元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 607.00 |
+ 3291120253201000000001 |
+ 320602197401310029 |
+ 4393.00 |
+ 药店购药补充 |
+ 0.000000 |
+ 0.000000 |
+ 607.000000 |
+ 607.00 |
+ 607.00 |
+
+
+
+
+
+ 理算金额合计: 607.00元
+
+
+
+
+
+
+
+
+
+
校验码
+
25447200000045325946
+
+
+
开票日期
+
+ 2025-01-20
+
+
+
+
+
购药及就医机构
+
+ 药店 | 广州美团大药房有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 化学药品制剂 | 玻璃酸钠滴眼液 |
+ 2.00 |
+ 119.56元 |
+ 0.00元 |
+ 0.00元 |
+ 119.56元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 119.56 |
+ 3291120253201000000001 |
+ 320602197401310029 |
+ 3786.00 |
+ 药店购药补充 |
+ 0.000000 |
+ 0.000000 |
+ 119.560000 |
+ 119.56 |
+ 119.56 |
+
+
+
+
+
+ 理算金额合计: 119.56元
+
+
+
+
+
+
+
+
+
+
校验码
+
25127200000015649326
+
+
+
开票日期
+
+ 2025-02-13
+
+
+
+
+
购药及就医机构
+
+ 药店 | 天津美团大药房有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 化学药品制剂 | 玻璃酸钠滴眼液 |
+ 2.00 |
+ 123.48元 |
+ 0.00元 |
+ 0.00元 |
+ 123.48元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 123.48 |
+ 3291120253201000000001 |
+ 320602197401310029 |
+ 3666.44 |
+ 药店购药补充 |
+ 0.000000 |
+ 0.000000 |
+ 123.480000 |
+ 123.48 |
+ 123.48 |
+
+
+
+
+
+ 理算金额合计: 123.48元
+
+
+
+
+
+
+
+
+
+
校验码
+
25427000000241125962
+
+
+
开票日期
+
+ 2025-07-08
+
+
+
+
+
购药及就医机构
+
+ 药店 | 湖北美团大药房有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 医疗仪器器械 | 口腔溃疡含漱液 |
+ 1.00 |
+ 13.00元 |
+ 0.00元 |
+ 0.00元 |
+ 13.00元 |
+
+
+
+ | 营养保健食品 | 钙维生素D片 |
+ 6.00 |
+ 467.80元 |
+ 0.00元 |
+ 0.00元 |
+ 0.00元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 13.00 |
+ 3291120253201000000001 |
+ 320602197401310029 |
+ 3542.96 |
+ 药店购药补充 |
+ 0.000000 |
+ 0.000000 |
+ 13.000000 |
+ 13.00 |
+ 13.00 |
+
+
+
+
+
+ 理算金额合计: 13.00元
+
+
+
+
+
+
+
+
+
+
校验码
+
25427000000241125963
+
+
+
开票日期
+
+ 2025-07-08
+
+
+
+
+
购药及就医机构
+
+ 药店 | 湖北美团大药房有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 营养保健食品 | 鱼油软胶囊 |
+ 1.00 |
+ 146.90元 |
+ 0.00元 |
+ 0.00元 |
+ 0.00元 |
+
+
+
+ | 营养保健食品 | 钙维生素D片 |
+ 2.00 |
+ 152.60元 |
+ 0.00元 |
+ 0.00元 |
+ 0.00元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 0.00 |
+ 3291120253201000000001 |
+ 320602197401310029 |
+ 3529.96 |
+ 药店购药补充 |
+ 0.000000 |
+ 0.000000 |
+ 0.000000 |
+ 0.00 |
+ 0.00 |
+
+
+
+
+
+ 理算金额合计: 0.00元
+
+
+
+
+
+
+
+
+
+
校验码
+
25427000000307614028
+
+
+
开票日期
+
+ 2025-07-19
+
+
+
+
+
购药及就医机构
+
+ 药店 | 湖北美团大药房有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 化学药品制剂 | 骨化三醇软胶囊 |
+ 5.00 |
+ 131.50元 |
+ 0.00元 |
+ 0.00元 |
+ 131.50元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 131.50 |
+ 3291120253201000000001 |
+ 320602197401310029 |
+ 3529.96 |
+ 药店购药补充 |
+ 0.000000 |
+ 0.000000 |
+ 131.500000 |
+ 131.50 |
+ 131.50 |
+
+
+
+
+
+ 理算金额合计: 131.50元
+
+
+
+
+
+
+
+
+
+
校验码
+
25342000000125542290
+
+
+
开票日期
+
+ 2025-07-23
+
+
+
+
+
购药及就医机构
+
+ 药店 | 安徽国胜大药房连锁有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 中成药 | 麝香痔疮栓 |
+ 10.00 |
+ 156.20元 |
+ 0.00元 |
+ 0.00元 |
+ 156.20元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 156.20 |
+ 3291120253201000000001 |
+ 320602197401310029 |
+ 3398.46 |
+ 药店购药补充 |
+ 0.000000 |
+ 0.000000 |
+ 156.200000 |
+ 156.20 |
+ 156.20 |
+
+
+
+
+
+ 理算金额合计: 156.20元
+
+
+
+
+
+
+
+
+
+
校验码
+
25622000000034278010
+
+
+
开票日期
+
+ 2025-07-23
+
+
+
+
+
购药及就医机构
+
+ 药店 | 德生堂医药股份有限公司
+
+
+
+
+
+
+
+ | 药品/医疗服务 |
+ 数量 |
+ 金额 |
+ 个人自费项 |
+ 个人自付项 |
+ 合理项 |
+
+
+
+
+
+ | 中成药 | 麝香痔疮栓 |
+ 6.00 |
+ 103.20元 |
+ 0.00元 |
+ 0.00元 |
+ 103.20元 |
+
+
+
+
+
+
+
+
+ | 剩余理算金额 |
+ 团单号 |
+ 个单号 |
+ 个单剩余保额 |
+ 理赔责任 |
+ 个人自费可理算金额 |
+ 个人自付可理算金额 |
+ 合理可理算金额 |
+ 可理算金额 |
+ 理算金额 |
+
+
+
+
+
+ | 103.20 |
+ 3291120253201000000001 |
+ 320602197401310029 |
+ 3242.26 |
+ 药店购药补充 |
+ 0.000000 |
+ 0.000000 |
+ 103.200000 |
+ 103.20 |
+ 103.20 |
+
+
+
+
+
+ 理算金额合计: 103.20元
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/票据理赔自动化/image.py b/票据理赔自动化/image.py
index 214f3c2..9b767c7 100644
--- a/票据理赔自动化/image.py
+++ b/票据理赔自动化/image.py
@@ -115,18 +115,15 @@ def image_classify(image_index: int, image_path: Path, dossier: Dict[str, Any])
image_format, image_ndarray, image_size_specified=2
)
- # 将影像件添加至影像件层
- dossier["images_layer"].append(
- {
- "image_index": f"{image_index:02d}", # 影像件编号
- "image_path": image_path.as_posix(), # 影像件路径
- "image_name": image_path.stem, # 影像件名称
- "image_format": image_format, # 影像件格式
- "image_guid": image_guid, # 影像件唯一标识
- "image_base64": image_base64, # 影像件BASE64编码
- "image_type": image_type, # 影像件类型
- }
- )
+ # 将已分类影像件添加至影像件层
+ dossier["images_layer"][f"{image_index:02d}"] = {
+ "image_path": image_path.as_posix(), # 影像件路径
+ "image_relative_path": image_path.relative_to(image_path.parent.parent).as_posix(), # 影像件相对路径
+ "image_format": image_format, # 影像件格式
+ "image_guid": image_guid, # 影像件唯一标识
+ "image_base64": image_base64, # 影像件BASE64编码
+ "image_type": image_type, # 影像件类型
+ } # 影像件编号作为键名
def image_read(
@@ -139,7 +136,7 @@ def image_read(
:return: 影像件图像数组
"""
try:
- # 先使用读取影像件,再解码为单通道灰度图数组对象
+ # 先使用读取影像件,再解码为单通道灰度图数组对象(因在windows系统中,cv2.imread就包含中文的影像件路径兼容较差,估使用numpy.fromfile)
image_ndarray = cv2.imdecode(
buf=numpy.fromfile(file=image_path, dtype=numpy.uint8),
flags=cv2.IMREAD_GRAYSCALE,
@@ -218,13 +215,15 @@ def image_compress(
def image_recognize(
+ image_index: str,
image: Dict[str, Any],
insurer_company: str,
dossier: Dict[str, Any],
) -> None:
"""
识别影像件并整合至赔案档案
- :param image: 影像件
+ :param image_index: 影像件编号
+ :param image: 影像件数据字典
:param insurer_company: 保险分公司
:param dossier: 赔案档案
:return: 无
@@ -237,19 +236,19 @@ def image_recognize(
"image_type": image["image_type"],
},
)["recognize_enabled"]:
+ dossier["images_layer"][image_index]["image_recognized"] = "否,无需识别"
return
# 根据影像件类型匹配影像件识别方法
match image["image_type"]:
- case "居民户口簿":
- raise RuntimeError("暂不支持居民户口簿")
case "居民身份证(国徽、头像面)" | "居民身份证(国徽面)" | "居民身份证(头像面)":
# 居民身份证识别并整合至赔案档案
identity_card_recognize(
image=image, insurer_company=insurer_company, dossier=dossier
)
- case "中国港澳台地区及境外护照":
- raise RuntimeError("暂不支持中国港澳台地区及境外护照")
+ case "银行卡":
+ # 银行卡识别并整合至赔案档案
+ bank_card_recognize(image=image, dossier=dossier)
case "理赔申请书":
application_recognize(
image=image, insurer_company=insurer_company, dossier=dossier
@@ -257,11 +256,15 @@ def image_recognize(
case "增值税发票" | "医疗门诊收费票据" | "医疗住院收费票据":
# 票据识别并整合至赔案档案
receipt_recognize(
- image=image, insurer_company=insurer_company, dossier=dossier
+ image_index=image_index,
+ image=image,
+ insurer_company=insurer_company,
+ dossier=dossier,
)
- case "银行卡":
- # 银行卡识别并整合至赔案档案
- bank_card_recognize(image=image, dossier=dossier)
+ case _:
+ raise RuntimeError(f"影像件类型未配置影像件识别方法")
+
+ dossier["images_layer"][image_index]["image_recognized"] = "是"
def identity_card_recognize(
@@ -327,7 +330,7 @@ def identity_card_recognize(
}
)
- # 根据保险分公司、被保险人、证件类型、证件号码和出险时间查询个单
+ # 根据保险分公司名称、被保险人姓名、证件类型、证件号码和报案时间查询被保险人的理赔责任
dossier["liabilities_layer"] = masterdata.query_liabilities(
insurer_company=insurer_company,
insured_person=insured_person,
@@ -577,17 +580,22 @@ def application_recognize(
def receipt_recognize(
- image: Dict[str, Any], insurer_company: str, dossier: Dict[str, Any]
+ image_index: str,
+ image: Dict[str, Any],
+ insurer_company: str,
+ dossier: Dict[str, Any],
) -> None:
"""
识别票据并整合至赔案档案
+ :param image_index: 影像件编号
:param image: 影像件
:param insurer_company: 保险分公司
:param dossier: 赔案档案
:return: 空
"""
# 初始化票据数据
- receipt = {"image_index": image["image_index"]}
+ receipt = {"image_index": image_index, "image_path": image["image_path"]}
+
# 请求深圳快瞳票据查验接口(兼容增值税发票、医疗门诊/住院收费票据)
response = request.post(
url=(url := "https://ai.inspirvision.cn/s/api/ocr/invoiceCheckAll"),
@@ -613,25 +621,29 @@ def receipt_recognize(
"真票"
if response["data"]["details"]["invoiceTypeNo"] == "0"
else "红票"
- ), # 红票为状态为失控、作废、已红冲、部分红冲和全额红冲的票据
- "number": response["data"]["details"]["number"],
+ ), # 查验状态,红票对应查验状态为失控、作废、已红冲、部分红冲和全额红冲
+ "number": response["data"]["details"]["number"], # 票据号
"code": (
response["data"]["details"]["code"]
if response["data"]["details"]["code"]
else None
- ),
+ ), # 票据代码
"date": datetime.strptime(
response["data"]["details"]["date"], "%Y年%m月%d日"
- ), # 转为日期时间(datetime对象)
- "verification_code": response["data"]["details"]["check_code"],
+ ), # 开票日期
+ "check_code": response["data"]["details"][
+ "check_code"
+ ], # 校验码
"amount": Decimal(
response["data"]["details"]["total"]
).quantize(
Decimal("0.00"),
rounding=ROUND_HALF_UP,
- ), # 深圳快瞳票据查验接口中开票金额由字符串转为Decimal,保留两位小数
- "payer": response["data"]["details"]["buyer"],
- "institution": response["data"]["details"]["seller"],
+ ), # 开票金额
+ "payer": response["data"]["details"]["buyer"], # 出险人
+ "institution": response["data"]["details"][
+ "seller"
+ ], # 购药及就医机构
"items": [
{
"item": item["name"],
@@ -642,13 +654,13 @@ def receipt_recognize(
)
if item["quantity"]
else Decimal("0.00")
- ), # 深圳快瞳票据查验接口中明细单位由空字符转为None,若非空字符由字符串转为Decimal,保留两位小数
+ ),
"amount": (
Decimal(item["total"]) + Decimal(item["tax"])
).quantize(
Decimal("0.00"),
rounding=ROUND_HALF_UP,
- ), # 深圳快瞳票据查验接口中明细的金额和税额由字符串转为Decimal,保留两位小数,并求和
+ ),
}
for item in response["data"]["details"]["items"]
],
@@ -689,7 +701,7 @@ def receipt_recognize(
if response["data"]["hospitalizationDate"]
else None
),
- "verification_code": response["data"]["checkCode"],
+ "check_code": response["data"]["checkCode"],
"amount": Decimal(response["data"]["amount"]).quantize(
Decimal("0.00"),
rounding=ROUND_HALF_UP,
@@ -781,9 +793,7 @@ def receipt_recognize(
fuzzy_match(response["data"], "开票日期"),
"%Y年%m月%d日",
),
- "verification_code": fuzzy_match(
- response["data"], "校验码"
- ),
+ "check_code": fuzzy_match(response["data"], "校验码"),
"amount": Decimal(
fuzzy_match(response["data"], "小写金额").replace(
"¥", ""
@@ -857,9 +867,7 @@ def receipt_recognize(
fuzzy_match(response["data"], "开票日期"),
"%Y-%m-%d",
),
- "verification_code": fuzzy_match(
- response["data"], "校验码"
- ),
+ "check_code": fuzzy_match(response["data"], "校验码"),
"amount": Decimal(
fuzzy_match(
response["data"], "合计金额(小写)"
@@ -882,7 +890,7 @@ def receipt_recognize(
"amount": Decimal(amount).quantize(
Decimal("0.00"),
rounding=ROUND_HALF_UP,
- ), # 深圳快瞳票据识别接口中明细的金额和税额由字符串转为Decimal,保留两位小数,并求和
+ ),
}
for name, quantity, amount in zip(
[
@@ -964,7 +972,7 @@ def receipt_recognize(
if isinstance(receipt["endtime"], dict)
else None
),
- "verification_code": fuzzy_match(
+ "check_code": fuzzy_match(
receipt["global_detail"]["region_specific"],
"校验码",
),
@@ -1063,6 +1071,8 @@ def receipt_recognize(
)
)
.assign(
+ personal_self_payment=Decimal("0.00"), # 个人自费项
+ non_medical_payment=Decimal("0.00"), # 个人自付项
reasonable_amount=lambda dataframe: dataframe.apply(
lambda row: Decimal(
# 基于扣除明细项不合理费用决策规则评估
@@ -1080,8 +1090,8 @@ def receipt_recognize(
rounding=ROUND_HALF_UP,
),
axis="columns",
- )
- ) # 扣除明细项不合理费用
+ ), # 合理项
+ )
)
receipt.update(
@@ -1092,7 +1102,7 @@ def receipt_recognize(
in receipt["payer"]
else None
), # 出险人姓名
- "accident": "药店购药", # 出险事故
+ "accident": "药店购药", # 理赔类型
"diagnosis": "购药拟诊", # 医疗诊断
"personal_self_payment": Decimal("0.00"), # 个人自费金额
"non_medical_payment": Decimal("0.00"), # 个人自付金额
diff --git a/票据理赔自动化/main.py b/票据理赔自动化/main.py
index fa80064..2251cc1 100644
--- a/票据理赔自动化/main.py
+++ b/票据理赔自动化/main.py
@@ -7,6 +7,7 @@ https://liubiren.feishu.cn/docx/WFjTdBpzroUjQvxxrNIcKvGnneh?from=from_copylink
from datetime import datetime
from pathlib import Path
+import re
from case import case_adjust
from image import image_classify, image_recognize
@@ -24,15 +25,16 @@ if __name__ == "__main__":
# 初始化赔案档案(推送至TPA时,保险公司会提保险分公司名称、报案时间和影像件等,TPA签收后生成赔案号)
dossier = {
"report_layer": {
- "case_number": case_path.stem, # 默认为赔案文件夹名称
"insurer_company": (
insurer_company := "中银保险有限公司苏州分公司"
- ), # 默认为中银保险有限公司苏州分公司
+ ), # 保险分公司名称默认为中银保险有限公司苏州分公司
"report_time": datetime(
2025, 7, 25, 12, 0, 0
- ), # 指定报案时间,默认为 datetime对象
+ ), # 报案时间默认为2025-07-25 12:00:00
+ "images_counts": 15, # 影像件数默认为15
+ "case_number": case_path.stem, # 赔案号默认为赔案文件夹名称
}, # 报案层
- "images_layer": [], # 影像件层
+ "images_layer": {}, # 影像件层
"insured_person_layer": {}, # 出险人层
"liabilities_layer": [], # 理赔责任层
"receipts_layer": [], # 票据层
@@ -43,11 +45,11 @@ if __name__ == "__main__":
for image_index, image_path in enumerate(
sorted(
[
- i
- for i in case_path.glob(pattern="*")
- if i.is_file() and i.suffix.lower() in [".jpg", ".jpeg", ".png"]
+ x
+ for x in case_path.glob(pattern="*")
+ if x.is_file() and x.suffix.lower() in [".jpg", ".jpeg", ".png"]
],
- key=lambda i: i.stat().st_birthtime, # 根据影像件创建时间顺序排序
+ key=lambda x: x.stat().st_birthtime, # 根据影像件创建时间顺序排序
),
1,
):
@@ -57,34 +59,47 @@ if __name__ == "__main__":
)
# 就影像件层按照影像件类型指定排序
- dossier["images_layer"].sort(
- key=lambda i: [
- "居民户口簿",
- "居民身份证(国徽面)",
- "居民身份证(头像面)",
- "居民身份证(国徽、头像面)",
- "中国港澳台地区及境外护照",
- "理赔申请书",
- "增值税发票",
- "医疗门诊收费票据",
- "医疗住院收费票据",
- "医疗费用清单",
- "银行卡",
- "其它",
- ].index(i["image_type"])
+ dossier["images_layer"] = dict(
+ sorted(
+ dossier["images_layer"].items(),
+ key=lambda x: [
+ "居民户口簿",
+ "居民身份证(国徽面)",
+ "居民身份证(头像面)",
+ "居民身份证(国徽、头像面)",
+ "中国港澳台地区及境外护照",
+ "银行卡",
+ "理赔申请书",
+ "其它",
+ "增值税发票",
+ "医疗门诊收费票据",
+ "医疗住院收费票据",
+ "医疗费用清单",
+ ].index(x[1]["image_type"]),
+ ),
)
+ # 统计已分类影像件数
+ dossier["classified_images_counts"] = len(dossier["images_layer"])
- # 遍历影像件层内影像件
- for image in dossier["images_layer"]:
- # 识别影像件并整合至赔案档案
- image_recognize(
- image=image,
- insurer_company=insurer_company,
- dossier=dossier,
- )
+ # 遍历影像件层内所有影像件
+ for image_index, image in dossier["images_layer"].items():
+ if re.match(pattern=r"^\d{2}$", string=image_index):
+ # 识别影像件并整合至赔案档案
+ image_recognize(
+ image_index=image_index,
+ image=image,
+ insurer_company=insurer_company,
+ dossier=dossier,
+ )
- # 就票据层按照开票日期和票据号顺序排序
- dossier["receipts_layer"].sort(key=lambda x: (x["date"], x["number"]))
+ # 统计已识别影像件数
+ dossier["recognized_images_counts"] = len(
+ [
+ image
+ for image in dossier["images_layer"].values()
+ if image["image_recognized"] == "是"
+ ]
+ )
# 理算赔案并整合至赔案档案
case_adjust(dossier=dossier)
diff --git a/票据理赔自动化/masterdata.py b/票据理赔自动化/masterdata.py
index 9ea0247..f28cd36 100644
--- a/票据理赔自动化/masterdata.py
+++ b/票据理赔自动化/masterdata.py
@@ -8,7 +8,7 @@ from decimal import Decimal, ROUND_HALF_UP
from hashlib import md5
from pathlib import Path
import sys
-from typing import Any, Dict, List
+from typing import Any, Dict, List, Optional
sys.path.append(Path(__file__).parent.parent.as_posix())
from utils.sqlite import SQLite
@@ -97,7 +97,7 @@ class MasterData(SQLite):
guid TEXT PRIMARY KEY,
--理赔责任名称
liability TEXT NOT NULL,
- --出险事故
+ --理赔类型
accident TEXT NOT NULL,
--个人自费理算比例
personal_self_ratio TEXT NOT NULL,
@@ -130,7 +130,7 @@ class MasterData(SQLite):
--个单唯一标识
person_policy_guid TEXT NOT NULL
)
- """
+ """
)
# 初始化购药及就医机构表
self.execute(
@@ -158,6 +158,20 @@ class MasterData(SQLite):
)
"""
)
+ # 初始化票据理算表
+ self.execute(
+ sql="""
+ CREATE TABLE IF NOT EXISTS adjustments
+ (
+ --票据号
+ receipt_number TEXT PRIMARY KEY,
+ --剩余理算金额
+ remaining_adjustment_amount TEXT NOT NULL,
+ --理算时间
+ adjust_time TEXT NOT NULL
+ )
+ """
+ )
except Exception as exception:
raise RuntimeError(f"初始化主数据发生异常:{str(exception)}") from exception
@@ -309,7 +323,72 @@ class MasterData(SQLite):
except Exception as exception:
raise RuntimeError(f"{str(exception)}") from exception
- def query_remaining_amount(
+ def query_remaining_adjustment_amount(
+ self,
+ receipt_number: str,
+ ) -> Optional[Decimal]:
+ """
+ 根据票据号查询剩余理算金额
+ :param receipt_number: 票据号
+ :return: 剩余理算金额
+ """
+ try:
+ with self:
+ result = self.query_one(
+ sql="""
+ SELECT remaining_adjustment_amount
+ FROM adjustments
+ WHERE receipt_number = ?
+ ORDER BY adjust_time DESC
+ LIMIT 1;
+ """,
+ parameters=(receipt_number,),
+ )
+ if not result:
+ return None
+
+ return Decimal(result["remaining_adjustment_amount"]).quantize(
+ Decimal("0.00"),
+ rounding=ROUND_HALF_UP,
+ )
+
+ except Exception as exception:
+ raise RuntimeError(f"{str(exception)}") from exception
+
+ def add_ajustment(
+ self,
+ receipt_number: str,
+ remaining_adjustment_amount: Decimal,
+ ) -> None:
+ """
+ 新增理算记录
+ :param receipt_number: 票据号
+ :param remaining_adjustment_amount: 剩余理算金额
+ :return: 无
+ """
+ if remaining_adjustment_amount < Decimal("0.00"):
+ raise ValueError("剩余理算金额小于0")
+
+ # 当前时间
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
+
+ with self:
+ if not self.execute(
+ sql="""
+ INSERT INTO adjustments
+ (receipt_number, remaining_adjustment_amount, adjust_time)
+ VALUES
+ (?, ?, ?)
+ """,
+ parameters=(
+ receipt_number,
+ f"{remaining_adjustment_amount:.2f}",
+ current_time,
+ ),
+ ):
+ raise RuntimeError("新增理算记录发生异常")
+
+ def query_after_change_amount(
self,
person_policy_guid: str,
) -> Decimal:
@@ -354,11 +433,6 @@ class MasterData(SQLite):
:param change_amount: 变动金额
:return: 无
"""
- if before_change_amount != self.query_remaining_amount(
- person_policy_guid=person_policy_guid,
- ):
- raise ValueError("变动前金额不等于最新一条保额变动记录的变动后金额")
-
# 变动后金额
after_change_amount = (before_change_amount - change_amount).quantize(
Decimal("0.00"),
diff --git a/票据理赔自动化/template.html b/票据理赔自动化/template.html
index 2907333..5f16412 100644
--- a/票据理赔自动化/template.html
+++ b/票据理赔自动化/template.html
@@ -1,692 +1,745 @@
-
-
+
- 赔案档案
+ 票据理赔自动化报告
-
+
-
+
-