91 lines
6.3 KiB
Markdown
91 lines
6.3 KiB
Markdown
# 2026-08-20 — 医生工作站 AI 问诊对话框布局优化
|
||
|
||
## 改动
|
||
- 文件: `app/src/doctor_workstation/ui/dialogs/ai_consult.py`
|
||
- 模块: `AiConsultDialog` 右侧「智能分析」卡片 + 问诊对话卡片密度优化
|
||
- 新增 helpers: `_InsightItem`, `_split_insight_segments`, `_split_numbered_list`, `_split_plain_paragraph`, `_split_label_or_sentence`, `_escape_html`
|
||
- `_set_insights` 改成将稠密段落拆成 lead / list / plain 三类, 每类单独 QLabel 显示
|
||
- CSS 新增 `AiConsultInsightMarker` / `AiConsultInsightText` / `AiConsultInsightPlaceholder`
|
||
- 卡片 padding `10,8,10,8` → `14,12,14,14`; 卡间距 `8` → `10`
|
||
- 聊天气泡垂直 margin `4` → `6`, chat_layout spacing `10` → `12`
|
||
|
||
## 拆分策略
|
||
- 文本以 `1./2、/3)/4)` 起头 → 视为完整编号列表
|
||
- 文本中嵌入 `…:1.…2.…` → 拆出序言 + 编号列表
|
||
- 短中文标签 `XX:YY` 开头 → 视为 lead, 标签部分加粗
|
||
- 其余按 `。/;/;/` 分句 → 单独成行
|
||
|
||
## 测试
|
||
- 现有 `tests/test_ai_consult_ui.py` 通过 `widget.objectName() == "AiConsultRecordTitle"` 校验标题仍在, 未受影响
|
||
- 暂未运行 UI 测试 (开发环境未装 PySide6); 解析逻辑单独脚本验证通过
|
||
|
||
## 注意
|
||
- PySide6 QSS 对 `QLabel` 的 `line-height` 支持有限; 主要靠 item 间距 `8px` 与 `padding:1px 0` 提供节奏
|
||
- 如果后续接入 `AiConsultInsightText` 还嫌密集, 可再调 `body_host.setSpacing`
|
||
|
||
---
|
||
|
||
# 接诊台 AI 智能分析详情对话框 (`ReceptionAiAnalysisDialog`) 优化
|
||
用户截图(`clipboard-2026-08-20T08-42-40`...)反馈: 「问诊详情 → AI 智能分析」卡片里的诊断建议 / 风险评估 / 治疗建议三段文字太稠密, 不利于阅读。
|
||
|
||
## 关键差异
|
||
这次的目标文件和上次不同 — 用户截图里出现 "诊断建议 / 风险评估 / 治疗建议" 标题, 而 `ai_consult.py` 用的是 "血糖控制评估 / 并发症风险评估 / 用药合理性评估"。实际是 `app/src/doctor_workstation/ui/pages/reception.py` 中的 `_ReceptionAiAnalysisDialog` 类。
|
||
|
||
## 改动
|
||
- 文件: `app/src/doctor_workstation/ui/pages/reception.py`
|
||
- 新增 helpers (与 ai_consult.py 类似但导出名加 `_ai_` 前缀):
|
||
- `_ai_split_into_segments` / `_ai_split_numbered_list` / `_ai_split_plain_paragraph`
|
||
- `_ai_segments_to_html` / `_ai_narrative_structured_html`
|
||
- `_ai_split_label_or_sentence` / `_ai_escape_html`
|
||
- 正则: `_AI_INSIGHT_LIST_RE`, `_AI_INSIGHT_LIST_LOOKAHEAD_RE`, `_AI_INSIGHT_LABEL_RE`
|
||
- `add_text_section` 闭包内多挂一个 RichText `_structured_labels[name]` 标签 (`.setObjectName(name + "Structured")`, `property="dialogAiStructured"`)
|
||
- 原 PlainText `value_label` 仍存在 (`hide()` + `setMaximumHeight(0)`) 用于兼容既有 findChild 测试
|
||
- `_render_model` 同步刷新两个结构化标签; 空 HTML 时回退显示原 label 文本
|
||
- 风险评估 FlowLayout spacing `7×7` → `9×9`
|
||
- `body_layout.setSpacing` `12` → `14`
|
||
- 卡片 contentsMargins `14,13,16,15` → `16,14,18,16`
|
||
- QSS: `dialogAiBody` 加 `line-height: 1.85`, 新增 `dialogAiStructured` 选择器 (line-height 175%); 风险 pill `min-height` `28` → `30`, padding `4 10` → `5 12`, font 12 → 12.5, border-radius `7` → `8`
|
||
|
||
## 兼容约束
|
||
- `tests/test_reception_parity_ui.py` 严格校验:
|
||
- `findChild(QLabel, "ReceptionAiAnalysisDialogDiagnosisText")` 必须能找到
|
||
- `.text()` 必须等于 `_ai_narrative_text` 归一化结果
|
||
- `.textFormat() == Qt.TextFormat.PlainText`
|
||
- 因此 PlainText 标签必须原样保留, 仅做 `.hide()` + `setMaximumHeight(0)` (避免 QSS `>` 子选择器不被 Qt 支持)
|
||
|
||
## 验证
|
||
- `py_compile` 通过, 无 `SyntaxWarning`
|
||
- 单独脚本跑过 4 组样本文本 (稠密段落 / 嵌入式编号列表 / 短句 / 纯编号列表), 拆分结果符合预期
|
||
- 未在本机跑 UI 测试 (缺 PySide6)
|
||
|
||
---
|
||
|
||
# 患者列表 AI 分析: 发送完整患者资料给 AI
|
||
|
||
用户需求: 患者列表 AI 分析时, 每日血糖报告、舌苔、AI 报告、视频录制回放的文字对话都要发给 AI。
|
||
|
||
## 改动
|
||
- `app/src/doctor_workstation/ui/dialogs/ai_consult.py`
|
||
- `_load_workspace` 新增 `list_call_records` section (视频问诊记录, 含 `transcript_text`)
|
||
- 模块级纯函数 (无 Qt 依赖, 便于测试):
|
||
- `AI_CONTEXT_MAX_CHARS=320`, `AI_PROMPT_LIMIT=500`, `AI_CONTEXT_SEPARATOR="\n\n— 医生提问 —\n"`
|
||
- `_truncate_for_context` / `_patient_context_blood_sugar` / `_patient_context_tongue` / `_patient_context_reports` / `_patient_context_prescriptions` / `_patient_context_videos` / `build_patient_ai_context` / `_compose_ai_prompt`
|
||
- `build_patient_ai_context` 返回 `(text, labels)`, 按 每日血糖→舌苔/脉象→视频问诊文字→历史AI报告→处方记录 顺序装入 320 字预算的信封
|
||
- `_patient_context_videos` 按 `diagnosis_id` 过滤归属 (`_exact_positive_id`), 只取最近一条有文字的
|
||
- `_compose_ai_prompt` 本地强制 500 字上限: 上下文 + "— 医生提问 —" + 问题, 超长时截断问题加 `…` (服务端 `tcm.diagnosis/aiAssistant` prompt 上限 500)
|
||
- `_ChatBubble.attach_extra()` 支持气泡下方挂载控件
|
||
- `_render_patient_context`: 聊天区系统气泡 "AI 已加载本诊单上下文(N 项…)", `_attach_context_detail` 挂 "查看本次发送给 AI 的资料全文" 展开按钮 (QSS: `AiConsultContextToggle`/`AiConsultContextReveal`)
|
||
- `_ask` 用 `_compose_ai_prompt(text, self._patient_ai_context)` 组装后传给 `_AiStreamWorker`; 医生气泡仍显示原始问题
|
||
- `open_for` / `_clear_chat` 重置 `_patient_ai_context*` 三个状态, 防止跨患者残留
|
||
|
||
## 测试
|
||
- `app/tests/test_ai_consult_ui.py` 新增 5 个测试 (总 15 个全通过):
|
||
纯函数: 全 section 信封+视频按诊单过滤 / 仅基本病历返回空 / 长问题截断≤500
|
||
UI: 上下文气泡+展开按钮+视频记录计数 / `_ask` 注入上下文 (FakeWorker 捕获 prompt, monkeypatch `QThreadPool`)
|
||
- `app/scripts/check_ai_context.py`: AST 提取纯函数的冒烟脚本 (无需 Qt)
|
||
- `tests/test_ai_consult_workspace_ui.py` 18 个测试无回归
|
||
|
||
## 环境要点 (重要)
|
||
- **PySide6 + pytest 在 `app/.venv` 里可用**: 用 `D:/web/zyt/app/.venv/Scripts/python.exe -m pytest` 跑 UI 测试 (QT_QPA_PLATFORM=offscreen)。之前"本机缺 PySide6"的结论只对系统 Python 成立。
|
||
- 测试 FakeWorker 需实现 `signals.event/error/finished.connect()` 和 `cancel()` (closeEvent 会调 cancel)
|