# DW 数据源问题清单(给 DW 团队反馈)

> **背景**:PAC W3 末系统审计 5 张 CH 表(fact_client_out / fact_appointment_out / fact_emr_treatment_out / fact_settlement_out / fact_settlement_mode_out)与 PAC 标准主体的映射,发现以下需 DW 改进的问题。
> **目的**:不是 PAC 单方面诉求,而是数据源质量提升 — 双方达成共识后由 DW 团队评估实施排期。

---

## 🔴 P0 — 直接影响业务

### 1. fact_client_out 没 phone
- **现状**:无 phone / mobile / tel 字段
- **影响**:PAC 召回硬过滤要求 `phone IS NOT NULL`,DW 不给 → 当前用假手机号兜底(无法真实触达)
- **诉求**:补 phone(合规允许范围内)

### 2. 字典完整性问题(根本性)
覆盖多种表现:
- **id 缺人类可读 name**:doctor_id / user_id 等数字 id 各事件表不带 name 快照(只 emr 表 doctor_name 有)
- **value 没 key**:settlement_type / treatName / check_name / card_type_name 全是中文名,无稳定语义码 → PAC 只能维护中文白名单(98% 命中+长尾兜底)
- **枚举值无语义文档**:status (1/2/3) / is_first (0/1/2) / recomend_type / appo_status 各码含义无文档
- **看似有 code 实际不可用**:treat_plan.planCode 是 hash(每条独立,非类别码)

**诉求**:
1. 所有 id 字段在事件表带 name 快照(doctor_name 进 settlement/appointment 表)
2. 类别字段补稳定语义码(诊断填 ICD;治疗归集团项目字典;card 拆 VIP/医保/活动/团购 独立字段)
3. 枚举值含义文档化(status 1=? 2=? 3=?)

### 3. fact_client_out 是 ADS 聚合层(应为 DWD)
| 表 | 实际层级 | 评价 |
|---|---|---|
| fact_client_out(12.9万=患者数)| **ADS 聚合** ❌ | is_dental_loss / first_visit_time / customer_level / favor_doctor 一堆衍生 |
| fact_appointment_out / fact_emr_treatment_out / fact_settlement_out / fact_settlement_mode_out | **DWD 明细** ✅ | 一事件一行 |

- **影响**:PAC 不敢用聚合衍生字段(只取 name/gender/birthday/file_num 原子字段)
- **诉求**:fact_client_out 提供 DWD 版(只给身份原子字段,衍生聚合 PAC 自己算)

### 4. 写入方式:appointment 是混合 upsert + 改约新建,其他未明
- **appointment**:状态流转(scheduled→arrived→completed)是 in-place upsert(只看终态);改约 = 新建 + 老的标"已改约"(部分留痕)。**已改约占 44.7%** 已映射 rescheduled
- **emr / settlement / settlement_mode**:写入方式未明确(append 还是 upsert?)
- **影响**:cold-import 只能看到终态,无法监控 appointment_created/changed/cancelled/fulfilled 等动作事件;爽约率/改约频率/漏斗分析需事件流
- **诉求**:除患者外用 **append 事件流**(状态变更新增行而不是改原行),或提供独立"状态变更日志表"

### 15. ⏰ 时间字段类型违约:`in_time` / `billing_date` 是 `DateTime`(UTC),其余是北京裸串
- **契约**:该数仓所有时间 = **北京时间(naive 字符串)**。PAC 据此一律按北京解析 → 存 UTC(PAC 内部恒 UTC)。
- **违约**:`fact_appointment_out.in_time`、`fact_settlement_out.billing_date` 是 ClickHouse **`DateTime` 类型**(内部 UTC 瞬时),其余时间列(created_date/updated_date/appo_datetime_str/rq…)都是 `String`(北京裸串)。
- **后果**:CH 把 DateTime 序列化成裸串(如 `in_time` UTC `2025-09-26 03:09:05` = 北京 11:09,无 `Z` 标记)→ PAC 把它当北京裸串又 −8 → 存成 UTC `2025-09-25 19:09`(**比真实慢 8 小时**)。
  - 实例:患者王振中,真实到诊北京 11:09(=created_date 11:08 吻合),encounter occurred_at 被弄成 UTC 19:09(本地 03:09)→ 与同次 treatment(北京 11:10)跨了 UTC 自然日 → **就诊次数虚增**(2 次,实 1 次),并殃及 lifecycle 分期 / RFM 频次。
- **PAC 立场**:不为错误数据在算法层做时区兼容(本末倒置)。PAC 内部恒 UTC、按契约(北京 naive)解析,这是对的。
- **诉求**:DW 把 `in_time` / `billing_date` 改成与其余列一致的**北京 naive 字符串**(或全库统一带 `Z`/offset 的 tz-aware,二选一,别混)。在宿主修正前,这两列派生的时间(尤其 encounter.occurred_at)系统性慢 8h。

---

## 🟡 P1 — 数据质量

### 5. settlement 在"按牙类"治疗项目上缺 tooth_position
- **现状**:settlement_out 无任何 tooth 字段(完整字段扫描确认);按牙治疗(种植/拔牙/根管/充填)的项目本应带牙位,实际只在 EMR 自由文本(emr.dispose 39% / treat_plan 46% 带 toothPosition)
- **临床合理**:洁牙/X光检查/全身检查等**全口/检查类项目本就无牙位**,不强求
- **影响**:PAC 牙位级排除做不了 — 36 牙做过种植会挡掉 21 牙缺口召回(只能 patient 级)
- **诉求**(收窄):**只对"按牙类"治疗项目** 在结算明细补 tooth_position(种植 36/拔牙 48 这种);全口类继续无牙位 OK
- **关联键不强求**(临床多对多本质,host 端也做不到 1:1 link;诊断/治疗/结算之间只能"同 encounter + 同 tooth + 名字模糊匹配")

### 6. fact_emr_treatment_out 子表炸行
- **现状**:file_url 子表 JOIN 无 groupArray,**193万 → 39.5万真实 EMR**(5 倍膨胀)
- **PAC 侧**:已用 dedup_by 兜底
- **诉求**:DW 修(groupArray)

### 7. plan 字段 vs treat_plan 语义未文档化
- **现状**:fact_emr_treatment_out 两个治疗计划字段,**65% 完全不同**(395656 行只 4 行相同)
- **影响**:PAC 暂不接 plan(避免重复/过时数据)
- **诉求**:DW 说明两字段语义区别

### 8. settlement 4 个时间字段语义需文档化(W3 末数据推论)
| 字段 | 实测语义 | PAC 选用 |
|---|---|---|
| `billing_date` | **99.96% = emr.rq(接诊日)= 治疗发生当天** | ⭐ PAC treatment.occurredAt / payment.paidAt 用这个 |
| `rq` / `settlement_time` | 结算操作日(97% 与 billing_date 同天,偶有延后 1-64 天) | 不用 |
| `created_date` | 记录创建时间 | 不用(元数据) |

- **教训**:字段名"billing_date"字面像"开单日"以为偏行政,实际是治疗发生时的开单 = 治疗日。PAC 起初误选 rq → 治疗时间 vs 诊断时间口径不一致。
- **诉求**:DW 文档化四个时间字段定义,推荐"治疗实际时间"用 billing_date

---

## 🟢 P2 — 业务覆盖缺口

### 9. 缺投诉 / 转介绍事实表
- **现状**:DW 没 complaint 表;转介绍只有 client_out.recommend_*(聚合,17% 充实)
- **影响**:PAC complaint_record / referral_record fact_type 已定但数据源缺
- **诉求**:DW 提供这两张明细表

### 10. 接诊结束时间缺失
- **现状**:in_time 有(接诊开始),无 out_time(结束)
- **影响**:无法算接诊时长、医生效率

### 11. 多 brand ID 重复(已知坑)
- patient_id / appointment_id 等跨 brand 重复
- PAC 用 brand → tenant_id 解决,接入新 brand 复用此模式

---

## 工程契约

### 12. updated_at 字段需保证(PAC 增量同步 cursor 基础)
- 每张表需有 updated_at 列单调递增刷新(每次写入/修改都更新)
- 这是 PAC 增量 pull 的 cursor 基础
- **诉求**:确认 DW 各表都有此列且 ETL 写入时刷新

### 13. ETL 频率需说明
- 表是 view 还是物理实表?ETL 频率(实时/小时/T+1)?
- 影响 PAC 增量 pull 节奏 + 数据新鲜度
- **诉求**:DW 说明各表 ETL 时延

---

## 沟通方法论(本次审计的收获)

1. **字段名 + 注释只能参考,真实含义靠数据推**(实战:billing_date 字面像"开单日",数据推实际是"治疗发生日")
2. **临床多对多是本质**:诊断/治疗/结算 1:1 强关联是不切实际的诉求(host 也做不到),PAC 接受隐式关联(同 encounter + 同 tooth + 名字模糊)
3. **不要让 host 形态裁剪 PAC 标准**:PAC fact 标准应基于临床通用语义,某 host 没的字段留 null,不删
4. **衍生 vs 事实**:host 给的聚合(is_dental_loss / customer_level / first_visit_time)PAC 不接,PAC 自己从事实算更可信(单一真理源)

### 14. 生产 host 数据混入测试账户
- **现状**:`fact_client_out` 含 `client_name='束乾凤test'` 测试账户,跨 18 个月(2024-09→2026-04)
  累积 1531 条 `fact_settlement_out` 退费记录(净退费 ¥489.66)
- **影响**:PAC 100-cohort 把它纳入 → refund_record 99% 集中在 1 个测试账户,真实业务退费观察被掩盖
- **诉求**:DW 侧标记 / 过滤测试账户(如加 `is_test` 字段或专用 organization_id 隔离),否则
  PAC 这边只能"按 client_name LIKE '%test%'" 拍黑名单,跨 host 不可移植
