Import sanitized HIS processing tools
This commit is contained in:
4
患者首页处理/数据可视化网页端/.dockerignore
Normal file
4
患者首页处理/数据可视化网页端/.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.env
|
||||
.env.*
|
||||
17
患者首页处理/数据可视化网页端/.env.example
Normal file
17
患者首页处理/数据可视化网页端/.env.example
Normal file
@@ -0,0 +1,17 @@
|
||||
PGHOST=DB_HOST
|
||||
PGPORT=5432
|
||||
PGDATABASE=DB_NAME
|
||||
PGUSER=DB_USER
|
||||
PGPASSWORD=请填写数据库密码
|
||||
PGTABLE=Patient_FrontPages
|
||||
|
||||
APP_HOST=0.0.0.0
|
||||
APP_PORT=8501
|
||||
REVIEW_ADMIN_USER=admin
|
||||
REVIEW_ADMIN_PASSWORD=change-me
|
||||
REVIEW_STATUS_CHECK_TIME=03:00
|
||||
|
||||
PDF_DIR=/data/pdfs
|
||||
|
||||
MOONSHOT_API_KEY=请填写 Kimi API Key
|
||||
KIMI_MODEL=kimi-k2.6
|
||||
19
患者首页处理/数据可视化网页端/Dockerfile
Normal file
19
患者首页处理/数据可视化网页端/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN pip install --no-cache-dir \
|
||||
fastapi==0.115.6 \
|
||||
"uvicorn[standard]==0.34.0" \
|
||||
psycopg2-binary==2.9.10 \
|
||||
python-dotenv==1.0.1 \
|
||||
PyMuPDF==1.24.14
|
||||
|
||||
COPY app /app/app
|
||||
|
||||
EXPOSE 8501
|
||||
|
||||
CMD ["sh", "-c", "uvicorn app.main:app --host ${APP_HOST:-0.0.0.0} --port ${APP_PORT:-8501}"]
|
||||
58
患者首页处理/数据可视化网页端/README.md
Normal file
58
患者首页处理/数据可视化网页端/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 患者首页可视化对照网页端
|
||||
|
||||
这个网页端用于人工复核患者首页:概览看批次状态,复核页只处理需复核记录,中间查看 PDF,右侧编辑结构化摘录信息;保存后直接同步到 PostgreSQL 表 `"Patient_FrontPages"`。
|
||||
|
||||
## 启动
|
||||
|
||||
在项目根目录确认 `.env` 已存在,然后运行:
|
||||
|
||||
```bash
|
||||
docker compose -f 数据可视化网页端/docker-compose.yml up -d --build
|
||||
```
|
||||
|
||||
浏览器打开:
|
||||
|
||||
```text
|
||||
http://localhost:8501
|
||||
```
|
||||
|
||||
首次进入会显示登录界面。默认管理员为 `admin`,默认密码为 `change-me`;可在 `.env` 中通过 `REVIEW_ADMIN_USER`、`REVIEW_ADMIN_PASSWORD` 修改。设置页创建的本地用户会同步作为登录账号,并按权限控制概览、复核、抽查、抽查一览、设置页面。
|
||||
|
||||
## 目录挂载
|
||||
|
||||
- `已处理-患者首页PDF/2026_5_25_处理/` 挂载为容器内 `/data/pdfs`,只读。
|
||||
- 网页端只预览 PDF,不再挂载或依赖 PDF 转图片目录。
|
||||
|
||||
## 保存规则
|
||||
|
||||
右侧字段保存后会:
|
||||
|
||||
- 更新被编辑的 PostgreSQL 字段。
|
||||
- 设置 `manual_corrected = true`。
|
||||
- 自动将 `review_status` 标记为 `reviewed`。
|
||||
- 把人工备注追加到 `review_notes` JSONB 数组中。
|
||||
- 在主表 `review_logs` JSONB 列中记录修改时间、修改字段、修改前后值和人工备注。
|
||||
- PostgreSQL 触发器会按非空 `inpatient_no` 自动同步 `Patient_Lists.has_front_page/front_page_id/front_page_source_file`;本网页端只校验患者号不能为空,不校验编号格式,同一患者号只保留一个首页,迁移时保留最新记录,后续同号写入会覆盖旧首页,删除首页记录时旧关联会自动解除;同一患者号姓名不一致时,以首页表中的非空姓名覆盖 `Patient_Lists.patient_name`。只有自动补建新列表记录时,才把主要诊断、入院时间、出院时间写入 `Patient_Lists`。
|
||||
|
||||
`.env` 含数据库密码,只保留本地,不提交到 Gitea;可参考 `.env.example` 创建。
|
||||
|
||||
## 页面规则
|
||||
|
||||
- 顶部页签包括:概览、复核、抽查、抽查一览、设置;设置内合并用户与权限、数据库状态和目录信息。
|
||||
- 复核页以住院号作为第一识别项;住院号由住院次数 2 位和首页病案号 10 位自动生成,例如 `ZY020001447443`。
|
||||
- 复核页默认进入“全部复核数据”,包含需复核、自动修正、已人工复核/已人工修改,不处理无需复核的自动通过记录。
|
||||
- 复核提示会自动定位到右侧模块;命中的模块和字段标红,无关模块默认收起。
|
||||
- 人工备注下方展示修改记录表,时间由 PostgreSQL 后台记录。
|
||||
- PDF 以 `inline` 方式展示,不再作为附件触发保存;浏览器不能彻底禁止保存已展示内容。
|
||||
- 顶部数据库/PDF 状态读取缓存;设置页可手动“立即检查”,也可设置每日凌晨等固定时间自动检查。
|
||||
- 复核状态、质控状态显示在 PDF 下方,只读。
|
||||
- 诊断、手术、费用等 JSONB 字段以表格方式展示和编辑。
|
||||
- 手术操作按编码、日期、级别、名称、术者、I助、II助、切口愈合等级、麻醉方式、麻醉医师拆分。
|
||||
|
||||
## 抽查规则
|
||||
|
||||
- 抽查页可从 `reviewed` 或 `auto_pass` 中随机抽样。
|
||||
- 抽查页右侧和复核页一样展示并可编辑全部结构化信息,下方有人工备注和修改记录。
|
||||
- 随机抽取不会写入“待抽查”记录;只有点击“归类通过并保存”“归类异常并保存”“归类不确定并保存”后,才写入主表 `audit_logs` JSONB 列。
|
||||
- 抽查一览只展示已归类的通过、异常、不确定记录,并显示修改内容、人工备注和更新时间。
|
||||
- 涉及患者资料的视觉识别仍需人工运行 `07_Kimi图片识别辅助.py`,不会由网页端自动上传。
|
||||
1
患者首页处理/数据可视化网页端/app/__init__.py
Normal file
1
患者首页处理/数据可视化网页端/app/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Patient front page visual review web app."""
|
||||
2779
患者首页处理/数据可视化网页端/app/main.py
Normal file
2779
患者首页处理/数据可视化网页端/app/main.py
Normal file
File diff suppressed because it is too large
Load Diff
1393
患者首页处理/数据可视化网页端/app/static/app.js
Normal file
1393
患者首页处理/数据可视化网页端/app/static/app.js
Normal file
File diff suppressed because it is too large
Load Diff
273
患者首页处理/数据可视化网页端/app/static/index.html
Normal file
273
患者首页处理/数据可视化网页端/app/static/index.html
Normal file
@@ -0,0 +1,273 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>患者首页复核工作台</title>
|
||||
<link rel="stylesheet" href="/static/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<section id="loginView" class="login-view">
|
||||
<form id="loginForm" class="login-panel">
|
||||
<div class="login-mark"></div>
|
||||
<h1>患者首页复核工作台</h1>
|
||||
<p>登录后进入复核、抽查与数据回写工作区</p>
|
||||
<label>
|
||||
<span>用户名</span>
|
||||
<input id="loginUsername" autocomplete="username" value="admin" placeholder="用户名">
|
||||
</label>
|
||||
<label>
|
||||
<span>密码</span>
|
||||
<input id="loginPassword" type="password" autocomplete="current-password" placeholder="密码">
|
||||
</label>
|
||||
<button id="loginBtn" type="submit">登录</button>
|
||||
<div id="loginMessage" class="login-message"></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<div id="appShell" class="app-shell is-hidden">
|
||||
<header class="topbar">
|
||||
<div class="brand">
|
||||
<span class="mark"></span>
|
||||
<div>
|
||||
<h1>患者首页复核工作台</h1>
|
||||
<p>PDF 预览 · 复核定位 · PostgreSQL 回写</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="top-nav" aria-label="工作台导航">
|
||||
<button class="nav-button is-active" type="button" data-page="overview">概览</button>
|
||||
<button class="nav-button" type="button" data-page="review">复核</button>
|
||||
<button class="nav-button" type="button" data-page="audit">抽查</button>
|
||||
<button class="nav-button" type="button" data-page="auditHistory">抽查一览</button>
|
||||
<button class="nav-button" type="button" data-page="settings">设置</button>
|
||||
</nav>
|
||||
<div class="status-grid" id="statusGrid">
|
||||
<div class="status-card"><span>数据库</span><strong id="dbState">检查中</strong></div>
|
||||
<div class="status-card"><span>记录总数</span><strong id="dbTotal">--</strong></div>
|
||||
<div class="status-card"><span>仅需复核</span><strong id="dbNeedsReview">--</strong></div>
|
||||
<div class="status-card"><span>AI无问题</span><strong id="dbAiPassed">--</strong></div>
|
||||
<div class="status-card"><span>AI待确认</span><strong id="dbAiPending">--</strong></div>
|
||||
<div class="status-card"><span>已人工复核</span><strong id="dbReviewed">--</strong></div>
|
||||
</div>
|
||||
<div class="session-box">
|
||||
<span id="currentUserLabel">未登录</span>
|
||||
<button id="logoutBtn" type="button">退出</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="overviewPage" class="page-view overview-page">
|
||||
<section class="metric-grid" id="overviewMetrics"></section>
|
||||
<section class="page-band">
|
||||
<div class="section-head">
|
||||
<h2>待处理概况</h2>
|
||||
<button id="overviewRefreshBtn" type="button">刷新</button>
|
||||
</div>
|
||||
<div id="overviewQueues" class="queue-summary"></div>
|
||||
</section>
|
||||
<section class="page-band">
|
||||
<div class="section-head">
|
||||
<h2>最近人工修改</h2>
|
||||
</div>
|
||||
<div id="overviewRecentLogs" class="data-table-wrap"></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<main id="reviewPage" class="workspace page-view is-hidden">
|
||||
<aside class="record-panel">
|
||||
<div class="panel-tools">
|
||||
<input id="searchInput" type="search" placeholder="搜索住院号、病案号、姓名、诊断">
|
||||
<select id="statusFilter">
|
||||
<option value="review_all">全部数据</option>
|
||||
<option value="needs_review">仅需复核</option>
|
||||
<option value="AI复核-无问题">仅AI复核-无问题</option>
|
||||
<option value="AI复核-待确认">仅AI复核-待确认</option>
|
||||
<option value="reviewed">已人工复核</option>
|
||||
</select>
|
||||
<div id="aiActions" class="ai-actions is-hidden">
|
||||
<button id="aiCurrentBtn" type="button">AI当前项</button>
|
||||
<button id="aiFiveBtn" type="button">AI后5项</button>
|
||||
<button id="aiAllBtn" type="button">AI后全部</button>
|
||||
</div>
|
||||
<button id="approveAiNoIssueBtn" class="bulk-action is-hidden" type="button">复核并保存所有项</button>
|
||||
</div>
|
||||
<div class="record-list" id="recordList" tabindex="0"></div>
|
||||
</aside>
|
||||
|
||||
<section class="pdf-panel">
|
||||
<div class="pdf-toolbar">
|
||||
<div>
|
||||
<strong id="pdfTitle">未选择病例</strong>
|
||||
<span id="pdfSubtitle"></span>
|
||||
</div>
|
||||
<div class="pdf-zoom-controls" aria-label="PDF缩放">
|
||||
<button id="pdfZoomOutBtn" type="button" title="缩小 PDF">−</button>
|
||||
<span id="pdfZoomLabel">110%</span>
|
||||
<button id="pdfZoomInBtn" type="button" title="放大 PDF">+</button>
|
||||
<button id="pdfZoomResetBtn" type="button" title="重置 PDF 缩放">1:1</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="viewer" id="pdfView">
|
||||
<iframe id="pdfFrame" title="患者首页 PDF" tabindex="-1"></iframe>
|
||||
</div>
|
||||
<div class="review-strip" id="reviewStrip"></div>
|
||||
</section>
|
||||
|
||||
<aside class="detail-panel">
|
||||
<div class="detail-head">
|
||||
<div>
|
||||
<h2 id="detailTitle">摘录信息</h2>
|
||||
<p id="detailMeta">选择左侧记录后开始复核</p>
|
||||
</div>
|
||||
<div class="detail-actions">
|
||||
<button id="saveBtn" type="button" disabled>复核并保存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="targetSummary" class="target-summary"></div>
|
||||
<form id="detailForm" class="detail-form structured-form"></form>
|
||||
<details class="review-log-panel collapsible-panel" open>
|
||||
<summary class="section-head compact">
|
||||
<h2>修改记录</h2>
|
||||
<span id="changeLogCount">0 条</span>
|
||||
</summary>
|
||||
<div id="changeLogTable" class="data-table-wrap"></div>
|
||||
</details>
|
||||
<div class="save-message" id="saveMessage"></div>
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
<main id="auditPage" class="page-view audit-page is-hidden">
|
||||
<section class="page-band audit-toolbar">
|
||||
<label>
|
||||
<span>抽查来源</span>
|
||||
<select id="auditSource">
|
||||
<option value="reviewed">已人工复核</option>
|
||||
<option value="auto_pass">自动通过</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>抽取数量</span>
|
||||
<input id="auditCount" type="number" min="1" max="50" value="5">
|
||||
</label>
|
||||
<button id="auditSampleBtn" type="button">随机抽取</button>
|
||||
<span id="auditMessage" class="inline-message"></span>
|
||||
</section>
|
||||
<section class="audit-layout">
|
||||
<aside class="record-panel">
|
||||
<div class="record-list" id="auditSampleList"></div>
|
||||
</aside>
|
||||
<section class="pdf-panel">
|
||||
<div class="pdf-toolbar">
|
||||
<div>
|
||||
<strong id="auditPdfTitle">未选择抽查记录</strong>
|
||||
<span id="auditPdfSubtitle"></span>
|
||||
</div>
|
||||
<div class="pdf-zoom-controls" aria-label="抽查PDF缩放">
|
||||
<button id="auditPdfZoomOutBtn" type="button" title="缩小 PDF">−</button>
|
||||
<span id="auditPdfZoomLabel">110%</span>
|
||||
<button id="auditPdfZoomInBtn" type="button" title="放大 PDF">+</button>
|
||||
<button id="auditPdfZoomResetBtn" type="button" title="重置 PDF 缩放">1:1</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="viewer" id="auditPdfView">
|
||||
<iframe id="auditPdfFrame" title="抽查 PDF" tabindex="-1"></iframe>
|
||||
</div>
|
||||
<div class="review-strip" id="auditReviewStrip"></div>
|
||||
</section>
|
||||
<aside class="detail-panel">
|
||||
<div class="detail-head audit-detail-head">
|
||||
<div>
|
||||
<h2 id="auditDetailTitle">抽查信息</h2>
|
||||
<p id="auditDetailMeta">随机抽取后开始抽查</p>
|
||||
</div>
|
||||
<div class="audit-save-actions">
|
||||
<button id="auditPassBtn" type="button">归类通过并保存</button>
|
||||
<button id="auditFailBtn" class="danger" type="button">归类异常并保存</button>
|
||||
<button id="auditUnsureBtn" type="button">归类不确定并保存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="auditTargetSummary" class="target-summary"></div>
|
||||
<form id="auditDetailForm" class="detail-form structured-form"></form>
|
||||
<div class="manual-note">
|
||||
<label for="auditNotes">抽查人工备注</label>
|
||||
<textarea id="auditNotes" rows="3" placeholder="填写抽查判断、修改原因或后续处理说明"></textarea>
|
||||
</div>
|
||||
<section class="review-log-panel">
|
||||
<div class="section-head compact">
|
||||
<h2>修改记录</h2>
|
||||
<span id="auditChangeLogCount">0 条</span>
|
||||
</div>
|
||||
<div id="auditChangeLogTable" class="data-table-wrap"></div>
|
||||
</section>
|
||||
</aside>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<main id="auditHistoryPage" class="page-view audit-history-page is-hidden">
|
||||
<section class="page-band">
|
||||
<div class="section-head">
|
||||
<h2>抽查一览</h2>
|
||||
<button id="auditHistoryRefreshBtn" type="button">刷新抽查记录</button>
|
||||
</div>
|
||||
<div id="auditHistoryTable" class="data-table-wrap"></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<main id="settingsPage" class="page-view settings-page is-hidden">
|
||||
<section class="settings-grid">
|
||||
<section class="page-band settings-wide">
|
||||
<div class="section-head">
|
||||
<h2>用户与权限</h2>
|
||||
<span id="settingsUserCount">0 个用户</span>
|
||||
</div>
|
||||
<form id="userForm" class="settings-form">
|
||||
<input id="newUsername" autocomplete="off" placeholder="新用户名">
|
||||
<input id="newPassword" type="password" autocomplete="new-password" placeholder="密码">
|
||||
<div id="newUserPermissions" class="permission-grid"></div>
|
||||
<button type="submit">新建用户</button>
|
||||
</form>
|
||||
<div id="userList" class="user-list"></div>
|
||||
</section>
|
||||
<section class="page-band">
|
||||
<h2>数据库与目录</h2>
|
||||
<div id="settingsStatus" class="settings-list"></div>
|
||||
</section>
|
||||
<section class="page-band">
|
||||
<h2>系统设置</h2>
|
||||
<div class="settings-list">
|
||||
<div>
|
||||
<span>状态检查</span>
|
||||
<form id="systemSettingsForm" class="status-check-form">
|
||||
<input id="statusCheckTime" type="time" value="03:00" aria-label="每日状态检查时间">
|
||||
<button id="saveSystemSettingsBtn" type="submit">保存时间</button>
|
||||
<button id="statusCheckBtn" type="button">立即检查</button>
|
||||
</form>
|
||||
</div>
|
||||
<div><span>检查计划</span><strong id="statusCheckMeta">尚未检查</strong></div>
|
||||
<div>
|
||||
<span>Kimi AI</span>
|
||||
<form id="kimiSettingsForm" class="status-check-form">
|
||||
<label class="toggle-field"><input id="kimiEnabled" type="checkbox">启用</label>
|
||||
<input id="kimiModel" type="text" placeholder="模型">
|
||||
<input id="kimiApiBase" type="url" placeholder="API Base">
|
||||
<input id="kimiConcurrency" type="number" min="1" max="6" value="3" aria-label="AI并发数">
|
||||
<button id="saveKimiSettingsBtn" type="submit">保存AI</button>
|
||||
</form>
|
||||
</div>
|
||||
<div><span>Kimi API</span><strong id="kimiMeta">未检查</strong></div>
|
||||
<div><span>复核工作台</span><strong>默认显示全部复核数据,仅使用 PDF 预览</strong></div>
|
||||
<div><span>保存动作</span><strong>写入字段、修改日志、reviewed 状态</strong></div>
|
||||
<div>
|
||||
<span>提交已复核</span>
|
||||
<button id="submitReviewedBtn" type="button">一键提交已人工复核项目</button>
|
||||
</div>
|
||||
<div><span>抽查来源</span><strong>reviewed / auto_pass</strong></div>
|
||||
<div><span>日志表</span><strong>复核和抽查日志并入主表 JSONB</strong></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1193
患者首页处理/数据可视化网页端/app/static/styles.css
Normal file
1193
患者首页处理/数据可视化网页端/app/static/styles.css
Normal file
File diff suppressed because it is too large
Load Diff
20
患者首页处理/数据可视化网页端/docker-compose.yml
Normal file
20
患者首页处理/数据可视化网页端/docker-compose.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
name: patient-frontpage-review
|
||||
|
||||
services:
|
||||
patient-frontpage-review:
|
||||
build:
|
||||
context: .
|
||||
container_name: patient-frontpage-review
|
||||
env_file:
|
||||
- ../.env
|
||||
environment:
|
||||
REVIEW_SETTINGS_PATH: /app/settings/review_settings.local.json
|
||||
ports:
|
||||
- "8501:8501"
|
||||
volumes:
|
||||
- ../已处理-患者首页PDF/2026_5_25_处理:/data/pdfs:ro
|
||||
- review-settings:/app/settings
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
review-settings:
|
||||
Reference in New Issue
Block a user