- 01 Aug, 2026 9 commits
-
-
luoqi committed
-
DW 新表 fact_complex_cases_out(comment 原文「潜在治疗ID」)= FRIDAY 侧 complex_case_info「复杂病例」:宿主自己在跟的患者,PAC 不该再发起电话召回。 落点复用 FRIDAY 那次已建好的 patient_profiles.host_follow_up_active, canonical / 副表 / 召回 SQL 一行未动 —— 本次全部改动落在 yaml + 通用引擎。 口径与 FRIDAY 对齐(实测 113,657 行 / 88,300 患者): 未删除(is_del=1;98.8% 为 1,反直觉但两家源库一致) 且 case_stage ∈ 1待跟进/2已咨询/3已预约/5诊疗中 → 在跟(70,275 患者) 6已成单 / 7已丢单 = 宿主停手 → 交还召回 【判定为什么内联进主档 SQL,而不是 lookup / join】 FRIDAY 的演进方向是「宿主侧 join,PAC 侧不 lookup」(其 manifest 四处注明), jvs-dw 是 pull、SQL 由 PAC 自己写,故用纪律允许的 `IN (SELECT …)` 形状, 且 tuple 版天然带 brand —— 集团内同号跨品牌是两个人,单键会误标瑞泰同号患者。 更关键的是这样判定是**实时全量**的:每次拉到主档都重算,不依赖增量窗口。 若改成"拉变化的病例再 join",窗口外的人会 lookup 未命中 → full upsert 落 null → 正在跟进的患者被静默洗回召回池。 【通用层两处改动 —— 都是既有脆弱性,被这个 SQL 形状第一次触发】 1. SQL 改写器改按括号深度定位顶层关键字(新增 topLevelIndexOf / splitSelectFrom): 主档 query 头一次出现 SELECT 列里的标量子查询,而 · injectIncrementalCursor 的 /SELECT (.+?) FROM (\w+)/ 非贪婪会撞上子查询的 FROM → 切出半截列表 + 错误表名,增量拉错表 · extractBusinessFilters 抓第一个 WHERE → 把子查询的 is_del=1 当成主档业务过滤 搬到外层 → 主档无该列 → CH 报错(增量空转探针一并错) 两者都是静默错法,故补 13 项测试锁死。 2. 反向拉主档:表名从 manifest.cohort.reverse_pull_from 读(不配 → 历史四张默认, 行为不变),键列/主档表名同步改读 cohort 配置。原先四张 jvs-dw 表名硬编码在通用 代码里,新增一张就要改 service —— 与「yaml 是宿主唯一差异」相悖。⭐ 同时修一处真 bug:反向拉写死 `SELECT *`,会丢掉主档 query 的派生列 —— 该列缺席 → canonical 无此键 → full 分支落 null → 在跟患者被反向拉这步洗回池。 改为复用主档 query 的 SELECT 列。 【增量怎么感知】主档 cursor 是 last_visit_time,人不来诊就拉不到 → 病例开/关 PAC 不知道。 故把该表作为独立 query 拉(不产 fact、无 assembler),仅为把变化的患者带进 cohort, 再由反向拉补出主档、重算派生列。cursor 用 SELECT 别名 changed_at = coalesce(updated_gmt_at, created_gmt_at):updated 有 44% 为 NULL(建后没改过, 恰是刚入池的新病例),直接当 cursor 会 `NULL > x` 恒 UNKNOWN 静默漏掉这批。 测试服 DW 实跑验证:主档/增量两条改写后的 SQL 均可执行,标记数 70,275 与直接统计一致。 790 tests / 51 suites 全绿。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
测试服务器每小时告警一次「friday DW 数据滞后 37 小时」,是误报: friday 是 **push 宿主**,数据由宿主主动推,sync_logs 的 cursor_before/after 按设计恒为 null,根本没有游标可推进。它之所以有游标,是历史上误跑过一次增量、 留下一条 fetched=0 的 incremental_bundle 记录,游标就永久停在 2026-07-30T10:00Z。 而同期 friday 正常 push 了 614 批 10.79 万行(含新接的跟进闸字段),数据流毫无问题 —— 拿"游标多久没推进"衡量 push 宿主,是**指标本身用错了**。 危害不只是烦人:该告警**永不自愈**(游标不可能再推进),每小时响一次, 最终把真告警淹掉 —— 一个永远在响的黄灯,看的人很快就不看了。 改法:遍历宿主时按 manifest 是否声明 sql_source 短路。 - 判据选 sql_source 而非 auto_sync:前者与 files **二选一**(manifest.schema 原话), 是"数据从哪来"的定义即摄入模式本身;auto_sync 只是"要不要自动跑",可临时关, 跟模式是两回事。 - 短路放在「还没跑过增量(无 cursor)」那条 warn **之前** —— 否则 push 宿主 只是换个姿势继续刷日志。 - getHostOpsConfig 读不到 manifest 时 hasSqlSource 兜底 false:宁可不报, 也不要对着未知宿主刷告警。 回归闸 tests/dw-lag-monitor-scope.spec.ts(5 项),已验证去掉守卫会报 2 项失败。 注:push 宿主的健康度应看「距上次成功 push 的时长」,是当前的监控盲区 (FRIDAY 断推三天也不会有任何告警),本次按要求不做,单独评估。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
按「文档即契约」推进,不再等宿主口头确认:契约已写明 has_active_complex_case, PAC 侧按此接好映射;宿主未推该列时 canonical 无此键 → 副表不写 → 闸保持未启用, 行为与接入前完全一致,所以提前接是零风险的。 【is_del 悬案已查源库坐实,不必再问宿主】 complex_case_info.is_del 注释「未删除1/已删除0」——**反直觉但是对的**,is_del=1 才是未删除: - 同注释的 complex_potential_demand 7 行全为 1(全存活,且被 30 行明细引用), 若 1=已删则全表皆删,不合理 - complex_case_info 47:8 ≈ 85% 存活是正常比例,反过来不是 之前之所以看着可疑,是因为**该库同时存在两种相反惯例**(customer_gift 是「未删除0/已删除1」) → 取数必须逐表看注释,不能套惯例。结论已写进 yaml 注释与契约文档。 【防护:脏值降级,不牵连主数据】 canonical hostFollowUpActive 加 .catch(null)。该字段是**可选的召回闸信号**, 而 patient 是**主数据**:若宿主推来无法识别的值(如 "Y2"/"待定"),不加 catch 会让 整条患者主档被 zod 拒收 —— 姓名/电话/生日全丢,为一个 nice-to-have 信号赔上主数据, 代价完全不成比例。脏值降级成 null(= 信号未提供 → 不启用闸),失败方向朝「照常召回」, 而不是「静默把人挡在池外」。0/1/true/false/y/n 等常见写法仍由 booleanFields 先行 coerce。 测试增至 21 项:新增映射存在断言 + 脏值不拖垮主档(校验 name 仍完好)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
宿主自己已建复杂病例 / 工单、客服或咨询师在推进的患者,PAC 不再发起召回: 两边同时联系同一个人,患者体验差,也显得两个系统各说各话。 落点 patient_profiles.host_follow_up_active(canonical hostFollowUpActive), 与 do_not_contact / deceased 并列在召回②合规硬过滤,但性质不同: 合规闸 = 法务/风险,永久,需人工解除 跟进闸 = 协作分工,临时,宿主停手下次推主档即回池 【本次最关键的决定:三态而非两态】 null = 宿主未提供该信号(jvs-dw 未接入)→ 不启用该闸 false = 宿主明确说"没在跟" true = 正在跟 → 排除 由此三条纪律,全部有测试锁住: 1. 列可空且**无 @default** —— 给默认值等于把存量 38 万患者一次性断言成某种状态, 且将来分不出"没接入"和"接了但没跟" 2. SQL 一律 `IS NOT TRUE`,**绝不能** `= false` —— 后者遇 NULL 恒 UNKNOWN, 会把未接入宿主的患者全部静默挡在池外(不报错、不留痕,只表现为池子空了) 3. upsert full 分支不能 `?? false`(那是替宿主表态);partial 分支未提供则不进 update 集合 改动: - canonical: hostFollowUpActive(可空无默认)+ 挂进 patient.booleanFields,自动吃 0/1 - prisma: 副表加列 + 索引;migration 加可空列不重写表,存量行为不变 - scenario SQL: 入池闸 + 注释框图同步 - recall-debug: compliance 增补该项,否则排查时会显示"合规通过"却查不到人 - 测试 host-follow-up-gate.spec.ts(18 项) FRIDAY 侧对应 has_active_complex_case,assembler 映射待宿主确认列名与 is_del 语义后再接, 故本次**未接 FRIDAY 映射** —— 该闸对所有宿主当前均为 NULL,行为与上线前完全一致。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
上一版定的是 active_complex_case_count(计数),多余: - 闸门只判有无,条数 PAC 不消费,纯属未被消费的精度 - complex_case_info 带 organization_id,跨诊所累加的条数在多品牌 SaaS 里语义含糊; EXISTS 反而是干净、且宿主易保证正确的口径 改为 has_active_complex_case(接受 1/0 或 true/false,PAC 归一层 coerce 成布尔)。 PAC 侧字段 hostFollowUpActive 不变(本就是布尔)。luoqi committed -
宿主自己已建复杂病例、客服/咨询师在推进的患者,PAC 不该再发起召回: 两边同时联系同一个人,患者体验差,也显得两个系统各说各话。 【落点】patient_profiles.host_follow_up_active(canonical: hostFollowUpActive), 与 do_not_contact / deceased 并列在②合规硬过滤,但性质不同: 合规闸 = 法务/风险,永久,需人工解除 跟进闸 = 协作分工,临时,宿主停止跟进即回池 判定用 `IS NOT TRUE` 而非 `= false` —— 该列可空,NULL 表示宿主未提供该信号 (如 jvs-dw 暂未接入),不能因 NULL != false 把这些患者全挡在池外。 【FRIDAY 侧口径】customer_basic_info 新增 inline 列 active_complex_case_count = 该患者未删除且 case_stage ∈ {待跟进,已咨询,已预约,诊疗中} 的 complex_case_info 条数; 已成单(需求已闭环)/ 暂停跟进(宿主已停手)不计,交还 PAC 召回。 为什么由宿主算而非整表推 complex_case_info: 1. case_stage 的码→中文映射不在库里(column_comment 只写「病例阶段:」值为空),PAC 无从翻译 2. 一患者可有多条病例,PAC 要的是患者粒度聚合结论 3. 与既有 inline 纪律一致(同 contacts_tel / std_code / class_name)⚠ ️ 两处待 FRIDAY 确认,确认前不启用该闸: ① 列名 active_complex_case_count 是 PAC 建议名(源表无此列,属新定义); 宿主若另有习惯叫法以宿主为准 —— 契约纪律是列名随宿主 ② complex_case_info.is_del 注释写「未删除1/已删除0」与惯例相反,实际数据 0/1 都有, 取数前须确认哪个值代表未删除,否则计数整体反掉 顺手修一处已与线上代码矛盾的文档:customer_referee_circle.referee_relationship 的方向说明还是旧的「本行 customer 是 referee 的 X」,而 a38f1b60 已按源库年龄实测 改成同向直译。文档同步为「本行 referee 是 customer 的 X」。 本次仅文档,无代码/schema 改动。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
friday 是 push 宿主,无游标可推进,却因一条 fetched=0 的误跑增量记录连报 37 小时。 按 manifest 是否声明 sql_source 短路,只对 pull/DW 宿主检查游标滞后。
luoqi committed -
测试服务器每小时告警一次「friday DW 数据滞后 37 小时」,是误报: friday 是 **push 宿主**,数据由宿主主动推,sync_logs 的 cursor_before/after 按设计恒为 null,根本没有游标可推进。它之所以有游标,是历史上误跑过一次增量、 留下一条 fetched=0 的 incremental_bundle 记录,游标就永久停在 2026-07-30T10:00Z。 而同期 friday 正常 push 了 614 批 10.79 万行(含新接的跟进闸字段),数据流毫无问题 —— 拿"游标多久没推进"衡量 push 宿主,是**指标本身用错了**。 危害不只是烦人:该告警**永不自愈**(游标不可能再推进),每小时响一次, 最终把真告警淹掉 —— 一个永远在响的黄灯,看的人很快就不看了。 改法:遍历宿主时按 manifest 是否声明 sql_source 短路。 - 判据选 sql_source 而非 auto_sync:前者与 files **二选一**(manifest.schema 原话), 是"数据从哪来"的定义即摄入模式本身;auto_sync 只是"要不要自动跑",可临时关, 跟模式是两回事。 - 短路放在「还没跑过增量(无 cursor)」那条 warn **之前** —— 否则 push 宿主 只是换个姿势继续刷日志。 - getHostOpsConfig 读不到 manifest 时 hasSqlSource 兜底 false:宁可不报, 也不要对着未知宿主刷告警。 回归闸 tests/dw-lag-monitor-scope.spec.ts(5 项),已验证去掉守卫会报 2 项失败。 注:push 宿主的健康度应看「距上次成功 push 的时长」,是当前的监控盲区 (FRIDAY 断推三天也不会有任何告警),本次按要求不做,单独评估。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed
-
- 31 Jul, 2026 6 commits
-
-
按文档即契约推进:PAC 侧按 has_active_complex_case 接好映射,宿主未推时闸保持未启用。 is_del 悬案已查源库坐实(is_del=1 才是未删除;该库同时存在两种相反惯例,须逐表看注释)。 canonical 加 .catch(null):脏值降级为「信号未提供」,不拖垮整条患者主档。
luoqi committed -
按「文档即契约」推进,不再等宿主口头确认:契约已写明 has_active_complex_case, PAC 侧按此接好映射;宿主未推该列时 canonical 无此键 → 副表不写 → 闸保持未启用, 行为与接入前完全一致,所以提前接是零风险的。 【is_del 悬案已查源库坐实,不必再问宿主】 complex_case_info.is_del 注释「未删除1/已删除0」——**反直觉但是对的**,is_del=1 才是未删除: - 同注释的 complex_potential_demand 7 行全为 1(全存活,且被 30 行明细引用), 若 1=已删则全表皆删,不合理 - complex_case_info 47:8 ≈ 85% 存活是正常比例,反过来不是 之前之所以看着可疑,是因为**该库同时存在两种相反惯例**(customer_gift 是「未删除0/已删除1」) → 取数必须逐表看注释,不能套惯例。结论已写进 yaml 注释与契约文档。 【防护:脏值降级,不牵连主数据】 canonical hostFollowUpActive 加 .catch(null)。该字段是**可选的召回闸信号**, 而 patient 是**主数据**:若宿主推来无法识别的值(如 "Y2"/"待定"),不加 catch 会让 整条患者主档被 zod 拒收 —— 姓名/电话/生日全丢,为一个 nice-to-have 信号赔上主数据, 代价完全不成比例。脏值降级成 null(= 信号未提供 → 不启用闸),失败方向朝「照常召回」, 而不是「静默把人挡在池外」。0/1/true/false/y/n 等常见写法仍由 booleanFields 先行 coerce。 测试增至 21 项:新增映射存在断言 + 脏值不拖垮主档(校验 name 仍完好)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
宿主自己正在跟进的患者不进召回池。patient_profiles.host_follow_up_active, 与 do_not_contact / deceased 并列在②合规硬过滤。 三态语义(有测试锁):null=宿主未提供该信号→不启用闸;false=明确没在跟;true=在跟→排除。 SQL 一律 IS NOT TRUE,写成 = false 会把未接入宿主的患者静默全挡在池外。 FRIDAY 映射(has_active_complex_case)待宿主确认列名与 is_del 语义后再接 —— 本次该闸对所有宿主均为 NULL,行为与上线前一致。
luoqi committed -
宿主自己已建复杂病例 / 工单、客服或咨询师在推进的患者,PAC 不再发起召回: 两边同时联系同一个人,患者体验差,也显得两个系统各说各话。 落点 patient_profiles.host_follow_up_active(canonical hostFollowUpActive), 与 do_not_contact / deceased 并列在召回②合规硬过滤,但性质不同: 合规闸 = 法务/风险,永久,需人工解除 跟进闸 = 协作分工,临时,宿主停手下次推主档即回池 【本次最关键的决定:三态而非两态】 null = 宿主未提供该信号(jvs-dw 未接入)→ 不启用该闸 false = 宿主明确说"没在跟" true = 正在跟 → 排除 由此三条纪律,全部有测试锁住: 1. 列可空且**无 @default** —— 给默认值等于把存量 38 万患者一次性断言成某种状态, 且将来分不出"没接入"和"接了但没跟" 2. SQL 一律 `IS NOT TRUE`,**绝不能** `= false` —— 后者遇 NULL 恒 UNKNOWN, 会把未接入宿主的患者全部静默挡在池外(不报错、不留痕,只表现为池子空了) 3. upsert full 分支不能 `?? false`(那是替宿主表态);partial 分支未提供则不进 update 集合 改动: - canonical: hostFollowUpActive(可空无默认)+ 挂进 patient.booleanFields,自动吃 0/1 - prisma: 副表加列 + 索引;migration 加可空列不重写表,存量行为不变 - scenario SQL: 入池闸 + 注释框图同步 - recall-debug: compliance 增补该项,否则排查时会显示"合规通过"却查不到人 - 测试 host-follow-up-gate.spec.ts(18 项) FRIDAY 侧对应 has_active_complex_case,assembler 映射待宿主确认列名与 is_del 语义后再接, 故本次**未接 FRIDAY 映射** —— 该闸对所有宿主当前均为 NULL,行为与上线前完全一致。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
上一版定的是 active_complex_case_count(计数),多余: - 闸门只判有无,条数 PAC 不消费,纯属未被消费的精度 - complex_case_info 带 organization_id,跨诊所累加的条数在多品牌 SaaS 里语义含糊; EXISTS 反而是干净、且宿主易保证正确的口径 改为 has_active_complex_case(接受 1/0 或 true/false,PAC 归一层 coerce 成布尔)。 PAC 侧字段 hostFollowUpActive 不变(本就是布尔)。luoqi committed -
宿主自己已建复杂病例、客服/咨询师在推进的患者,PAC 不该再发起召回: 两边同时联系同一个人,患者体验差,也显得两个系统各说各话。 【落点】patient_profiles.host_follow_up_active(canonical: hostFollowUpActive), 与 do_not_contact / deceased 并列在②合规硬过滤,但性质不同: 合规闸 = 法务/风险,永久,需人工解除 跟进闸 = 协作分工,临时,宿主停止跟进即回池 判定用 `IS NOT TRUE` 而非 `= false` —— 该列可空,NULL 表示宿主未提供该信号 (如 jvs-dw 暂未接入),不能因 NULL != false 把这些患者全挡在池外。 【FRIDAY 侧口径】customer_basic_info 新增 inline 列 active_complex_case_count = 该患者未删除且 case_stage ∈ {待跟进,已咨询,已预约,诊疗中} 的 complex_case_info 条数; 已成单(需求已闭环)/ 暂停跟进(宿主已停手)不计,交还 PAC 召回。 为什么由宿主算而非整表推 complex_case_info: 1. case_stage 的码→中文映射不在库里(column_comment 只写「病例阶段:」值为空),PAC 无从翻译 2. 一患者可有多条病例,PAC 要的是患者粒度聚合结论 3. 与既有 inline 纪律一致(同 contacts_tel / std_code / class_name)⚠ ️ 两处待 FRIDAY 确认,确认前不启用该闸: ① 列名 active_complex_case_count 是 PAC 建议名(源表无此列,属新定义); 宿主若另有习惯叫法以宿主为准 —— 契约纪律是列名随宿主 ② complex_case_info.is_del 注释写「未删除1/已删除0」与惯例相反,实际数据 0/1 都有, 取数前须确认哪个值代表未删除,否则计数整体反掉 顺手修一处已与线上代码矛盾的文档:customer_referee_circle.referee_relationship 的方向说明还是旧的「本行 customer 是 referee 的 X」,而 a38f1b60 已按源库年龄实测 改成同向直译。文档同步为「本行 referee 是 customer 的 X」。 本次仅文档,无代码/schema 改动。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed
-
- 30 Jul, 2026 22 commits
-
-
## 嵌宿主可用性(修一个已上线的回归) - 宿主槽位跳转三级兜底:新标签页 → 顶层跳转 → 本 frame。上一版只有一行 window.open, 宿主 iframe 缺 allow-popups 时**点了没反应**(生产 actionUrls 全配着,路径可达)。 - 一步 open 带 URL,不再"先开 about:blank 再导航" —— 那个写法在 sandbox 下会因 「断了 opener 就无权导航该弹窗」抛 SecurityError。 - 交付文档 docs/integration/postmessage-actions.mdx(可直接给宿主开发)。 ## 宿主对接 - 打开潜在治疗的 postMessage 补 desc / treatments / stage 三字段(treatments 发项目名,不带「治疗」后缀)。 - 顶栏「回访」→「跟进」;去掉「已在新标签页打开」toast。 ## 画像 / 详情页 - 标签按业务字典 A/B/C/D 类区上色 + 定序(C→B→D→A),首屏与画像详情抽屉收成一套(删原三分组)。 - 话术头部新增「关联客户」:亲戚姓名 / 关系 / 年龄 + 档案链接(配了 VIEW_PATIENT 跳宿主,否则回落 PAC 工单页)。 - 亲戚关系方向按年龄实时纠正 —— 兜的是人工录错(瑞尔 4%),与 friday 摄入侧根治是两件事,两者都要。 - 潜在治疗中文一律 code 查表,修「潜在补牙」vs「充填治疗」口径不一致。 - 选患者列 / 详情左栏 300 → 320;召回池卡片去掉优先级五色点。 ## 数据 / 权限 - 「机会识别不准确」补必填多选:哪几类推荐治疗不准 → plan_executions.inaccurate_treatments (存 code / 仅统计 / 不参与抑制)。 - 医生名单缓存 6h → 10min + 重算收尾主动清;客服可自助返池(带归属闸,只能退自己的)。
luoqi committed -
patient_relation.yaml 的注释断言「码 = 本人(customer)是对方(referee)的 X」(依据 RecommendRelationshipEnum.reverseValue() 源码 + 早期年龄差推断),于是 enum_mapping 整体取逆:码2 爸爸→child、码3 子女→father/mother、码5 爷爷→grandchild…… 数据把这个假设推翻了:码本来就是「对方是本人的 X」,与 PAC 契约同向,不该反转。 【证据一:PAC 侧方向矛盾率】按「关系人比本人年长/年轻是否合理」判定,只算两边都建档 且都有生日的边: friday 1187/1255 = 94.6% ← 系统性反转 jvs-dw 432/10842 = 4.0% ← 人工零星录错的正常水平(其 yaml 是对的,不动) 互反对更硬:mother
↔ child 326 对里只有 8 对方向正确(2.5%);father↔ child 231 对里 9 对。 实样:秦佳(47) --mother--> 韩秦瑜(21),同时 韩秦瑜(21) --child--> 秦佳(47) —— 两条边互相自洽但都与年龄矛盾,去掉逆映射两条就都对了。 【证据二:源库独立复核(不经 PAC 摄入)】join customer_basic_info 两侧 birthday: 码10 妈妈 关系人年长 419/430(97%),平均大 27.4 岁 码 3 子女 本人年长 643/675(95%),平均小 26.8 岁 码 2 爸爸 关系人年长 252/277(91%),平均大 26.1 岁 码15 外公 关系人年长 20/22 (91%) | 码11 奶奶 17/19(89%) | 码16 外婆 15/17(88%) 码17 外孙 本人年长 31/36 (86%) | 码 6 孙辈 32/46(70%) 方法学对照:对称码(1配偶 348/354、4兄弟 56/55、7朋友 265/264)全是 50/50,说明这个 年龄判据本身干净,有方向码上的强烈偏斜不是判据偏差。 剩余 3–5% 符合逆读法的边判为源侧人工录错(与 jvs-dw 的 4% 同量级),不为它们反转全局。 顺手修掉一个静默 bug:旧版码3 只配了 "3|1"/"3|2",没有 "3|" —— 对方性别缺失的边 (源库 15 条)会掉进 _default: other。本版每个码的 |1 / |2 / | 三个变体都列全。 同向映射后性别不再参与判定(码2 自带 father、码10 自带 mother、码3→child 不分性别), transforms 里的 _referee_with_sex + push_fallback 成了死配置 —— 留待单独一次清理, 不跟方向修正混在一起改。 回归闸 tests/friday-relation-direction.spec.ts:不比对字符串字面量,而是用源库实测的 辈分方向锁语义(长辈码不得映射成晚辈词,反之亦然)。已验证该闸对旧 yaml 报 21 项失败。⚠ ️ 未含数据修复:patient_relations 唯一键是 (patient_id, related_external_id, relationship),relationship 变了就是新行而非更新 —— 重摄只会在错边旁边新增对边。 必须先删 friday 存量关系边再重摄。该步骤待部署后单独执行。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
- 潜在治疗中文一律 code 查表 —— 修详情页「潜在补牙」vs 卡片「充填治疗」的口径不一致 (data.labels 是算画像那刻烤死的旧措辞;纪律:措辞可改的标签一律 code→查表)
luoqi committed -
同一个患者两处口径不一致(测试服实证): 召回池卡片 充填治疗 ← 拿 types=['filling'] 查 POTENTIAL_TREATMENT_CARD_LABEL 详情页 chip 潜在补牙 ← 读 data.labels,那是**算画像那一刻烤进 JSON 的**旧措辞 画像 JSON 长这样:{"types":["filling"],"labels":["潜在补牙"]} —— 措辞这几天改过三轮 (潜在种植→种植治疗→种植),烤死的那份自然全是旧词,要改回来得全量重算画像(百万级、几小时)。 labels.ts 里当初就写了这条预警,卡片按它改了,详情页那两处漏改。 改:首屏 chip + 画像标签云 + 画像详情抽屉,potential_treatment 的中文统统从 code 查表。 compactPersonaValue / personaValueLabels 多收一个 featureKey 参数,只对 potential_treatment 生效 —— 其余多值特征(治疗史/权益/禁忌/时间偏好…)没有 code 表、措辞也没改过,继续读 labels。 纪律写进注释:**凡是中文措辞可能改的标签,展示一律 code → 查表,别读 data.labels。** 本地实测(赵欣冉,types=[endo,filling]):首屏 chip 从「潜在根管 +1」变成「根管治疗 +1」, 与卡片一致。717 tests / 47 suites + web typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
FRIDAY 关系边方向系统性反转(PAC 侧父母/子女/祖孙矛盾率 94.6%),源库年龄实测证实 码语义是「对方是本人的 X」,与 PAC 契约同向,enum_mapping 那次逆映射是错的。去掉逆映射 + 补码3性别缺失变体 + 加辈分方向回归闸。jvs-dw 不受影响未改。
⚠ ️ 部署后需删 friday 存量关系边再重摄(relationship 在唯一键,不删只会新增对边)。luoqi committed -
patient_relation.yaml 的注释断言「码 = 本人(customer)是对方(referee)的 X」(依据 RecommendRelationshipEnum.reverseValue() 源码 + 早期年龄差推断),于是 enum_mapping 整体取逆:码2 爸爸→child、码3 子女→father/mother、码5 爷爷→grandchild…… 数据把这个假设推翻了:码本来就是「对方是本人的 X」,与 PAC 契约同向,不该反转。 【证据一:PAC 侧方向矛盾率】按「关系人比本人年长/年轻是否合理」判定,只算两边都建档 且都有生日的边: friday 1187/1255 = 94.6% ← 系统性反转 jvs-dw 432/10842 = 4.0% ← 人工零星录错的正常水平(其 yaml 是对的,不动) 互反对更硬:mother
↔ child 326 对里只有 8 对方向正确(2.5%);father↔ child 231 对里 9 对。 实样:秦佳(47) --mother--> 韩秦瑜(21),同时 韩秦瑜(21) --child--> 秦佳(47) —— 两条边互相自洽但都与年龄矛盾,去掉逆映射两条就都对了。 【证据二:源库独立复核(不经 PAC 摄入)】join customer_basic_info 两侧 birthday: 码10 妈妈 关系人年长 419/430(97%),平均大 27.4 岁 码 3 子女 本人年长 643/675(95%),平均小 26.8 岁 码 2 爸爸 关系人年长 252/277(91%),平均大 26.1 岁 码15 外公 关系人年长 20/22 (91%) | 码11 奶奶 17/19(89%) | 码16 外婆 15/17(88%) 码17 外孙 本人年长 31/36 (86%) | 码 6 孙辈 32/46(70%) 方法学对照:对称码(1配偶 348/354、4兄弟 56/55、7朋友 265/264)全是 50/50,说明这个 年龄判据本身干净,有方向码上的强烈偏斜不是判据偏差。 剩余 3–5% 符合逆读法的边判为源侧人工录错(与 jvs-dw 的 4% 同量级),不为它们反转全局。 顺手修掉一个静默 bug:旧版码3 只配了 "3|1"/"3|2",没有 "3|" —— 对方性别缺失的边 (源库 15 条)会掉进 _default: other。本版每个码的 |1 / |2 / | 三个变体都列全。 同向映射后性别不再参与判定(码2 自带 father、码10 自带 mother、码3→child 不分性别), transforms 里的 _referee_with_sex + push_fallback 成了死配置 —— 留待单独一次清理, 不跟方向修正混在一起改。 回归闸 tests/friday-relation-direction.spec.ts:不比对字符串字面量,而是用源库实测的 辈分方向锁语义(长辈码不得映射成晚辈词,反之亦然)。已验证该闸对旧 yaml 报 21 项失败。⚠ ️ 未含数据修复:patient_relations 唯一键是 (patient_id, related_external_id, relationship),relationship 变了就是新行而非更新 —— 重摄只会在错边旁边新增对边。 必须先删 friday 存量关系边再重摄。该步骤待部署后单独执行。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed -
## 测试服全量实证(不是本地样本) jvs-dw 43,279 条亲戚边,与年龄矛盾 428 条 → 1.0%,方向基本可信 friday 2,427 条;只看父母/子女**对**更刺眼: mother↔ child 326 对里只有 8 对方向对(2.5%) father↔ child 231 对里只有 9 对(3.9%) → 97% 反了 ## 根因 data/friday/assemblers/patient_relation.yaml 的 enum_mapping 假设源码语义是 「本人是对方的 X」,于是映射时取了**逆关系**(码2 爸爸 → child)。数据说这个假设是错的: FRIDAY 存的码本来就是「对方是本人的 X」,跟 PAC 契约同向 —— 多反转了一次。 互反边可以证:秦佳(47)--mother-->韩秦瑜(21) 与 韩秦瑜--child-->秦佳 两条边互相自洽、 但都与年龄矛盾;去掉那次反转两条就都对了。⚠ ️ 摄入侧的修法(改 yaml + 重摄该资源)另开一件事 —— patient_relation 是 upsert 资源、 不进 transaction,没有原文可 reparse,得从源重拉。 ## 这次做的是展示侧:年龄定方向,不降级 按你的要求不降级成「亲属」,而是**实时算出真实关系**: · 标成长辈但对方更年轻 → 子女 / 孙辈 · 标成晚辈但对方更年长 → 按对方性别拆父/母(性别缺 → 中性「父母」,不硬猜) · 配偶 / 兄弟姐妹是对称关系,没有方向可纠,原样返回(同岁配偶很正常,不许被误标) · 任一方缺生日 → 原样返回,不猜(会显示的边 98.8% 两边都有生日) 纠正过的在关系后打一个 `*`,hover 显示「源数据记的是「mother」,与双方年龄不符,已按年龄纠正」—— 客服跟宿主对账时能看出差异在哪,而不是以为 PAC 显示错了。载荷同时留 relationshipRaw。 口径收在 @pac/types/kin-relationship.ts(纯函数,附实证数字),11 条用例锁住, 其中"同岁也算矛盾""配偶不许被纠""缺生日不猜"三条是最容易被后来人改坏的。 本地实测(王红兵 58 岁):源里的「妈妈 王迪 31岁」现在显示「子女* · 31岁」,配偶 59 岁不动。 717 tests / 47 suites + 两端 typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
落表:plan_executions 加一列 inaccurate_treatments TEXT[] NOT NULL DEFAULT '{}'。 · **加列不建表**:它是 abandon_reasons 里 'inaccurate' 那项的限定词,不是独立事实 —— 同一次提交、同一条 execution、基数 ≤8。单独建表只多一次 join。 · **存 code 不存中文**:这列唯一价值是可统计(哪类召回最容易被判不准 → 回去改规则)。 中文措辞两天内改过三轮(潜在种植→种植治疗→种植),存中文等于把当时措辞烤进历史数据。 (同类教训:原 abandon_other 自由文本列就是因为"只能取到字面量、统计不了"被删的。) · **不塞 notes**:自由文本统计不了,正好废掉这个字段的唯一价值。⚠ ️ 按业务口径明确两条,都写进 schema 注释免得后来人误解: ① **不参与抑制**。抑制仍是信号级、按 plan 全部 reason 一起压 —— 客服选"只有种植不准" 不会只放过根管那条。看到这列别以为闸接上了。 ② **跟「其他原因」不构成关联**。只在勾了 inaccurate 时收集/落库;后端落库时再判一次, 免得前端残留勾选污染统计口径。 必填校验前后端各一道。服务端那道不是冗余:这条反馈事后补不回来(没人会为已结案的单再来一遍), 收进来一批空的就等于白填。 候选项 = 本患者画像 potential_treatment.types(同召回池卡片那排标签的来源),所以每人不同; 中文在前端查 POTENTIAL_TREATMENT_CARD_LABEL(改措辞即时生效)。 患者没有潜在治疗标签时不卡必填 —— 理论上不该发生,真遇到宁可放行,不让客服卡在填不了的必填项上。 708 tests / 46 suites + 两端 typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
## 测试服全量实证(不是本地样本) jvs-dw 43,279 条亲戚边,与年龄矛盾 428 条 → 1.0%,方向基本可信 friday 2,427 条;只看父母/子女**对**更刺眼: mother↔ child 326 对里只有 8 对方向对(2.5%) father↔ child 231 对里只有 9 对(3.9%) → 97% 反了 ## 根因 data/friday/assemblers/patient_relation.yaml 的 enum_mapping 假设源码语义是 「本人是对方的 X」,于是映射时取了**逆关系**(码2 爸爸 → child)。数据说这个假设是错的: FRIDAY 存的码本来就是「对方是本人的 X」,跟 PAC 契约同向 —— 多反转了一次。 互反边可以证:秦佳(47)--mother-->韩秦瑜(21) 与 韩秦瑜--child-->秦佳 两条边互相自洽、 但都与年龄矛盾;去掉那次反转两条就都对了。⚠ ️ 摄入侧的修法(改 yaml + 重摄该资源)另开一件事 —— patient_relation 是 upsert 资源、 不进 transaction,没有原文可 reparse,得从源重拉。 ## 这次做的是展示侧:年龄定方向,不降级 按你的要求不降级成「亲属」,而是**实时算出真实关系**: · 标成长辈但对方更年轻 → 子女 / 孙辈 · 标成晚辈但对方更年长 → 按对方性别拆父/母(性别缺 → 中性「父母」,不硬猜) · 配偶 / 兄弟姐妹是对称关系,没有方向可纠,原样返回(同岁配偶很正常,不许被误标) · 任一方缺生日 → 原样返回,不猜(会显示的边 98.8% 两边都有生日) 纠正过的在关系后打一个 `*`,hover 显示「源数据记的是「mother」,与双方年龄不符,已按年龄纠正」—— 客服跟宿主对账时能看出差异在哪,而不是以为 PAC 显示错了。载荷同时留 relationshipRaw。 口径收在 @pac/types/kin-relationship.ts(纯函数,附实证数字),11 条用例锁住, 其中"同岁也算矛盾""配偶不许被纠""缺生日不猜"三条是最容易被后来人改坏的。 本地实测(王红兵 58 岁):源里的「妈妈 王迪 31岁」现在显示「子女* · 31岁」,配偶 59 岁不动。 717 tests / 47 suites + 两端 typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
- 话术头部新增「关联客户」:亲戚姓名/关系/年龄 + 档案链接(配了 VIEW_PATIENT 跳宿主, 没配回落 PAC 工单页,都新标签页开) - 画像标签按业务字典 A/B/C/D 类区上色 + 定序,首屏与画像详情抽屉收成一套(删掉原三分组) - 选患者列 / 详情左栏宽度 300 → 320
luoqi committed -
原来只跳 PAC 自己的工单页。宿主档案才是客服真正要看的那一页(有真手机号、有全量病历), PAC 工单页只是兜底 —— 所以跟本人的「原始档案」同一套逻辑: 配了 actionUrls.VIEW_PATIENT → 跳宿主档案,占位换成**这位关系人**的 {patientId} / {medicalRecordNumber} 没配 → 回落 /plans/<planId>(本 scope 内的活跃工单) 都没有 → 只显示信息,标「无档案入口」(原来叫「无工单」,现在两条路都可能缺,措辞跟着改) 为此后端 include 补了关系人的 externalId / medicalRecordNumber —— 注意 VIEW_PATIENT 模板里的 {patientId} 是**宿主侧 id**,不是 PAC 的 uuid,拿 relatedPatientId 去填会得到一个查不到人的链接。 关系人没建档时 externalId 退回边上的 relatedExternalId(那个始终有)。 本地两条路都实测: 未配 → /plans/2669df95… 配了 → …/patient?pid=115199&mrn=JN0A016246 —— pid/mrn 是**关系人的**(本人是 110959/JN0A025573), 没有串成本人的 id 708 tests / 46 suites + 两端 typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
一家人常在同一家诊所看牙。客服打电话前看到"这人的配偶也是我们的客户",既能顺口关心、 也能顺手跟进那一位,所以放在话术头部(AI 简报下方)而不是收进抽屉。 patient_relations 表本来就有(摄自 fact_customer_referee_out,family_structure 特征在用), 详情接口原来也已经返回 contacts,这次补三件:
⭐ 只列**亲戚**。新增 KIN_RELATIONSHIPS 白名单(配偶/子女/孙辈/父母/祖辈/兄弟姐妹), friend 和 other **刻意不进** —— 那条边表摄自推荐关系,other 占了一半以上(本地 4043/6222), 混的是推荐人、代付人这类"认识但不是亲属"。字典把 other 译成「亲属」,但照它列出来 客服会把推荐人当家属去问病情,比不显示更糟。 (sibling 这里算亲戚,而 family_structure 把它算"非直系" —— 两处判的不是同一件事,口径不同是有意的)⭐ 年龄:从关系人 birthDate 现算(include 补 birthDate)。⭐ 链接目标 = PAC 自己的工单页,**且必须过 scope**。关系人常和本人不同品牌/诊所,不过滤就会 给出一个点进去 404 的链接 —— 那比"没有链接"更糟,客服会以为系统坏了。 批量一条 SQL 查(loadRelatedPlanIds),查不到 → planId=null → 只显示信息、位置上标「无工单」。 新标签页走 openHostUrl(带 sandbox 三级兜底),不顶掉当前工单。 未建档(linked=false / 无姓名)的关系不出行:一行只有关系没有人,客服拿不到任何可用信息。 本地实测(王红兵):渲染出「王希亮 配偶·59岁 → 关联客户档案」「王迪 妈妈·31岁 → …」, href=/plans/<id> target=_blank rel=noopener noreferrer。 708 tests / 46 suites + 两端 typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
## ① 类区 = 唯一一套分类 A/B/C/D 本来就在 PERSONA_FEATURE_SPECS 每个标签上方的注释里(《客户画像标签字典 v3.0》的 编号 A.1.1 / B.1.1 / C.1.1 / D.2.4…),16 个全有,只是没结构化。现在提成 display.category: C 临床需求(琥珀) 治疗史 · 潜在治疗 · 急迫等级 B 价值与阶段(靛蓝) 价值分群 · 生命周期 · 权益身份 · 转介绍达人 D 行为与偏好(玫红) 折扣锚点 · 禁忌标签 · 特别关注 · 治疗敏感 · 时间偏好 A 基础属性(灰) 获客渠道 · 年龄段 · 性别 · 家庭构成 类区序 C→B→D→A(业务定,不是字母序 —— 字母序会把"基础属性"排首屏开头,客服最不需要的 东西占最显眼的位置)。类区内序沿用 7-29 业务给的相对顺序。
⭐ **删掉了原来那套三分组**(跟进要点/价值与阶段/基础属性)。两套分类并存的代价是加标签时 要想"归哪组"两遍、而且两边各自漂;更直接的问题是同一批标签在首屏和抽屉里是**两个顺序**, 客服在抽屉里得重新找一遍。现在首屏 chip 和抽屉共用 personaFeatureSortKey,颜色共用 PERSONA_FEATURE_CATEGORY_META —— 真正同出一源。 首屏 chip 从统一素色改成**按类区 4 色**(不是按标签 16 色 —— 一标签一色等于没有层次); 抽屉分组标题补同色小圆点,两处认的是同一套色。 ## ② 两栏宽度 300 → 320(选患者列 + 详情左栏)⚠ ️ 副作用:xl 断点(1280)恰好那档,中栏被压到 196px(原 236px)。宽屏无影响, 1280 附近本来就挤,这次更挤了 —— 要治得动断点,不在本次范围,已在回话里说明。 ## ③ Tailwind 陷阱记在注释里 chip 的颜色类必须整串来自 TONE 表(字面量),别写 `'hover:' + C.bg` —— Tailwind 静态扫源码, 拼出来的类名不会被生成,表现为"颜色没生效"且难查。 新增 5 条防漂移测试:每个标签必有合法类区、类区序覆盖四类且等于 C→B→D→A、 每类有中文名+颜色、同类区内 order 不重复、首屏白名单排完类区下标单调不减。 708 tests / 46 suites + web typecheck 通过。本地实测首屏三色与抽屉分组色一致。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
- 新标签页改一步 open —— 修宿主加 allow-popups 后的 SecurityError(两步走断 opener 导致) - 召回池卡片去掉优先级那排五个色点,只留分数 - postMessage 字段名简化 desc / treatments / stage;早期矫治 → 早矫 - 交付文档补 allow-popups-to-escape-sandbox 的说明与完整 sandbox 推荐串
luoqi committed -
宿主加上 allow-popups 后炸了两条,都是上一版那个两步走导致的: Unsafe attempt to initiate navigation for frame with URL 'about:blank' … The frame attempting navigation is sandboxed and is trying to navigate a popup, but is not the popup's opener and is not set to propagate sandboxing to popups. Uncaught SecurityError: Failed to execute 'replace' on 'Location': The current window does not have permission to navigate the target frame to '<host url>'. 因果:**opener 一断,我们就不再是那个弹窗的 opener**,而沙箱化的 frame 只有作为 opener 才有权 导航它 → 第二步 location.replace 被拒,新标签页停在空白页。顺序反过来也不行(导航到跨源之后 再设 opener 会抛)。上一版之所以两步走,是想"手工断 opener 以等效 noopener、同时保留可检测性" —— 这个组合在沙箱下不成立。 改成一步 `window.open(url, '_blank')`: · 可检测性仍在 —— 被拦时返回 null(features 里**不写** noopener;写了成功也返回 null, 那才是分不清被拦和成功的写法) · opener 只做尽力而为的切断(跨源会抛,catch 掉)。可接受:目标是宿主管理页配好的自家地址, 不是用户输入的任意站点,tabnabbing 面本来就很窄。 三级兜底(新页 → 顶层 → 本 frame)保持不变。 交付文档补上:只给 allow-popups 时新标签页会**继承沙箱**,宿主自己的页面在里面可能功能不全, 建议连 allow-popups-to-escape-sandbox 一起加,并给出完整推荐的 sandbox 串。⚠ ️ 内嵌浏览器面板自身拦弹窗,"正常开出新标签页"这一支我这边仍证不了; 但两步导航已从结构上去掉,SecurityError 那类报错不会再有。web typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
业务 2026-07-30。分数本身已经带档位信息(而且还按档着色),五个点是同一件事的 第二种编码 —— 一行里两处讲优先级,反而要人对照着看。 档位色保留在数字上(极低/低 绿 → 中/高 琥珀 → 极高 玫红),扫一眼仍分得出轻重; 算分明细仍走外层 PriorityHover,没动。
⚠ ️ 只改左栏列表卡片。详情页顶栏那个「优先级 ●●○○○ 低」是 shared.tsx 的 PriorityBar (带文字档位标签),不是同一个组件,业务没要求动。 本地实测:卡片只剩 3.12 / 2.74,色点已无;web typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
- 嵌宿主 sandbox iframe 时新标签页被拦 → 三级兜底(新页 → 顶层 → 本 frame) - 去掉「已在新标签页打开宿主预约页」toast;顶栏「回访」按钮改「跟进」 - 打开潜在治疗的 postMessage 补 desc / treatments / stage 三个字段 - 新增交付文档 docs/integration/postmessage-actions.mdx(可直接给对接方)
luoqi committed -
字段名按对接方意见简化: pendingTreatmentDesc → desc potentialTreatments → treatments caseStage → stage 信封(source/type/action)不动。这三个字段还没交付给任何宿主,现在改是零成本; 之后再改就是毁约(宿主的解构会拿到 undefined),所以测试里把三个名字锁死、 并断言旧名字不许还留在交付文档里(留着对方会照旧名写)。 早矫的项目名从「早期矫治」改「早矫」。它砍后缀砍不出来 —— 加一张**只有一条**的例外表, 注释写明"能靠砍后缀得到的别往这里堆",免得又退化成两张手维护的中文表。 界面上仍叫「早期矫治」(卡片措辞不动)。
⚠ ️ 与列表 API 的 PlanPatientBrief.potentialTreatments 无关 —— 那是 PAC 自己的 REST 字段, 不是宿主契约,不跟着改。 703 tests / 46 suites + web typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
发给宿主的是 ["种植","修复"] 而不是 ["种植治疗","修复治疗"] —— 宿主拿它当**项目名**落单, 带后缀读起来像句子、不像项目。界面上客服看到的仍是「种植治疗」(业务 7-29 定的展示措辞), 两处措辞不同是有意的。
⭐ 从卡片措辞**推导**而非另立一张表(potentialTreatmentItemName = 砍掉结尾的「治疗」): 两张手维护的表必然漂。early_ortho 的「早期矫治」本来就没这个后缀,原样保留 —— 它不叫「早矫治疗」,也不该被砍成「早矫」。 交付文档同步:字段说明 / 示例 JSON / 8 类取值表 / 监听示例注释全部换成项目名, 并加一段 Callout 说清"界面带后缀、载荷不带"是有意的。 测试跟着改成查**项目名**而不是卡片标签(发出去的是前者,文档要跟载荷一致、不是跟界面一致), 再加一条:砍后缀不许砍出空串、不许换词、early_ortho 必须原样。 702 tests / 46 suites + web typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
载荷从 `{patientId}` 扩到: pendingTreatmentDesc 待治疗描述 —— 取页面顶部那句 AI 召回简报;还没生成好则退回 结构化召回原因文本;都没有 → 空串(字段一定在,别让宿主判 undefined) potentialTreatments 关联治疗项目 —— 中文标签数组,与召回池卡片同一套措辞(8 类) caseStage 病例阶段 —— 恒「已咨询」⭐ 三个字段一律取**客服此刻在页上看到的东西**,不另算一套:宿主建出来的单要和客服刚读的 那句话对得上,否则对账时说不清是谁改的。所以简报由 RecallBriefLine 拿到后回报给父层 (它本来就负责 get-or-generate),不再单独查一次 —— 否则会出现"页面显示 A、发过去 B"。⭐ 契约收在 @pac/types/host-action-message.ts 而不是组件里:它是**对外接口**,宿主照它写监听。 放在 types 意味着改字段过类型检查 + 有文档同源,而不是某个组件里悄悄多塞一个 key。 兼容纪律写进注释:只增字段、不改已有语义、不删字段。⚠ ️ 这三个字段**刻意不进 URL 模式**:长文本 + 数组塞 query string 会撞长度上限, 还会把病情描述写进浏览器历史和宿主 access log。要 URL 模式也带得宿主改成 POST。 交付文档 docs/integration/postmessage-actions.mdx(可直接给对接方): 信封 / 字段表 / 完整示例 JSON / 8 类治疗项目的中文↔ code 对照 / 宿主监听示例 (含必须校验 e.origin)/ 注意事项(URL 模式不带、单向无回调、sandbox 别漏 allow-popups)。 加防漂移测试:信封字面、caseStage 恒值、8 类标签与 code 必须在交付文档里列全 —— 这份文档是发给外部照着写的,漂了要等联调才暴露。 701 tests / 46 suites + web typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
## ① 内嵌宿主时控制台报错、点了没反应 线上(嵌在宿主 iframe 里)实测: Blocked opening '<url>' in a new window because the request was made in a sandboxed frame whose 'allow-popups' permission is not set. 宿主的 <iframe sandbox> 没给 allow-popups → window.open 直接被浏览器拦掉。 上一版(7-29 改新标签页)只写了一行 window.open,**连"被拦了"都不知道**,表现就是点了没反应。 openHostUrl 改成三级兜底:新标签页 → 顶层跳转(老 _top 行为)→ 本 frame 内跳转。 能开新页就开,开不了也得让人到得了那个页面。
⚠ ️ 检测"被拦"不能靠 window.open 的返回值配 noopener —— features 里带 noopener 时 **成功也返回 null**,永远分不清。改成先开同源 about:blank、拿到句柄手工断 opener 再导航, 效果同 noopener 但可检测。⚠ ️ 两个 a 标签(原始档案 / 原始病历)也改成 onClick 走 openHostUrl:声明式的 target="_blank" 在 sandbox 下同样被拦,而那条路我们既拦不到也补不了。href 保留让 右键"复制链接"仍可用。 根治仍在宿主侧 —— 注释和 console.warn 都写明了:请对接方给 iframe 加 allow-popups (想让新页不继承沙箱再加 allow-popups-to-escape-sandbox)。 ## ② 去掉「已在新标签页打开宿主预约页」toast 新标签页开出来用户自己看得见,再报一句是噪音。失败路径的提示都还在。 兜底路径也刻意不弹 toast:下一行就导航走了,提示根本来不及被看见。 ## ③ 顶栏「回访」按钮文案改「跟进」 这个按钮跳的是宿主侧动作页,落到宿主那边不一定叫回访;而 PAC 里「回访」已被 "诊所回访记录 / 历史联系"占着,同一个词指两件事。⚠ ️ 只改按钮字面,槽位 key(OPEN_RETURN_VISIT)不动 —— 那是宿主配置里的键名。 本地实测:打桩让 window.open 返 null(模拟 sandbox)→ 确实走兜底跳到了宿主页; 按钮 DOM 是 title="跟进" / 文案"跟进";预约不再弹成功 toast。⚠ ️ 未实测的:真 Chrome 里"正常开出新标签页"这一支 —— 内嵌浏览器面板本身拦弹窗, 两种 window.open 形式都返 null,证不了。697 tests / 45 suites + web typecheck 通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed
-
- 29 Jul, 2026 3 commits
-
-
- 品牌:主色换 PANTONE 286 C(#0032A0),teal-* 全站改名 brand-* - 话术:自报家门改「我是{诊断医生}医生的助理X」(三档 promptVersion 全 bump) - 详情页:关键画像标签上首屏;宿主槽位跳转改新标签页;隐藏「牙位」「历史联系·详情」 - 召回池:到诊派生字段落 patient_profiles + 筛选索引;卡片信息重排;医生筛选可搜索 - 修复:排序键精度、筛选 chip 显示原始 code、筛选面板 React 重复 key、 医生名单缓存 6h→10min、客服可自助返池(带归属闸)luoqi committed -
luoqi committed
-
冲突解法:两处 ensurePatientStub 之后的 stats.patientStubsCreated++ 保 main 侧 —— 那是 host-id-format-defense 加的空壳计数(回执要透出空壳患者数,否则宿主看不出 自己推早了);本分支只是没有这行,不是要删它。
luoqi committed
-