This commit is contained in:
Your Name
2026-09-21 10:26:14 +08:00
parent bbe3e870a1
commit dbf474ddd7
67 changed files with 10636 additions and 279 deletions
+210
View File
@@ -278,3 +278,213 @@ php server/tests/PrescriptionAiPipelineTest.php
6. **复核条**:两个模型的复核状态、意见、保存与重试压缩到一行,原来纵向占用的约 200px 让给报告正文。免责声明降为脚注。
改动只在展示层:接口、轮询、权限、进度语义和落库数据都没变;`model_views` 的既有键全部保留,新增 `score`、`status_chip`、`card`、`model_label`。97 项桌面回归与 Ruff 通过,示意图见 `app/artifacts/issued-prescription-ai/redesign-running-20260910.png` 与 `redesign-finished-20260910.png`(合成数据,无网络)。
## 2026-09-10 三栏对照工作区与医生原方快照
上一版把两个模型的分数做成了重点,但医生仍要靠记忆比对自己开的方。本轮把"医生原方"接进窗口,改成一次看三列。
**服务端:报告详情新增 `doctor_snapshot`。** 由批次入队时冻结的处方密文投影而来(`PrescriptionAiDoctorSnapshot::project()`),因此模型还没开始就能显示,且选择历史批次时始终是当时那一版——线上处方后来被改动也不会回写。投影只保留展示所需的标量字段:患者姓名/性别/年龄、`clinical_diagnosis`、剂型与用法、逐味药名剂量单位炮制与煎服说明、独立的辅方用法;明确排除电话、签名、`case_record`、药材 ID 等。权限沿用 `loadBatch`(当前处方 + 历史来源双重校验),列表与状态摘要不带该字段。源数据没有的单位或中西医诊断拆分一律不补造。
**桌面端:`PrescriptionReviewWorkspace` 三栏工作区。**
- 左栏"医生原方":患者、医生诊断、逐味药方与用法,按保存顺序原样展示(含重复行)。
- 中栏"诊断对照 + 逐味剂量差异":两个模型的辨证结论并排,下面是逐味剂量点线图(医生原方 / 千问 / OpenAI 三点同轴),支持药名搜索与"仅看剂量差异"过滤,未保存值显示"—"。
- 右栏"复核笔记":待确认差异、已保存对比计数、复核状态与意见表单。
- 顶部导航收敛为"对比总览 / 完整报告 / 资料记录"三个主入口,方义与用法、逐味明细、处理进度、重新分析移入"更多内容"菜单;上方"本次关注"一行给出本批次最该看的一句话。
展示层之外没有改动:轮询、权限、进度语义、比较算法与落库数据均不变。
本轮验证:桌面 182 项(报告窗口 / 对照组件 / 工作区三个套件)通过,Ruff 通过(顺手修了本轮编辑引入的一处 import 排序);后端 12 个离线套件通过,独立 MySQL 上 Queue 78 / Pipeline 90,旧结构 Queue 76 / Pipeline 85 通过,其中新增了原方快照的隐私、权限、不可变与"不补造字段"检查。界面渲染脚本 `app/scripts/render_issued_prescription_ai.py` 用合成数据离线产出 1024/1280/1440 三种宽度及部分完成、历史、等待中状态的截图。
## 2026-09-10 并发生成:多张处方同时分析
此前每个模型最多只跑 1~2 个任务,多张处方只能排队等待,界面上就表现为"一次只能生成一个"。本轮把并发做成真正可调的能力:
- `prescription_analysis.max_parallel_per_model` 默认提到 **4**,并改为环境变量 `prescription_analysis.MAX_PARALLEL_PER_MODEL` 可调(最小 1)。该值只是上限,**每一个并发任务都需要一个消费者进程**:本地按 `prepare×2、qwen×4、openai×4` 启动,启动脚本 `artifacts/prescription-ai-runtime/start_workers.ps1` 支持 `-Mode Add`(保留在跑的进程,补齐缺的实例)与 `-Mode Restart`(全部重启,仅在没有任务运行时使用)。
- `daily_model_tasks`、`daily_patient_batches` 同样改为环境变量可调;单张处方每日分析上限由 10 提到 20——每次修复后重新分析是正常复核方式,这是预算护栏而不是正确性限制。超限时接口仍明确返回"今日分析次数已达预算"。
- **资料扫描不再挡住新处方。** 准备通道原来在同一轮里既领批次又跑来源/处方扫描,而扫描会重建完整上下文;现在扫描只在准备通道无事可做时执行,刚保存的处方永远优先被领取。
实测:同时对 3 张处方发起重新分析,加上原有 1 个在跑的任务,**7 个模型任务并行执行**(千问 3 + OpenAI 4),队列为空,无失败。批次 13(处方 7556,今天反复失败的那张)两个模型都完成:千问 18.88%、OpenAI 16.73%,均为可比。批次 14/15/16 的千问侧一致度分别为 19.29%、42.97%、17.36%,OpenAI 侧三个任务同时在跑。
线上同步部署时,需要在进程守护器里为每个模型通道配置与 `max_parallel_per_model` 相同数量的实例,并确认上游应用与账号的并发额度;数据库连接数也要留出余量(每个消费者一条长连接)。
`PrescriptionAiWorkerResilienceTest` 增加 4 项契约检查(并发下限、三个预算/并发值可由环境变量覆盖、扫描仅在空闲时执行),共 11 项。
## 2026-09-10 科技蓝指标条与图表
报告窗口补上"一眼看懂"的一层:在三栏工作区之上增加科技蓝指标条(`issued_prescription_ai_metrics.py`),全部取自已保存的不可变报告,不新增任何接口调用。
- **两张模型卡**:模型名 + 状态药丸 + 覆盖情况;大号一致度数字与刻度条(千问 #1769E8、OpenAI #0E9384);下面是药味构成堆叠条与图例——医生独有 / 共同 / 该模型独有,并标出药味重合度。没有可比结果时数字为灰色"—"、刻度条留空,绝不显示 0%。
- **历史一致度折线**:来自历史批次列表(按时间从左到右),不可比的批次断线不补点;这是真正能看出"这次比上次接近还是更远"的图。
- **资料覆盖卡**:附件已读 / 受限不支持堆叠条、缺口总数与其中关键项数、资料构成(病历、医生备注、历史处方、问诊通话、监测记录、聊天记录、记录合计)。
- **自适应高度**:窗口高度小于 780 时指标条收起副图(折线、构成条、阶段行),只留数字与刻度条,把首屏让给处方工作区;`MAX_HEIGHT` / `COMPACT_HEIGHT` 有回归测试守住。
同时修掉一个真实显示缺陷:逐味剂量差异表的列宽按"尚未布局完成的视口宽度"按比例计算,结果所有列被压到二三十像素,药名一个字一行、剂量显示成省略号。改为药名列与模型列有最小宽度、图表列吸收剩余宽度,窗口过窄时优先保证药名和剂量可读,并在 `showEvent` 里再测一次。
新增 `app/tests/test_issued_prescription_ai_metrics.py` 19 项检查(空值不变 0、0% 是真实测量、共同药味不超过任一侧、覆盖与缺口计数、历史折线时间顺序、紧凑模式取舍、百分比格式边界)。四个报告窗口相关套件共 **201 项**通过,Ruff 通过。
需要注意:本地客户端连接的是线上 `admin.zhenyangtang.com.cn`,而**医生原方快照(`doctor_snapshot`)尚未部署到线上**,所以左栏会显示"原方快照未保存",图表只能回退到对比结果里记录的医生剂量。要看到完整左栏,需要把服务端这轮代码部署上线。
## 2026-09-11 报告窗口改版落地(第一批)
按确认的设计稿开始落地,配色全部取自工作站的 `reception_style.TECH_BLUE`(主色 #1769E8、深色 #124EA9、画布 #F3F7FD、线条 #DBE5F2),OpenAI 侧继续用 #0E9384 区分。设计稿 `app/artifacts/ui-mockups/v2/` 已同步换色。
**① 蓝色渐变头部条。** 面包屑、标题、版本与对照类型药丸、批次选择与刷新/重新分析/保存复核、六列信息带(处方·诊单 / 患者 / 临床诊断 / 剂型·剂数 / 资料截止 / 比较口径)以及主导航全部收进同一条渐变带,下方白色区域完整留给处方内容。信息带取自冻结快照,线上未部署 `doctor_snapshot` 时逐项显示"—",不猜测。窗口高度小于 780 时信息带与副标题自动收起,保证处方工作区仍占首屏一半以上(有回归测试守住)。
**② 模型失败可就地重试。** 失败原因从隐藏的进度页移到该模型卡片上:中文原因 + 「重试 千问 / 重试 OpenAI」按钮同行显示,只重试该模型并使用原资料快照;手动重试额度用尽时按钮隐藏,改为提示改用"重新分析"。无权限、批次过期、进度暂停时按钮不可用。
**③ 新图表进入程序。** 新增 `issued_prescription_ai_charts.py`(QPainter 自绘):
- `VennChart` 三方用药交集——由两个模型的候选药味与对比行中的医生药味算出七个区域,无候选方时不画空图;
- `WaffleCoverage` 附件华夫图——一格一个附件,蓝=模型已读、橙=受限或不支持;
- `DivergingDoses` 剂量差异发散条——已实现待接入逐味页。
回归:报告窗口相关四个套件 **208 项通过**,Ruff 通过。后续按设计稿推进:逐味页发散条与贡献列、资料与缺口页、处理进度页、历史与趋势页、统计页。
## 2026-09-11 报告窗口改版落地(第二批,全部页面完成)
第一批只完成了头部条、失败重试与三个图表控件;这一批把设计稿里剩下的页面全部做进程序,并顺手修掉改版过程中暴露出来的几处旧问题。
**① 导航按目的地命名,不再按下标。** 新增页面后原来写死的 `setCurrentIndex(2/4/6)` 全部错位("资料记录"实际跳到了原文页,"处理进度"出现了两个同名标签)。改成 `tab_pages` 字典 + `_goto(key)`:主导航是 `对比总览 / 逐味明细 / 资料与缺口 / 历史与趋势`,`更多内容` 菜单收 `方义与用法 / 综合分析 / 处理进度 / 原方记录 / 逐味原文 / 来源原文`。工作区内的"查看依据"链接现在指向新的组合页而不是原始文本页。回归测试断言标签名不重复、每个链接落到正确的页面。
**② 逐味明细页。** 发散条形图(相对医生原方,只画两侧都有剂量的药味)+ 合并表:一行一味药,列出医生 / 千问 / OpenAI 三方剂量与两个模型的贡献值,说明列区分"仅医生使用""模型新增""剂量差 N g"。支持搜索药名与"仅看剂量差异"过滤,脚注写明贡献的算式。
**③ 资料与缺口页。** 左栏资料构成(病历 / 问诊通话 / 记录合计等,按行数自适应高度)与读取范围(资料截止、比较算法、提示词版本、覆盖状态、对照类型);右栏附件华夫图 + 一句话说明"读取 N 个、受限 N 个、未送达 N 个",下方缺口清单按类型合并并标注关键 / 一般。
**④ 处理进度页(与旧进度页合并)。** 批次流程与实时进度条保留在上方,下方是新的每模型阶段卡与调用记录表(模型 / 阶段 / 耗时 / 附件数 / 输出 tokens / 结果)。阶段键 `text:0`、`files:1`、`final` 一律翻成中文(文字资料 1、附件读取 2、开方与对比),错误码走词表。
**⑤ 历史与趋势页。** 一致度趋势柱(按时间从左到右,不可比的批次留空)+ 批次列表(批次号、时间、状态、两个模型的分数、算法版本、对照类型)。分数同时兼容列表接口的扁平 `score` 与详情接口的 `comparison.score`,并且只有 `comparable` 才给分——之前趋势图与列表全是"—"就是漏了这一层。排序也从"把时间戳当数字"改成按时间字符串排。
**⑥ 统计窗口改版。** 四张 KPI 卡(合格开方事件 / 两个模型的有效比较数与均值 / 专家复核合格率)、样本口径漏斗、排除原因合计、按医生列表,下方保留原有的分层明细 HTML。统计窗口现在也套用同一套样式表,卡片有边框有底色。脚注继续写明"一致度不是医生准确率",没有复核样本时合格率显示"—"而不是拿一致度顶替。
**⑦ 顺带修掉的问题。**
- `资料截止:` 一行因为写错表达式一直是空的,现在正常显示时间,没有就显示"—"。
- `覆盖状态:partial` 直接吐英文;覆盖语境下译为"部分资料缺失"(沿用 STATE_LABELS 的"部分完成"会误导成进度)。
- 表头默认居中,在被拉宽的最后一列里飘到中间;统一改为左对齐。
- 指标条在压缩模式下高度不够,华夫图与文字叠在一起;压缩时只留计数文字。
- 打开自带图表的页面(逐味 / 资料 / 历史 / 进度)时指标条自动收起,把高度让给正文。
- `QFrame#AiMetricCard` 此前根本没有样式,卡片是"白底白字";补上边框与圆角。
- 短表格(资料构成、样本口径、排除原因)按行数固定高度,不再出现四行内容配一个滚动条。
回归:
- 桌面端报告窗口五个套件 **226 项通过**,Ruff 通过。
- 服务端 12 个离线套件全部通过(对比 266 项、统计 64 项、进度 38 项、Worker 韧性 11 项等)。
- 服务端两个落库套件在独立 MySQL(127.0.0.1:13379)上通过:`PrescriptionAiQueueTest` 78 项、`PrescriptionAiPipelineTest` 90 项。
- `scripts/render_issued_prescription_ai.py` 现在额外产出 `per-herb / sources / history / pipeline / failed / statistics` 六张截图用于核对。
已知与本次无关的失败,均已用"把本次改动 stash 掉再跑"验证过是既有问题:
- `tests/test_busy_overlay.py::test_shell_construction_never_shows_orphan_business_controls`;
- `tests/test_diagnosis_drawer_visual.py` 整个模块跑到第 8 项时进程级崩溃(access violation / 0xc0000374),stash 后同样在同一位置崩溃。
## 2026-09-11 报告窗口按设计稿重做(第三批)
上一批做出来的界面和确认过的设计稿 `app/artifacts/ui-mockups/v2/` 并不是一回事——指标条、图表形态、页面骨架都是旧的。这一批按设计稿逐页重做。
**导航。** 六个目的地全部平铺在蓝色带里,和设计稿一致:`对比总览 / 完整报告 / 候选与逐味 / 资料与缺口 / 处理进度 / 历史与趋势`。「更多内容」下拉去掉;「重新分析」从菜单里挪到band 上,和「刷新」「保存复核」并排。原始文本页(逐味原文、来源原文、原方记录)不再占导航位,由页面内的链接进入。跳转全部改成按名字寻址(`tab_pages` + `_goto`),不再用会随页面增减而错位的下标。
**① 对比总览(设计稿 01)。** 整页重写:
- KPI 行:两张模型卡(左侧色条 + 环形一致度 + 大号分数 + 药味重合/共同药味/候选药味三列),加一张「本次关注」卡(标题句 + 关键缺口/附件受限/共识药味三个药丸)。原来横在标题下的指标条取消,KPI 行现在属于总览页本身,其它页面不再被它占掉高度。
- 左卡:三方用药交集韦恩图(七个区域各自的药味数,三个集合各自标注「医生 N / 千问 N / OpenAI N」)+ 资料覆盖华夫图与图例。
- 中卡:剂量差异分布——一行一味药,左侧药名与原方剂量,中间以「与原方相同」为中轴的双色发散条,右侧两个模型各自的差值;某个模型没收录就画「OpenAI 未收录」的虚线徽标,两边都一致则画绿色「两模型一致 6 g」,模型新增的药味用浅色条画出全量。底部一条共用刻度尺。
- 右卡:复核清单——按严重度排序的圆点列表(模型风险 > 模型缺口 > 系统缺口),每条注明是哪个模型提出的;下方是复核意见表单。
**② 候选与逐味(设计稿 03)。** 顶部三张处方卡(医生原方 / 千问候选 / OpenAI 候选),各自列出药味与剂量、味数与剂型、服法与风险提示;中间是逐味对照表,贡献列改为「小条 + 数值」,说明列区分三方共有/仅医生使用/某模型未收录/剂量差 N g;筛选从一个复选框换成设计稿的四个计数筹码(全部 / 仅共同 / 仅差异 / 仅未收录)加搜索;底部四格用法对照(服法、疗程、剂型、辅方,医生与模型分行)。
**③ 资料与缺口(设计稿 04)。** 改成三栏:资料构成用按类型的比例条(问诊通话、监测记录、医生备注、历史处方、病历、聊天归档),读取范围列出资料截止、比较算法、提示词、药材字典与覆盖状态;中栏是附件华夫图、读取说明与「模型各自读取到的量」表;右栏是按严重度排序的缺口清单,配一段说明缺口含义的提示块。
**④ 处理进度(设计稿 05)。** 顶部四格统计(批次总耗时 / 模型调用 / 格式修复·追问 / 失败调用);两张模型泳道卡,阶段列表由保存的调用记录归并得出(文字资料分析、附件读取、生成候选与报告、格式修复、药名回问……),没有调用记录就不编造阶段;调用记录表增加「耗时分布」条,按本批次最慢的一次调用取比例。
**⑤ 历史与趋势(设计稿 06)。** 左右两栏:左边是趋势柱与一段「分层说明」——相邻两个批次之间只要比较算法或提示词版本变了,就写清楚 `v1.0.1 → v1.1.0`、`v3 → v4` 并注明两侧分数不能直接相减;右边是批次列表(批次号、时间、状态/原因、两个模型的分数、算法版本),失败批次直接显示模型给出的原因。
**⑥ 统计窗口(设计稿 07)。** 四张 KPI 卡之后是一致度分布直方图(按 20% 分箱,两个模型并排)与「均值 / 中位 · 配对样本」三格小结;右侧样本口径与排除原因改成条形(标签写在条内),下面是那段说明「一致度不是医生准确率」的琥珀色提示块;按医生列表保留在最下方。
为此在服务端把已经算好但没往外传的分布补上:`PrescriptionAiLogic::statistics()` 的每个模型多返回一个 `distribution`(`PrescriptionAiStatistics::summarize()` 早就在算 `[0,20) … [80,100]` 五个分箱,只是之前没进接口)。`PrescriptionAiQueueTest` 增加一条断言,保证分箱确实随统计接口返回。
**⑦ 完整报告(设计稿 02)。** 每一栏加上模型名 + 状态药丸 + 生成时间的抬头。设计稿左侧的章节目录(TOC)没有做——报告正文是服务端结构化字段渲染的,没有可跳转的锚点,硬做会是个假目录。
**顺带修掉的问题。** `比较口径` 改为从对比行真实的单位与剂量基准推导(全部一致才显示,否则回退到冻结处方的剂数单位);`QFrame#AiMetricCard` 之前完全没有样式;统计窗口现在也套用同一套样式表。指标条里已经用不到的堆叠条、迷你折线、覆盖卡与交集卡整体删除,不留死代码。
回归:报告窗口五个套件 **243 项通过**,Ruff 通过;服务端 12 个离线套件全部通过,落库套件 `PrescriptionAiQueueTest` 79 项、`PrescriptionAiPipelineTest` 90 项通过。`scripts/render_issued_prescription_ai.py` 的示例数据换成三份真正不同的处方(医生 8 味 / 千问 7 味 / OpenAI 6 味)与 5 个历史批次,六张页面截图逐张核对过。
## 2026-09-11 报告窗口改造为深色分析控制台(ai-redesign-v2)
按 `C:/Users/pc/WorkBuddy/2026-09-11-16-46-03/ai-redesign-v2.html` 与同目录 `overview.md` 重做。注意:动手前发现这些文件正在被另一个进程同时修改(白色顶栏、可展开交集卡、"保存千问复核"都不是本会话写的),与用户确认另一会话已停止后才接手,本轮是在那个状态上继续改的。
**① 主题令牌独立成模块。** 新增 `issued_prescription_ai_theme.py`:海军蓝底 `#080D18`、卡片 `#0F1726`/`#131D2E`、线 `#22304A`;科技蓝 `#2E7BF6` 只做界面色(按钮、激活态、焦点),千问改天青 `#17BFDD`、OpenAI 改紫 `#9B7BFF`,风险保留琥珀 `#F5B942` 与玫红 `#F4697A`。窗口内六个模块统一从这里取色,不再各自写死浅色值;原先散落的 30 多个浅色硬编码全部换成令牌。
**② 顶栏。** 面包屑「甄养医生工作站 / AI 分析报告」+ 标题 + 版本药丸;右侧四组事实(处方/诊单、剂量口径、资料截止、批次)、版本选择、刷新、重新分析、登录医生。患者 / 主诉 / 临床诊断 / 剂型收在标题下一行——设计稿顶栏没有这几项,但这是医疗窗口,冻结快照的身份信息不能丢。
**③ 一致率对比条(新控件 `issued_prescription_ai_console.py`)。** 两侧各自:模型点 + 状态 + 资料覆盖 + 用时,大号一致率,0/25/50/75/100 刻度条,以及对手所在位置的灰线标注「OpenAI 在此」。中间给差值 `+7.2pt` 与领先方,并列出两侧药味重合。下面一行写明一致率的定义与可比条件。模型失败的原因与「重试 X」按钮也移到这里——那是整个窗口里唯一只属于单个模型的位置。
**④ 左侧编号步骤导航。** 01–06 六个目的地,各带数量徽标(剂量差异项数、报告数、药味数、缺口数、调用次数、批次数),下面是「本次关注」四项计数与「保存本次复核」。原来的横向标签条与顶部 KPI 卡整块删除,`MetricsPanel` 及其 21 项测试一并移除。
**⑤ 对比总览重排为设计稿的两行。**
- 本批结论:四条按规则生成的编号结论(候选方味数 / 一致率与差值 / 剂量偏差最大的三味 / 资料缺口分布),配「结论由规则生成,不代表医生判断」的说明条。
- 药味归属分布:三方共识 / 医方独有 / 千问独有 / OpenAI 独有,各自条形 + **直接列出药名**(韦恩图读不出结论,换成这个)。四组不含"医方与单一模型共用"的药味,标题会把这部分单独说明,避免四个数加不回总数。
- 复核清单:关键 / 一般徽标 + 归并后的条数 + 来源模型,复核意见与状态表单折进同一张卡。
- 剂量差异分解:以「与医方相同」为轴,右=加量、左=减量,刻度 −N/−N/2/0/+N/2/+N,右侧两列分别是千问 Δ 与 OpenAI Δ。
- 高风险提示:偏差 ≥10 克或幅度 ≥50% 自动进清单,标高/中并写明触发原因;阈值只做排序,不替代医生判断。
- 底部四个快捷入口。
**⑥ 其余五页。** 完整报告、候选与逐味、资料与缺口、处理进度、历史与趋势跟随新色板;四页包在滚动容器里,窗口不够高时滚动而不是把控件压在一起。图表的模型色统一换成天青/紫。
**图标。** 窗口不带图片资源,新增 `issued_prescription_ai_glyphs.py`,所有图标(含两个模型徽标、窗口标记)都用 QPainter 画线稿,颜色由调用方给。
**设计稿里剩下的四项也已补齐:**
- **深 / 浅主题切换(◐)**:`issued_prescription_ai_theme.py` 里给出 `DARK` / `LIGHT` 两套令牌,`use_theme()` 就地改写 `CONSOLE` 并回调所有派生值(图表色、卡片样式表、窗口样式表)。窗口构建整段抽成 `_build_ui()`,切换时原地重建:当前页面、选中批次与**尚未保存的复核意见**都会带过去。
- 顺带查出一个真 bug:`QPushButton#AiStep:checked QLabel#AiStepName` 这种"父级伪状态 + 后代"选择器在 Qt 里是按子控件的状态判定的,于是六个步骤的文字**全部**取了选中态的白色 —— 深色底看不出来,一换浅色就成了白底白字。改为在 `set_current()` 里按选中与否直接给标签上色。
- **附件缩略图网格**:资料与缺口页把华夫图换成一格一个附件的磁贴,标注类型与编号,蓝=已读、琥珀=受限/不支持,悬停给出完整编号、状态、原因与版本核验情况。为此服务端 `PrescriptionAiGenerator` 在覆盖清单的四条写入路径上都补了 `type` 字段(原来只有 `file_id`),并在 `PrescriptionAiGeneratorTest` 里加了断言守住。
- **图表悬停说明**:贡献条给出算式、来源构成条说明归一口径、调用耗时条给出占最慢一次的比例、趋势柱与分布柱逐批次/逐分箱列出数值、归属分布的条形与药名列出完整药味。
- **完整报告页章节目录**:报告按字段切成带锚点的章节,左栏列出章节可跳转(两栏同时滚动),并提供「并排对照 / 仅千问 / 仅 OpenAI / 只看不同段落」四种显示方式;"只看不同段落"按章节渲染结果是否逐字相同来判定,规则可解释。
- 另外把左栏「附件受限」的口径改成**模型确实没读到的附件数**(原先取的是缺口记录条数,与页面上的附件状态对不上)。
回归:
- 报告窗口四个套件(`test_issued_prescription_ai{,_pages,_workspace,_comparison}.py`)**231 项通过**,Ruff 通过;服务端 `PrescriptionAiGeneratorTest` 通过。
- 全量桌面套件(排除早已进程级崩溃的 `test_diagnosis_drawer_visual.py`)有 18 项失败,全部落在本轮没有碰过的模块里:`busy_overlay`、`list_detail_geometry`、`patients_visual_blue`、`patient_orders_visual_blue`、`patient_progress_visual_blue`、`reception_completion_button_ui`、`reception_parity_ui`、`silent_list_loading`、`diagnosis_order_video_visual`、`patient_ai_report_desktop`、`diagnosis_index_visual`。
- 与改造前的一次完整跑(同一工作区、深色化之前)逐条比对:失败集合完全一致,只多出 `diagnosis_index_visual` 一项,而该项单独跑通过 —— 是整轮跑到后段(进程占用 4.6 GB)时的顺序/内存相关抖动。
- 另外验证过:把 `DEBUG_MODE` 临时改回 `True` 后这些用例仍然失败,所以与工作区里那处版本/调试开关的改动无关;`git status` 显示本轮只动了 `issued_prescription_ai*` 这一组文件。
## 逐项对齐设计稿 `ai-redesign-v2.html`(字号 / 间距 / 描边)
上一轮把结构搭到位,但字号与间距是估的,医生反馈"好多细节都不匹配",并指出两处硬伤:一致率刻度的百分比标签被裁掉、逐味对照矩阵的列错位。这轮按设计稿的 CSS 逐条换算(`html{font-size:15px}`,所以 .68rem = 10.2px、.82rem = 12.3px、.9rem = 13.5px、1.8rem = 27px),把两边的数值对齐。
**取值方式。** 设计稿是本地 HTML,直接读它的 CSS 而不是量截图:`--radius:10px`、`--radius-sm:7px`、卡片描边用 `--line-soft` 而不是 `--line`、`.card-h{padding:15px 18px 0}`、`.card-body{padding:14px 18px 18px}`、`.grid{gap:14px}`。浅色令牌与 `rgba()` 色调(`--blue-dim` 等)按叠在卡片底色上的合成值写进调色板,Qt 这边没有 alpha 混合也能落到同一颜色。
**改到的地方:**
- **卡片头**。设计稿里 `.card-h` 只有标题 + 小字说明 + 右侧图例,**没有图标**;`_panel_head()` 去掉了 glyph 参数,标题 13.5px/600、说明 10.65px `--text-faint`。说明文字换成 `HintLabel`:宽度不够时省略号收尾并留 tooltip,绝不再把卡片顶宽(上一版为此把它设成 `Ignored`,结果整行说明直接消失)。
- **左侧步骤导航**。`.stepnav` 的 26px|1fr|auto 栅格:序号片 22×22/圆角 6/10.2px,名称 12.6px,数量徽标做成 20px 起宽的胶囊(9.9px、圆角 8、上下 17px 固定高)——之前徽标会被拉满整行高度,六个连成一条竖条,看着像滚动条。选中态按设计稿画左侧 3px 圆角竖条(`QSS` 的 `border-left` 会把内容顶偏,改在 `_StepButton.paintEvent` 里画),徽标按严重度分色:待决项有关键项时取玫瑰,资料缺口取琥珀。
- **一致率对比条**。百分号按 `.val sup` 处理成小一号、上标、`--text-dim`,新增 `ScoreLabel` 自绘,`text()` 仍返回 "56.1%" 这一个字符串,调用方与测试不受影响。刻度条填充改成设计稿的 `rgba(hue,.45) → hue` 渐变,四分位分隔线换 `--grid-line`,对手模型的标记改成 2px 圆角竖条。卡片左缘补上蓝→紫渐变竖条(按卡片圆角裁剪),中间差值列的分隔线改虚线。两侧模型块顶部对齐,失败的一侧不再把整列往下推。
- **刻度标签**。设计稿里 `.axis` 的 `bottom:-1px` 让 0%/25%/… 压在轨道上、被填充盖掉一半 —— 这正是医生截图里"看不清"的那处。这里**不照抄**:标签仍排在轨道下方 31px 处,可读优先。
- **逐味对照矩阵**。列固定为 药味 / 千问(克/剂 · 贡献度)/ OpenAI(克/剂 · 贡献度)/ 医方原方 / 结论,后四列定宽、首列拉伸,每个模型一格自绘(剂量 + 贡献条 + 数值),不再靠自动列宽挤在一起。右上补回「千问贡献度 / OpenAI 贡献度」图例;「仅医方使用」按设计稿不套胶囊,用弱化文字。
- **列表行**。本批结论、复核清单按 `.finding li` / `.rv-item` 加 1px `--line-soft` 分隔线与 10/11px 的上下留白;结论正文 12.3px/160%,复核标题 12.3px、副题 10.5px、计数用等宽字 13px。
- **表格与统计卡**。所有表头 10px/`--text-faint`/`padding:10px 16px`,单元格 12px/`padding:8px 16px`,分隔线 `--line-soft`;处理进度页四个数字改 26px 等宽字,标签 10.65px 带字距。
- **完整报告正文**。文档样式表原先写死了浅色(`#EFF4F9` 表头、`#244C79` 标题),深色主题下表格是一块白。改成按当前调色板生成:正文 13px/195%、章节标题 11px `--blue-text`、表头 10px。Qt 的富文本引擎会自己决定 `<h3>` 的字号、无视样式表,所以章节标题改用 `<p class=sec>`。
- **趋势图**。每根柱子按设计稿在顶部标出自己的数值(用该模型的颜色),两根柱子拉开到能容下各自标签的间距。
- **说明条**(`ReadingNote`)。虚线描边、9/12 内边距、10.8px、圆角 7,并在 `resizeEvent` 里按换行后的高度设最小高——否则 QLabel 只申报一行的高度,第二行被卡片裁掉。
**窄窗口跟着设计稿的断点走。** 设计稿在 `@media (max-width:1240px)` 把卡片栅格收成一列、把左栏改成横向;照着这个思路补了三处,都是这轮出图时才暴露的:
- 对比总览的两行卡片改用 `QGridLayout`,工作区窄于 960px 时收成一列。原先三张卡各占 1/3,940 宽时「本批结论」只剩两个字一行。
- 左栏放进自己的滚动区。窗口不够高时它原来会被压到最小高度以下,「本次关注」四行直接塌成四条空条。
- 顶栏在窗口窄于 1400px 时把资料摘要(处方 / 诊单、剂量口径、资料截止、批次)换行到第二排;窄于 1240px 再收起翻页箭头、面包屑与读取状态。原先 1280 宽时标题会被裁成「诊断与药...」。
**原生标题栏跟着主题走。** 深色控制台原先只画到窗口边框为止,Windows 自己画的标题栏还是浅色,接缝很突兀。`apply_window_chrome()` 用 `DwmSetWindowAttribute` 把标题栏底色、文字色、边框色设成当前调色板(Windows 11 22000+ 生效;其他系统调用失败即保持原样,不报错),窗口显示时与主题切换时各调一次;统计窗口同样处理。
**候选卡片不再自己截断。** 三张处方卡原来写死 `setMaximumHeight(250)`,方义长一点(真实数据里常有五六行)就把药味表和「另有 N 味」一起切掉。去掉上限,药味表按内容申报高度,方义换成 `WrappedLabel` —— 普通 `QLabel` 开了自动换行也只申报一行的高度,布局照给一行,剩下的被裁;这个类在 `resizeEvent` 里按实际宽度回问一次所需高度并设为最小高度。`ReadingNote` 里原来那段同样的代码合并到这个类上。
**表格直接往下铺,不再挤在剩余空间里。** 调用记录原来带 stretch 塞在页面底部,实际只剩六十来像素,表头被切、一次只看得到一行。`_fit_rows()` 统一成按实际行高量一次(原先那版假定每行 31px),关掉表格自己的纵向滚动条,把高度设成表头 + 所有行;逐味对照矩阵、缺口清单同样处理。页面长了就由页面自己滚——这也是设计稿的做法,矩阵在稿子里就是十二行一次铺完。
顺带修掉两处连带问题:
- 处理进度页是唯一没进滚动容器的页,表格铺开后整页被压到 464px,两个模型的阶段行直接塌成零高。改成和其余五页一样走 `_add_scrolled_tab()`。
- 顶栏换行阈值原来写死 1400px,但批次翻页箭头出现时横向要 1482px,1440 下资料摘要又被切了。改成按 `top_row.sizeHint()` 与顶栏可用宽度实测比较,换行后把收起的宽度加回去再比,不会来回抖。
回归:
- 报告窗口四个套件 **234 项通过**(新增一个跳过分隔线取结论文本的辅助函数),Ruff 通过。
- 六个页面在 1440 / 1280 / 1024 / 940 四档宽度重新出图,深浅两套主题各出一张,逐张与设计稿截图对照。
- 全量桌面套件(排除早已进程级崩溃的 `test_diagnosis_drawer_visual.py`)**17 项失败**,全部落在本轮没碰过的模块里,与改造前基线的 18 项相比只少了 `diagnosis_index_visual` 那条——该条单独跑通过,是整轮跑到后段的顺序/内存抖动,此前已记录。全仓只有那四个 AI 测试文件 import 本轮改动的 dialog 模块。