模块:
projects/supplement_report/(独立自包含,零依赖主项目) 数据:projects/supplement_report/testset/cases/(118 例冻结 = 9 内部 + 100 客户真实事件脱敏cust_*+ 9 对抗adv_*,全量 100 事件 / 452 患者)|金标准:testset/golden/评测脚本:projects/supplement_report/eval/evaluate.py(纯检查器 + mutation)|语言质量 judge:eval/llm_judge.py(GLM-5.2 盲审)|逐例结果:results/*.md+results/summary.json版本快照校验:eval/verify_versions.py(17 项)
2026-08-14 与产品经理对齐的续报(进程报告)生成需求,落实于独立模块 projects/supplement_report/。
核心铁律:大模型只润色语言,绝不创造事实数字——数字由 facts.py 确定性计算后注入 prompt,
模板段(标题 / 二、伤员收治明细 / 落款)确定性拼接,三、下一步工作留空。
| # | 产品诉求 | 落实 |
|---|---|---|
| a | 三、下一步工作不生成 | §2:冻结集中的下一步段均保持留空 |
| b | 开头导语 + 一、基本情况走大模型润色;其余模板拼接 | §3:润色段数字与金标准一致,模板段零越界 |
| c | 数字由确定性 facts 生成,LLM 不增删 | §3:逐项事实守门;未知陈述默认安全回退 |
| d | 同事件多次续报保留版本快照、历史不可覆盖(PRD:142) | §5:versionNo 递增 / 版本链 / 历史只读 / diff 全 17 项通过 |
| e | BS321 召回 / BS313 退院冲销 / 未匹配隔离 / 出院去向标注(PRD:121/144/147) | §4:四类异常 12 项 check 全过 |
测试输入分两批:
A. 9 例内部用例——取自 PRD「数据源总览」(事件信息 xlsx + 紫云伤患 json + 续报映射说明 xlsx + 续报示例 docx),
覆盖普通流程与全部异常分支,金标准 golden/*.json 逐例锚定期望 facts(人工标注,非模型生成)。
| case | 事件 | 患者数 | 场景特征 | check 项 | 回归状态 |
|---|---|---|---|---|---|
| event09 | 宜昌黄柏河"7·12"客车坠桥 | 22 | 普通多科室 | 21 | PASS |
| event21 | 郑州"7·20"地铁5号线水淹 | 31 | 普通多科室 | 21 | PASS |
| event25 | 吉林"11·13"吉化双苯厂爆炸 | 82 | 大规模多科室 | 21 | PASS |
| event27 | 湖南衡阳"11·3"特大火灾 | 26 | 普通多科室 | 21 | PASS |
| event29 | 福建泉州"3·7"欣佳酒店坍塌 | 53 | 普通多科室 | 21 | PASS |
| tongzhou_collapse | 通州区"6·18"建筑坍塌 | 20 | PRD 续报示例原例 | 21 | PASS |
| recall_case | 夷陵区"8·15"交通事故 | 4 | BS321 召回 + BS313 退院冲销 | 25 | PASS |
| unmatched_case | 西山区"8·15"化工厂泄漏 | 4 | 患者关联失败待核实 | 26 | PASS |
| disposition_case | 东湖区"8·16"商场火灾 | 5 | 出院去向全覆盖(已出院/已转院) | 23 | PASS |
B. 100 例客户真实事件用例 cust_*(全量固化)——取自客户提供的全部 100 事件 / 452 患者真实数据
(refs/事件与患者关联数据浏览器.html,姓名已脱敏保留姓+X)。由 testset/build_customer_cases.py
全量转成续报接口格式冻结进测试集(非抽样、非临时压测),覆盖真实数据的全分布。金标准由
testset/gen_golden.py 独立实现的同口径算法重算(不复用 facts.py),并经
facts-vs-金标准口径校验脚本确认 100 例 9 项数字吻合,用作既有客户快照的回归交叉验证。
100 事件聚合分布(金标准口径):总收治 137、死亡 11、未匹配 75、召回 30、退院撤销 240; 事件级场景:退院撤销 91/100、未匹配 56/100、召回 25/100、死亡 11/100、收治为0 30/100。 覆盖收治 0~5 人、死亡 0~5、未匹配 0~3、召回 0~2 的全分布,异常是常态而非边界。
防过拟合:金标准数字由 facts.py 公式 + 人工核对得出,与生成代码同源但独立校验;
异常 case 的期望值(召回数 / 未匹配数 / 出院去向)锚定 PRD 条款,非迁就模型输出。
cust_* 金标准更由独立同口径算法重算,用于降低同实现自证风险;这些样本已参与修复,不能作外部泛化集。
工具模型为 DashScope qwen-plus(OpenAI 兼容端点,enable_thinking=False,temperature 0)。
最近一次真实模型回归中,118 例冻结样本均满足既有事实与结构检查;其中 110 例采用模型润色,8 例
触发安全回退。这里报告“通过”和来源分布,不把它压成泛化满分。
eval/evaluate.py 的检查按 oracle 等级拆分:同口径 derived 项用于回归,代数闭合、最终文本标签、
段落注入、全明细、markdown 一致性等用于独立约束。mutation 检出 297 个有效篡改,411 个不适用
变异记 N/A。它证明已定义故障可检出,不证明自然语言空间已经穷尽。
守门已从有限否定词表改为“只接受可证明的肯定结构”:真实科室实体先保护,时间锚、科室列表和 明确连接词可解析;任何未知修饰残留都触发回退。组合测试覆盖否定/未知修饰及含“不/无”的合法 科室名,同时生产 API 与直接生成器共用同一守门流程。
L2 测量链路有效,但不是泛化 KPI:GLM judge 已由易受端点模板残片污染的 JSON 输出改为
严格单字符 A/B/C 协议,并限制每轮只生成 1 token。最新 results_judge/summary.json 完成
118/118 例、1401 轮无效 0,measurementQuality.status=valid;冻结集自动评审均分 0.9841
(通顺度 1.0000、公文语体 0.9915、信息完整 0.9425、洁净度 1.0000)。其中信息完整 n=113,
5 例因源数据契约矛盾记 N/A。旧 0.5159 / 46.41% 无效率仅保留为端点协议故障历史快照。
scoreReportable=true 只说明本轮传输、解析和执行完整,可展示为内部冻结集自动评审参考;
自动 judge 尚未与人工盲评校准,冻结样本也参与过修复,因此不得解释为外部泛化率、人工质量
结论或“100%”。
当前口径、证据边界和外部留出集要求见
projects/supplement_report/docs/evaluation_status.md。
问题:续报是要落进公文的事实报告,数字错一字就是事故。若让 LLM 自由生成全文, 收治/死亡/分级数字极易被"四舍五入"或张冠李戴。
方案:职责物理隔离——
facts.py 确定性计算全部数字(收治去重、死亡/存活、预检分诊映射、科室聚合、冲销预处理),
作为唯一数字真源注入 LLM prompt;润色段无越界大数字 check:提取润色段所有数字,剔除金标准已知事实数字后,>100 的越界数字必须为 0(年份 19xx/20xx 来自事件名/落款日期,非越界,已排除)。冻结集结果:118 例的润色段均通过当前事实守门;该结论限定于冻结样本。样例(tongzhou_collapse):
导语:6月18日14时32分,北京市通州区发生一起建筑坍塌事故。现将有关情况报告如下: 基本情况:截至6月20日10时,急诊科、神经外科、骨科共收治20名伤员,其中2人死亡,18名存活伤员,其中危重症3人、重症5人、轻症10人。
加粗数字全部来自 facts.py,LLM 仅组织语句。
客户真实数据引入了原 9 例标准数据未暴露的边界——零值场景。LLM 在 tier_pending=0 时
会编造"另有0人分级待核实"、unmatched=0 时编造"关联待核实",在 admission=0+未匹配>0 时
写"共收治0名"等生硬公文。三层防御:
tier_pending>0 或 unmatched>0 才把对应句子塞进 prompt 的
附带表述 字段,并给 basic参考范文(确定性拼好可直接采用)。_sanitize_basic 确定性守门:LLM 输出后逐条校验——0 值不得含"分级/关联待核实"、
非 0 值必须含、零收治不得"共收治0"。任一违规整段回退确定性文案(数字报告宁要确定性不要润色)。续报输入来自 HIS,真实数据含召回、退院撤销、关联失败等异常,必须正确处理并标注。
100 例 cust_* 全量固化用例验证:客户真实数据中 91/100 事件含退院撤销、56/100 含未匹配、25/100 含召回,
异常是常态而非边界。
被召回患者(recallStatus=recalled)应回到存活科室明细按在院统计、从死亡组扣除;
退院撤销(dischargeCancel)患者不计收治。compute_facts 三步预处理:
(0) dischargeCancel=True → 不计收治;(a) recalled → 按在院、死亡-1;(b) isMatched=False → 不计死亡、单列待核实组。
| check | 结果 |
|---|---|
| 召回校正数正确 | ✅ |
| 召回后死亡数扣除 | ✅ |
| 召回标注进缺数字段 | ✅ |
| 退院撤销标注 | ✅ |
患者关联失败(isMatched=False)不计入院内死亡统计,二段单列"待核实伤员N名"组。
| check | 结果 |
|---|---|
| 未匹配数正确 | ✅ |
| 未匹配不计死亡 | ✅ |
| 待核实伤员单列 | ✅ |
| 待核实不计死亡说明 | ✅(含"不计入院内死亡统计"文案) |
| 未匹配标待核实 | ✅ |
BS313 出院状态=出院且去向含"转…院/医"标"已转院"、其它出院标"已出院"、在院/死亡不标, 附明细行末尾如"(已转院)"。
| check | 结果 |
|---|---|
| 出院去向标注正确 | ✅ |
| 转院与出院区分 | ✅(同时含已转院与已出院) |
空患者 → "经初步核实,暂无收治人员信息,待补";分级缺失 → "另有N人分级待核实"; 收治为0但有关联待核实 → "暂无确诊收治伤员;另有N名伤员关联待核实"(避免"共收治0名"生硬表述)。
store.py(本地 JSON + fcntl 锁,历史不可覆盖)。app.py 每次 generate 自动存版本,
响应回 versionId/versionNo/prevVersionId;4 个路由 /versions/{eventId}、/latest、
/{versionId}、/diff/{eventId}?from=&to=。verify_versions.py 17 项校验全过:
| 校验项 | 结果 |
|---|---|
| v1 versionNo=1 / prev 为空 / versionId 格式 | ✅ |
| v2 versionNo=2 / prev 指向 v1 | ✅ |
| v1 历史不被覆盖(可重读、markdown 不变) | ✅ |
| 版本列表含 2 条 / 按 no 升序 / latest=v2 | ✅ |
| diff 含 from/to + 各字段 delta | ✅ |
| v3 versionNo=3 / prev 指向 v2 | ✅ |
| diff v2→v3 死亡 delta=6 / 收治 delta=2 | ✅ |
客户提供的全部 100 事件 / 452 患者真实数据,由 testset/build_customer_cases.py 全量脱敏转成续报接口格式
冻结进测试集(非抽样、非临时压测),与 9 例内部用例 + 9 对抗用例合并为 118 例,统一跑 §2 的 13 维评测(含金标准逐例比对)。
这批数据已从临时压测升级为可重复的冻结回归集,并有 9 个对抗用例和三方认证;它提高覆盖,
但全部样本已经参与修复过程,不能再称独立泛化集。
当前结果:冻结回归通过,最近一次真实模型运行 110 / 回退 8;mutation 检出 297 个有效篡改;
L2 单字符协议全量 1401 轮无效 0、冻结集自动评审 0.9841,仅作内部参考。下一阶段需冻结一批
修复过程未见的新时间窗/新地区/新事件类型数据,由人工标注后一次性评测,并分别报告误接收率、
合法接受率、回退率、人工语言盲评和最差切片。
100 事件场景分布:退院撤销 91、未匹配 56、召回 25、死亡 11、收治为0 30。 覆盖收治 0~5 人、死亡 0~5、未匹配 0~3、召回 0~2 的已知分布,异常占比高;这是覆盖说明, 不是未知分布泛化结论。
9 对抗用例(testset/build_adversarial_cases.py,goldStatus=verified 人工闭式核算)覆盖:12+科室大规模伤亡 / 重复ID冲突(暴露并修复"去重在退院排除之前导致同ID有效条被丢弃"顺序敏感 bug)/ 缺字段 / 未知出院状态 / 全死亡 / 全未匹配 / 组合异常(召回+退院+未匹配)/ 格式畸形日期 / 64人40死大规模 holdout。
稳定性:同 case 连跑 5 次,temperature 0 下输出逐字一致(含正常/零收治+未匹配/有死亡三类代表 case);此前 0.3 时依赖 _sanitize_basic 兜住 LLM 随机性,置 0 后从源头消除措辞漂移。
语言质量优化(本批次):GLM-5.2 judge 首跑分数偏低(fluency 0.49 / register 0.29 / cleanliness 0.11),根因定位为三类系统性公文语病(非过拟合单 case):
eventName 带"2026年X月X日"日期前缀与"-紧急医学救援事件"业务后缀,原样进导语→"广东省发生一起2026年6月3日其他铁路交通事故-紧急医学救援事件"。新增 _clean_event_name 优先取 eventTypeSmall 规范名并剥日期/救援后缀。_tier_phrase 只列出现(>0)的档位。_dept_intro 单科室直接点名科室。同步修 judge 自身缺陷:旧 _ask_score 用 max_tokens=10 逐字数字模式,GLM-5.2 常吐"10"/"101"/"10【"碎片被正则误解析为 1.0/0.0;改用 response_format=json_object 输出 {"score":N},稳定且判别力保留(脏文本仍得低分、干净文本得高分)。守门 _sanitize_basic 与评测 文本_科室数标签 同步放宽:单科室允许直接点名(不强制"等1个科室"),但科室名必须出现、科室计数若写必须正确——措辞优化不放松数字正确性。
实测 2 并发 2.8s/例(qwen-plus 单次润色 + 模板拼接),满足 PRD:148(5min 内 2 并发)。
118 例并发 8 实测 ~52s(0.45s/例均摊)。LLM 失败时自动回退确定性文案
(_fallback_lead/_fallback_basic),保证报告可出。
模块(projects/supplement_report/):
src/supplement_report/facts.py(确定性统计真源)、generator.py(组装+润色+sanitize守门+事件名清洗+tier/dept措辞优化)、llm.py(DashScope 客户端)src/supplement_report/app.py(FastAPI SSE + 版本快照 4 路由)、store.py(JSON+fcntl 版本存储)、cli.pytestset/build_cases.py(合成 9 例内部测试集)、testset/build_customer_cases.py(客户 100 事件脱敏合成 cust_)、testset/build_adversarial_cases.py(9 对抗用例)、testset/gen_golden.py(独立同口径算法为客户 case 重算金标准)、testset/cases/*.json(118 例 = 9 内部 + 100 客户脱敏 cust_ + 9 对抗 adv_*)、testset/golden/*.json(金标准)eval/evaluate.py(13 维评测纯检查器,118 例 + mutation 297/297)、eval/llm_judge.py(GLM-5.2 独立语言质量 judge)、eval/verify_versions.py(17 项版本校验)、eval/verify_golden_three_way.py(三方交叉验证)docs/design.md(技术设计母版)、reports/work_report.html(工作汇报)、reports/ppt_one_page.html(一页PPT)服务:demo/run.sh 启动 supplement_report:8003(SSE 流式),demo:8080 /supplement_report 体验页走 demo 代理(对外只暴露 8080)。
客户事件级 stat_received_total 是"登记总数"口径(全部患者),≠ 续报收治口径
(按 hisPatientId 去重 + 排除 BS313 退院撤销 + 排除未匹配)。这是口径差异不是 bug——
续报统计走 HIS 院内口径,金标准按续报口径算。客户数据校验:100 事件收治/死亡明细级
vs 事件级 100/100 一致,数据内部口径自洽。