From 8755647784e9c732509ac3a9e934fc22e67d77f4 Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Thu, 11 Jun 2026 02:50:39 +0800 Subject: [PATCH] =?UTF-8?q?Use=20HIS=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86?= =?UTF-8?q?=20as=20Gitea=20backup=20root?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 26 ++- 患者列表处理/.env.example | 1 + 患者列表处理/人工复核网页端/.env.example | 1 + 患者列表处理/人工复核网页端/app.py | 35 ++- .../人工复核网页端/docker-compose.yml | 2 + 患者列表处理/工作流_Gitea版.md | 202 ------------------ 6 files changed, 57 insertions(+), 210 deletions(-) delete mode 100644 患者列表处理/工作流_Gitea版.md diff --git a/.gitignore b/.gitignore index a0c0412..62353e7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,17 @@ .pgpass # Patient data and generated outputs -数据处理结果区/ -待处理-患者目录图片集群/ -已处理-患者目录图片集群/ -待处理-患者首页PDF/ -已处理-患者首页PDF/ +**/数据处理结果区/ +**/待处理-患者目录图片集群/ +**/已处理-患者目录图片集群/ +**/待处理-患者首页PDF/ +**/已处理-患者首页PDF/ +**/待处理-患者目录图片集群 +**/已处理-患者目录图片集群 +**/待处理-患者首页PDF +**/已处理-患者首页PDF +**/参考-过去很烂的程序/ +**/参考-病案首页图片/ *.csv *.jsonl *.pdf @@ -18,10 +24,16 @@ *.jpeg *.xlsx *.xls +*.dump +*.backup +*.sql.gz +*.zip # Runtime/cache __pycache__/ *.py[cod] +*.db +*.sqlite *.log *.tmp .DS_Store @@ -29,7 +41,9 @@ __pycache__/ .vscode/ # Local review settings +工作流_本地使用版.md +工作流_本地使用版*.zip review_settings.local.json -数据可视化网页端/review_settings.local.json +**/数据可视化网页端/review_settings.local.json 人工复核网页端配置.json 03_人工复核修正.json diff --git a/患者列表处理/.env.example b/患者列表处理/.env.example index 6a97306..8c71d92 100644 --- a/患者列表处理/.env.example +++ b/患者列表处理/.env.example @@ -9,6 +9,7 @@ REVIEW_APP_PASSWORD=请填写网页端登录密码 REVIEW_APP_SECRET_KEY=请填写随机字符串 REVIEW_APP_PORT=8090 REVIEW_POSTGRES_SYNC=1 +HIS_LIST_DATA_ROOT=/path/to/HIS数据/患者列表 KIMI_API_KEY=请填写Kimi或Moonshot API Key KIMI_MODEL=kimi-k2.6 diff --git a/患者列表处理/人工复核网页端/.env.example b/患者列表处理/人工复核网页端/.env.example index a5aaf90..becdcb4 100644 --- a/患者列表处理/人工复核网页端/.env.example +++ b/患者列表处理/人工复核网页端/.env.example @@ -3,6 +3,7 @@ REVIEW_APP_PASSWORD=change-me REVIEW_APP_SECRET_KEY=change-me-to-a-random-string REVIEW_APP_PORT=8090 REVIEW_POSTGRES_SYNC=1 +HIS_LIST_DATA_ROOT=/path/to/HIS数据/患者列表 KIMI_API_KEY=change-me KIMI_API_ENABLED=1 KIMI_MODEL=kimi-k2.6 diff --git a/患者列表处理/人工复核网页端/app.py b/患者列表处理/人工复核网页端/app.py index 1c290b5..eb9a59b 100644 --- a/患者列表处理/人工复核网页端/app.py +++ b/患者列表处理/人工复核网页端/app.py @@ -47,6 +47,7 @@ DATETIME_PATTERN = re.compile(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}") FLEX_DATETIME_PATTERN = re.compile(r"^(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})$") INPATIENT_NO_PATTERN = re.compile(r".*") IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".bmp", ".webp"} +IMAGE_DATA_LINK_NAMES = ("已处理-患者目录图片集群", "待处理-患者目录图片集群") IGNORED_REVIEW_TIPS = {"缺少出院时间"} IGNORED_AI_ISSUE_PATTERNS = ( "最后书写时间与出院时间顺序异常", @@ -1579,11 +1580,41 @@ def filtered_items() -> list[dict[str, Any]]: return items +def path_is_relative_to(path: Path, root: Path) -> bool: + try: + path.relative_to(root) + return True + except ValueError: + return False + + +def allowed_file_roots() -> list[Path]: + roots = [WORKSPACE_ROOT.resolve()] + for name in IMAGE_DATA_LINK_NAMES: + link_path = WORKSPACE_ROOT / name + if link_path.exists() or link_path.is_symlink(): + roots.append(link_path.resolve(strict=False)) + extra_roots = os.getenv("REVIEW_ALLOWED_PATH_ROOTS", "") + for root_text in extra_roots.split(os.pathsep): + root_text = root_text.strip() + if root_text: + roots.append(Path(root_text).expanduser().resolve(strict=False)) + + unique_roots: list[Path] = [] + seen: set[str] = set() + for root in roots: + key = str(root) + if key not in seen: + seen.add(key) + unique_roots.append(root) + return unique_roots + + def safe_workspace_path(path_text: str) -> Path: raw = Path(path_text) path = raw if raw.is_absolute() else WORKSPACE_ROOT / raw - resolved = path.resolve() - if WORKSPACE_ROOT not in resolved.parents and resolved != WORKSPACE_ROOT: + resolved = path.resolve(strict=False) + if not any(path_is_relative_to(resolved, root) or resolved == root for root in allowed_file_roots()): abort(403) if not resolved.exists(): abort(404) diff --git a/患者列表处理/人工复核网页端/docker-compose.yml b/患者列表处理/人工复核网页端/docker-compose.yml index 472f8d1..8945322 100644 --- a/患者列表处理/人工复核网页端/docker-compose.yml +++ b/患者列表处理/人工复核网页端/docker-compose.yml @@ -12,8 +12,10 @@ services: WORKSPACE_ROOT: /workspace REVIEW_APP_HOST: 0.0.0.0 REVIEW_APP_PORT: 8000 + REVIEW_ALLOWED_PATH_ROOTS: ${HIS_LIST_DATA_ROOT:-/data/his-list-images} ports: - "${REVIEW_APP_PORT:-8090}:8000" volumes: - ..:/workspace + - ${HIS_LIST_DATA_ROOT:-../待处理-患者目录图片集群}:${HIS_LIST_DATA_ROOT:-/data/his-list-images}:ro restart: unless-stopped diff --git a/患者列表处理/工作流_Gitea版.md b/患者列表处理/工作流_Gitea版.md deleted file mode 100644 index 2cefa05..0000000 --- a/患者列表处理/工作流_Gitea版.md +++ /dev/null @@ -1,202 +0,0 @@ -# HIS 患者列表 OCR 归档工作流 - -这是可提交到 Gitea 的无密钥版本。不要在本文件中写入腾讯云密钥、数据库密码、Gitea 密码或患者处理结果。 - -## 目标 - -把 HIS 患者列表截图整理为结构化患者记录,并同步到 PostgreSQL 的 `"Patient_Lists"` 单表。 - -最终数据库只保留正式查看和追溯需要的信息: - -- 数据定位:`batch_name`、`source_folder`、`image_path`、`image_name`、`image_row_no` -- 科室信息:`major_department`、`sub_department` -- 患者信息:姓名、性别、年龄、住院号、诊断、入院时间、最后书写时间、住院天数、出院时间、手术后天数 -- 复核信息:`review_status`、`review_notes`、`manual_corrected` - -OCR 请求号、OCR 缓存路径、拼接图路径、拼接组号等中间态字段不进入数据库正式表。 - -## 目录结构 - -- `待处理-患者目录图片集群/`:尚未处理的 HIS 图片批次。 -- `已处理-患者目录图片集群/`:已经完成处理的原始图片批次。 -- `数据处理工作区/`:程序、科室规则、数据库结构、说明文档。 -- `数据处理结果区/已处理-患者目录图片集群/`:各批次本地处理结果。 -- `数据处理结果区/信息记录/`:全局信息记录、批次汇总、待处理盘点等。 - -仓库只提交程序和文档。图片、OCR 缓存、处理结果、人工修正数据都应被 `.gitignore` 排除。 - -## 关键文件 - -1. `数据处理工作区/01_科室分类规则.json` -2. `数据处理工作区/02_患者列表OCR归档.py` -3. `数据处理工作区/03_人工复核修正.template.json` -4. `数据处理工作区/04_合并批次结果.py` -5. `数据处理工作区/05_同步PostgreSQL单表.py` -6. `数据处理工作区/06_PostgreSQL建表结构.sql` -7. `数据处理工作区/07_处理程序说明.md` -8. `数据处理工作区/08_PostgreSQL调整Patient_Lists列顺序.sql` - -本地实际使用时,可以从模板复制出 `数据处理工作区/03_人工复核修正.json`。这个文件可能包含患者信息,不提交到 Git。 - -患者列表字段顺序固定为:姓名、性别、年龄、住院号、诊断、入院时间、最后书写时间、住院天数、出院时间、手术后天数。 - -## 处理前准备 - -安装依赖: - -```bash -python3 -m pip install pillow -psql --version -``` - -设置腾讯云 OCR 环境变量: - -```bash -export TENCENTCLOUD_SECRET_ID='填入腾讯云 SecretId' -export TENCENTCLOUD_SECRET_KEY='填入腾讯云 SecretKey' -``` - -OCR 接口使用腾讯云 `RecognizeTableAccurateOCR` 表格识别 V3: - -- 表格识别 V3 文档:https://cloud.tencent.com/document/product/866/86721 - -## 处理一个批次 - -示例批次名用 `批次文件夹名` 表示: - -```bash -python3 数据处理工作区/02_患者列表OCR归档.py \ - --input "待处理-患者目录图片集群/批次文件夹名" \ - --output "数据处理结果区/已处理-患者目录图片集群/批次文件夹名-列表归档结果" \ - --ocr-engine table-v3 \ - --batch-size 6 \ - --image-padding-y 24 \ - --workers 1 \ - --folder-workers 2 \ - --timeout 90 \ - --max-retries 1 -``` - -推荐正式处理优先使用 `--batch-size 6`。程序会根据行数校验和接口错误自动降到 4/3/2/单张;降档会增加调用次数,但能降低漏行风险。 - -处理完成后检查: - -- `患者列表_结构化.json` -- `患者列表_记录.csv` -- `复核报告.json` -- `重复住院号报告.json` -- `信息记录/汇总.json` - -## 复核规则 - -推荐使用 Docker 化网页端进行复核和抽查: - -```bash -cd 人工复核网页端 -docker compose up -d --build -``` - -网页端包含概览、复核、抽查、设置四个区域;抽查功能使用 Kimi 多模态模型,配置项通过 `.env` 或网页端设置页提供,不在 Gitea 文档中保存密钥。 - -`review_status` 常见值: - -- `自动复核通过`:OCR 结果经过程序规则校验后没有发现明显异常,`manual_corrected=false`。 -- `人工复核通过`:该行命中了 `03_人工复核修正.json` 中的人工修正项,修正后校验通过,`manual_corrected=true`。 -- `需人工复核`:程序无法可靠判断,需要人工查看图片和记录,原因写在 `review_notes`。 - -`出院时间` 允许为空;若填写出院时间,则需要满足标准时间格式,且不应早于入院时间。PostgreSQL 约束会阻止“入院时间晚于出院时间”却仍被标记为通过的记录。 - -人工修正流程: - -1. 打开该批次的 `复核报告.json`。 -2. 根据 `图片路径` 和 `图片内行号` 回看原图。 -3. 把确认后的患者字段写入 `数据处理工作区/03_人工复核修正.json`。 -4. 使用缓存重建批次: - -```bash -python3 数据处理工作区/02_患者列表OCR归档.py \ - --input "待处理-患者目录图片集群/批次文件夹名" \ - --output "数据处理结果区/已处理-患者目录图片集群/批次文件夹名-列表归档结果" \ - --ocr-engine table-v3 \ - --batch-size 6 \ - --image-padding-y 24 \ - --workers 1 \ - --folder-workers 2 \ - --rebuild-from-cache -``` - -## 合并所有批次 - -```bash -python3 数据处理工作区/04_合并批次结果.py -``` - -输出包括: - -- `数据处理结果区/合并_患者列表_结构化.json` -- `数据处理结果区/合并_患者列表_记录.csv` -- `数据处理结果区/信息记录/全局汇总.json` -- `数据处理结果区/信息记录/批次汇总.csv` -- `数据处理结果区/信息记录/重复住院号报告.json` - -## 同步 PostgreSQL - -设置数据库环境变量: - -```bash -export HIS_DB_HOST='数据库主机' -export HIS_DB_PORT='5432' -export HIS_DB_NAME='DB_NAME' -export HIS_DB_USER='DB_USER' -export HIS_DB_PASSWORD='填入数据库密码' -``` - -同步: - -```bash -python3 数据处理工作区/05_同步PostgreSQL单表.py -``` - -核对: - -```sql -SELECT count(*) FROM "Patient_Lists"; -SELECT review_status, manual_corrected, count(*) FROM "Patient_Lists" GROUP BY review_status, manual_corrected; -SELECT inpatient_no FROM "Patient_Lists" GROUP BY inpatient_no HAVING count(*) > 1; -``` - -`"Patient_Lists"` 是带大小写的 PostgreSQL 表名,查询时必须加双引号。 - -## 处理完成后 - -确认批次结果和数据库无误后,将原始图片批次移动到: - -```bash -mv "待处理-患者目录图片集群/批次文件夹名" "已处理-患者目录图片集群/" -``` - -## Gitea 提交 - -仓库地址: - -```text -https://gitea.huijutec.cn/admin/HIS_List.git -``` - -提交前检查不要包含数据和密钥: - -```bash -git status --short --ignored -git diff --cached --name-only -grep -R "Secret\\|PASSWORD\\|密码" -n README.md 数据处理工作区 --exclude-dir='__pycache__' || true -``` - -提交: - -```bash -git add .gitignore .env.example README.md 工作流_Gitea版.md 人工复核网页端 数据处理工作区/01_科室分类规则.json 数据处理工作区/02_患者列表OCR归档.py 数据处理工作区/03_人工复核修正.template.json 数据处理工作区/04_合并批次结果.py 数据处理工作区/05_同步PostgreSQL单表.py 数据处理工作区/06_PostgreSQL建表结构.sql 数据处理工作区/07_处理程序说明.md 数据处理工作区/08_PostgreSQL调整Patient_Lists列顺序.sql -git commit -m "Update HIS patient list workflow" -git push origin main -``` - -如果本地 `origin` 没有保存认证信息,需要使用一次性认证或交互式登录,但不要把密码写入文档或 Git 配置。