本页由whitebook.html拆分而来:主白皮书为控制单页体积,把各预警项的历次变更记录与体系设计原则移到本页。主白皮书中出现的「见第 11.x 条 / 第 12.x 条」等引用,即指向本页对应条目;带id的条目(bp-v1535/bp-v1535b/glucose-v1536/baseline-v1537/calib-v1538/drink-v1539/glucose-v1539)可直接用锚点访问,主白皮书的校准说明亦已改为跳转到本页。
下列条目原位于主白皮书 §4.12「AI 预警体系」各预警项的说明列表内,现按所属预警项分组汇集于此。每条记录都写明现象 → 根因 → 改法 → 实测 → 一键回退,可直接作为变更审阅材料。
act_median > 10)拦下的,与 HR 幅度无关。详见《预警系统说明文档.md》§6.4 / §10.18。realtime_sedentary 保持一致。该项实际干预的是「长时间没有有效活动」(低代谢状态),与具体姿势无关。日报侧判据未改动——它的输入是全天 state 统计(sedentary_ratio),本身就是久坐口径;此处仅改展示名与措辞。120 / 180 / 240 上移到 180 / 240 / 300,新增硬下限 min_alert_minutes = 180(分档 / 加分 / 降档 / combo 一律不得突破),bonus.ppg_fatigue_tier_shift_minutes 由 30 降为 0。原 2h 档取消。详见《预警系统说明文档》§10.29。bouts:V15.23 只修了组合判据的时长,没修集合——mv 仍取自 all_bouts(含 is_driving=False 的 churn-only 段),而证据门一收紧,它反而成了唯一的宽松口。实测线上误报 864249079484364 / 2026-10-06:一段 183.3 min 的 churn-only 段(6 区间仅 1 次小区切换 < need 3,cell_confirmed=False)满足 minutes ≥ 180 就被选中 ⇒「疲劳驾驶风险」0.6 medium;当日已确认在车中的最长段只有 152.6 min(分档路径正确地没报)。修复:mv 候选集改取 bouts——组合判据只能放宽档位,不得放宽证据门。详见《预警系统说明文档》§10.30。report_warner.daytime_sleepiness.drink_score_threshold_boost 0.40 → 0.60。原值低于 score_gate = 0.50,而代码是 score_threshold = boost if alcohol_dominant else score_gate、判定式为 score < threshold → 不报 ⇒ 注释写「饮酒主导时提高阈值」,实际却是降低门槛,酒精日反而更容易被判「日间疲劳」。成因:V14.11 把 score_gate 0.30 → 0.50(当时 boost = 0.40 相对 0.30 确实是「提高」),但 boost 没跟着抬。现取 score_gate + 0.10 = 0.60,保留原「+0.10」增量语义。sym_alcohol_impact(「酒精影响」独立告警),刻意不用 cross_inhibit.daytime_drink_score_threshold = 0.20 这类内部中间量(会把门开得过宽)。命中时 duration_tier / ppg_fatigue_combo / daily_total 三条判据全部抑制,且闸门放在判据汇合点(best / combo_used / day_max 全部算完之后、组装告警之前),因此新增第四条判据会自动被覆盖,不必再回改代码;三个早退分支(开关关闭 / 样本不足 / 时间跨度不足)都在闸门之前,行为完全不变。抑制为静默:warning / reason / driving_note 全为 null,页面不出现任何「当日检出饮酒」的说明,留痕只进响应 driving.alcohol_guard(policy = alcohol_day_no_driving_attribution)与服务端审计日志。回退开关 driving_warner.alcohol_guard.enabled(置 false 即恢复 V15.24 行为)。验证:本地单元 83 项(逐判据抑制 / 非饮酒日对照组 / A/B 模块克隆反事实 / 静默性断言 / _alcohol_day_source 形态与弱信号排除 / Flask 真实 /api/zq-report 端到端)+ 既有全套回归(63 / 58 / 20 / 16 / 36,全绿)+ 线上 36/36(A1 现场生成路径与 A2 缓存命中路径逐字段一致 ⇒ 两条调用点都已接线;B 非饮酒日对照仍照常告警 sym_driving_fatigue / 0.82 / high / duration_tier)。详见《预警系统说明文档》§10.31。extract_segments 的 min_length 依次取 1800 / 900 / 450 / 300 拍,四种段长下判定结果逐字节相同 ⇒ 分段长度不是抖动主因。
tail_slack_minutes(45) 把「累计证据」偷换成「最近 45 分钟内的证据」——files/864249079484364-血糖问题.txt 22:30 raw 155.0 / tail 158.0 / total 0.0、22:41 raw 154.6 / tail 154.6 / total 0.0、23:00 raw 135.8 / tail 135.8 / total 0.0 ⇒ tiers[1..3] 结构性不可达,22:41 完全静默(正是投诉现场);② 整段体动门一次抖动就把整段清零——21:00 ⇒ n_segs=1, n_kept=0, raw_min=0.0,约 142 分钟被判无效。
hour ∈ [18, 23])承担,取消 tail_slack_minutes;② 体动门由「段内 ma_act_1m 中位数 < 10」改为「逐点 < 30 进候选 + 段内 > 30 的占比 ≥ 0.2 才整段弃」(中位数门对双峰分布失明);③ 候选段长由 = min_beats(1800) 解耦为 min_segment_beats(300);④ 日间对照门改为 act_mode='fraction'(它才是特异度的真正承担者——862413079574788-开车误报成饮酒.txt 累计仍达 203.9 真实分钟,最终由它压掉);⑤ 心率地板提为共用 _local_hr_floor()(傍晚窗之外低体动样本 P10,只降不升;floor_lookback_hours 8)——窗内样本可能已被酒精抬高(862413079574788-开车误报成饮酒.txt 21:00/4h 窗内 P10 达 119.8 bpm,采用会把门槛抬到 153.8);⑥ 定位扣除仅当 available is True(修掉 _drive_intervals_sec 在 available is False 时仍按 intervals 扣除的缺陷)。
hint:累计 40 真实分钟(min_minutes 40 / confidence 0.55)即播报「疑似饮酒信号 · 证据累积中」(severity = MEDIUM、evidence.hint = true),达 60 min 自动升级为正式「饮酒信号」;正式信号时刻不变。取 40 的实测依据(10 分钟网格 17:00→23:50):真阳性首命中 19:20 / 20:00 / 21:00,三份负对照末端累计上限仅 24.3 / 29.9 / 0.0 ⇒ 余量 10.1 min(45/50/60 门槛为 15.1/20.1/30.1;30 仅 0.1,不可用)。confidence 取 0.55:下界 confidence_gate(0.55)、上界 scores[0](0.55) 恰好同值(中间无取值空间),故与最低档同分、靠 severity 与文案区分。提示的 id 仍为 realtime_drinking(下游路由依赖,绝不能改),因此会照常触发 V15.25「饮酒归因门」(与用户口径「饮酒了,叫的代驾」自洽)。
864249079484364-饮酒 act_med 10~13、864249079486195-饮酒 act_med 10~15)在整段窗口不过绝对值体动门(minutes 恒 0.0,完全漏检),而 862413079574788-开车误报成饮酒(act_med 9.6~15.5)反而能过 ⇒ 绝对值门会系统性误杀「体动整体偏高的人」。
surge.hint.min_minutes ≤ 0。
temp/_test_surge_v1539.py 54 通过 / 0 失败(含提示断言族 D1–D5)+ 全量 8 套件 失败行合计 0。完整证据链见《预警系统说明文档》§9.1 / §10.43。864249079484364-未饮酒 29.9 min、862413079574788-疑似饮酒但没饮酒 24.2 min、864249079484364-未饮酒@日间对照 3.0 min;提示门 40 min 的余量 10.1 min 已是最紧可接受值,30 min 档只剩 0.1 min ⇒ 不可用。已交付的提前量为首命中 19:20 / 20:00 / 21:00(正式首命中 19:30 / 20:30 / 21:30),其中最紧的一份现场(864249079484364-血糖问题)19:30 即出声,与用户描述的「19:30 饮酒」同一时刻 ⇒ 再提前已越过「行为发生」这条线。
tiers[3]=240 属预留上限档:实测正例峰值累计 153.3 真实分钟(tiers [60, 90, 150, 240] ⇒ tiers[1]=90 / tiers[2]=150 可达、tiers[3]=240 从未触发)。保留原值——若降到 180 会改变已验证的 0.62/0.70 分数映射,需重跑全部回归,代价大于收益;仅在配置注释、配置编辑器标签与本文档中标明「预留档 · 可达性未验证」。
862413079574788-开车误报成饮酒.txt 的原始累计高达 203.9 真实分钟(22:41 末端 42.5 min、全时段最大 48.4 min)——高于真阳性峰值 153.3,而两个真阳性(864249079484364-饮酒、864249079486195-饮酒)的日间对照值均为 0.0 ⇒ 该负例的静默完全由日间对照门独力承担(逐点体动门与定位扣除都不足以压掉它:定位不可用时无扣除,act 波动也不足以整段弃掉)。不得改回 V2 双模日门口径,也不得放宽/关闭本门,任何改动本门阈值/口径/时段的行为都必须先重跑 temp/_test_surge_v1539.py 的 C5 与【B】负例矩阵。
temp/_test_surge_v1539.py 54 通过 / 0 失败(C1–C6 证据链 + D1–D5 提示档)+ 全量 8 套件 失败行合计 0。完整证据链见《预警系统说明文档》§10.43.7、《白皮书》V15.39 收口条目。862413079574788 的饮酒夜,判据 A 结构性不可达——饮酒窗内 pw_median / 基线 仅 0.72 ~ 0.74(门限 1.30),且这些窗 PW 有效率仅 27 ~ 28%(质量门要 ≥ 30%),两条路同时堵死,实时侧 0 触发;而同一夜同一设备的日报侧(V15.10 后的 surge 判据)能命中 131 min。同一物理事件、两层判据不同构是本次漏报的根因,故实时侧补齐与日报侧同值标定(hr_rise 34 / min_beats 1800 / act_median 10 / act_high_threshold 30 / act_high_ratio 0.2)的 surge 判据。max(dur) 取最长的一段,于是「早已结束的长段」会压过「仍在持续但较短」的尾段(后者 < 45 min 被时长门刷掉)。实测 864249079484364-入睡早@1.00:选定 bout 56.3 min / 距窗末 26.1 min,其后 24 min 处即有一段有效活动,窗末段仅 24.2 min(< 45 被刷)⇒ 用户 26 分钟前刚起身动过,系统仍说「已有 56 分钟没有有效活动」。修法:_sedentary_bout_spans 返回值六元组 → 七元组(尾项 tail = 该段之后本块内不再有有效活动事件);分支A 三条线 → 四条(只接受 tail=True);分支B 的「持续时长」口径由「清醒块起点 → 窗末」改为「距上次有效活动」。两分支共用同一语义:此刻确实已 ≥ 45 min 无有效活动。新开关 sedentary.gates.require_ongoing_bout(默认 true,置 false 一键回退 V15.25);ongoing_wording_max_minutes(5) 决定是否说「且仍在持续!」;evidence 新增 bout_ongoing / last_activity_gap_min。效果(116 窗配对回归 = 58 台 × {0.50, 1.00}):触发 6 窗/5 台 → 4 窗/3 台,抑制 2 窗(864249079484364-入睡早@1.00 分支A、864249079322473@1.00 分支B),新增 0 窗,直接调用 == 生产入口 0 窗不一致;真阳性靶 862413079574788-久坐没提醒 @0.50/@1.00 BEFORE == AFTER(bout_ongoing=True)。已知限制:末段体动常弱到不构成「有效活动事件」(须 ma_act_1m ≥ 12 且持续 ≥ 60 拍),而 ma_act_1m 按本段 payload 自身 p99 归一化(跨 payload 可差约 3 倍),故规则取「最后一次*有效*活动之后的那一段」而非「任何体动都压住告警」。symptom 由「久坐提醒」改为「运动提醒」,内部 id 仍为 realtime_sedentary(对客户端零影响)。效果:104 窗配对回归(52 台 × 2 截断)触发 5 窗/4 台 → 6 窗/5 台,无设备被抑制;新增的恰是 862413079638799@1.00(久坐态 0.43 < 0.50、心率上浮仅 +1 bpm、低体动占比 0.97、bout 52 min、时效 0 min),正是姿态门原本会拦下、而用户希望被提醒的「清醒躺/趴」场景。真阳性靶 862413079574788-久坐没提醒 在 @0.50 / @1.00 下均仍触发。temp/_test_chest_v1532.py:18 通过 / 0 失败。注意:丁在小基线文件上是放宽(pw_base=104.0 时固定比值门 74.88 < p10 87.3)——它修正的是「固定比值」这一错误形式,而非单调收紧。content 解析:server._read_locate_record 对对象元素({'sync_time_str':…, 'content':…},即接口文档推荐的写法)存的是整个 dict,而 build_samples 把该 dict 直接交给 parse_locate_content。str(dict) 会把 content 内的换行转义成字面 \n,于是第② 行「服务小区」永远解析不到 ⇒ cell_key = None ⇒ cell_changed 恒为 None ⇒ cell_confirmed 永远 False ⇒「服务小区切换」这条强证据、move.min_cell_changes 门槛、ppg_fatigue_combo.cell_confirmed_bonus 线上全部失效;同时 dict 的键值对会被误当成一个 MAC(含逐条不同的 sync_time_str)⇒ 相邻样本 Jaccard 被拉低 ⇒ 更容易被误判成 churn。修复:parse_locate_content 遇 dict 时先取出 content 字段(兼容 data / payload / raw 别名)。线上复验:862413079574788 @ 2026-09-30~10-01 ⇒ cell_confirmed=True cell_changes=3 minutes=150 rule=duration_tier conf=0.82 high。driving_note:用户投诉「线上正气报告不显示疲劳驾驶」。审计真实请求后发现两条独立根因:(1) 网页入口 report.html 的请求体从不携带 locate_record(审计记录:referer=https://report.hzszy.cn/report.html?...、body_keys=[age,date,deviceId,gender,height,weight]、locate_record.present=false)⇒ _driving_view_for_report([]) 首行 if not items: return None 静默短路,页面上连「未评估」都不提示;(2) 真实调度方(/api/ppg/warn)一直在传定位,但形态是二元组而被丢弃(见 V15.21)。修复 (1):新增响应字段 driving_note + 页面「· 疲劳驾驶评估」卡片(只改可见性,不动任何阈值)。同时上线 API 审计日志(logs/api_audit_zq.jsonl / api_audit_ppg.jsonl,每请求 2 条结构摘要,8 MB 轮转,支持免重启开关),正是它上线 5 分钟内就暴露了根因 (2)。ua=Apache-HttpClient/4.5.13 (Java/17.0.8)、remote=127.0.0.1)传的是 [["94:14:57:01:7D:FA,195;…;\n460,11,40886,79640242,-91;", "2026-10-09 12:42:42"], …](二元组:内容在前、时刻在后)。新增 _split_pair_element() 解析(2~4 元组,取唯一/最后一个可解析时间戳为 ts),并使原始元素原样回传(不改写调用方形态)。线上复验:elem_pair=52 dropped_invalid=0 received=52 used=52 ⇒ sym_driving_fatigue 0.97 high(rule=duration_tier)。862413079574788 / 2026-10-04:longest_minutes = 90.7 → conf 0.67 → medium_high(算术复现:90.7 + 降档 30 = 120 min 档 0.55,+ 疲劳加分 0.12 = 0.67)。三条根因 —— ① need = max(min_cell_changes, ceil(区间数 × ratio)) 在短段退化为绝对下限(ceil(3 × 0.25) = 1)⇒「换 1 次小区即确认驾驶」;② 已确认疲劳时整体下调分档门槛 30 min,使 90.7 min 越级;③ ppg_fatigue_combo 的 mv 取自未经证据门筛选的 all_bouts,同时绕过分档表与证据门。修复:分档 180 / 240 / 300、硬下限 min_alert_minutes = 180、降档归零、min_cell_changes 3 + ratio 0.30、combo.min_moving_minutes 180。实时期与日报版共用同一份 config/config.json 的 driving_warner 块,同步生效。验证:本地单元 57 项(含反事实复现旧口径 conf=0.67 medium_high tier=120)+ 端到端 20 项 + 线上 2/2。详见《预警系统说明文档》§10.29。864249079484364-饮酒和疲劳驾驶同时出现.txt「从图看应该算饮酒,不知道疲劳驾驶怎么来的」。当日确为酒精日(daytime_drink_score 0.45 ≥ 0.30),sym_alcohol_impact 正确;但 sym_fatigue_driving 0.6(睡眠项来自酒精后果)把 ppg_fatigue_conf 顶到 0.6 ≥ 门 0.50 ⇒ 触发 combo,而 combo 从 all_bouts 里选中了 183.3 min 的 churn-only 段 ⇒ 误报 sym_driving_fatigue 0.6 medium(decision_rule = ppg_fatigue_combo)。修复后线上复验:combo_used=false、driving.warning=null、无 sym_driving_fatigue、有 sym_alcohol_impact,warnings_summary.active 4 → 3。另修正 drink_score_threshold_boost 方向反转(0.40 → 0.60)。验证:本地单元 58 项(含A/B 模块克隆反事实,旧代码逐字段复现线上告警)+ V15.23 全套 63 项 + 端到端 20 项 + 疲劳回归 16/36 项 + 线上 10/10。详见《预警系统说明文档》§10.30。src_v2/driving_warner.py,故 V15.25 闸门同样生效——本窗若已报出「饮酒信号」realtime_driving,则整体不出「疲劳驾驶风险」(realtime_driving_fatigue 静默消失,无任何用户可见文案;留痕在返回的 driving.alcohol_guard)。日报侧触发 id 为 sym_alcohol_impact,两侧 id 清单均从 config.driving_warner.alcohol_guard.trigger_ids 读取;日报侧没有 realtime_driving、实时侧没有 sym_alcohol_impact,故同一请求内两个 flag 永不冲突。详见《预警系统说明文档》§10.31。blood_pressure_1 = 收缩压、blood_pressure_2 = 舒张压,故误差是 SBP −30 / DBP −26(不是 4/38);而袖带 DBP 91 已属1 级高血压 ⇒ 系统报「正常」是假阴性,方向比误差幅度更危险。
reference_bp(35,'男') = 122/77 本就比该用户低 ⇒ −7.0 / −14.0;② 偏移段 pw_front 相对个人基线 +16.6% ⇒ dln = −0.1537 被线性外推 ⇒ −23.1 / −11.5;合计 −30.1 / −25.5,与实测误差完全对上。⇒ 即使 Δln 项完全正确,该用户也必错 ≈ −31/−11。
pw_front 226.90→264.60(+16.62%)、pw_back 381.70→453.40(+18.78%)同步升高 17~19%,而 pw_front/pw_back 仅 0.5944→0.5836(−1.83%)。跳 61 份历史文件:CV(pw_front) 中位 0.4629 vs CV(比值) 0.0710,61/61(100%) 文件里比值都更稳;逐小时极差 pw_front 1.36× vs 比值 1.00×;跨文件 pw_front 中位相差 2.76×。本模块只读 pw_front、从不看 pw_back,违反 warner_utils.pw_valid_mask 的既有不变量。
max_plausible_ln(原方案从 0.25 改 0.10,已量化否决):61 文件 / 689 个日间小时,通过率 71.7% @0.25 → 35.8% @0.10,≈64% 窗口会退回人群参考 122/77 —— 而它本身就被分级为「正常血压」,只是把间歇性假阴性变成系统性假阴性。故模型常数一字未动。
measurement / bp_measurement / sugar_measurement / display_label_cn / uncalibrated_fields(均在 blood 块内;blood.measurement 为保守语义:血压与血糖都标定才为 true);② 未标定路径不下分级/分层结论(key uncalibrated),数值照旧输出;③ disclaimer 前缀「【非测量值】」、calibration_hint 明说「不是测量值…也不能用于危急值判定」。
blood.measurement=false;只给血压 ⇒ false / bp_measurement=true,血压恢复真实分级;只给血糖 ⇒ false / sugar_measurement=true;都给 ⇒ true。五个新字段均在 blood 块内(响应顶层不提升)。前端应用逐项文案(bp.display_label_cn / glucose.display_label_cn)贴在各自数字旁,不要用整体文案描述单个指标。
crisis 数学上不可达、alert_eligible 恒 false,实测 warnings=[] 不变。一键回退:vitals_estimator.labeling.suppress_uncalibrated_grade = false。
pw_front/pw_back 仍需成对的(袖带值 + 同时段 PPG)数据,而 App 从不上传校准值。compute() 里 sbp_out, dbp_out = int(round(c_sbp)), int(round(c_dbp)) 直接把模型输出整值覆盖。实测:同一份 PPG 配 129/91 或 145/95,输出就分别是 129/91 与 145/95;换一台设备的 PPG(模型自算 115/74)输出仍是 129/91 ⇒ PPG 对结果的贡献为 0,校准值沦为「把最后一次测量延长 24 小时」而非参考。
SBP = 袖带SBP + k_sbp(50.0)·ln(pw_ref/pw_now)、DBP = 袖带DBP + k_dbp(30.0)·ln(pw_ref/pw_now),偏移双向限幅 max_delta_mmHg = 15。source 由 "user" 改为 "calibrated"。
ΔP ≈ 100·Δln 的推导前提是主动脉 PTT(大动脉弹性主导);本 payload 的 pw_front 是手指 PPG 上升支,另受血管张力、体温、接触压力、探头位置支配——实测 pw_front 逐小时 CV ≈ 0.46 而 pw_front/pw_back 只有 0.07(61/61 份文件成立)。沿用 150/75 等于假设这些干扰都是血压,会把 ±5 mmHg 的真实变化放大成 ±30 mmHg 的假变化。
pw_ref 恒为「全记录日间静息中位」,而 pw_now 在窗口静息样本不足时会退化成 pw_valid(含运动段)——实测同一份文件、同一台设备,仅把 lookback_hours 从 12h 改成 4h 就得到 pw_ref = 112.6 / pw_now = 259.6(2.3 倍,Δln = −0.836,非生理比值)。现按窗口自身那道门取窗口外同条件样本回算基线,由 bp.calibration.use_out_of_window_baseline 控制。
bp_measured_at(epoch 秒/毫秒 或 ISO 字符串)/ bp_measured_hours_ago;超 max_age_hours = 24 ⇒ blood.bp.stale = true ⇒ 撤销告警资格(数字与分级照常回显,标签改「已过期,不建议用于判定」)。★ 未知 ≠ 陈旧:未送时间戳时 stale = null(不是 false)且仍可告警(require_measured_at_for_crisis = false,fail-open;网关开始送时间戳后建议置 true 转 fail-closed)。
blood.bp 内):source = "calibrated"、anchor_kind、anchor_value、calibration_anchor、calibration_mode、cal_delta_ln、calibrated(最终输出,不再是袖带值)、delta_vs_model(对人群锚点模型)、offset_from_anchor_mmHg、offset_limit_mmHg、calibration_age_hours、stale、ref_pw_src、pw_base_out_ms、n_base_out_beats;响应顶层另有 bp_cal_age_hours / bp_cal_age_source(与 age_used / age_source 并列)。
bp.reference_bp._comment 原写「模型输出的是相对该锚点的偏移,故本表绝对精度不敏感」——被实测推翻:锚点是加性截距,误差 1:1 穿过整个模型(Δln = 0 时输出字面等于 122/77)。已重写。
bp.calibration.mode = "replace"。未触碰:bp.ptt.k_sbp(150) / k_dbp(75) / max_plausible_ln(0.25) / delta_ln_cap(0.25) / reference_bp.bands / bp.crisis(180/110) / glucose.*。
temp/_test_vitals_v1535.py 174 通过 / 0 失败;temp/_test_vitals_v1530.py 301 通过 / 0 失败;全量 temp/_run_regress.py 八套件失败行合计 0。完整证据链见《预警系统说明文档》§9.11.7 / §10.39。129/91 + lookback_hours = 12 ⇒ 返回 129/91、offset_from_anchor_mmHg = {0, 0} —— 架构改了、字段有了、偏移恒为 0,等于没改。
|Δln| = 0.282 > max_plausible_ln(0.25) ⇒ plausible = false ⇒ V15.35 把 cal_delta_ln 置 null;② 数据门 valid_ratio = 0.26 < 0.30 ⇒ data_ok = false ⇒ 有值也被拦掉;③ 基线退化 lookback_hours ≥ 数据跨度 ⇒ 窗口即全集 ⇒ pw_ref == pw_now ⇒ Δln ≡ 0(数据事实,本版不修)。讽刺之处:同一台设备小窗口被「太不可信」归零、大窗口被「没有差异」归零。
bp.calibration.implausible_ln = "clamp"(超范围 ⇒ 限幅到 ±0.25 后仍作趋势偏移,偏移绝对值 ≤ k × 0.25)与 bp.calibration.data_gate = "beats_only"(拍数够即可取偏移);二者都把 cal_ln_trusted 置 false。
Δln 超范围恰恰意味着采集尺度变了(pw_front 绝对值 CV ≈ 0.46,混着探头位置/接触压力),此时 +12.5 mmHg 的偏移会把正常的 175/105 顶进 180/110,报出用户并不存在的「危急值」。方案 A 的两条护栏:① 危急值 / 告警资格改用袖带锚点值判定(threshold_basis = "anchor")——锚点是用户实测真值,PPG 尺度漂移不可能制造危急值;② 偏移上限动态收窄 max_d = min(15, 180 − 锚点SBP − 1, 110 − 锚点DBP − 1)(下限 0)⇒ 展示值也不越线,卡片数字与 crisis 永不自相矛盾。实测:锚点 176/106 ⇒ 上限收窄到 3(输出 179/109);锚点 179/109 ⇒ 上限 0(输出即锚点)。
blood.bp 内):cal_ln_trusted(本次 Δln 是否可信)、threshold_basis("anchor" / "output")、offset_limit_configured_mmHg(配置值 15.0)、offset_limit_narrowed(仅收窄时出现,字段缺省即未收窄)、offset_limit_mmHg(实际生效上限,可能被收窄,不再恒等于 15)。
/api/ppg/warn 端到端,同一份真实数据):lb=12 + 129/91 ⇒ 142/98(偏移 +13/+7、cal_delta_ln = 0.25、cal_ln_trusted = false、threshold_basis = anchor、crisis = false);lb=6 ⇒ 118/85(偏移 −11/−6,data_ok = false 但拍数门放行);176/106 ⇒ 179/109(限幅收窄到 3.0、crisis = false);未标定路径 120/76 零变化。
implausible_ln = "zero" + data_gate = "full" + anchor_threshold_when_implausible = false。未触碰:bp.ptt.max_plausible_ln(0.25) / delta_ln_cap(0.25) / k_sbp(150) / k_dbp(75) / reference_bp.bands / bp.crisis(180/110) / glucose.*。
temp/_test_vitals_v1535.py 174 通过 / 0 失败(新增 t10_plan_a_clamp_reversion / t11_plan_a_data_gate);temp/_test_vitals_v1530.py 301 通过 / 0 失败(顺带修好两条过期的「整值替换」断言:旧断言 == 188 / == 116 是 V15.30~V15.34 的口径,输出早已改为「锚点 + 偏移」);全量 temp/_run_regress.py 八套件失败行合计 0。完整证据链见《预警系统说明文档》§9.11.8 / §10.40。band)只在 source == "user" 时生效;未标定时统一为「未标定,不予分层」(band_key = "uncalibrated"),数值照旧输出,并新增 measurement / display_label_cn 等标注字段(详见第 11.1 条)。
注意这与 V15.31 的「估算路径不告警」是两件独立的事:后者管 crisis / alert_eligible,V15.34 管 band 这个结论;即使把 suppress_uncalibrated_grade 置 false 恢复分层,估算路径仍不会产生告警。一键回退:vitals_estimator.labeling.suppress_uncalibrated_grade = false。cal_delta_ln 就能定位。血糖侧是字面量回显:_compute_glucose() 里 g_out = round(float(self.sugar_cal), 2),compute() 里又覆写一次 ⇒ HR / SI / HRV 三项代理的贡献恒为 0,偏移从未被计算过。同一份 PPG 配 5.6 / 12.5 / 14.5 就得到 5.6 / 12.5 / 14.5,换一台设备的 PPG 一字不变;且从输出数字上无法区分「字面量回显」与「模型恰好算成同一个数」(这才是它被漏掉的原因)。
sugar_cal 被当作「答案」而不是「锚点」——同一模块内血压/血糖两条链路语义不一致,却共用同一个 confidence 门与告警接线;② 血糖没有 calibration 配置块 ⇒ 没有 mode、没有回退开关,也无处记录「本版改了它」(这就是「被漏掉」的机制:无配置块 ⇒ 无人回扫);③ alert_eligible 对 source = "user" 无条件放行 ⇒ 只要每次上报都带同一个陈旧值(如 14.5),realtime_blood_sugar_high 永久重复告警(血糖还多一条 ≥ 13.9 直升 HIGH 的路径);④ 完全没有新鲜度机制(血压侧 V15.35 已有 max_age_hours)。
bp.calibration 同构):输出 = clamp(上传值 + clamp(gain × (G_模型_raw − base(age)), ±max_delta_mmol_l), 3.5, 16.0)(2 位小数),gain = 0.25、max_delta_mmol_l = 1.2;source 由 "user" 改为 "calibrated"(sugar_measurement 仍为 true,分层结论照常给出);上传值语义从「最终值」变为「校准锚点」。
ΔP ≈ 100·Δln 来自主动脉);血糖那三项是很弱的代理——模型自身置信度上限只给 0.35(血压 0.60),三项满量程合计约 −2.7 ~ +4.8。若 α = 1,一个上传 5.6 的健康人会因心率快就显示 8.5——一个「看起来像临床数字、其实在为非血糖原因跳动」的值,比「恒定 5.6」更危险。α = 0.25 ⇒ 偏移落在 −0.7 ~ +1.2,只表达方向与趋势,不足以把 5.6 推进偏高(≥ 7.8)、也推不进危急(≥ 11.1)。
data_gate = "beats_only"(该设备 valid_ratio = 0.26 < 0.30,若仍要求完整 data_ok,偏移会静默归零、输出又退化成常数)——未过门则 offset_trusted = false;② anchor_threshold_when_untrusted = true ⇒ 危急值与告警资格改按「上传值(锚点)」判定(threshold_basis = "anchor"),且偏移上限收窄到「刚好不越过 11.1」:max_delta = min(1.2, 11.1 − 锚点 − 0.1)(留 0.1 mmol/L 余量 = 一个显示刻度,对应血压版的 1 mmHg);锚点本身已 ≥ 11.1 时不收窄。删掉那 0.1 余量会让展示值恰好落在 11.1 上,而判定是「≥ 11.1」——一旦有浮点/舍入误差,卡片数字与告警结论就会互相矛盾;该缺陷正是被本次新写的边界断言 t15 一次跑出 4 条失败才发现。
sugar_measured_at(epoch 秒/毫秒 或 ISO 字符串)/ sugar_measured_hours_ago(小时,优先;等价键 blood_sugar_measured_at / glucose_measured_at);超 max_age_hours = 24 ⇒ blood.glucose.stale = true ⇒ 撤销告警资格(值与分层照常回显,标签改「用户上传实测值(已过期,不建议用于判定)」)。★ 未知 ≠ 陈旧:未送时间戳时 stale = null(不是 false)且仍可告警(require_measured_at_for_crisis = false,fail-open)。
blood.glucose 内):source = "calibrated"、calibration_anchor、calibrated(锚定输出,不再等于上传值)、delta_vs_model(对人群模型,不是对上传值)、offset_from_anchor_mmol_l、offset_limit_mmol_l、offset_limit_configured_mmol_l(1.2)、offset_limit_narrowed(仅收窄时出现)、offset_capped、offset_trusted、threshold_basis("anchor" / "output")、calibration_age_hours、stale、note;display_label_cn 改「用户上传实测值(锚点 + PPG 趋势,偏移限幅 1.2 mmol/L)」;响应顶层另有 sugar_cal_age_hours / sugar_cal_age_source(与 bp_cal_age_hours / bp_cal_age_source、age_used / age_source 并列,不在 blood 块内)。告警文案(realtime_warner._check_blood_sugar)三分岔:"calibrated" 分支带「(上传值锚点 + PPG 偏移)」与「判定基准:上传值 X mmol/L」。
/api/ppg/warn 端到端,files/862413079574788-血压估算不准.txt 尾部 50000 token):blood_sugar = 5.6 ⇒ 5.72、10.9 ⇒ 11.02、14.6 ⇒ 14.72(三者偏移均为 +0.12,与 gain = 0.25 的预期量级一致);14.6 + sugar_measured_hours_ago = 30 ⇒ stale = true、alert_eligible = false、warnings = [](值 14.72 与分层结论照常回显);未标定路径仍为 5.37;同一次请求里的 blood_pressure_1/2 = 120/76 零变化。
glucose.calibration.mode = "replace"(回到 V15.30~V15.35 整值回显,source = "user");anchor_threshold_when_untrusted = false 关保护;data_gate = "full" 恢复完整数据门。未触碰:glucose.base(4.9 / 0.010 / 40) / k_hr(0.03) / hr_delta_cap(30.0) / k_si(0.90) / si_reference(1.7) / si_delta_cap(2.0) / k_hrv(3.50) / hrv_drop_cap(0.60) / population_weight(0.30) / clamp(3.5~16.0) / bands(3.9 / 7.8 / 11.1) / severity.high(13.9) / confidence.* / crisis_confidence_gate(0.18),以及全部血压键——本版只新增锚定外壳与新鲜度门。
temp/_test_vitals_v1535.py 254 通过 / 0 失败(新增 t12_glucose_anchor / t13_glucose_stale / t14_glucose_replace_rollback / t15_glucose_anchor_threshold);temp/_test_vitals_v1530.py 311 通过 / 0 失败(新增 gl_cfg() 并把血糖的「原值回显」断言改为锚点断言——旧套件里两条过期的绿色断言,是改契约时的第二次踩坑);全量 temp/_run_regress.py 八套件失败行合计 0。完整证据链见《预警系统说明文档》§9.12.5 / §10.41。输出 = 上传值 + 偏移(PPG),本质上是「以用户值为原点,PPG 只贡献相对变化」。这在「PPG 只能测变化、不能测绝对值」的假设下成立,但本系统的 PPG 代理模型(血压 PTT、血糖 HR+SI+HRV)本身是有绝对标定的——只是存在系统性偏差(个体差异 + 探头位置 + 指围)。正确的做法不是「把模型废掉、只留变化量」,而是「用参考值测出系统偏差,然后把整个模型平移过去」。
blood_pressure_time / blood_sugar_time 时刻附近找最近的 PPG 记录,算出当时的模型值 m0;
gap = 参考值 − m0(系统偏差 = 用户真值 − 模型在该时刻的输出);
w × gap,钳位到 [ref − max_dev, ref + max_dev];
max_dev 是「声明可信度边界」,不是「压缩模型动态范围」——超出这个范围的值我们不敢信,但范围内的值是真实的模型输出。
20 mmHg(ISO 81060-2:2018 袖带设备验证要求 mean ≤ ±5 mmHg、SD ≤ 8 mmHg ⇒ 单次测量与参考的合理差异上限约 5 + 2×8 ≈ 21 mmHg);血糖 1.2 mmol/L(ISO 15197:2013 血糖监测系统 95% 结果在 ±0.83 mmol/L 或 ±15% 内)。PPG 代理的趋势声明不应超过医用设备的误差容忍度。
baseline_use_iso_allowance = true 时,实际偏离上限取 min(baseline_max_dev_mmol_l, ISO 15197:2013 允许误差)——低浓度区(< 5.55 mmol/L)更严(±0.83)、高浓度区(≥ 5.55 mmol/L)更宽(±15%),与行业标准对齐。
blood_pressure_time / blood_sugar_time(字符串,如 "2026-10-12 12:22:00");
blood.bp / blood.glucose 内):calibration_mode_effective(baseline / anchor / replace)、baseline_time、baseline_model_value、baseline_gap_mmHg / baseline_gap_mmol_l、baseline_offset_applied_mmHg / baseline_offset_applied_mmol_l、baseline_window_beats、baseline_distance_minutes、baseline_weight、baseline_max_dev_mmHg / baseline_max_dev_mmol_l;
bp_cal_time / sugar_cal_time。
bp.calibration.mode / glucose.calibration.mode(默认 "baseline")、baseline_max_dev_*、baseline_max_gap_hours(默认 24h)、baseline_window_hours(null = 与实时窗口同长)、baseline_weight(默认 1.0)。
mode = "anchor" 回到 V15.35/V15.36 锚点语义;mode = "replace" 回到 V15.30~V15.34 整值替换。三档平滑过渡。
baseline 的 gap = ref − model(t0) 是常数,会永久作用于所有后续输出——一次不准的读数(袖带测量本身有 ±8 mmHg 量级的 SD)会污染后面所有日子,且无法自动清除;同时模型真实趋势再强,偏移量也永远不变,等于「把模型冻结在参考时刻的偏差上」。
baseline 路径的输出没有围绕参考值收敛(t0 处不等于上传值),根因是 gap 里 model(t0) 与展示时刻 model(t) 的口径错位(model(t0) 取的是窗口均值,而 model(t) 取的是当刻值)。修复后 t0 处输出严格等于参考值。
mode = "exp_decay",默认):
offset₀ = (ref − model(t0)) × α(α = 0.5,只吸收一半偏差——一次读数本身带测量误差,不该全额归因给模型);
输出(t) = model(t) + offset₀ × (1 − λ)^Δh(λ_hourly = 0.05,Δh 单位小时);
t0 处输出 = ref + offset₀ × (1 − α) = ref − offset₀(不再是精确等于参考值,但仍在参考值附近);
ln 0.5 / ln 0.95 ≈ 13.51),约 45 h 后残权 < 10%,自动退场,无需人工清除;
offset₀ 取代旧值,越新的观测权重越大);
max_offset_sbp/dbp = 30/20 mmHg、血糖 max_offset_mmol_l = 2.0;max_age_hours 血压 72 / 血糖 24(超龄不再参与,回退纯模型)。
gap 做一次 1/2 的收缩估计(shrinkage),在只观测到一个样本时是方差最小的稳健选择。
baseline 的永久污染。
glucose.calibration 里 max_age_hours 写了两次(72 与 24),JSON 同键后者胜 ⇒ 实际生效 24,但两条 _comment 互相矛盾,且从代码侧看不出哪个生效。已去重并声明唯一语义(血糖 24 h / 血压 72 h)。
replace(V15.30~V15.34,输出 = 上传值)→ anchor(V15.35/36,ref + k·Δln,缓释双向有界)→ baseline(V15.37,model(t) + w·gap,随模型真实趋势走)→ exp_decay(V15.38,model(t) + offset₀·(1−λ)^Δh,逐渐吸收、逐渐遗忘)。
bp.calibration.mode / glucose.calibration.mode = "baseline" / "anchor" / "replace" 四档任选,平滑过渡。
temp/_test_vitals_v1535.py 258 通过 / 0 失败;temp/_test_vitals_v1530.py rc = 0;全量 temp/_run_regress.py 八套件失败行合计 0。完整证据链见《预警系统说明文档》§9.11.7 / §9.12.5 / §10.42。glucose.confound——饮酒窗口内停用三项偏移:用户口径「用户实测的血糖是 4.4,时间点晚上 22:12,但是我们在 22:41 给出的血糖值是 5.86」,以及「为啥不能区分啊,可以区分的啊,开车的话有定位可以帮助判断」。
HR↑ + SI↑ + HRV↓)与酒精的生理效应方向正相关——酒精 → 交感激活 → HR↑ / SI↑ / HRV↓,模型把酒精引起的交感激活读成了血糖升高。这不是数值精度问题,而是归因错误:三项代理在该窗口内测的不再是糖代谢。
glucose.confound 配置块(enabled / alcohol_attenuation / confound_min_minutes 40 / confound_min_segment_minutes 5 / confound_lookback_hours 8);调用方(realtime_warner)检测到饮酒混杂后传入 confound={...},模块停止对 vitals→glucose 做线性外推,并在响应中声明 confound / confound_attenuation / confound_note。
alcohol_attenuation = 1.0(全停用):① 急性饮酒在空腹态抑制肝糖异生,血糖倾向下降而非上升 ⇒ 现有正相关外推方向本身就错,负向修正同样没有依据;② 酒精直接改变 PPG 波形与外周灌注 ⇒ 该窗口 SI/HRV 已不是糖代谢代理,任何由它推得的数字都是「看起来像临床值」的伪精确;③ 该场景「宁可不说」比「说错方向」安全——血糖代理自身置信度上限只有 0.35,用它去报「偏高」属不可承受的假阳性风险。⚠️ 该开关不改变任何模型常数,只在特定混杂窗口内停用偏移项;退出窗口后自动恢复。
confound_min_minutes = 40,回答「本窗口酒精的系统效应是否仍在」(这是一次抑制决策,本窗口不产生任何用户可见结论,门槛宜低);而饮酒告警取 tiers[0] = 60,回答「现在是否仍处于酒后上升段」(会产出一条用户可见告警,门槛宜高)。两者故意不同值,且该布尔本身绝不产生任何告警。
alcohol 语义(偏移的机制是酒精的生理效应),而驾驶行为由定位证据排除(driving_warner.bouts → 上下文 intervals → 扣除重叠分钟)。一个做生理归因,一个做行为排除,二者不是同一件事;机制决定判据,证据决定排除。同时修掉 _drive_intervals_sec 在 available is False(定位记录缺失 / 不可解析)时仍按 intervals 扣除的缺陷——该缺陷会让「无定位数据」被当成「无驾驶时段」,方向上是漏扣,产生假阴性。
files/864249079484364-血糖问题.txt,投诉现场):45 岁未标定 22:41 → 4.95 mmol/L;带 22:12 的 4.4 参考(exp_decay)→ 4.75(均在 baseline_max_dev_mmol_l 1.2 内)。混杂证据强度区分度:total_min 真饮酒 138.4 / 171.1 min vs 负对照 29.5 / 32.9 min。⚠️ 配置注释中记录的实测否定:曾测过 confound_min_minutes = 24 h,已否决(会把次日早晨的正常血糖也一起停用;生理上酒精的系统效应不会持续 24 h)。
glucose.confound.alcohol_attenuation = 0.0(enabled = false 亦等效)。
temp/_test_confound_v1539.py 30 通过 / 0 失败(覆盖 6 种定位场景,含「无定位数据 ⇒ cause = unknown」);全量 temp/_run_regress.py 八套件失败行合计 0。 864249079484364-血糖问题.txt(temp/_verify_problemB_closure.py / _verify_problemB_age.py):① 22:00 → 22:05 相邻分钟最大 |Δ| 仅 +0.19(唯一台阶在 22:03,由窗口静息子集昼夜多数票 cond 由 day 翻转为 night 触发,属分钟级量化边界)⇒ 无 +0.90 阶跃伪影(V15.39 之前 age=65 时为 4.96 → 5.86);② 22:41 未校准 age45 = 4.95,age 35~90 全区间 4.90 ~ 5.40 ⇒ 「22:41 给 5.86」不可复现(需 age ≳ 118);③ 以 4.4 @ 22:12 为参考值三路径一致:24 h 上传段 → 4.74、2/4/8 h 段 → 4.75。⇒ 该现象是 V15.37 / V15.38「常偏置」时代(窗口静息子集昼夜翻转使参考量整体回落到人群池)的产物,已随本项酒精混杂抑制消失。详见《预警系统说明文档》§10.43.7(4)。Δln 改「比值(尺度不变量)口径」+ 血糖「现在」改短时滞窗:用户口径「在 2026-10-16 13:06,你计算的血压是 151/92,我用血压计测的是 138/87。造成这个差距的原因需要你分析一下,究竟是我们算法的问题,还是 PPG 信号本身的问题」,以及「血糖那里也有问题,餐前餐后没差别,这肯定不对」。
pw_front 与 pw_back 同步移动,而绝对量口径 Δln = ln(pw_ref_ms / pw_now_ms) 把这种漂移读成了血压变化。该窗口实测 pw_front −14.40 %(256.6 → 219.7 ms)、pw_back −8.17 %(412.4 → 378.7 ms),而二者比值只变 −6.78 %(0.6223 → 0.5801):绝对量给 dln = +0.1555 ⇒ +23.3 mmHg(输出 151/92,+13/+5),比值口径给 dln = +0.0702 ⇒ +10.5 mmHg(输出 139/85,+1/−2,同刻轴带 138/87)。跨 63 份历史文件复核逐小时变异系数(temp/_diag_crossfile2.py):CV(pw_front) 中位 0.458 vs CV(pw_front/pw_back) 中位 0.071(6.4 倍抑制),63/63(100 %)份文件里比值都更稳;跨文件中位 pw_front 107.8 → 297.6(2.76×)而比值只 0.470 → 0.791(1.68×)。
bp.ptt.use_ratio_baseline = true,把 dln 的输入量换成无量纲的 pw_front/pw_back(= 1/SI,与 SI 含同一信息、取对数后符号相反);基线只接受窗口外、同门、同条件的比值(_window_stats 新增 si_base_out,与 pw_base_out 逐行同池 —— 延续 V15.35「同门同尺」)。⚠️ 字段名契约:bp.pw_ref_ms / bp.pw_now_ms 恒为毫秒(比值口径也不改写),无量纲量另走新增的 bp.pw_ref_ratio / bp.pw_now_ratio,本次 dln 用哪一套由新增的 bp.dln_basis("absolute" / "ratio")标注。一键回退:bp.ptt.use_ratio_baseline = false。
hr_now / si_now / hrv_now 与参考侧同用 4 h 窗口 ⇒ 1~2 h 的餐后逃逸被中位稀释 ~4 倍;lookback 覆盖全记录时参考侧 n_pool = 0 ⇒ hr_now ≡ hr_ref,输出钉在 base + 0.010·(age−40)。新增 glucose.now_window_minutes = 45 / glucose.now_window_min_beats = 60:「现在」改取末 45 min,拍数不足时回落整窗并标 now_window_scope = "fallback_long";午餐 11:00 → 13:00 对比由 +0.00 恢复为 +0.5 ~ +1.9 mmol/L。新增响应字段 now_window_scope / now_window_minutes / now_window_beats。一键回退:glucose.now_window_minutes = 0。
temp/_test_vitals_v1530.py 312/0、temp/_test_vitals_v1535.py 264/0(含新增 6 条比值口径断言:对真实血压变化仍然敏感 + 对共模漂移归一)、temp/_test_surge_v1539.py 54/0、temp/_test_confound_v1539.py 30/0、temp/_run_regress.py 失败行合计 0。
pw_front 与 pw_back」就是共模漂移夹具,而不是血压变化夹具 —— 真实血压变化只移动 pw_front(pw_back 由反射路径几何决定)。夹具的物理假设会变成隐形的规格书。详见《预警系统说明文档》§10.44。0.30 + 0.20 = 0.50 < 0.55 的结构,使该项在数学上永不触发(实测 23/23 台 0 次)。此后每次调整权重都必须列出「可达触发组合表」。lookback_hours 发现同一份健康数据可得出 Δln = 0.0 与 0.625 两种矛盾结论)。confidence 不得复用症状预警的门(V15.30 提炼):估算型项的 confidence 语义是「估算可信度」而非「异常概率」(血糖上限仅 0.35),若沿用 0.50 的全局门会永远无法输出。因此必须:① 豁免全局门;② 各自设定 crisis_confidence_gate;③ 降级/受限必须显式告知调用方(本次为 blood.calibration_hint),否则会被当成功能故障。min_beats 早退之前先算好 vitals,以保证顶部三个字段始终存在)。si_ref ≡ si_now、hrv_ref ≡ hrv_now,三项代理里两项报废。因此:① 差值类指标必须显式校验基线索引与窗口无交集;② 基线的取用口径必须作为结果字段回传(*_ref_mode + baseline_n_beats),供回归测试「断言走的是哪条路」。population_weight = 0.30),因为个体绝对值受探头位置/指围影响极大;而不同条件或不同设备之间根本不可比的量(HRV),正确动作是整项停用而不是硬凑。这与第 7 条「不可比 ⇒ 回退」同源,但更强调「回退也要按可信度打折」。HR↑ 与 PW↓ 常被写成两项「独立证据」再线性相加,但全语料实测 corr(hr_dev, pw_dev) = +0.602(n=10821)、corr(hr_med, pw_med) = −0.492。相加的后果是双临界边缘命中恒定拿到同一个分数(V15.32 处为 0.5+0.08+0.07 = 0.650),使置信度门形同虚设。正确做法是对同一证据族取 max()(与日报侧 min_signal_families 的「按族计数」一致):相关量各自可弱、但至少一个要够强。pw_front 的 IQR 达中位的 2.1×,中位 × 0.72 实际落在 p32 —— 阈值本身就会被约 32% 的正常静息样本穿过,这不是「敏感」,是阈值形式选错了。离散度大的量应改用个人分位阈值(如 pw_percentile = 10),并为其设最小样本量(pw_pctl_min_beats)以便不足时回退。ma_act_1m 的中位数 < 5」,而触发窗口的真实形态是「一半静息 + 一半运动」的双峰分布 —— 中位数只有 2.1,门照样放行;实测该门在 10821 个窗口中命中 8631(80%),几乎不筛除。凡是「占比/成员资格」有意义的场景,都应该直接使用状态分类器的占比(静息/久坐占比 ≥ 0.80),而不是再拿一个连续量的人工阈值去近似它。注意前提:改动前后必须做「固定数据、只改规则」的影子普查(落盘原始量 → 离线对所有候选规则求值),才能得到逐窗口可比的修复前后对照,否则只能靠零散样本推测。HR↑ 与 PW↓ 会同时出现,是因为它们是同一个物理事件(佩戴松脱)的两次读数——光学通路断裂既压塌 PW,又制造假 R 峰抬高 HR。此时「两条都异常」不增加可信度,反而说明同一次伪迹被计了两遍。判据的可信度必须来自不同物理机制的证据族;同一机制内的多个量只能互为一致性检查,不能互为独立佐证。age 只影响 HRmax 与血压人群参考锚点,不参与任何检查器的触发门槛,缺失时的正确行为是回落默认值照常分析(本次即回落 35),并把「是否回落」回显给调用方(age_used / age_source),而不是返回 400 ——因为 400 惩罚的是终端用户(整次预警消失、界面上什么都看不到),而送错数据的是上游网关。实测量级:最近 500 条真实请求中 17 条(3.4%) 带 age=0,全部被旧守卫拒绝,即每天都有一批用户在「没有任何预警」的界面上。同类缺陷还包括:字符串年龄 "35" 被判非法、age=200 无上限(max_hr = 208 − 0.7×200 = 68,相对门限被压得过低 ⇒ 静息判定崩塌)。完整证据链与复现步骤见《预警系统说明文档》§10.37。bool(V15.33 提炼):isinstance(True, int) is True,旧守卫因此把 age=true 静默算成 age=1(报告基于「1 岁」生成而不报错)。凡是「接受数字」的入口都必须先写 if isinstance(x, bool): 视为非法。age=0 与 17 条 400 一一对应,才把「上游送坏值 → 服务端丢结果 → 用户看不到预警」连成完整因果链;只有单侧日志时,这类缺陷通常会被误判为「上游没送数据」或「功能未上线」。plausible = true、confidence = 0.6,却与袖带差 −30 / −26 mmHg。plausible 只证明「没有超出自己设的量程」,完全不证明接近真值。任何「可信度/合理性」标记都必须与外部真值做过对照才有意义,否则它只是在复述自己的假设。pw_front 逐小时 CV ≈ 0.46,而 pw_front/pw_back 只有 0.07(V15.34 当时称 61/61 份文件成立;V15.40 以 63 份历史文件重测:CV(绝对值) 中位 0.458 vs CV(比值) 中位 0.071,63/63 份成立,逐小时极差中位 1.36× vs 1.00×)⇒ 绝对值主要测的是采集尺度,形态信息在比值里。同一 4 h 窗口内 pw_front +16.6% 与 pw_back +18.8% 同步升高就是这类漂移的直接指纹。指标选型应优先选对采集增益不敏感的形式(比值、相对个人分位),并在文档里写明「为什么这个量跨时段可比」。measurement = false、display_label_cn、grade_cn_key = "uncalibrated")把「这不是测量值」传给调用方。上线这类标注前必须验证告警链路零影响(本次 crisis 数学上不可达、alert_eligible 恒 false、实测 warnings = [])。max_plausible_ln 从 0.25 收到 0.10 看上去更「保守」,实测通过率 71.7% → 35.8%,≈64% 窗口会退回人群参考 —— 而 reference_bp(35,'男') = 122/77 本身就被分级为「正常血压」,所以收紧只是换来一个更稳定、更系统的假阴性。「更保守的阈值」不等于「更安全的结果」,必须按输出结论而不是按参数方向评估。measurement 只在血压与血糖都标定时为 true(宁可少声明)。因此只给血压校准时 bp_measurement = true 而顶层仍为 false;若前端拿顶层文案去描述某一个指标,就会把「已标定的实测血压」标成「非测量值」。逐项数值旁边必须用逐项文案(bp.display_label_cn / glucose.display_label_cn)。normal)与实现的真实输出(high_normal)不符而报错。正确做法是:① 文档/注释中的数字由脚本从真实管线算出;② 测试期望调用被实现的那个纯函数(grade_bp_cn(...) / grade_glucose(...))再比较,而不是另抄一份表。k_sbp = 150 是从 ΔP ≈ 100·Δln 反推的,而该式的前提是主动脉脉搏波传导时间;本项目的 pw_front 是手指 PPG 上升支,混入血管张力、体温、接触压力、探头位置。k 的作用本就是吸收这些未建模干扰,绝不能继续取「理论值」。判断依据只能来自实测离散度:pw_front 逐小时 CV ≈ 0.46 vs pw_front/pw_back 0.07 ⇒ 绝对值主要测的是采集尺度,故把 k 从 150/75 降到 50/30(约 1/3)并加 ±15 mmHg 限幅。校准点不是「把模型锁死」,而是「把模型钉在一个已知坐标上」——钉死(整值替换)会让输入失去意义,正确做法是锚点 + 有界偏移。true(已过期)/ false(新鲜)/ null(未提供时间戳,未知)。若把「未知」塌缩成 false,则「未送时间戳」会被当成「刚测过」,正好把最不可信的输入放行;若塌缩成 true,则会静默关闭所有 Excel 式旧客户端的告警。三态还必须配一个显式策略开关(require_measured_at_for_crisis)来声明「未知时向哪边倒」,而不是把这个决策藏在默认值里。Δln = ln(pw_ref / pw_now) 只有在两者取自同一条件时才是「血压变化」。旧实现里 pw_ref 是「全记录日间静息中位」而 pw_now 在静息样本不足时退化为「含运动段的 pw_valid」——同一份文件只把 lookback_hours 从 12h 改成 4h,Δln 就从 0 跳到 −0.836(2.3 倍 pw 比,非生理)。这与 V15.31 的「基线不得与窗口相交」是同一族缺陷的两面:一个防「自己减自己」,一个防「不同尺子相减」。凡输出「差值/变化」的模块,都必须把两端的取值口径作为结果字段回传(本次为 ref_pw_src / pw_base_out_ms / n_base_out_beats),以便回归测试断言走的是哪条路。baseline)会把一次不准的读数永久作用于后面所有输出,且无法自动清除。任何「用户提供的参考值」型功能都必须回答两个问题:① 它怎么衰减(本次 λ_hourly 0.05,半衰期 13.5 h);② 它怎么被取代(新校准覆盖旧 offset₀)。另:一次性观测不应全额归因给模型(α = 0.5 收缩估计)——用户设备也有测量误差。此外,「同一时刻的模型值」必须用同一个取数口径:V15.37b 的缺陷就是 model(t0) 取窗口均值、model(t) 取当刻值,导致 t0 处不收敛。tail_slack_minutes 的初衷是「防次日回溯」,但它被施加在累计证据上,等价于要求「最近 45 分钟内就要凑齐 90/150/240 分钟」——后者结构性不可达。正确分解:防回溯用「请求时刻门」(现在是不是 18–23 时),证据用「累计量」(过去这段时间累计多少分钟)。凡看到「滑动窗 / 时效 / 新鲜度」类门限,都要问一句:它想约束的是「什么时候」还是「有多少」?两者混在一道门里,就会把慢积累的信号全部静默掉。另:分段长度、门槛数值等参数变更前,必须先做「固定数据、只扫该参数」的证伪测试(V15.39 即扫 min_length = 1800/900/450/300,结果逐字节相同 ⇒ 参数无关)。severity + 不确定措辞(本次 hint:40 min 播报「疑似饮酒信号 · 证据累积中」),而不是把告警门槛下移。理由是可量化的余量红线:提示门 40 min 对最紧负对照(864249079484364-未饮酒 累计上限 29.9 min)只剩 10.1 min 余量;若再加一档 30 min,该负例距命中只剩 0.1 min,会把「确实没饮酒」立刻打成正例。分层只改变「说什么」(措辞/等级),不改变「何时说」(门槛),因此可以在零特异度代价下把首命中提前到 19:20 / 20:00 / 21:00;而任何下调提示或立案门槛的改动,都必须先证明全部负例余量 ≥ 10 分钟。配套两条规格性核查:① 立案档必须做可达性核查(tiers[3]=240 相对实测正例峰值 153.3 min 属预留上限档,现有语料从未触发——保留原值以免改变已验证的 0.62/0.70 映射,但必须在文档中标为「可达性未验证」);② 若某条负例的唯一拦截者是单一门(本次日间对照门:驾驶负例原始累计 203.9 min 高于真阳性峰值 153.3),则该门是单点依赖,必须显式标注、并禁止「放宽/关闭/改口径」这类看似无害的调整。alcohol 语义(它描述的是生理机制:酒精 → 交感激活 → 代理量同向偏移);定位证据负责行为排除(把「可证驾驶/移动」的时段区间扣除)。机制决定判据,证据决定排除——若把两者混成一个「开车或饮酒」布尔,则两个原因都不能被单独验证,也无法分别回退。同时:混杂判定的门槛应与告警门槛故意不同值(本次 40 min vs 60 min)——抑制决策只需确认「效应是否仍在」(门槛宜低,且本窗口不产生任何用户可见结论),告警则要求「现在是否仍处于上升段」(门槛宜高);合并为一个值会把其中一侧推到错的位置。「法于阴阳,和于术数,食饮有节,起居有常,不妄作劳。」——《黄帝内经·上古天真论》
中医正气评估系统 · 版本变更记录(白皮书附录)
本页为主白皮书 whitebook.html 的拆分页,
与主白皮书同步随代码变更更新