Commit 835edcc9 by luoqi

docs(sync): 更正增量感知的机制描述 —— cohort 模式走 UNION 分支,不走反向拉

前两次 commit 把机制写成"靠 reverse_pull_from 反向拉主档"。测试服实测日志里
cohort 模式**没有**出现反向拉那一行:reversePullPatientMaster 只在 loadAllTables
(single-shot)里调用,日常增量走 loadTablesForCohort,不经过它。

真正生效的是 listPatientPairs 把每张「配了 cursor 且有水位」的表拼成 UNION 分支来列患者
—— 也就是 incremental.per_query 里给 fact_complex_cases_out 配的 changed_at。
reverse_pull_from 的声明仍保留(single-shot 路径要用,且把硬编码挪进 yaml 本身是目的),
但注释已写明它在 cohort 模式下不参与。

同时补一条上线须知:**新表首轮不生效** —— 无历史水位 → cursorValue 为空 → 该分支被跳过,
第一轮只建水位,第二轮起才感知变化。实测第一轮 UNION 是 6 张、第二轮才 7 张。

最终验证(第三轮增量,10153aab 之后):6 个"末次就诊在 2025-09 ~ 2026-04、但复杂病例刚变"
的患者全部写上 host_follow_up_active=t,时间戳与该轮一致 —— 主档 cursor 不可能够到他们,
只能是复杂病例表的变化带进来的。全库 t=1164 / f=14553 / null=323576。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent 8d21b26f
...@@ -115,9 +115,12 @@ sql_source: ...@@ -115,9 +115,12 @@ sql_source:
clinic_scope: clinic_scope:
from_table: dw_group.fact_emr_treatment_out from_table: dw_group.fact_emr_treatment_out
org_column: organization_id org_column: organization_id
# 增量「反向拉主档」的来源表 —— 这些表有自己的 cursor,它们变了就要把对应患者的主档补拉回来 # 增量「反向拉主档」的来源表 —— 这些表变了就把对应患者的主档补拉回来
# (主档 cursor 是 last_visit_time,人不来诊拉不到)。前四张是历史默认(EMR 编辑等场景); # (主档 cursor 是 last_visit_time,人不来诊拉不到)。
# fact_complex_cases_out 是本次新增:宿主开/关复杂病例时把人带进 cohort,跟进闸才跟得上。 # ⚠️ **仅 single-shot(非 cohort)模式走这条**。jvs-dw 日常增量是 cohort 模式,
# 靠 incremental.per_query 的 cursor 拼 UNION 分支列患者(见上面 fact_complex_cases_out
# 的注释),不经过反向拉。这里保持声明是为了两点:① 该列表原先四张表名硬编码在通用
# service 里,挪进 yaml 才符合"宿主差异只在 yaml";② single-shot 路径仍然会用到。
reverse_pull_from: reverse_pull_from:
- fact_appointment_out - fact_appointment_out
- fact_emr_treatment_out - fact_emr_treatment_out
...@@ -151,8 +154,15 @@ sql_source: ...@@ -151,8 +154,15 @@ sql_source:
# ── 复杂病例(潜在治疗池)── # ── 复杂病例(潜在治疗池)──
# ⚠️ 本表**不产 fact、无 assembler**,拉它只为一件事:增量时把"病例变了但人没来诊"的患者 # ⚠️ 本表**不产 fact、无 assembler**,拉它只为一件事:增量时把"病例变了但人没来诊"的患者
# 带进 cohort(见 cohort.reverse_pull_from)。真正的判定在上面主档 SQL 的标量子查询里。 # 带进 cohort。真正的判定在上面主档 SQL 的标量子查询里。
# 不拉它的话:患者不来诊 → 主档 cursor(last_visit_time)拉不到 → 病例开/关 PAC 永远不知道。 # 不拉它的话:患者不来诊 → 主档 cursor(last_visit_time)拉不到 → 病例开/关 PAC 永远不知道。
# ⭐ 生效的是下面 incremental.per_query 给它配的 cursor:listPatientPairs 会把每张
# **配了 cursor 且有水位**的表拼成 UNION 分支来列患者(cohort 模式走这条,不走反向拉)。
# 2026-08-01 测试服实测:接入后 UNION 从 6 张变 7 张,窗口内 19 个"末次就诊在几个月前、
# 但病例刚变"的患者被正确带进 cohort 并写上闸。
# ⚠️ **新表首轮不生效**:无历史水位 → cursorValue 为空 → 该分支被跳过(见
# clickhouse-source.service.ts 的 `if (!cfg.cursorValue) continue`)。第一轮只建水位,
# 第二轮起才开始感知变化。上线时别把首轮的"没反应"当 bug。
# changed_at:updated_gmt_at 有 44% 为 NULL(建后没改过,恰是刚入池的新病例), # changed_at:updated_gmt_at 有 44% 为 NULL(建后没改过,恰是刚入池的新病例),
# 直接拿它当 cursor 会 `NULL > x` 恒 UNKNOWN 静默漏掉这批 → 必须 coalesce 到 created_gmt_at。 # 直接拿它当 cursor 会 `NULL > x` 恒 UNKNOWN 静默漏掉这批 → 必须 coalesce 到 created_gmt_at。
fact_complex_cases_out: | fact_complex_cases_out: |
......
...@@ -126,6 +126,11 @@ export const ClickHouseSourceSchema = z.object({ ...@@ -126,6 +126,11 @@ export const ClickHouseSourceSchema = z.object({
/// 患者不来诊则主档拉不到他 —— 但他的**事实**可能变了(EMR 被编辑、复杂病例被关闭)。 /// 患者不来诊则主档拉不到他 —— 但他的**事实**可能变了(EMR 被编辑、复杂病例被关闭)。
/// 增量跑完后从这些表里收集患者键,反向补拉一次主档,保证"事实变了主档也在场"。 /// 增量跑完后从这些表里收集患者键,反向补拉一次主档,保证"事实变了主档也在场"。
/// ///
/// ⚠️ **只作用于 single-shot(非 cohort)模式** —— reversePullPatientMaster 仅在
/// loadAllTables 里调用。cohort 模式下同一诉求由 listPatientPairs 的 UNION 分支满足
/// (每张"配了 cursor 且有水位"的表各出一条分支来列患者),不经过反向拉;
/// 且主档在 cohort 模式下不再注入 cursor,被带进来的患者主档一定拉得到。
///
/// 【为什么要可配】此前是四张 jvs-dw 表名硬编码在 service 里,新增一张要改通用代码; /// 【为什么要可配】此前是四张 jvs-dw 表名硬编码在 service 里,新增一张要改通用代码;
/// 而"哪些表的变化该带出主档"本就是**宿主口径**(取决于该宿主哪些表有独立 cursor)。 /// 而"哪些表的变化该带出主档"本就是**宿主口径**(取决于该宿主哪些表有独立 cursor)。
/// 不配 → 保持历史默认(见 DEFAULT_REVERSE_PULL_FROM),行为不变。 /// 不配 → 保持历史默认(见 DEFAULT_REVERSE_PULL_FROM),行为不变。
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment