55
票据理赔自动化/1.py
|
|
@ -1,55 +0,0 @@
|
||||||
import csv
|
|
||||||
|
|
||||||
import chardet
|
|
||||||
|
|
||||||
|
|
||||||
def convert_csv_to_utf8(input_csv: str, output_csv: str, encoding: str = None):
|
|
||||||
"""
|
|
||||||
将非UTF-8编码的CSV文件转换为UTF-8编码
|
|
||||||
:param input_csv: 输入CSV文件路径
|
|
||||||
:param output_csv: 输出UTF-8编码的CSV文件路径
|
|
||||||
:param encoding: 手动指定输入文件编码(如"gbk"),若为None则自动检测
|
|
||||||
"""
|
|
||||||
# 步骤1:检测输入文件的原始编码(若未手动指定)
|
|
||||||
if not encoding:
|
|
||||||
print(f"🔍 正在检测 {input_csv} 的编码...")
|
|
||||||
with open(input_csv, "rb") as f:
|
|
||||||
raw_data = f.read(10240) # 读取前10KB数据用于检测(足够识别编码)
|
|
||||||
result = chardet.detect(raw_data)
|
|
||||||
encoding = result["encoding"]
|
|
||||||
confidence = result["confidence"]
|
|
||||||
print(f"✅ 检测到编码:{encoding}(置信度:{confidence:.2f})")
|
|
||||||
# 处理chardet检测结果为空的情况(兜底用gbk,适配中文常见编码)
|
|
||||||
if not encoding:
|
|
||||||
encoding = "gbk"
|
|
||||||
print(f"⚠️ 编码检测失败,兜底使用 {encoding}")
|
|
||||||
|
|
||||||
# 步骤2:按原始编码读取CSV并转换为UTF-8保存
|
|
||||||
try:
|
|
||||||
# 读取原始CSV(处理编码错误:replace表示用<E7A4BA>替换无法解码的字符,避免程序崩溃)
|
|
||||||
with open(input_csv, "r", encoding=encoding, errors="replace") as infile:
|
|
||||||
# 兼容CSV的不同分隔符(默认逗号,若为制表符可改delimiter='\t')
|
|
||||||
reader = csv.reader(infile)
|
|
||||||
rows = list(reader) # 读取所有行
|
|
||||||
|
|
||||||
# 保存为UTF-8编码的CSV(newline=''避免空行,encoding='utf-8-sig'带BOM,适配Excel打开)
|
|
||||||
with open(output_csv, "w", encoding="utf-8-sig", newline="") as outfile:
|
|
||||||
writer = csv.writer(outfile)
|
|
||||||
writer.writerows(rows)
|
|
||||||
|
|
||||||
print(f"✅ 转换完成!UTF-8编码文件已保存至:{output_csv}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ 转换失败:{str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# ========== 示例调用 ==========
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# 输入/输出文件路径(替换为你的实际路径)
|
|
||||||
input_path = "转换后_UTF8.csv"
|
|
||||||
output_path = "转换后_UTF8.csv"
|
|
||||||
|
|
||||||
# 自动检测编码并转换
|
|
||||||
convert_csv_to_utf8(input_path, output_path)
|
|
||||||
|
|
@ -118,10 +118,10 @@ if __name__ == "__main__":
|
||||||
# 初始化药品表
|
# 初始化药品表
|
||||||
self._execute(
|
self._execute(
|
||||||
sql="""
|
sql="""
|
||||||
CREATE TABLE IF NOT EXISTS drugs
|
CREATE TABLE IF NOT EXISTS medicines
|
||||||
(
|
(
|
||||||
--药品
|
--药品/医疗服务
|
||||||
drug TEXT PRIMARY KEY
|
medicine TEXT PRIMARY KEY
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
@ -218,35 +218,36 @@ if __name__ == "__main__":
|
||||||
raise RuntimeError("查询并获取多条个单和被保险人记录发生异常")
|
raise RuntimeError("查询并获取多条个单和被保险人记录发生异常")
|
||||||
|
|
||||||
# noinspection PyShadowingNames
|
# noinspection PyShadowingNames
|
||||||
def query_drug(
|
def query_medicine(
|
||||||
self,
|
self,
|
||||||
content: str,
|
content: str,
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
根据明细项具体内容查询药品
|
根据明细项名称中具体内容查询药品/医疗服务
|
||||||
:param content: 明细项具体内容
|
:param content: 明细项具体内容
|
||||||
:return: 药品
|
:return: 药品/医疗服务
|
||||||
"""
|
"""
|
||||||
|
# TODO: 暂仅支持查询药品,后续完善查询医疗服务
|
||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
try:
|
try:
|
||||||
with self:
|
with self:
|
||||||
# noinspection SqlResolve
|
# noinspection SqlResolve
|
||||||
result = self._query_all(
|
result = self._query_all(
|
||||||
sql="""
|
sql="""
|
||||||
SELECT drug
|
SELECT medicine
|
||||||
FROM drugs
|
FROM medicines
|
||||||
WHERE ? LIKE '%' || drug || '%'
|
WHERE ? LIKE '%' || medicine || '%'
|
||||||
""",
|
""",
|
||||||
parameters=(content,),
|
parameters=(content,),
|
||||||
)
|
)
|
||||||
if result:
|
if result:
|
||||||
return max(result, key=lambda x: len(x["drug"]))[
|
return max(result, key=lambda x: len(x["medicine"]))[
|
||||||
"drug"
|
"medicine"
|
||||||
] # 仅返回最大长度的药品
|
] # 返回药品最大长度的药品
|
||||||
raise
|
raise
|
||||||
# TODO: 若根据明细项具体内容查询药品发生异常则流转至主数据人工处理
|
# TODO: 若根据明细项名称中具体内容查询药品/医疗服务发生异常则流转至主数据人工处理
|
||||||
except Exception:
|
except Exception:
|
||||||
raise RuntimeError("根据明细项具体内容查询药品发生异常")
|
raise RuntimeError("根据明细项名称中具体内容查询药品/医疗服务发生异常")
|
||||||
|
|
||||||
# 实例化主数据
|
# 实例化主数据
|
||||||
master_data = MasterData()
|
master_data = MasterData()
|
||||||
|
|
@ -859,6 +860,23 @@ if __name__ == "__main__":
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def parse_name(name: str) -> Tuple[str, Optional[str]]:
|
||||||
|
"""
|
||||||
|
根据明细项名称解析明细项类别和具体内容,并根据明细项名称中具体内容查询药品/医疗服务
|
||||||
|
:param name: 明细项名称
|
||||||
|
return 明细项类别和药品/医疗服务
|
||||||
|
"""
|
||||||
|
if match := re.match(
|
||||||
|
r"^\*(?P<category>.*?)\*(?P<specific>.*)$",
|
||||||
|
name,
|
||||||
|
):
|
||||||
|
return match.group("category"), master_data.query_medicine(
|
||||||
|
match.group("specific")
|
||||||
|
)
|
||||||
|
# 一般增值税发票明细项格式形如*{category}*{specific},其中category为明细项类别,例如中成药;specific为明细项具体内容,例如[同仁堂]金贵肾气水蜜丸 300丸/瓶,需要据此查询药品。其它格式则将明细项内容作为明细项类别,药品为空值
|
||||||
|
else:
|
||||||
|
return name, None
|
||||||
|
|
||||||
# 初始化票据数据
|
# 初始化票据数据
|
||||||
receipt = {"影像件编号": image["影像件编号"]}
|
receipt = {"影像件编号": image["影像件编号"]}
|
||||||
# 请求深圳快瞳票据查验接口(兼容增值税发票、医疗门诊/住院收费票据)
|
# 请求深圳快瞳票据查验接口(兼容增值税发票、医疗门诊/住院收费票据)
|
||||||
|
|
@ -1291,24 +1309,22 @@ if __name__ == "__main__":
|
||||||
lambda dataframe: dataframe["金额"] != 0
|
lambda dataframe: dataframe["金额"] != 0
|
||||||
] # 仅保留金额非0的明细项
|
] # 仅保留金额非0的明细项
|
||||||
.reset_index()
|
.reset_index()
|
||||||
.to_dict("records")
|
.pipe(
|
||||||
|
lambda dataframe: dataframe.join(
|
||||||
|
dataframe["名称"]
|
||||||
|
.apply(
|
||||||
|
parse_name
|
||||||
|
) # 根据明细项名称解析明细项类别和具体内容,并根据明细项名称中具体内容查询药品/医疗服务
|
||||||
|
.apply(
|
||||||
|
pandas.Series
|
||||||
|
) # 就明细项类别和药品/医疗服务元组展开为两列
|
||||||
|
.rename(columns={0: "类别", 1: "药品/医疗服务"})
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
for item in items:
|
print(items)
|
||||||
# 匹配并解析明细项大类和具体内容
|
print()
|
||||||
if match := re.match(
|
|
||||||
r"^\*(?P<category>.*?)\*(?P<content>.*)$",
|
|
||||||
item["名称"],
|
|
||||||
):
|
|
||||||
category = match.group("category") # 明细项大类
|
|
||||||
# 根据明细项具体内容查询药品
|
|
||||||
drug = master_data.query_drug(match.group("content"))
|
|
||||||
# TODO: 若匹配明细项大类和具体内容发生异常则流转至人工处理
|
|
||||||
else:
|
|
||||||
raise RuntimeError("匹配明细项大类和具体内容发生异常")
|
|
||||||
|
|
||||||
print(dossier["被保险人层"])
|
|
||||||
exit()
|
|
||||||
|
|
||||||
case ("增值税发票", "私立医院"):
|
case ("增值税发票", "私立医院"):
|
||||||
receipt["购药及就医类型"] = "门诊就医"
|
receipt["购药及就医类型"] = "门诊就医"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
{
|
||||||
|
"contentType": "application/vnd.gorules.decision",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "d0fbd74f-6514-4c42-87e5-fdf849b8ab6f",
|
||||||
|
"name": "request",
|
||||||
|
"type": "inputNode",
|
||||||
|
"content": {
|
||||||
|
"schema": ""
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"x": 110,
|
||||||
|
"y": 292.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d717ec6d-d082-4f23-8fe6-3fa52b228e65",
|
||||||
|
"name": "decisionTable",
|
||||||
|
"type": "decisionTableNode",
|
||||||
|
"content": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"_id": "16c78c24-882b-4428-b429-e66ed67e0e85",
|
||||||
|
"_description": "中银保险有限公司苏州分公司就中成药不扣除",
|
||||||
|
"1fdeb8d9-62c5-4f07-b5d1-69dbaf6020fe": "amount",
|
||||||
|
"2522d0a7-437b-421b-90e7-165e2365ebba": "\"中银保险有限公司苏州分公司\"",
|
||||||
|
"4ed5e43b-f519-46cb-8c92-80097788d21a": "\"中成药\"",
|
||||||
|
"8a5837ec-3df9-4445-a515-34762a244a68": "",
|
||||||
|
"cd1dc117-39f5-4dc2-96bb-f67236199851": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "33343bbe-ee06-44f2-8247-84106cdaad8a",
|
||||||
|
"_description": "中银保险有限公司苏州分公司就化学药品制剂不扣除",
|
||||||
|
"1fdeb8d9-62c5-4f07-b5d1-69dbaf6020fe": "amount",
|
||||||
|
"2522d0a7-437b-421b-90e7-165e2365ebba": "\"中银保险有限公司苏州分公司\"",
|
||||||
|
"4ed5e43b-f519-46cb-8c92-80097788d21a": "\"化学药品制剂\"",
|
||||||
|
"8a5837ec-3df9-4445-a515-34762a244a68": "",
|
||||||
|
"cd1dc117-39f5-4dc2-96bb-f67236199851": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "87dd3e93-5b9b-4aa3-a7f8-899f0745260a",
|
||||||
|
"_description": "中银保险有限公司苏州分公司就医疗仪器器械不扣除",
|
||||||
|
"1fdeb8d9-62c5-4f07-b5d1-69dbaf6020fe": "amount",
|
||||||
|
"2522d0a7-437b-421b-90e7-165e2365ebba": "\"中银保险有限公司苏州分公司\"",
|
||||||
|
"4ed5e43b-f519-46cb-8c92-80097788d21a": "\"医疗仪器器械\"",
|
||||||
|
"8a5837ec-3df9-4445-a515-34762a244a68": "",
|
||||||
|
"cd1dc117-39f5-4dc2-96bb-f67236199851": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "5a39b06b-deb9-411b-9aa3-e110588f3f7a",
|
||||||
|
"_description": "中银保险有限公司苏州分公司就其它明细项类别扣除",
|
||||||
|
"1fdeb8d9-62c5-4f07-b5d1-69dbaf6020fe": "\"0.00\"",
|
||||||
|
"2522d0a7-437b-421b-90e7-165e2365ebba": "\"中银保险有限公司苏州分公司\"",
|
||||||
|
"4ed5e43b-f519-46cb-8c92-80097788d21a": "",
|
||||||
|
"8a5837ec-3df9-4445-a515-34762a244a68": "",
|
||||||
|
"cd1dc117-39f5-4dc2-96bb-f67236199851": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "cd1b883f-2dc8-459a-8eba-bbae94b3d9b2",
|
||||||
|
"_description": "其它保险分公司就所有明细项类别扣除",
|
||||||
|
"1fdeb8d9-62c5-4f07-b5d1-69dbaf6020fe": "\"0.00\"",
|
||||||
|
"2522d0a7-437b-421b-90e7-165e2365ebba": "",
|
||||||
|
"4ed5e43b-f519-46cb-8c92-80097788d21a": "",
|
||||||
|
"8a5837ec-3df9-4445-a515-34762a244a68": "",
|
||||||
|
"cd1dc117-39f5-4dc2-96bb-f67236199851": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "2522d0a7-437b-421b-90e7-165e2365ebba",
|
||||||
|
"name": "保险分公司",
|
||||||
|
"field": "insurer_company"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4ed5e43b-f519-46cb-8c92-80097788d21a",
|
||||||
|
"name": "明细项类别",
|
||||||
|
"field": "category"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cd1dc117-39f5-4dc2-96bb-f67236199851",
|
||||||
|
"name": "药品/医疗服务",
|
||||||
|
"field": "medicine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8a5837ec-3df9-4445-a515-34762a244a68",
|
||||||
|
"name": "金额",
|
||||||
|
"field": "amount"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "1fdeb8d9-62c5-4f07-b5d1-69dbaf6020fe",
|
||||||
|
"name": "合理金额",
|
||||||
|
"field": "reasonable_amount"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hitPolicy": "first",
|
||||||
|
"inputField": null,
|
||||||
|
"outputPath": null,
|
||||||
|
"passThrough": true,
|
||||||
|
"executionMode": "single"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"x": 430,
|
||||||
|
"y": 292.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b7e720d7-9ef7-4470-be68-a38a1a711b45",
|
||||||
|
"name": "response",
|
||||||
|
"type": "outputNode",
|
||||||
|
"content": {
|
||||||
|
"schema": ""
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"x": 750,
|
||||||
|
"y": 292.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"id": "0b72f551-884f-41c9-b9a7-2eb711e93181",
|
||||||
|
"type": "edge",
|
||||||
|
"sourceId": "d0fbd74f-6514-4c42-87e5-fdf849b8ab6f",
|
||||||
|
"targetId": "d717ec6d-d082-4f23-8fe6-3fa52b228e65"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9dc2590c-8061-4974-b06e-40e7c49a1d97",
|
||||||
|
"type": "edge",
|
||||||
|
"sourceId": "d717ec6d-d082-4f23-8fe6-3fa52b228e65",
|
||||||
|
"targetId": "b7e720d7-9ef7-4470-be68-a38a1a711b45"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 241 KiB |
|
Before Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 262 KiB |
|
Before Width: | Height: | Size: 251 KiB |
|
Before Width: | Height: | Size: 262 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 177 KiB |
|
Before Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 282 KiB |
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 251 KiB |
|
Before Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 183 KiB |
|
Before Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 304 KiB |
|
Before Width: | Height: | Size: 298 KiB |