# MeMind Health 架构与通道设计 日期:2026-09-02 状态:架构设计稿。**未改动业务代码、未碰生产、未读写用户数据。** 适用范围:MeMind H5、微信服务号、MindSpace、Page Data、Memory V2、Agent Gateway --- ## 0. 一句话定位 MeMind Health 不是「另一个健康 App」,而是: > **持续理解本人健康基线,并在长期变化中发现「偏离」的个人健康 Agent。** 核心数据链路: ```text 本人 → 录入/设备/报告 → Health Timeline → Personal Baseline → 偏离事件 → Agent 推理 → 行动建议 → 医疗资料沉淀 ``` 与传统健康产品的根本差异: | 传统健康 App | MeMind Health | |---|---| | `140/90 → 标红` | 该用户 90 天基线 `119–126/70–77`,近 14 天持续漂移 +8% | | 数据看板 | 「最近和以前不一样」的事件叙事 | | 用户自己盯曲线 | 只在偏离时介入,无事静默 | | 单次录入 | 时间序列 + 关联假设 + 长期学习 | **MVP 验证命题**: > MeMind 能不能比我自己更早发现:我最近和以前不太一样? 若成立,血压计只是传感器;**Personal Baseline + Health Timeline + Event Engine** 才是产品。 --- ## 1. 已确认的产品决策(不可随意推翻) 以下决策已确认,后续实现必须遵守。变更需重新评审本文档。 ### D1 单人模型:只监测账号本人 - 监测对象 **默认且仅为账号本人**,`HealthProfile` 与 `user_id` 严格 1:1。 - **不做** `FamilyGroup` / 多 `Subject` / 子女代录代管。 - 理由:录入标准、测量情境、回顾深度必须统一,否则 Personal Baseline 失真(「参差不齐」)。 - 未来若做「关注家人」,只能是 **本人主动授权只读分享摘要**(P4),录入权永远在本人。 ### D2 他人健康数据走辅轨,不进引擎 用户说「我妈上周体检血压 150/95」这类内容: - 写入 Memory V2(`fact` / `experience`)或普通备忘,Agent 可对话召回。 - **禁止**进入 `HealthObservation`、Baseline、Trend、Event Engine。 - Agent 话术须明确告知:「这不会进入你的健康分析」。 ### D3 存储走 Page Data(P0 决策) - 结构化健康数值存入 **用户隔离的 PostgreSQL schema**(Page Data 体系,`private_data_execute` 建表 + `private_data_register_dataset` 注册)。 - 复用现成的用户级 schema 隔离、dataset 授权、操作日志、导出、软删除恢复能力。 - P1 若基线/趋势聚合出现性能瓶颈,再评估迁移到专用表;迁移方案见 §12.3。 ### D4 服务号允许规则命中 + 强制确认 - 自由文本先走**确定性规则**(正则/关键词);命中即进入对应流程。 - 规则未命中 → 回交互式菜单,**不让 LLM 硬猜**。 - 所有由识别/解析得到的数值,**一律经用户确认后才落库**。 ### D5 健康会话完全独立 - 健康会话与普通聊天会话**完全隔离**(独立 session、独立工具白名单)。 - 普通聊天中识别到健康意图,**只做引导跳转**,给入口链接/按钮。 - **绝不在普通聊天里直接落库健康数据。** --- ## 2. 四条不可违背的工程原则 1. **健康通道是「锁定态会话」**,不是「聊天里聊健康」。进入后 skill 被 pin,意图分类器不再参与判断。 2. **拍照/上传不直接落库**,一律「抽取 → 确认 → 提交」三段式。未确认数据不进基线引擎。 3. **意图不明确时不猜,直接给选项。** 菜单式交互在健康场景比自然语言理解更精准、更快、更可审计。 4. **健康区数据默认加密,无口令不允许发布。** 服务端硬阻断,不靠前端自觉。 配套的分层职责: ```text 规则判断事实 → Rule Engine(确定性、可审计) 算法发现趋势 → Trend Engine(统计、可复现) LLM 负责解释 → Agent(不可作为 primary detector) ``` **禁止**的反模式:`血压 128/85 → LLM → "你觉得危险吗?"` --- ## 3. 系统总览 ```text MeMind Health(本人) │ ┌───────────────────┼───────────────────┐ │ │ │ Health Connector Health Timeline Medical Documents (P2: Apple/蓝牙) (结构化时序) (报告 OCR + 归档) │ │ │ └───────────────────┼───────────────────┘ ↓ Personal Baseline Engine ↓ ┌───────────────────┴───────────────────┐ ↓ ↓ Rule Engine Trend Engine (医学阈值 / 漏测 / 药物变化) (个体偏移 / 变异性 / 联动) │ │ └───────────────────┬───────────────────┘ ↓ Event Engine (Health Event Cluster) ↓ MeMind Agent(解释 + 追问 + 建议) ↓ 提醒 / 复测 / 就医建议 / 就诊摘要 / Memory 沉淀 ``` ### 3.1 与现有 MeMind 能力的映射 | 设计层 | 复用的现有能力 | 关键文件 | |---|---|---| | 通道锁定不漂移 | `selectedChatSkill` 短路 + `enforceSelectedSkillRuntime` | `chat-intent-router.mjs`、`agent-run-routes.mjs` | | 结构化存储 | Page Data 用户隔离 PG schema | `page-data-service.mjs`、`mindspace-userdata-postgres.mjs` | | 健康区与发布策略 | `h5_space_categories`(`visibility/ai_access/publish_policy`) | `mindspace.mjs`(`SYSTEM_CATEGORIES`) | | 加密发布与口令 | 发布 `access_mode` + `password_hash` | `mindspace-pages.mjs`、`page-data-public-service.mjs` | | 私有页不被收录 | `INDEXABLE_ACCESS_MODE = 'public'`,非 public 一律 noindex | `mindspace-index-policy.mjs` | | 报告原文件存储 | MindSpace 资产(`h5_assets`)+ 分区归属 | `mindspace.mjs` | | 服务号图文/语音接入 | `msgType` 分流 + `eventKey` 菜单事件 | `wechat-mp.mjs` | | 图片轮次隔离 | 带图轮次摘掉 `read_image`、污染触发会话轮换 | `chat-image-turn-scope.mjs` | | 用药/复诊提醒 | `h5_schedule_items` + `schedule-assistant` | `schedule-service.mjs` | | 长期关联假设 | Memory V2 candidate → promote | `memory-v2.mjs`、`memory-v2-lifecycle.mjs` | | 灰度 / fail-open | 现有 canary + fail-open 模式 | `memory-v2-runtime.mjs` 等 | ### 3.2 需要新增的模块(隔离在健康域内) ```text health-channel-state.mjs 通道状态机(纯函数,可单测) health-observation-service.mjs Observation 读写 + 合理性校验 + 幂等去重 health-extraction.mjs 图片/报告结构化抽取 + confidence 归一 health-baseline-engine.mjs 滚动基线计算(P1) health-event-engine.mjs Rule + Trend + Event 聚合(P1) health-document-service.mjs 报告归档 + 指标映射 health-share-service.mjs 就诊摘要快照与令牌(P3) skills/health-assistant/ skill 定义 src/pages/health/* H5 健康入口页面 ``` --- ## 4. 入口设计:H5 主入口 + 服务号轻入口 两个入口共用同一个 Health Channel 内核,只是壳不同。 ```text ┌──────────────────────────┐ H5 首页 ──────→│ 健康助手入口(卡片 / Tab) │ └────────────┬─────────────┘ │ 服务号菜单 / 关键词 ─────────→│ 「健康助手」 │ ↓ ┌────────────────────────────┐ │ Health Channel 内核 │ │ (skill pin + 状态机) │ └────────────┬───────────────┘ ↓ ┌────────────┬────────┴───────┬────────────┐ ↓ ↓ ↓ ↓ 健康录入 健康评估 报告归档 健康档案 (值/照片) (基线/趋势) (OCR→档案) (MindSpace 健康区) ``` ### 4.1 H5 主入口(功能完整,主战场) H5 是主入口,因为需要结构化表单、数值键盘、Timeline 图表、可编辑确认卡、档案浏览。 建议做成**独立页面**而非普通聊天里的一个 skill 按钮: ```text /health 健康首页(今日录入状态 + 最近趋势 + 待确认项) /health/chat 健康助手会话(锁定通道) /health/records Health Timeline /health/documents 报告档案 /health/profile 基线 / 既往史 / 长期用药 ``` `/health/chat` 使用独立会话空间,不与普通聊天会话混用(见 §7)。 ### 4.2 服务号轻入口(随手录入,主打拍照/语音) 服务号的价值是**随手**:量完血压直接拍照发送。 进入方式两条,均有现成钩子: - **自定义菜单点击** → `msgType === 'event'` + `eventKey === 'MEMIND_HEALTH'` - **关键词命中** → 文本命中「健康助手」「健康」「录血压」等 进入后回复固定菜单(服务号仅文本/图文,用数字选择): ```text 已进入【健康助手】专用通道 本通道只处理健康相关内容,数据自动归入你的健康档案。 请回复数字: 1 健康录入(血压/心率/体重/血氧/体温/睡眠/症状) 2 上传报告(直接发送照片即可) 3 健康评估(看看最近和平时比有什么变化) 4 查看健康档案 0 退出健康通道 ``` 关键约束: - 进入后**所有消息(含图片)只走健康链路**,直到用户回复 `0` 或超时。 - 建议 **30 分钟无交互自动退出**,退出时必须明确告知,避免用户以为还在通道内。 - 通道状态与现有 canary/stable 会话钉选逻辑并存,**不改动现有分流**。 --- ## 5. 防漂移三层保障 这是本设计的核心,对应用户最关心的「不允许漂移,要精准」。 ### 5.1 第一层:Skill Pin(复用现有短路) 现有 `chat-intent-router.mjs` 已具备完整短路能力: - `readSelectedChatSkillId(message)` 读取 `metadata.memindRun.selectedChatSkill` - `buildSkillSelectionClassification()` 返回 `route: agent_orchestration`、`confidence: 1`、`reason: '用户已选择 skill'` - 结果:**LLM 意图分类器根本不会被调用** 健康通道发消息时携带: ```json { "metadata": { "memindRun": { "selectedChatSkill": "health-assistant", "healthChannel": { "sessionKind": "health", "step": "await_value", "pendingDraftId": "drf_...", "metricSet": "blood_pressure" } } } } ``` 服务号侧从会话状态读出「当前在健康通道」,注入相同 metadata。 `agent-run-routes.mjs` 的 `enforceSelectedSkillRuntime` 进一步固定 `toolMode` / `taskType` / `requiredExecutor`,保证工具集不越界。 **这一层解决「会不会被路由到别的 skill」。** ### 5.2 第二层:Channel 状态机(解决「这句话是什么意思」) Pin 住 skill 不够 —— 用户说「137」,是血压还是体重?必须有显式状态机,不靠 LLM 猜。 ```text IDLE │ 选择「健康录入」 ↓ AWAIT_METRIC_TYPE ──→ 展示可录入项(血压/心率/体重/血氧/体温/睡眠/症状/用药) │ 选择「血压」 ↓ AWAIT_CONTEXT ──────→ 晨起 / 睡前 / 服药后 / 其他(可跳过取默认) │ ↓ AWAIT_VALUE ────────→ 期望数值;接受手输 / 拍照 / 语音 │ ↓ CONFIRM ────────────→ 结构化确认卡(可编辑) │ 确认 ↓ COMMITTED ──────────→ 写入 Observation + 返回 Timeline 即时反馈 ``` 完整状态转移表: | 当前状态 | 期望输入 | 事件 | 下一状态 | |---|---|---|---| | `IDLE` | 菜单选择 / 规则命中文本 / 图片 | `select_action` | 对应流程首状态 | | `IDLE` | 无法高置信解析 | `ambiguous` | `AWAIT_ACTION_CHOICE` | | `AWAIT_ACTION_CHOICE` | 1/2/3/4 | `choose` | 对应流程首状态 | | `AWAIT_METRIC_TYPE` | 指标名 | `pick_metric` | `AWAIT_CONTEXT` | | `AWAIT_CONTEXT` | 情境 / 跳过 | `pick_context` | `AWAIT_VALUE` | | `AWAIT_VALUE` | 数值 / 图片 / 语音 | `submit_value` | `CONFIRM` | | `AWAIT_VALUE` | 无关输入 | `unexpected` | `AWAIT_VALUE`(回提示,不猜) | | `CONFIRM` | 确认 | `commit` | `COMMITTED` | | `CONFIRM` | 修改 | `revise` | `AWAIT_VALUE` | | `CONFIRM` | 放弃 | `discard` | `IDLE` | | 任意 | `0` / 「退出」 | `exit` | 通道退出 | | 任意 | 30 分钟无交互 | `timeout` | 通道退出(告知用户) | 状态机每一步都有**明确的期望输入类型**。用户在 `AWAIT_VALUE` 说「137/82」语义完全确定,不存在歧义。 若在某状态收到完全无关输入,通道**不猜测**,回: ```text 当前正在录入「血压」。 - 直接发送数值,如 137/82 - 或拍照上传血压计读数 - 回复「取消」放弃本次录入 - 回复「0」退出健康通道 ``` 状态机应实现为**纯函数**(`health-channel-state.mjs`),输入 `(state, event)` 输出 `(nextState, actions)`,便于完整单测覆盖。 ### 5.3 第三层:意图不明时强制选择(D4) `IDLE` 状态自由发言的判定顺序,**确定性优先**: ```text 1. 确定性规则匹配(正则 / 关键词)→ 命中即执行,不调 LLM 2. 规则未命中 → LLM 做候选动作分类,要求返回 top-2 + confidence 3. confidence < 0.75 或 top-2 差距 < 0.15 → 不执行,返回选择卡 4. 用户选择后 → 进入状态机,此后无歧义 ``` 选择卡内容: ```text 我不太确定你想做什么,请选择: 1 记录健康数据 2 查看健康评估 3 上传 / 归档报告 4 查看健康档案 ``` 规则库设计(示例,需持续维护并单测): | 意图 | 规则示例 | 落地动作 | |---|---|---| | 血压录入 | `(\d{2,3})\s*[//]\s*(\d{2,3})`,或含「血压」+ 两个数 | `metric_set=blood_pressure` → `CONFIRM` | | 体重录入 | 含「体重」+ `(\d{2,3}(\.\d)?)` | `metric_set=weight` | | 心率录入 | 含「心率/脉搏」+ `(\d{2,3})` | `metric_set=heart_rate` | | 血氧 | 含「血氧/SpO2」+ `(\d{2,3})` | `metric_set=spo2` | | 体温 | 含「体温/发烧」+ `(3\d(\.\d)?)` | `metric_set=temperature` | | 睡眠 | 含「睡了」+ 时长表达 | `metric_set=sleep` | | 症状 | 含「头晕/胸闷/乏力/心慌/疼」 | `metric_set=symptom` | | 评估 | 「最近怎么样」「帮我看看」「有没有异常」 | 评估流 | | 档案 | 「我的档案」「历史记录」「报告」 | 档案流 | **关键原则**:宁可多问一次,也不要错记一条。错误的 Observation 会污染 Personal Baseline,代价远高于多一次交互。 歧义值必须强制确认,例如「体重 137」显然异常但格式合法 → 走合理性校验(§6.2)标记 `suspect`。 --- ## 6. 拍照精准记录设计 最易漂移的环节,单独设计。 ### 6.1 先分清两类图片 | 类型 | 内容 | 归属 | |---|---|---| | **设备读数照** | 血压计、体重秤、血氧仪、体温计屏幕 | → `HealthObservation`(数值) | | **医疗报告** | 体检报告、化验单、影像报告、处方 | → `HealthDocument`(档案)+ 抽取指标 | 判定策略分层,成本递增: 1. **用户已在 `AWAIT_VALUE`**(先选「血压」再拍照)→ **类型已确定**,直接按血压解析。**这是最精准路径,产品应主推。** 2. **`IDLE` 直接发图** → 轻量视觉判断「设备屏幕 vs 文档」,然后**回确认**:「这看起来是血压计读数,要记录为血压吗?」 3. **判断不确定** → 直接给选项:「这张图是:1 设备读数 2 体检/化验报告 3 其他」 产品引导上把「先选类型再拍」做成主路径: ```text 健康录入 → 选「血压」 → 「请输入数值或拍照上传」 → 拍照 ``` 先验极强,几乎不可能错。 ### 6.2 设备读数:抽取 → 确认 → 提交 **绝不允许「拍照即入库」。** ```text 图片 ↓ ① 图片指纹(内容 hash)去重 ← 防止同一照片重复录入 ↓ ② 结构化抽取(视觉模型 + 强约束 schema) ↓ ③ 合理性校验(Rule Engine,确定性) ↓ ④ 生成 ObservationDraft(草稿态,不进基线) ↓ ⑤ 用户确认卡(H5 可编辑表单;服务号文本 + 回复确认) ↓ ⑥ 确认后 → HealthObservation(正式)+ 原图存档 + 抽取留痕 ``` 第 ② 步要求模型输出严格 schema: ```json { "metric_set": "blood_pressure", "systolic": 137, "diastolic": 82, "pulse": 72, "device_time": "2026-09-02 07:20", "confidence": { "systolic": 0.97, "diastolic": 0.95, "pulse": 0.88 }, "unreadable": [] } ``` 第 ③ 步校验规则(确定性,不经 LLM): | 校验 | 规则 | 不通过处理 | |---|---|---| | 量程 | 收缩压 60–260;舒张压 40–160;脉搏 30–200;SpO₂ 50–100;体温 33–43 | 标记 `unreadable`,要求重拍或手输 | | 逻辑 | 收缩压 > 舒张压 | 提示可能识别颠倒,要求确认 | | 基线偏离 | 与个人基线偏离 > 50% | 标记 `suspect`,**强制**人工确认 | | 置信度 | 任一字段 `confidence < 0.9` | 该字段标记待确认,高亮展示 | 第 ⑥ 步的**留痕**至关重要:`raw_payload` 保留原始抽取 JSON、图片引用、用户是否修改过。将来审计「这条 137 是怎么来的」可完整回溯,也是 Event Engine evidence chain 的基础。 ### 6.3 时间戳:隐藏的漂移源 「什么时候测的」与「值是多少」同样重要,且极易出错。优先级: ```text 1. 设备屏幕显示时间(OCR 得到) ← 最准 2. 图片 EXIF 拍摄时间 ← 次准 3. 用户手动指定 ← 兜底 4. 消息到达时间 ← 最后手段,必须提示用户 ``` 若只能用第 4 种,确认卡必须明确写:「测量时间按当前时间记录,如非刚测请修改」。 **必须支持补录改时间** —— 否则 Timeline 全错,基线随之失真。 ### 6.4 报告类:归档与指标抽取分离 ```text 报告图片 / PDF ↓ ① 归档(必做,成功率高) → HealthDocument:类型、机构、报告日期、原文件、OCR 全文 → 原文件落入 MindSpace 健康分区资产 ↓ ② 指标抽取(尽力而为,允许失败) → 化验项映射:血脂 / 血糖 / 肝功 / 肾功 / 血常规 ... → 每项带 confidence + 参考范围 → 高置信项 → ObservationDraft(待确认) → 低置信项 → 仅存 OCR 文本,不入结构化 ``` **归档必须成功,抽取允许失败。** 即使指标识别不出,用户报告也已安全存进档案随时可查 —— 这是产品底线价值。 ### 6.5 与现有视觉守卫的兼容(硬约束) `docs/regression-guards/vision-turn-read-image-isolation.md` 的守卫**必须继续生效**: - 带图轮次必须摘掉 `read_image` - 工具图片污染必须触发会话轮换 健康通道的图片处理**必须走同一套** `chat-image-turn-scope.mjs`,**禁止**另开绕过守卫的路径。健康通道只是在其**之上**叠加「结构化 schema 约束 + 确认闸门」,底层图片轮次隔离不变。 --- ## 7. 会话隔离(D5) 「不影响现有功能」的关键。 ```text 普通会话 健康会话 ───────── ───────── sessionKind: 'chat' sessionKind: 'health' 完整 skill 集 仅 health-assistant + 白名单工具 Memory V2 全量 resolve 健康上下文 + 有限 profile 可发布任意分区页面 仅健康区(强制加密) ``` 具体隔离点: 1. **会话独立**:健康会话使用独立 `session_id` 命名空间。普通聊天历史不注入健康会话;健康数据不会出现在普通聊天上下文。 2. **工具白名单**:健康会话内由 `enforceSelectedSkillRuntime` 限定工具集 —— 仅允许健康数据读写、文档归档、健康区页面生成。**不允许** `edit_file` 写非健康区路径,**不允许**公开发布。 3. **Memory 分轨**:Observation 走独立数据面;仅「本人对睡眠敏感」这类**关联假设**晋升 Memory V2,带 `health_correlation` 标记。他人健康数据只进备忘(D2)。 4. **引导跳转**:普通聊天识别到健康意图时,**只回引导**,不落库: ```text 你想记录健康数据吗?健康数据需要在健康助手通道里录入, 这样才能保证记录准确并纳入你的健康基线分析。 [进入健康助手 →] ``` 5. **服务号状态**:健康通道状态存于服务号会话状态,与现有 canary/stable 钉选并存。 --- ## 8. 数据模型(Page Data 落地,D3) ### 8.1 分层存储决策 | 数据 | 存储 | 理由 | |---|---|---| | 健康数值(Observation) | Page Data 用户隔离 PG schema | 结构化、可聚合、已有隔离与审计 | | 报告原文件(图片/PDF) | MindSpace 健康分区资产(`h5_assets`) | Page Data 不适合大文件 | | 报告元数据 + OCR 全文 | Page Data(PG) | 需检索与关联 | | 基线 / 事件 | Page Data(PG),P1 | 由引擎写入 | | 关联假设 | Memory V2 | 需对话召回 | | 提醒 / 复诊 | `h5_schedule_items` | 复用现有提醒链路 | **注意**:报告文件与其元数据分离存储,通过 `asset_id` 关联。 ### 8.2 表设计(PostgreSQL,用户隔离 schema) ```sql -- 健康档案(本人,1 行) CREATE TABLE IF NOT EXISTS health_profile ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, birth_year INT NULL, sex TEXT NULL, height_cm NUMERIC(5,1) NULL, chronic_conditions JSONB NOT NULL DEFAULT '[]'::jsonb, long_term_medications JSONB NOT NULL DEFAULT '[]'::jsonb, notes TEXT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 原子观测(所有体征 / 行为 / 主观状态的唯一写入格式) CREATE TABLE IF NOT EXISTS health_observations ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, observed_at TIMESTAMPTZ NOT NULL, -- 测量时刻,非录入时刻 metric_type TEXT NOT NULL, -- bp_systolic | bp_diastolic | hr | spo2 | weight | temperature | sleep_minutes | symptom | ... value_num NUMERIC(10,2) NULL, value_text TEXT NULL, -- 症状等枚举/文本 unit TEXT NULL, context TEXT NULL, -- morning | evening | post_medication | fasting | other source TEXT NOT NULL, -- manual | photo | voice | ocr_report | apple_health | ble_device source_ref TEXT NULL, -- 图片 hash / asset_id / 设备标识 time_source TEXT NOT NULL, -- device_screen | exif | user_specified | message_arrival confidence NUMERIC(4,3) NULL, quality_flag TEXT NULL, -- ok | suspect | user_corrected raw_payload JSONB NULL, -- 原始抽取结果 + 审计留痕 created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMPTZ NULL ); CREATE INDEX IF NOT EXISTS idx_health_obs_metric_time ON health_observations (metric_type, observed_at DESC); -- 幂等:同一图片 + 同一指标 + 同一测量时刻不重复入库 CREATE UNIQUE INDEX IF NOT EXISTS uq_health_obs_dedup ON health_observations (metric_type, observed_at, source_ref) WHERE source_ref IS NOT NULL AND deleted_at IS NULL; -- 待确认草稿(不进基线引擎) CREATE TABLE IF NOT EXISTS health_observation_drafts ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, draft_key TEXT NOT NULL UNIQUE, metric_set TEXT NOT NULL, -- blood_pressure | weight | spo2 | ... extracted JSONB NOT NULL, -- 抽取结果 + confidence validation JSONB NOT NULL, -- 校验结果 + 标记 status TEXT NOT NULL DEFAULT 'pending', -- pending | committed | discarded | expired channel TEXT NOT NULL, -- h5 | wechat created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMPTZ NOT NULL, committed_at TIMESTAMPTZ NULL ); -- 医疗资料档案 CREATE TABLE IF NOT EXISTS health_documents ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, doc_type TEXT NOT NULL, -- checkup | lab | imaging | prescription | discharge | other report_date DATE NULL, institution TEXT NULL, title TEXT NOT NULL, asset_id TEXT NULL, -- MindSpace 健康分区资产引用(原文件) ocr_text TEXT NULL, extracted_metrics JSONB NOT NULL DEFAULT '[]'::jsonb, extraction_status TEXT NOT NULL DEFAULT 'pending', -- pending | partial | done | failed created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMPTZ NULL ); -- 个人基线(P1,引擎写入) -- 注意:基线必须按 context 分层(见 §15 O3)。晨间与晚间血压生理上不同, -- 混算基线会同时掩盖晨峰异常并制造假偏离。 CREATE TABLE IF NOT EXISTS health_baselines ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, metric_type TEXT NOT NULL, context TEXT NOT NULL, -- morning | evening | any(不分层指标用 any) window_days INT NOT NULL, -- 7 | 30 | 90 computed_at TIMESTAMPTZ NOT NULL, sample_count INT NOT NULL, mean_val NUMERIC(10,2) NULL, std_val NUMERIC(10,2) NULL, p25_val NUMERIC(10,2) NULL, p75_val NUMERIC(10,2) NULL, typical_low NUMERIC(10,2) NULL, typical_high NUMERIC(10,2) NULL, maturity TEXT NOT NULL, -- cold | weak | stable UNIQUE (metric_type, context, window_days) ); -- 健康事件(P1) CREATE TABLE IF NOT EXISTS health_events ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, event_type TEXT NOT NULL, -- threshold_breach | baseline_drift | missed_measurement | symptom_cluster | correlation_hypothesis severity TEXT NOT NULL, -- info | watch | alert detected_at TIMESTAMPTZ NOT NULL, rule_id TEXT NULL, -- 可审计:哪条规则触发 metrics_involved JSONB NOT NULL DEFAULT '[]'::jsonb, evidence_observation_ids JSONB NOT NULL DEFAULT '[]'::jsonb, agent_summary TEXT NULL, -- LLM 生成,不可作为 detection 依据 status TEXT NOT NULL DEFAULT 'open', -- open | acknowledged | resolved | dismissed created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 关联假设(P1;晋升到 Memory V2 后记录 memory_item_id) CREATE TABLE IF NOT EXISTS health_correlations ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, factor_a TEXT NOT NULL, -- sleep_deterioration factor_b TEXT NOT NULL, -- bp_elevation confidence NUMERIC(4,3) NOT NULL, occurrence_count INT NOT NULL DEFAULT 1, first_seen_at TIMESTAMPTZ NOT NULL, last_seen_at TIMESTAMPTZ NOT NULL, user_confirmed BOOLEAN NOT NULL DEFAULT FALSE, memory_item_id TEXT NULL ); ``` 设计要点: - `health_observations` 是**唯一写入格式**。血压拆成 `bp_systolic` / `bp_diastolic` 两行,便于统一基线计算;确认卡在 UI 层合并展示。 - 所有含软删除的表都带 `deleted_at`(Page Data `soft_delete` 硬要求)。 - 唯一索引实现**幂等**:重复发送同一张照片不会产生重复观测。 - `time_source` 显式记录时间来源,用于后续数据质量分析。 ### 8.3 Dataset 注册 每个表独立注册 dataset,遵守 `page-data-collect` 的「一任务一表一 dataset」原则。 ```json { "name": "health_observations", "table": "health_observations", "description": "MeMind Health 个人健康观测记录", "actions": ["read", "insert", "update", "softDelete"], "columns": { "read": ["id", "observed_at", "metric_type", "value_num", "value_text", "unit", "context", "source", "quality_flag", "created_at"], "insert": ["observed_at", "metric_type", "value_num", "value_text", "unit", "context", "source", "source_ref", "time_source", "confidence", "quality_flag", "raw_payload"], "update": ["observed_at", "value_num", "value_text", "context", "quality_flag"] } } ``` **关键安全约束**:`raw_payload`、`source_ref`、`ocr_text` **不得**出现在任何公开页的 `read` 白名单中(见 §9.4)。 ### 8.4 写入路径(重要澄清) Page Data 有两条 API:公开页 API 与 Owner Admin API。健康数据的写入**不走公开页 API**: ```text H5 健康录入 / 服务号录入 → 平台内登录态请求 → health-observation-service.mjs → Owner 侧 Page Data 访问(/api/admin/page-data/* 或服务端直连 dataset 授权) → 用户隔离 PG schema MindSpace 健康区页面(Timeline / 档案展示) → 走公开页 API,但仅 read → 且只读「摘要快照 dataset」,不直连原始 observations(见 §9.4) ``` 这样原始健康数据永不通过公开页 API 暴露。 --- ## 9. MindSpace 健康区:强制加密 ### 9.1 新增系统分区 在 `mindspace.mjs` 的 `SYSTEM_CATEGORIES` 增加一项(设计,尚未实现): ```js { code: 'health', name: '健康档案区', visibilityPolicy: 'private', aiAccessPolicy: 'explicit_asset_grant', // AI 需显式授权才能读具体资料 publishPolicy: 'password_required', // 新增策略值 sortOrder: 30, } ``` `h5_space_categories.publish_policy` 是 `VARCHAR(64)`,新增枚举值**不需要改表结构**。 现有系统分区(`oa` / `public` / `draft` / `archive`)策略保持不变。 ### 9.2 发布闸门(服务端硬阻断) ```text publish(pageId, accessMode, password) ↓ category = resolveCategory(pageId) ↓ if (category.publishPolicy === 'password_required') { if (accessMode === 'public') → 拒绝:健康区禁止完全公开 if (!password && !hasExistingPasswordHash) → 拒绝:必须设置口令 if (accessMode not in ['password', 'login_required']) → 拒绝 } ↓ 继续现有发布流程(不改动其他分区行为) ``` 必须是**服务端阻断**,不是前端禁用选项。前端仅额外做「不展示完全公开选项」的体验优化。 口令须满足现有平台约束:**≥ 6 位**,bind 时必须传入并写入 `password_hash`。禁止只改 `access_mode` 不写 `password_hash`。 ### 9.3 访问体验:本人免密,外部必密 ```text 访问健康页 ↓ 是 owner 且已登录? ├─ 是 → 直接放行(不要求输口令) └─ 否 → 要求口令 → 校验 → 发 page-data token → 放行 ``` - 健康区默认 `password` 模式(可分享给医生)。 - owner 登录态访问**短路**口令校验。 - 口令可重置、令牌可撤销,复用现有能力: - `POST /api/admin/page-data/policies/:pageId/password/reset` - `POST /api/admin/page-data/policies/:pageId/tokens/revoke` ### 9.4 分享安全:快照而非直连(关键设计) `password` 模式**无法区分访客身份** —— 拿到口令的人能读该页授权的所有行。因此: **禁止**健康区公开页直接 `read` `health_observations` / `health_documents`。 **正确做法**:为分享场景生成**只读摘要快照 dataset**: ```sql CREATE TABLE IF NOT EXISTS health_share_snapshots ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, snapshot_key TEXT NOT NULL UNIQUE, scope TEXT NOT NULL, -- clinic_summary | trend_30d | lab_selected period_start DATE NOT NULL, period_end DATE NOT NULL, payload JSONB NOT NULL, -- 已脱敏、已聚合的展示数据 created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMPTZ NULL, revoked_at TIMESTAMPTZ NULL ); ``` - 分享页只 `read` `health_share_snapshots`,且 `columns.read` 仅含 `payload` 等必要字段。 - 快照**由用户显式生成**,范围、时间窗、包含项由用户勾选。 - 快照可设过期时间、可撤销。 - 原始 `observations` / `ocr_text` / `raw_payload` **永不**进入任何公开页授权。 这解决了「分享给医生」与「数据最小暴露」的矛盾。 ### 9.5 SEO 自动安全(无需改动) `mindspace-index-policy.mjs` 中 `INDEXABLE_ACCESS_MODE = 'public'`,`isPublicationIndexable()` 要求 `accessMode === 'public'`。 健康区强制 `password` / `login_required` 后 → **自动 noindex**,无需额外改 SEO 逻辑。 此行为落在现有回归守卫 `docs/regression-guards/mindspace-seo-geo.md` 保护范围内,改动健康区时须一并跑 `npm run verify:seo-geo`。 ### 9.6 健康区内容结构 ```text 健康档案区/ timeline/ 健康时间线页(加密,owner 免密) documents/ 报告归档 2026-08-体检报告/ 2026-09-血脂化验/ assessments/ 评估摘要 shares/ 就诊摘要快照页(口令 + 可撤销 + 可过期) ``` --- ## 10. Health Timeline 与 Personal Baseline ### 10.1 Health Timeline(日聚合视图,非存储层) ```text 2026-09-01 BP 128/76 HR 68 Weight 67.2kg Sleep 6h32m Symptom none 2026-09-02 BP 137/82 ↑ vs 30d baseline +9.8% HR 72 Weight 67.4kg Sleep 5h10m ↓ vs 30d baseline -17% 7-day trend: BP baseline deviation +9.8% Sleep baseline deviation -17% ``` 实现:`health_observations` 按日 rollup。P0 用查询时聚合;数据量增长后再引入物化视图或定时 job。 ### 10.2 Personal Baseline(P1) 滚动窗口,每日凌晨重算 7 / 30 / 90 天基线。 冷启动策略(`maturity` 字段): | 阶段 | 样本量 | maturity | 行为 | |---|---|---|---| | 冷启动 | < 7 个有效点 | `cold` | 只展示原始值,**不产生偏离事件** | | 弱基线 | 7–29 | `weak` | 可产生 `info` 级提示,不发 `alert` | | 稳定 | ≥ 30 | `stable` | 全量启用 Trend Engine | 这条很重要:**基线未成熟就报警会摧毁用户信任。** 有效点定义:`quality_flag != 'suspect'` 且 `deleted_at IS NULL`。`suspect` 只是**待裁决**状态 —— 用户确认后必须落到 `ok` 或 `user_corrected`,不得长期停留,否则真实的恶化会被永久过滤掉。 **样本量按 context 分层计算**(O3 决策的直接后果):晨间血压基线只统计 `context='morning'` 的观测。这意味着若用户每天只测一次晨间血压,达到 `stable` 需 30 天;若晨晚各一次,两条基线各自独立计数,**不会因为总数翻倍而提前成熟**。 `weight`、`sleep_minutes` 等本身不分晨晚的指标使用 `context='any'`。 ### 10.3 Rule Engine(确定性) | 规则类 | 示例 | 严重度 | |---|---|---| | 绝对阈值 | SpO₂ < 90% | `alert` | | 绝对阈值 | 收缩压 ≥ 180 或 舒张压 ≥ 110 | `alert` | | 漏测 | 连续 3 天无晨间血压 | `watch` | | 新症状 | 连续 3 天报告同一症状 | `watch` | | 药物变化 | 新增/停用药物后 7 天内加强监测 | `info` | 输出 `health_events(event_type='threshold_breach', rule_id=...)`。 ### 10.4 Trend Engine(统计,不用 LLM) | 检测项 | 方法 | |---|---| | 基线漂移 | 近 14 天均值 vs 90 天基线,偏离 > 阈值且持续 ≥ N 天 | | 变异性增大 | 近 7 天 std > 历史 std × 1.5 | | 多指标联动 | 睡眠 ↓ + 静息 HR ↑ + BP ↑ 同期发生 → cluster | 输出 `health_events(event_type='baseline_drift' | 'symptom_cluster')`。 ### 10.5 Event Engine → Agent Event Engine 合并 Rule + Trend 结果,去重、分级、聚合为 Cluster,然后才调用 Agent。 **Agent 的输入是结构化 Event,不是原始时序**: ```json { "cluster_id": "...", "window": "14d", "baseline_maturity": "stable", "findings": [ { "metric": "bp_systolic", "deviation": "+8%", "baseline": "119-126", "recent": "132-138", "days_above": 5 }, { "metric": "sleep_minutes", "deviation": "-17%" } ], "open_questions": ["recent_insomnia", "medication_change", "alcohol", "stress"] } ``` Agent 职责: 1. 用自然语言解释「发生了什么变化」(**不是诊断**) 2. 提出 1–2 个高价值追问 3. 给出行动建议:复测计划 / 记录给医生 / 何时就医 4. 将用户回答与关联假设写入 `health_correlations` → 满足条件后晋升 Memory V2 Agent **不能**反向修改 Event 严重度 —— 那是 Engine 的职责。 理想输出示例: > 过去 30 天整体生命体征较稳定。最近 7 天晨间收缩压较过去 90 日个人基线上升约 8 mmHg,其中 5 天高于近期常态;同期睡眠时间有所下降。建议未来 7 天按规范继续早晚测量,如持续偏高,可将趋势记录提供给医生评估。 ### 10.6 关联假设的长期学习 ```text 第一次失眠期 → BP +7% 第二次失眠期 → BP +9% 第三次失眠期 → BP +8% ↓ occurrence_count = 3, confidence ↑ ↓ 晋升 Memory V2(label: health_correlation) ↓ "这个人的血压可能对睡眠变化比较敏感" ``` **术语纪律**:必须称「关联假设」,**禁止**表述为医疗诊断或因果结论。 --- ## 11. 零侵入保证与灰度 ### 11.1 四条保证 1. **全局 Feature Flag**:`MEMIND_HEALTH_ENABLED=false` 时,健康入口不展示、路由不注册、服务号关键词不生效,现有链路**行为不变**。 2. **只做加法**:意图路由**不改判定逻辑**,健康会话天然携带 `selectedChatSkill`,走的是已存在的短路分支。 3. **发布闸门只对 `health` 分区生效**:其他分区 `publish_policy` 不变,现有发布行为不受影响。 4. **fail-open**:抽取/基线服务不可用时,退化为「仅归档原图 + 提示稍后重试」,不阻塞会话、不影响普通聊天。 ### 11.2 灰度 沿用仓库既有 canary 模式:按 `user_id` 灰度,先内部账号 → 小流量 → 全量。健康引擎(P1)应支持 `off / shadow / active` 三态,shadow 模式下只记录会产生哪些事件、不实际通知用户。 ### 11.3 必须遵守的既有守卫 改动触及以下路径时,按 `AGENTS.md` 规则必跑对应 verify: ```bash npm run verify:mindspace-publish-guards npm run verify:mindspace-publish-guards:full npm run verify:seo-geo npm run verify:seo-discovery npm run verify:page-data ``` 特别注意: - `chat-image-turn-scope.mjs` — 带图轮次 `read_image` 隔离 - `mindspace-index-policy.mjs` — 私有页 noindex - `mindspace-page-sync-service.mjs` — remote 模式也必须 sync public HTML ### 11.4 新增测试 ```text health-channel-state.test.mjs 状态机全转移覆盖 + 无关输入不猜测 health-extraction.test.mjs schema 约束 / 量程校验 / 置信度门限 health-observation-service.test.mjs 幂等去重 / 时间来源优先级 / suspect 标记 health-baseline-engine.test.mjs 冷启动 maturity / 有效点过滤 health-event-engine.test.mjs 规则触发 + evidence chain 完整性 health-publish-guard.test.mjs 健康区禁止 public 发布(硬阻断) health-share-snapshot.test.mjs 分享页不得授权原始 observations ``` --- ## 12. 合规与产品边界 1. **非医疗器械声明**:MeMind Health 是健康记录与趋势提醒,**不提供诊断**。 2. **关联假设 ≠ 诊断**:Agent 输出统一附 disclaimer 模板。 3. **Evidence chain 可追溯**:每条 alert 可展开到 `evidence_observation_ids` + `rule_id`。监管与客诉必备。 4. **数据分级**:健康观测为敏感数据。分享须用户显式生成快照、可过期、可撤销。 5. **LLM 边界**:detection 层**零 LLM**;explanation 层 LLM 输入仅为结构化 Event。 6. **删除权**:用户可删除任意 Observation / Document;删除后基线须重算。 ### 12.1 隐私默认值 - 健康区默认 `private`,`aiAccessPolicy: explicit_asset_grant`。 - Agent 读取具体报告内容需显式授权,不默认全量注入上下文。 - 健康数据**不参与** SEO/GEO 收录(§9.5 自动保证)。 ### 12.2 数据保留 复用 Memory V2 生命周期思路:Observation 长期保留(健康趋势需要长历史);但抽取中间产物(原始视觉输出)可设较短保留期。 ### 12.3 P1 存储迁移预案 若 Page Data 聚合性能不足,迁移到专用表的前提: - 保持 `health_observations` 列结构不变,仅换存储位置 - 提供双写 + 校验期 - 迁移前后 Timeline 与基线结果必须一致(回归对账脚本) --- ## 13. 分阶段路线 ### P0 — 验证入口与录入精度(不做引擎) - H5 `/health` 入口 + 通道状态机 - 手动录入 + 拍照录入(三段式确认) - Health Timeline 展示 - MindSpace 健康区(强制加密)+ 报告归档 - 服务号:关键词/菜单进入 + 拍照上传 + 确认落库 - `health-assistant` skill v0(读 Timeline 摘要回答「最近怎么样」) **不做**:基线、Event Engine、设备接入、分享。 **验证指标**: - 拍照识别经确认后准确率 ≥ 98% - 通道内漂移事件(被路由到非健康 skill)= 0 - 健康区无法以 `public` 模式发布(自动化测试保证) - 连续 14 天录入完成率 ### P1 — Personal Baseline + Event Engine(核心命题验证) - Baseline Engine(7/30/90 天 + maturity) - Trend Engine + Rule Engine v1 - Event Engine + Agent 解释 - 关联假设 v0 → Memory V2 晋升 **验证方式**:回溯已有 30 天数据,Event Engine 能否在用户主观感知前 3–7 天产生 `watch` 事件;并访谈「这条提醒有用吗?会不会太吵?」 ### P2 — 设备接入与提醒 - Apple Health / 蓝牙设备 Connector(设备可信 → 免确认路径,但异常值仍标 `suspect`) - 分级通知策略(`info` 静默 / `watch` 日摘要 / `alert` 即时) ### P3 — 医疗纵深 - 用药 Timeline(整合 `schedule-assistant`) - 检验指标长期趋势 - 就诊摘要加密分享页(§9.4 快照机制) ### P4 — 授权分享 - 本人授权家人/医生**只读摘要**(非代录、非原始数据) - 机构侧只读接口(如有需求) --- ## 14. P0 前置最小实验(先做,成本极低) 在写任何平台代码前,用 14 天人工实验验证核心假设。 **完整可执行方案见 [memind-health-p0-experiment.md](memind-health-p0-experiment.md)**,此处仅摘要。 ```text 参与者:8–10 人(最低 5 人);有意纳入更可能出现波动的人群 工具: 现有 Page Data 匿名问卷页(方案 A),无需新代码 D1–D7 : 基线建设期,每日录入晨间 BP + 睡眠 + 症状 + 扰动因素 D8 : 手工计算 7 天基线(Excel 即可) D8–D14: 继续录入,研究者每日手工标记偏离(不告知参与者) D14 : 盲态访谈(必须在揭盲前完成) ``` **主结局 H1(一定可测)**:14 天晨测完成率 ≥ 80% 的参与者 ≥ 3/5。 **次结局 H2(可能不可判定)**:手工偏离标记早于主观察觉 ≥ 3 天的参与者 ≥ 3/5。 关键设计取舍: - **H1 是主结局,不是 H2。** 若用户坚持不下来,再精妙的基线算法都无意义;此时正确反应是把设备自动同步提到最高优先级,而不是优化算法。 - **14 天内可能无人偏离**,这是最可能的失败模式,方案中已预设无偏离处理路径,不得因 H2 无结论就否定项目方向。 - **漏检比误报更值得研究**:用户明确感到变化而基线检测不到,说明指标组合或窗口有问题。 - **顺带产出照片校验集**:参与者同时提供设备屏幕照片 + 手工录入值,形成 `(图片 → 正确值)` 标注对,直接用作 P0 拍照识别的回归用例。这是很难另外获得的资产。 --- ## 15. 已决事项(O1–O6) 以下六项已决策,实现必须遵守。变更需重新评审本章。 ### O1 症状录入:固定枚举 + 严重度 + 可选备注 **决策**:采用固定枚举,附三级严重度,允许自由备注,但备注**不参与**结构化分析。 ```text 枚举(可多选;无症状必须显式选「无」): 无 / 头晕 / 头痛 / 胸闷 / 心慌心悸 / 乏力 / 气短 / 下肢水肿 / 失眠 / 其他 严重度(每个勾选项必填): 轻 = 1 / 中 = 2 / 重 = 3 自由备注:可选,存 value_text,仅供 Agent 解释时参考 ``` **理由**:`symptom_cluster` 检测需要可比较的离散值。自由文本会产生「有点晕」「头晕晕的」「晕」等语义漂移,无法计数、无法判断「连续 3 天同一症状」。 **存储**:每个症状一行 `health_observations`,`metric_type='symptom'`,`value_text=` 枚举码,`value_num=` 严重度。这样症状严重度也可做趋势(如「头晕从轻转中」)。 **「无症状」必须显式记录**,不能靠「没有记录」推断 —— 否则无法区分「今天没症状」与「今天忘了记」。 ### O2 语音录入:必须确认,不得直接落库 **决策**:语音识别结果**一律**进入 `CONFIRM` 状态,禁止跳过确认。 **理由**:数字类语音识别错误率显著高于文本,且错误形态危险 —— 「一百三十七比八十二」可能识别为「137、82」也可能是「130、782」。血压场景一次错记就污染基线。 **附加要求**:语音录入的确认卡必须**同时展示识别原文**,让用户能判断是识别错了还是自己说错了: ```text 听到:「血压一百三十七比八十二」 识别:收缩压 137,舒张压 82 确认保存 / 修改 / 重说 ``` ### O3 血压强制记录情境,且基线按情境分层 **决策**:血压录入**强制**记录情境,三选一,按测量时刻自动预选,用户可改。 ```text morning 晨间(默认:测量时刻 < 10:00) evening 晚间(默认:测量时刻 ≥ 18:00) other 其他(10:00–18:00 默认,或用户手动指定) ``` **这不只是一个字段 —— 基线必须按 context 分层计算**(已反映到 §8.2 `health_baselines` 的 `UNIQUE (metric_type, context, window_days)`)。 **理由**(这是本次评审中最重要的修正):晨间血压与晚间血压生理上是不同指标,个体内可差 10–20 mmHg。若混算基线: - 晨峰升高会被晚间的低值稀释 → **漏掉最有临床意义的异常** - 用户某天只测了晚间 → 与混合基线比较 → **产生假偏离** 两种错误方向相反,但都由同一个原因造成。因此分层是必需的,不是优化项。 **代价**:达到 `stable` 所需天数不因多测而减少(见 §10.2)。这个代价可以接受 —— 宁可基线成熟慢,不要基线本身是错的。 `other` 情境的观测**记录但不参与**晨/晚基线计算,仅在 Timeline 展示。 ### O4 报告 OCR:复用现有图片/文件分析链路,不新建 OCR 服务 **决策**:健康侧**不实现**独立 OCR。复用仓库现有的图片与文件分析能力(服务号侧 media analysis、Agent 侧图片/文件读取),健康域只在其**之上**叠加两层: ```text 现有图片/文件分析链路(不改动) ↓ health-extraction.mjs ├─ 强约束输出 schema(metric_set + 字段 + confidence) └─ 合理性校验(量程 / 逻辑 / 基线偏离 / 置信门限) ↓ ObservationDraft(待确认) ``` **理由**:重复实现 OCR 会造成两套图片处理路径,直接威胁 `chat-image-turn-scope.mjs` 的回归守卫(带图轮次 `read_image` 隔离、工具图片污染触发会话轮换)。健康通道必须走同一条底层路径。 **实施前置检查**:动工前需确认现有链路对「多页 PDF」「长图化验单」的支持程度。若不支持,应作为独立需求补齐**通用能力**,而不是在健康域内私建分支。 ### O5 alert 通知:不承诺推送,主机制为「下次进入即见」 **决策**:`alert` 触达**不作为产品承诺**。核心机制是进入即见,推送为 best-effort 补充。 分级策略: | 严重度 | 触达方式 | |---|---| | `info` | 静默,仅健康首页与 Timeline 可见 | | `watch` | 进入即见 + 可选日摘要(用户须主动开启) | | `alert` | 进入即见(置顶未读)+ 48 小时内有交互时发服务号客服消息 + 已订阅者发订阅通知 | **理由**:服务号客服消息受 48 小时交互窗口限制,订阅通知需用户主动订阅且有频次场景限制;H5 无主动推送能力。**恰恰是长期不互动的用户最需要提醒,而这类用户技术上最难触达。** **因此产品措辞必须修正**: ```text 禁止:「异常时我们会通知你」 应为:「异常会记录在你的健康档案,并在你下次进入时优先展示」 ``` **实现要求**:任何入口进入健康通道时(无论何种原因),必须先展示未读 `alert`,且未读状态在用户明确确认前不清除。 **同时**:应引导用户主动订阅服务号通知,并在引导文案中说明原因,而不是默认用户已可触达。 ### O6 偏离阈值:P0 不定死,由实验数据标定 **决策**:不在设计阶段固化阈值。P0 采用下列**初始建议值**,并在前置实验与 P0 数据积累阶段完成标定。 初始建议值(仅供实验期手工判定与 P1 起点,**非最终值**): | 指标 | 单日标记 | 持续偏离(触发事件) | |---|---|---| | 晨间收缩压 | `dev% ≥ +5%` 或 `z ≥ +1.5` | 连续 ≥ 2 天同向 → `watch`;≥ 5 天或 `dev% ≥ +10%` → `alert` 候选 | | 晨间舒张压 | 同上 | 同上 | | 睡眠时长 | `dev% ≤ -15%` 或 `z ≤ -1.5` | 连续 ≥ 3 天 → 参与 cluster 判定 | | 静息心率 | `dev% ≥ +10%` 或 `z ≥ +1.5` | 连续 ≥ 3 天 → 参与 cluster 判定 | | 体重 | 7 日内变化 ≥ 1.5kg | 直接 `watch` | **双重判定(相对偏差 OR 标准分)的理由**:相对偏差直观但忽略个体变异性 —— 变异大的人频繁误报;z 分数考虑变异性但小样本下 `std` 不稳定。两者取「或」,宁可多标记后人工/规则复核,也不要漏检。 **标定流程**: ```text 前置实验(14 天,手工判定) → 记录首次偏离日 vs 主观察觉日 → 计算不同阈值下的提前天数与误报数 → 选择「提前 ≥ 3 天且误报可接受」的阈值组合 → 写回本节,作为 P1 Trend Engine 初始配置 ``` **阈值必须可配置**,不得硬编码在检测逻辑中 —— 后续需按真实数据持续调整。 --- ## 16. 相关文档 - [memind-health-p0-experiment.md](memind-health-p0-experiment.md) — **P0 前置 14 天实验可执行方案** - [ENGINEERING_WORKFLOW_RULES.md](../ENGINEERING_WORKFLOW_RULES.md) - [PRODUCTION_RELEASE_RULES.md](../PRODUCTION_RELEASE_RULES.md) - [page-data-api-usage.md](page-data-api-usage.md) - [schedule-reminder-design.md](schedule-reminder-design.md) - [memory-v2/README.md](memory-v2/README.md) - [ai-mind-memind-integration-architecture.md](ai-mind-memind-integration-architecture.md) - [regression-guards/vision-turn-read-image-isolation.md](regression-guards/vision-turn-read-image-isolation.md) - [regression-guards/mindspace-seo-geo.md](regression-guards/mindspace-seo-geo.md) - [regression-guards/README.md](regression-guards/README.md)