续报(救援进程报告)v2 评测报告:冻结集回归通过 · 事实守门泛化补强 · L2 链路有效

模块: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 项)

0. 任务与产品诉求

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 全过

1. 测试集构造(118 例 · 9 内部 + 100 客户真实事件脱敏 + 9 对抗)

测试输入分两批:

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_* 金标准更由独立同口径算法重算,用于降低同实现自证风险;这些样本已参与修复,不能作外部泛化集。

2. 评测维度与当前状态

工具模型为 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。

3. 产品点 b/c — LLM 边界与数字可回溯(核心铁律)

问题:续报是要落进公文的事实报告,数字错一字就是事故。若让 LLM 自由生成全文, 收治/死亡/分级数字极易被"四舍五入"或张冠李戴。

方案:职责物理隔离——

冻结集结果:118 例的润色段均通过当前事实守门;该结论限定于冻结样本。样例(tongzhou_collapse):

导语:6月18日14时32分,北京市通州区发生一起建筑坍塌事故。现将有关情况报告如下: 基本情况:截至6月20日10时,急诊科、神经外科、骨科共收治20名伤员,其中2人死亡,18名存活伤员,其中危重症3人、重症5人、轻症10人。

加粗数字全部来自 facts.py,LLM 仅组织语句。

3.1 条件表述守门(客户真实数据暴露的泛化问题)

客户真实数据引入了原 9 例标准数据未暴露的边界——零值场景。LLM 在 tier_pending=0 时 会编造"另有0人分级待核实"、unmatched=0 时编造"关联待核实",在 admission=0+未匹配>0 时 写"共收治0名"等生硬公文。三层防御:

  1. prompt 条件性附带:仅当 tier_pending>0 或 unmatched>0 才把对应句子塞进 prompt 的 附带表述 字段,并给 basic参考范文(确定性拼好可直接采用)。
  2. _sanitize_basic 确定性守门:LLM 输出后逐条校验——0 值不得含"分级/关联待核实"、 非 0 值必须含、零收治不得"共收治0"。任一违规整段回退确定性文案(数字报告宁要确定性不要润色)。
  3. 评测 6 项守门 check:零分级不编造/非零必含、零未匹配不编造/非零必含、零收治措辞得当, 并入"LLM 未越界"维度,固化防回归。

4. 产品点 e — 异常处理(PRD:121/144/147 四类边界)

续报输入来自 HIS,真实数据含召回、退院撤销、关联失败等异常,必须正确处理并标注。 100 例 cust_* 全量固化用例验证:客户真实数据中 91/100 事件含退院撤销、56/100 含未匹配、25/100 含召回, 异常是常态而非边界。

(a) BS321 召回 + BS313 退院冲销(recall_case + cust_* 多例)

被召回患者(recallStatus=recalled)应回到存活科室明细按在院统计、从死亡组扣除; 退院撤销(dischargeCancel)患者不计收治。compute_facts 三步预处理: (0) dischargeCancel=True → 不计收治;(a) recalled → 按在院、死亡-1;(b) isMatched=False → 不计死亡、单列待核实组。

check 结果
召回校正数正确 ✅
召回后死亡数扣除 ✅
召回标注进缺数字段 ✅
退院撤销标注 ✅

(b) 未匹配伤员隔离(unmatched_case + cust_* 多例)

患者关联失败(isMatched=False)不计入院内死亡统计,二段单列"待核实伤员N名"组。

check 结果
未匹配数正确 ✅
未匹配不计死亡 ✅
待核实伤员单列 ✅
待核实不计死亡说明 ✅(含"不计入院内死亡统计"文案)
未匹配标待核实 ✅

(c) 出院去向标注(disposition_case · 21 项,PRD:121)

BS313 出院状态=出院且去向含"转…院/医"标"已转院"、其它出院标"已出院"、在院/死亡不标, 附明细行末尾如"(已转院)"。

check 结果
出院去向标注正确 ✅
转院与出院区分 ✅(同时含已转院与已出院)

(d) 缺数措辞

空患者 → "经初步核实,暂无收治人员信息,待补";分级缺失 → "另有N人分级待核实"; 收治为0但有关联待核实 → "暂无确诊收治伤员;另有N名伤员关联待核实"(避免"共收治0名"生硬表述)。

5. 版本快照(PRD:142 · 同事件多次续报保留历史)

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 ✅

6. 回归覆盖与待完成的泛化验证

客户提供的全部 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):

  1. 脏事件名入正文:客户 eventName 带"2026年X月X日"日期前缀与"-紧急医学救援事件"业务后缀,原样进导语→"广东省发生一起2026年6月3日其他铁路交通事故-紧急医学救援事件"。新增 _clean_event_name 优先取 eventTypeSmall 规范名并剥日期/救援后缀。
  2. 0 值分级全列:"危重症0人、重症0人、轻症2人"是公文冗余。新增 _tier_phrase 只列出现(>0)的档位。
  3. 单科室配"等":"重症医学科等1个科室"单数配"等"不自然。新增 _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个科室"),但科室名必须出现、科室计数若写必须正确——措辞优化不放松数字正确性。

7. 性能

实测 2 并发 2.8s/例(qwen-plus 单次润色 + 模板拼接),满足 PRD:148(5min 内 2 并发)。 118 例并发 8 实测 ~52s(0.45s/例均摊)。LLM 失败时自动回退确定性文案 (_fallback_lead/_fallback_basic),保证报告可出。

8. 交付物清单

模块(projects/supplement_report/):

服务:demo/run.sh 启动 supplement_report:8003(SSE 流式),demo:8080 /supplement_report 体验页走 demo 代理(对外只暴露 8080)。

9. 数据口径备忘

客户事件级 stat_received_total 是"登记总数"口径(全部患者),≠ 续报收治口径 (按 hisPatientId 去重 + 排除 BS313 退院撤销 + 排除未匹配)。这是口径差异不是 bug—— 续报统计走 HIS 院内口径,金标准按续报口径算。客户数据校验:100 事件收治/死亡明细级 vs 事件级 100/100 一致,数据内部口径自洽。