Use HIS数据处理 as Gitea backup root
This commit is contained in:
26
.gitignore
vendored
26
.gitignore
vendored
@@ -5,11 +5,17 @@
|
|||||||
.pgpass
|
.pgpass
|
||||||
|
|
||||||
# Patient data and generated outputs
|
# Patient data and generated outputs
|
||||||
数据处理结果区/
|
**/数据处理结果区/
|
||||||
待处理-患者目录图片集群/
|
**/待处理-患者目录图片集群/
|
||||||
已处理-患者目录图片集群/
|
**/已处理-患者目录图片集群/
|
||||||
待处理-患者首页PDF/
|
**/待处理-患者首页PDF/
|
||||||
已处理-患者首页PDF/
|
**/已处理-患者首页PDF/
|
||||||
|
**/待处理-患者目录图片集群
|
||||||
|
**/已处理-患者目录图片集群
|
||||||
|
**/待处理-患者首页PDF
|
||||||
|
**/已处理-患者首页PDF
|
||||||
|
**/参考-过去很烂的程序/
|
||||||
|
**/参考-病案首页图片/
|
||||||
*.csv
|
*.csv
|
||||||
*.jsonl
|
*.jsonl
|
||||||
*.pdf
|
*.pdf
|
||||||
@@ -18,10 +24,16 @@
|
|||||||
*.jpeg
|
*.jpeg
|
||||||
*.xlsx
|
*.xlsx
|
||||||
*.xls
|
*.xls
|
||||||
|
*.dump
|
||||||
|
*.backup
|
||||||
|
*.sql.gz
|
||||||
|
*.zip
|
||||||
|
|
||||||
# Runtime/cache
|
# Runtime/cache
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
*.log
|
*.log
|
||||||
*.tmp
|
*.tmp
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@@ -29,7 +41,9 @@ __pycache__/
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
# Local review settings
|
# Local review settings
|
||||||
|
工作流_本地使用版.md
|
||||||
|
工作流_本地使用版*.zip
|
||||||
review_settings.local.json
|
review_settings.local.json
|
||||||
数据可视化网页端/review_settings.local.json
|
**/数据可视化网页端/review_settings.local.json
|
||||||
人工复核网页端配置.json
|
人工复核网页端配置.json
|
||||||
03_人工复核修正.json
|
03_人工复核修正.json
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ REVIEW_APP_PASSWORD=请填写网页端登录密码
|
|||||||
REVIEW_APP_SECRET_KEY=请填写随机字符串
|
REVIEW_APP_SECRET_KEY=请填写随机字符串
|
||||||
REVIEW_APP_PORT=8090
|
REVIEW_APP_PORT=8090
|
||||||
REVIEW_POSTGRES_SYNC=1
|
REVIEW_POSTGRES_SYNC=1
|
||||||
|
HIS_LIST_DATA_ROOT=/path/to/HIS数据/患者列表
|
||||||
|
|
||||||
KIMI_API_KEY=请填写Kimi或Moonshot API Key
|
KIMI_API_KEY=请填写Kimi或Moonshot API Key
|
||||||
KIMI_MODEL=kimi-k2.6
|
KIMI_MODEL=kimi-k2.6
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ REVIEW_APP_PASSWORD=change-me
|
|||||||
REVIEW_APP_SECRET_KEY=change-me-to-a-random-string
|
REVIEW_APP_SECRET_KEY=change-me-to-a-random-string
|
||||||
REVIEW_APP_PORT=8090
|
REVIEW_APP_PORT=8090
|
||||||
REVIEW_POSTGRES_SYNC=1
|
REVIEW_POSTGRES_SYNC=1
|
||||||
|
HIS_LIST_DATA_ROOT=/path/to/HIS数据/患者列表
|
||||||
KIMI_API_KEY=change-me
|
KIMI_API_KEY=change-me
|
||||||
KIMI_API_ENABLED=1
|
KIMI_API_ENABLED=1
|
||||||
KIMI_MODEL=kimi-k2.6
|
KIMI_MODEL=kimi-k2.6
|
||||||
|
|||||||
@@ -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})$")
|
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".*")
|
INPATIENT_NO_PATTERN = re.compile(r".*")
|
||||||
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".bmp", ".webp"}
|
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".bmp", ".webp"}
|
||||||
|
IMAGE_DATA_LINK_NAMES = ("已处理-患者目录图片集群", "待处理-患者目录图片集群")
|
||||||
IGNORED_REVIEW_TIPS = {"缺少出院时间"}
|
IGNORED_REVIEW_TIPS = {"缺少出院时间"}
|
||||||
IGNORED_AI_ISSUE_PATTERNS = (
|
IGNORED_AI_ISSUE_PATTERNS = (
|
||||||
"最后书写时间与出院时间顺序异常",
|
"最后书写时间与出院时间顺序异常",
|
||||||
@@ -1579,11 +1580,41 @@ def filtered_items() -> list[dict[str, Any]]:
|
|||||||
return items
|
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:
|
def safe_workspace_path(path_text: str) -> Path:
|
||||||
raw = Path(path_text)
|
raw = Path(path_text)
|
||||||
path = raw if raw.is_absolute() else WORKSPACE_ROOT / raw
|
path = raw if raw.is_absolute() else WORKSPACE_ROOT / raw
|
||||||
resolved = path.resolve()
|
resolved = path.resolve(strict=False)
|
||||||
if WORKSPACE_ROOT not in resolved.parents and resolved != WORKSPACE_ROOT:
|
if not any(path_is_relative_to(resolved, root) or resolved == root for root in allowed_file_roots()):
|
||||||
abort(403)
|
abort(403)
|
||||||
if not resolved.exists():
|
if not resolved.exists():
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ services:
|
|||||||
WORKSPACE_ROOT: /workspace
|
WORKSPACE_ROOT: /workspace
|
||||||
REVIEW_APP_HOST: 0.0.0.0
|
REVIEW_APP_HOST: 0.0.0.0
|
||||||
REVIEW_APP_PORT: 8000
|
REVIEW_APP_PORT: 8000
|
||||||
|
REVIEW_ALLOWED_PATH_ROOTS: ${HIS_LIST_DATA_ROOT:-/data/his-list-images}
|
||||||
ports:
|
ports:
|
||||||
- "${REVIEW_APP_PORT:-8090}:8000"
|
- "${REVIEW_APP_PORT:-8090}:8000"
|
||||||
volumes:
|
volumes:
|
||||||
- ..:/workspace
|
- ..:/workspace
|
||||||
|
- ${HIS_LIST_DATA_ROOT:-../待处理-患者目录图片集群}:${HIS_LIST_DATA_ROOT:-/data/his-list-images}:ro
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -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 配置。
|
|
||||||
Reference in New Issue
Block a user