# Treatment Aftercare Recall — 治疗链已完成患者的疗效保障召回

> **scenario key**:`treatment_aftercare_recall`
> **版本**:v1(PAC v1 主战场,改名自 `post_treatment_recall`)
> **状态**:🟢 已落地(数据 + 算法 + mock 全通,#3 / #4 主战场;#1 / #2 待业务拍板)

---

## 1. 业务定位 — 一句话

> **PAC 帮诊所追上"治疗链已完成患者的疗效保障"。**

PAC 关注的核心 = **治疗链已完成后**,本应执行的**周期性疗效保障**(年度复查 / 长期维护 / 定期保养)**漏掉了**。

这是**长效服务**(月级 / 年级),不是治疗中的短期复诊。

### 边界(产品红线)

| ✅ PAC v1 关注 | ❌ PAC v1 不关注 |
|---|---|
| 治疗链已完成,长效疗效保障漏做 | 治疗链进行中的复诊提醒(host 自己管)|
| 例:种植冠装完 → 年度复查没回来 | 例:正畸调整、根管多次治疗 |
| 例:牙周治疗结束 → 周期维护没做 | 例:挂号 / 短期预约 |
| | 漏诊(医生没看出来)— **W6+ AI 影像** |
| | 治疗链内中断(根管→未戴冠)— **业务拍板** |

### 跟 PAC 第二战场的关系

```
PAC v1 两条主战场(plan 1:N reason):
  ① treatment_aftercare_recall   ← 本场景(主战场,本文)
  ② treatment_initiation_recall  ← 新链召回(漏治新链),见独立文档
```

同一 patient 命中两条 → 同一 plan 内两个 reason → 客服一张卡片汇总展示。

---

## 2. "治疗链已完成"如何定义

"已完成" = **整条治疗链的临床主治疗节点全部做完**,进入长效保养期。

| 治疗 | "链已完成"的判定(医生临床定义)| 之后的"疗效保障期" |
|---|---|---|
| 种植 | 种植冠装完(植体 + 修复完成)| 年度复查终身 |
| 牙周 | 主治疗结束(SRP / 翻瓣术等做完)| 3-6 个月维护周期 |
| 正畸 | 主治疗 + 保持期均完成(医生判定)| 长期保持复查 |
| 根管 | 根管 **+ 冠修复** 都完成 | 6 个月定期检查 |

⚠️ **正畸保持器 / 根管未戴冠 — 属于"链内中断"待业务拍板**,本场景**严格不含**。

---

## 3. 已落地的子场景 ✅

| 子场景 | 触发 | 时间窗(默认,业务可调)| 业务价值 |
|---|---|---|---|
| **#3 种植年度复查** | 种植冠装完,无年度复查 encounter | 330-540 天 | VIP 含金量高,防种植失败 |
| **#4 牙周维护期** | 牙周治疗完成,无周期维护 | 150-360 天 | 长尾高频复购,防复发 |

数据要求(已具备):
- `encounter_record.content.treatments[]` 含 `treatment_category = implant / periodontic` + `status = completed`
- 后续无 `related_treatment_subject_id = <主治疗 encounter>` 的复查 encounter

---

## 4. 待业务拍板的子场景 ⏳

| 子场景 | 现状 | 业务问题 |
|---|---|---|
| **#1 根管未戴冠** | mock 数据已含,fact 已落地 | 根管完成未戴冠 = 治疗链内中断 — 算不算 PAC?(host 治疗中提醒 vs PAC 长效中断召回)|
| **#2 正畸保持期** | mock 数据已含,fact 已落地 | 保持期算"正畸链一环"还是"链结束后保养"?医生定义 |

**业务方决策点**:
1. 拍板"链结束"的医学边界(医生确认)
2. 决定 #1 / #2 是否归入本场景,**或拆独立 scenario** `treatment_chain_disruption_recall`(待启用)
3. 现 mock 数据先留,业务定后再调整 verify-scenarios.ts 的期望集

---

## 5. 召回 SQL 形态(已实现,JSONB 路径)

```sql
-- #3 种植年度复查(示意)
SELECT DISTINCT p.external_id
FROM patients p
JOIN patient_profiles pp ON pp.patient_id = p.id
JOIN patient_facts pf_impl ON pf_impl.patient_id = p.id
WHERE pf_impl.status = 'active'
  AND pf_impl.type = 'encounter_record'
  AND pf_impl.kind = 'actual'
  AND pf_impl.occurred_at BETWEEN <540 天前> AND <330 天前>
  AND EXISTS (
    SELECT 1 FROM jsonb_array_elements(pf_impl.content->'treatments') t
    WHERE t->>'treatment_category' = 'implant'
      AND t->>'status' = 'completed'
  )
  AND p.active = true
  AND pp.do_not_contact = false AND pp.deceased = false
  AND p.phone IS NOT NULL AND p.phone <> ''
  AND NOT EXISTS (
    SELECT 1 FROM patient_facts pf_review
    WHERE pf_review.patient_id = p.id
      AND pf_review.type = 'encounter_record'
      AND pf_review.content->>'related_treatment_subject_id' = pf_impl.subject_id
      AND pf_review.occurred_at > pf_impl.occurred_at + INTERVAL '300 days'
  )
```

详见 `apps/pac-service/src/cli/verify-scenarios.ts`(4 子场景全 SQL)。

---

## 6. priorityScore 算法

```
priorityScore =
    sub_scenario_base_weight       // 子场景基线(种植 85,牙周 60,业务可改 Host.priorityRules)
  + value_bonus(persona.value)      // VIP 加权
  + risk_bonus(persona.recall_risk) // 失访风险
  + recency_bonus(daysSinceLastVisit)
```

| 子场景 | 默认基线 | 理由 |
|---|---|---|
| #3 种植年度复查 | 85 | VIP 含金量高 + 失败后果重 |
| #4 牙周维护期 | 60 | 长尾高频但单次价值低 |

---

## 7. 数据完整性(实施前置)

| 必备数据 | 状态 |
|---|---|
| `encounter_record.content.treatments[]`(含 category + status)| ✅ 已就位 |
| `encounter_record.content.next_steps[]`(含 expected_next_step)| ✅ 已就位 |
| 复查 encounter 通过 `related_treatment_subject_id` 引用主治疗 | ✅ assembler.yaml 已配 |
| Persona feature(value / recall_risk)| ⏳ W3 PersonaService 实施后 |

---

## 8. 触发频率与去重

- 每日 cron 全扫(主路径,W3 PlanGeneratorService)
- 单 patient 最多 1 条 active plan(同 scenario 自动合并到现有 plan 的 reason 集合)
- 触达频控:`plan.contact_attempts` 累计(patient 级,熔断阈值 3 次)

---

## 9. 文档关联

- 启治召回(漏治新链) → `docs/scenarios/treatment_initiation_recall.md`
- DB 模型 → `docs/db-design-v2.md`(FollowupPlan / PlanReason)
- mock 数据集 → `apps/pac-service/data/mock-demo/`
- 通用 schema 决策 → `docs/db-review-confirm.md`
