Files
HIS/患者列表处理/数据处理工作区/04_合并批次结果.py

248 lines
11 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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Consolidate patient archive result folders."""
from __future__ import annotations
import csv
import json
from collections import Counter
from pathlib import Path
from typing import Any
ROOT = Path("数据处理结果区")
RESULT_SUFFIX = "-列表归档结果"
INFO_DIR_NAME = "信息记录"
def write_json(path: Path, data: Any) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
def write_jsonl(path: Path, records: list[dict[str, Any]]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as file:
for record in records:
file.write(json.dumps(record, ensure_ascii=False) + "\n")
def flatten_record(batch_name: str, record: dict[str, Any]) -> dict[str, Any]:
patient = record["患者信息"]
image = record["图片信息"]
review = record["复核"]
return {
"处理批次": batch_name,
"大科室": record["大科室"],
"子科室": record["子科室"],
"来源文件夹": record["来源文件夹"],
"图片路径": image["图片路径"],
"图片名": image["图片名"],
"图片内行号": image["图片内行号"],
"拼接组序号": image["拼接组序号"],
"OCR请求ID": image.get("OCR请求ID", ""),
"姓名": patient["姓名"],
"性别": patient["性别"],
"年龄": patient["年龄"],
"住院号": patient["住院号"],
"诊断": patient["诊断"],
"入院时间": patient["入院时间"],
"最后书写时间": patient["最后书写时间"],
"住院天数": patient["住院天数"],
"出院时间": patient["出院时间"],
"手术后天数": patient["手术后天数"],
"复核状态": review["状态"],
"复核提示": review_note_text(review),
"人工修正": bool(review.get("人工修正")),
}
def review_note_text(review: dict[str, Any]) -> str:
notes = [str(item) for item in review.get("提示", []) if str(item) and str(item) != "缺少出院时间"]
if review.get("人工备注"):
notes.append(f"人工备注: {review['人工备注']}")
return "".join(notes)
def record_quality_rank(record: dict[str, Any]) -> tuple[int, int, int, int]:
patient = record["患者信息"]
review = record["复核"]
review_ok = 0 if review.get("状态") == "需人工复核" else 1
manual_corrected = 1 if review.get("人工修正") else 0
date_count = int(bool(patient.get("入院时间"))) + int(bool(patient.get("出院时间")))
filled_count = sum(1 for value in patient.values() if value not in ("", None))
return (review_ok, manual_corrected, date_count, filled_count)
def summarize_record_for_duplicate(record: dict[str, Any]) -> dict[str, Any]:
patient = record["患者信息"]
image = record["图片信息"]
review = record["复核"]
return {
"处理批次": record["处理批次"],
"大科室": record["大科室"],
"子科室": record["子科室"],
"来源文件夹": record["来源文件夹"],
"图片路径": image["图片路径"],
"图片名": image["图片名"],
"图片内行号": image["图片内行号"],
"姓名": patient.get("姓名", ""),
"住院号": patient.get("住院号", ""),
"入院时间": patient.get("入院时间", ""),
"出院时间": patient.get("出院时间", ""),
"复核状态": review.get("状态", ""),
}
def deduplicate_records_by_inpatient_no(
records: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]:
kept_by_no: dict[str, dict[str, Any]] = {}
kept_order: list[str] = []
duplicates: list[dict[str, Any]] = []
missing_inpatient_no_records: list[dict[str, Any]] = []
for record in records:
inpatient_no = str(record["患者信息"].get("住院号", "")).strip()
if not inpatient_no:
missing_inpatient_no_records.append(
{
"剔除记录": summarize_record_for_duplicate(record),
"规则": "住院号为空,未纳入合并总表和数据库",
}
)
continue
if inpatient_no not in kept_by_no:
kept_order.append(inpatient_no)
else:
previous = kept_by_no[inpatient_no]
duplicates.append(
{
"住院号": inpatient_no,
"保留记录": summarize_record_for_duplicate(record),
"剔除记录": summarize_record_for_duplicate(previous),
"规则": "住院号重复,后出现记录覆盖先出现记录",
}
)
kept_by_no[inpatient_no] = record
return [kept_by_no[inpatient_no] for inpatient_no in kept_order], duplicates, missing_inpatient_no_records
def slim_department_summary(batch_name: str, item: dict[str, Any]) -> dict[str, Any]:
raw_tips = item.get("提示", [])
if isinstance(raw_tips, list):
tips = "".join(str(tip) for tip in raw_tips)
else:
tips = str(raw_tips)
return {
"处理批次": batch_name,
"来源文件夹": item["来源文件夹"],
"大科室": item["大科室"],
"子科室": item["子科室"],
"图片数": item["图片数"],
"记录数": item["记录数"],
"拼接组数": item["拼接组数"],
"提示": tips,
}
def write_csv(path: Path, rows: list[dict[str, Any]]) -> None:
if not rows:
return
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8-sig", newline="") as file:
writer = csv.DictWriter(file, fieldnames=list(rows[0].keys()))
writer.writeheader()
writer.writerows(rows)
def main() -> None:
ROOT.mkdir(exist_ok=True)
raw_records: list[dict[str, Any]] = []
batch_summaries: list[dict[str, Any]] = []
for result_dir in sorted(ROOT.rglob(f"*{RESULT_SUFFIX}")):
archive_path = result_dir / "患者列表_结构化.json"
review_path = result_dir / "复核报告.json"
if not archive_path.exists():
continue
batch_name = result_dir.name[: -len(RESULT_SUFFIX)]
archive = json.loads(archive_path.read_text(encoding="utf-8"))
review = json.loads(review_path.read_text(encoding="utf-8")) if review_path.exists() else {}
department_summaries = [
slim_department_summary(batch_name, item) for item in archive.get("科室汇总", [])
]
summary = {
"处理批次": batch_name,
"结果目录": str(result_dir),
**archive.get("汇总", {}),
"科室归类": archive.get("科室归类", []),
"科室汇总": department_summaries,
"需人工复核记录数": len(review.get("需人工复核记录", [])),
"人工修正记录数": len(review.get("人工修正记录", [])),
}
batch_summaries.append(summary)
summary_dir = result_dir / INFO_DIR_NAME
write_json(summary_dir / "汇总.json", {**summary, "科室汇总": department_summaries})
write_csv(summary_dir / "科室汇总.csv", department_summaries)
for record in archive.get("患者记录", []):
enriched = {"处理批次": batch_name, **record}
raw_records.append(enriched)
all_records, duplicate_records, missing_inpatient_no_records = deduplicate_records_by_inpatient_no(raw_records)
flat_records = [flatten_record(record["处理批次"], record) for record in all_records]
kept_by_batch = Counter(record["处理批次"] for record in all_records)
dropped_by_batch = Counter(item["剔除记录"]["处理批次"] for item in duplicate_records)
missing_by_batch = Counter(item["剔除记录"]["处理批次"] for item in missing_inpatient_no_records)
batch_level_duplicate_count = sum(int(item.get("重复住院号剔除记录数", 0)) for item in batch_summaries)
batch_level_raw_count = sum(
int(item.get("去重前患者记录数", int(item.get("患者记录数", 0)) + int(item.get("重复住院号剔除记录数", 0))))
for item in batch_summaries
)
for summary in batch_summaries:
batch_name = summary["处理批次"]
original_count = int(summary.get("患者记录数", 0))
summary["原始患者记录数"] = original_count
summary["合并阶段重复住院号剔除记录数"] = int(dropped_by_batch.get(batch_name, 0))
summary["合并阶段缺少住院号剔除记录数"] = int(missing_by_batch.get(batch_name, 0))
summary["患者记录数"] = int(kept_by_batch.get(batch_name, 0))
write_json(Path(summary["结果目录"]) / INFO_DIR_NAME / "汇总.json", summary)
global_summary = {
"批次数": len(batch_summaries),
"总图片数": sum(int(item.get("图片数", 0)) for item in batch_summaries),
"批次内去重前患者记录数": batch_level_raw_count,
"批次内重复住院号剔除记录数": batch_level_duplicate_count,
"合并前患者记录数": len(raw_records),
"总患者记录数": len(all_records),
"合并阶段重复住院号剔除记录数": len(duplicate_records),
"合并阶段缺少住院号剔除记录数": len(missing_inpatient_no_records),
"重复住院号剔除记录数": batch_level_duplicate_count + len(duplicate_records),
"需人工复核记录数": sum(int(item.get("需人工复核记录数", 0)) for item in batch_summaries),
"人工修正记录数": sum(int(item.get("人工修正记录数", 0)) for item in batch_summaries),
"批次汇总": batch_summaries,
}
write_json(ROOT / INFO_DIR_NAME / "全局汇总.json", global_summary)
write_csv(ROOT / INFO_DIR_NAME / "批次汇总.csv", batch_summaries)
write_json(
ROOT / "合并_患者列表_结构化.json",
{
"汇总": global_summary,
"重复住院号剔除记录": duplicate_records,
"缺少住院号剔除记录": missing_inpatient_no_records,
"患者记录": all_records,
},
)
write_jsonl(ROOT / "合并_患者列表_记录.jsonl", all_records)
write_csv(ROOT / "合并_患者列表_记录.csv", flat_records)
write_json(ROOT / INFO_DIR_NAME / "重复住院号报告.json", duplicate_records)
write_json(ROOT / INFO_DIR_NAME / "缺少住院号报告.json", missing_inpatient_no_records)
print(json.dumps(global_summary, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()