Commit d4865450 by luoqi

docs: 删 W1 评审过程稿 + 新增 AI 话术生成设计 + 修死链

- 删除 db-review / db-suggest-after-review / db-review-confirm(~3.5k 行 W1 评审
  ping-pong,决议已并入 db-design-v2 §13 + schema.prisma,无独立价值)
- 修 db-design-v2 / treatment_aftercare_recall 对已删稿的死链
- 新增 docs/algorithm/ai-script-generation.md:话术生成分档设计
  (投入档=输入/输出/步数联动;脊柱=AiCallRunner、策略=AiCall;
   知识 vs 模板分离;ScriptContext 聚焦切片不含治疗链;单一源收口清单)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 4eee5874
# AI 话术生成 — 设计文档(v1 设计稿)
> **状态**:🎨 设计稿(抛开现状重设计,待评审后落地)
> **定位**:PAC 五大算法之"⑤ AI 话术生成"的重写设计。上游消费 fact.content / persona / plan_reasons,产出客服通话脚本。
> **背景**:现状是"技能组合 + 单次生成"(skill-composer + 1 次 AiCall → 4 段)。本文重新设计为**分档(投入档)+ 脊柱-策略分离**架构。
>
> **关联**:[`pac-algorithms-overview.md`](./pac-algorithms-overview.md) §五 · [`canonical-fact-layer.md`](./canonical-fact-layer.md)(接地纪律)· [`potential-treatment-recall-flow.md`](./potential-treatment-recall-flow.md)(上游召回)
---
## 一、话术要达成什么(目标函数)
一通召回电话的脚本,本质给客服 4 件事:
```
开场 → 切入(把"该治没治"自然带出)→ 异议(患者推脱怎么接)→ close(约下一步)
```
**硬约束**:
- **接地**:只用真事实,不编(医生没说的不准说)。
- **不承诺**:不保证疗效、不报价、不写死时间(诊所无排班接口,时间只能"示例")。
- **贴这个患者**:病种/牙位/年龄(儿童对家长)/新老客。
- **失败兜底**:任何环节挂 → 退回安全模板,客服永远有东西可用。
---
## 二、核心架构:一个旋钮,三样齐动;安全地板恒定
用户(客服)只感知**一个"投入档"旋钮**(稳健 / 标准 / 深度),它同时拉动三样;而**安全/接地地板对所有档恒定**
```
投入档 ──┬─→ ① 输入 context: base → base+ → base++ (扩展块增长)
├─→ ② 输出 rail: 4段模板 → 4段自由 → 多段自由(段数模型自定)
└─→ ③ 步数: 1 → 1 → 3(深度加 计划/写/自检)
恒定地板(所有档):聚焦接地(只 canonical,不喂 raw EMR dump)+ 机器安全闸 + 兜底 + 落账
```
**关键自洽**:深度档拿到更多料(base++),正好配它多一步的**自检** —— 料越多越易跑题/编,自检来兜底。**富输入 ↔ 强校验,成对出现**
### 2.1 档位定义(输出自由度 + 投入阶梯)
| 档 | 标签(客服看到) | 输入 context | 输出 rail | 步数 | 本质 |
|---|---|---|---|---|---|
| 1 | **稳健** | base | **4 段 + 模板骨架**(标题固定、部分句式固定,填空+润色) | 1 | 紧 rail,低方差,可预测 |
| 2 | **标准**(默认) | base + 小扩展 | **4 段,标题/结构自由**(去模板) | 1 | 松 rail,贴人 |
| 3 | **深度** | base + 大扩展 | **段数不限、多段**(模型自定结构) | 3(计划→写→自检) | 无 rail,质量最高 |
- **档位选择**:系统按患者价值/复杂度给**智能默认**,客服在"重新生成"处下拉**覆盖**(与已有"模型选择 Qwen/Gemini"并列两个旋钮)。3 档封顶,避免决策疲劳。
- **"组装提示词"是三档共用底座,不分档** —— 按 患者/病种/人群 选块拼指令,稳健也做,只是拼得更死。档位差在**输出 rail / 输入丰富度 / 步数**,不在"是否组装"。
### 2.2 步骤:控制流定死,内容开放
| 层 | 放不放开 |
|---|---|
| **pipeline 编排步骤**(跑几步/什么顺序) | ❌ 代码定死(可预测/可重放/自检保证跑) |
| **话术内容的段数/多步**(深度档多段) | ✅ 模型自定(输出 rail 放开) |
| **上下文丰富度**(露出多少事实) | ✅ 按档放开(扩展块,确定性预选) |
| **步骤自主**(模型自己决定 pipeline 跑几轮) | ❌ 不放开 |
> 深度档若缺料,升级路径 = 加一个**有界事实拉取工具**(模型可就某牙再要一段 canonical 事实,调用次数/范围代码卡死),**而非**开放步骤自主。
---
## 三、脊柱(固定)+ 策略(可插)
三档共用一根**脊柱**,只换中间一段**策略插件**(同 PlanScenarioPlugin / assembler 范式)。
```
┌───────────────── 固定脊柱(所有档共用)─────────────────┐
输入 → [1 buildContext(reason, tier)] → [ ScriptStrategy(可插) ] → [3 机器安全闸] → [4 兜底] → [5 落账] → 输出
└────────────────────────────┘ ↑换这里 └────────────────────────────────────────┘
```
```ts
// 脊柱契约
interface ScriptStrategy {
tier: 'stable' | 'standard' | 'deep';
generate(ctx: ScriptContext): Promise<ScriptDraft>; // 内部步骤自己定死
}
// 脊柱控制流(定死):
// buildContext(focusedReason, tier) → strategy.generate(ctx)
// → machineSafetyGate(draft) → (fail ? templateFallback) → audit(agent_invocations)
```
加一档 = 实现一个 `ScriptStrategy`,脊柱一行不动。
### 脊柱六件(共性,恒定地板)
| # | 件 | 说明 |
|---|---|---|
| 1 | 输入契约 `ScriptContext` | 聚焦切片 + 薄画像 + 分档扩展(§四) |
| 2 | 输出契约 `ScriptDraft` | 统一结构,UI/存储不关心哪档产的(§五) |
| 3 | 机器安全闸 | 禁词/不承诺/不报价/不写死时间 —— **每档都过,不可谈判** |
| 4 | 兜底 | 任何失败 → 模板兜底(`source='template_fallback'`) |
| 5 | 落账 | `agent_invocations`:档位 / 模型 / token / cost / 用了哪些块或步骤 |
| 6 | provider 抽象 | 走 AiCall,DeepSeek / Gemini 可切 |
---
## 四、ScriptContext — 输入契约(地基)
**原则**:**聚焦切片 + 薄画像 > 全历史**。召回只为一件事(focusedReason),喂全历史 → 跑题、编造、token 爆。画像字段只**定语气**不进内容。
### base(三档一致,接地地板)
```
召回主体(聚焦,核心)——「说什么」
scenario / sub_key、诊断码+名、牙位、拖了多久(daysSince)、目标治疗(expectedCategories)、
做该诊断的医生(focusedReason 证据 fact 的 doctor_name)、
医生医嘱原话(emr.doctor_advice / treat_plan.message,**仅引用、限本次诊断相关**)
薄画像(只定语气,不是内容)——「怎么对他说」
称呼、年龄→人群(儿童对家长 / 老人慢)、新老客、价值档(persona.value)、
流失风险(persona.recall_risk)、距上次就诊、(儿童/老人)监护人
合规态(已硬过滤,带过即可)
doNotContact / deceased 已在召回层排除;触达对象 = 本人 or 监护人(儿童/老人)
```
> ⚠️ **"诊断了未启动"不需要治疗链** —— focusedReason 存在本身就代表"诊断未启动治疗",不读 chain。
### 扩展块(随档增长,仍全是 canonical 切片,非 raw dump)
| 档 | 在 base 上加 |
|---|---|
| 稳健 | 无(就 base) |
| 标准 + | 同区域相关诊断/缺口(可顺带提)、最近 1-2 次就诊**做过什么治疗**(treatment_record:类目/牙位/时间)、**上次回访结果**(returnVisits) |
| 深度 ++ | **历史召回 outcome**(recallHistory:上次为啥没成/被暂缓)、可一并邀约的其他牙问题、家庭/联系人(家庭决策)、投诉历史(避雷)、消费结构(VIP 维系话术)、相关 EMR 自由文本(**仅引用,不演绎**) |
- 扩展由脊柱 `buildContext(reason, tier)` **确定性抽取**(按 focusedReason 选块),不是把全表丢进去、更不是让模型自己捞。
---
## 五、ScriptDraft — 输出契约
```
status: ready | failed
source: agent | template_fallback
tier: stable | standard | deep
sections: [{ id, title, markdown }] // 稳健=固定4段;标准=4段自由标题;深度=多段
metadata: { model, promptVersion, blocksUsed[], stepsRun[], tokens, costCents }
```
-`plan_scripts.content`(Markdown 多子段),`source` / `agentInvocationId` 同现状 schema。
- **UI/存储与档位解耦** —— 不管哪档,都是 `sections[]`
---
## 六、安全 / 接地(地板,所有档)
| 闸 | 机制 |
|---|---|
| **接地** | 只喂 `ScriptContext`(canonical 切片);**严禁拼 raw EMR 全文 dump**;EMR 自由文本只作"引用",指令"只能引用医生原话,不可演绎" |
| **机器安全闸** | 正则/规则:禁词、无"保证治好"式承诺、无报价、close 段无加粗写死时间。**每档输出必过** |
| **深度档额外**:独立 LLM 校验 | 逐句核"每个说法能追到 ScriptContext 里的 fact" + 复查安全 → 不过则 repair / 兜底(呼应召回 oracle 的对抗哲学) |
| **兜底** | 任何失败 → 模板话术,`source='template_fallback'` |
---
## 七、患者信息清单 —— 详情页能给的 vs 能进大模型
> 调研自 `/plans/:id/full`(plan-aggregate)实际 payload + 本地全量 fact content 字段。
### ✅ 可进(结构化 canonical,安全)
| 来源 | 字段 | 用途 |
|---|---|---|
| diagnosis_record | code / name_zh / tooth_position / onset_date / severity | 召回主体 |
| treatment_record | category / subtype / tooth_position / status / started_at·completed_at | **"做过什么治疗"**(替代治疗链) |
| recommendation_record | code / tooth_position / window_days / confidence | 医生建议信号 |
| appointment_record | scheduled_at / status / complaint_category | 是否已有预约 / 就诊意向 |
| payment·recharge·refund | amount_cents | 价值/消费(薄画像) |
| returnVisits | type / status / treatmentItems / **result** | 上次回访怎么说的 |
| recallHistory | outcome / notes / scheduledNextAt | 上次召回结果(深度) |
| persona | **value / recall_risk**(只这两个,定语气) | 价值/流失 |
| patient/profile | 称呼 / 年龄→人群 / 新老客 / 距上次就诊 / 监护人 / dedicatedCs | 薄画像 |
| plan_reasons | scenario / sub_key / signals(牙位·daysSince·expectedCategories) | 召回主体 |
### ⚠️ 慎入(自由文本 → 编造风险;仅"引用"、限相关、深度档为主)
| 来源 | 字段 |
|---|---|
| emr_record | **doctor_advice / treatment_plan**(医嘱原话,base 可引用本次相关那条)<br/>illness_desc / pre_illness / exam_findings / past_history / disposal(深度档,仅引用) |
| image_record | finding(影像所见,仅引用) |
### ❌ 不进
| 排除项 | 原因 |
|---|---|
| **chains[](治疗链 5 阶段 / chain-composer)** | **后续废弃,链相关一律不进**;需要"做过什么"用 treatment_record |
| **persona.treatment_chain_status** | 名为"链状态",概念与废弃的治疗链重叠 → 不用;"诊断未启动"由 focusedReason 自带 |
| raw EMR 全文 dump | 接地纪律:不整段塞自由文本(只受控引用) |
| 身份证 / 详细地址 / 婚育等 | 召回不需要 + 合规(PAC 主档本就不存) |
| 完整消费流水 / 无关旧诊断 | 聚焦原则:让话术跑题 + 涨 token |
---
## 八、智能默认(档位路由,草案)
```
默认档 = f(患者价值, 复杂度):
低值 / 单病种单牙 → 稳健
常规 → 标准(默认)
高值(VIP/钻金)/ 多病种多牙 → 深度
客服可在"重新生成"下拉覆盖(档位 + 模型 两旋钮)
```
(阈值待业务方拍;真理源放配置,可调。)
---
## 八bis、现状落地(诚实记录)
> 抛开任务清单的"声称",**实际在跑的稳健档** = 以下三件,别的(病种 SKILL.md / 异议库 / 安全规则包)**没建**。
| 件 | 现状 |
|---|---|
| system 提示词 | `base-system.md`(铁律)+ **人群包 `population/adult|child/SKILL.md`**(4段模板骨架)|
| user 提示词 | `script-facts.ts` **确定性前置算好**(称呼/智能日期/漏诊项/风险/优势/复查时长)注入,LLM 只润色 |
| 病种差异化 | **不在 SKILL.md**,而在 `script-facts.ts` 字典(`MISSED_DIAGNOSIS_KEY_POINTS` / `SUBKEY_TO_MISSED` / `TREATMENT_DURATION`)插值 |
**已知 bug**:`jaw_cyst` 的 key `颌骨囊肿``MISSED_DIAGNOSIS_KEY_POINTS` 里只有 `囊肿` → 精确查表 miss → 颌骨囊肿召回无风险/优势要点(双跳 + exact/includes 混用的脆弱性,§九 P0 修)。
---
## 八ter、⭐ 知识 vs 模板 分离(核心架构,多档共用的关键)
> **洞察**:现在的"人群包"和"病种字典"都是**稳健档的模板**(固定结构 + 插值)。其他档接入时,**模板不能共用** —— 要把"知识/规则"从"模板"里抽出来:**知识 tier-agnostic 共用,模板只属稳健**。
### 两类内容,必须切开
| | 知识 / 规则(tier-agnostic,单一源)| 模板(稳健档专用)|
|---|---|---|
| **病种** | 风险(不处理后果)/ 趁早理由 / 复查周期 / 临床要点 —— **规则事实** | "4段·小节1现状/小节2风险…"脚手架 + 挑 1-2 条插槽 |
| **人群** | 儿童对家长 / 老人慢+复述 / 新老客语气 —— **沟通规则** | 成人 4 句 / 儿童 5 句的固定句位 |
### 三档怎么消费同一份知识
```
┌─ 病种知识(canonical-keyed)─┐ ┌─ 人群知识(child/adolescent/adult/elderly)─┐
└──────────── 单一源 知识库(tier-agnostic)────────────────────────┘
稳健 ───────────────────────────────────┼─ 程序挑选(1-2 条)→ 填 4段模板槽 → LLM 只润色
标准 ───────────────────────────────────┼─ 规则全量 → 放进 prompt 作"事实+规则" → LLM 自由组织 4 段
深度 ───────────────────────────────────┴─ 同标准 + 多段 + 计划/写/自检
```
- **稳健 = 知识 + 模板**(强约束,LLM 填空)。
- **标准/深度 = 知识(只给规则,不给模板)**,LLM 自己组织 → 模板不复用。
### 文件落位(收口)
```
script-common/ ← tier-agnostic 知识(单一源,三档共用)
knowledge/
disease-knowledge.ts ← 病种规则,**按 subKey/canonical 码 keyed**
{ label, risks[], advantages[], reviewCadence, ageFit? }
(合并现 SUBKEY_TO_MISSED + MISSED_DIAGNOSIS_KEY_POINTS + TREATMENT_DURATION
三套双跳 → 单跳;顺手修 颌骨囊肿)
population-knowledge.ts ← 人群沟通规则(child/adolescent/adult/elderly)— 从 SKILL.md 抽出的"知识"部分
safety-rules.ts ← 禁词/承诺/拍片 **单一源**(机器闸 + prompt 同引)
tiers/
stable/
template.md ← 4段模板骨架(从 population SKILL.md 抽出的"模板"部分,**仅稳健**)
stable.call.ts ← StableScriptCall(AiCall)
standard/ (future) ← 只引 knowledge,不引 template.md
deep/ (future)
```
**关键**:现 `population/adult|child/SKILL.md`**一拆为二** —— 沟通知识 → `population-knowledge`(共用);4段脚手架 → `stable/template.md`(独占)。
---
## 八quater、单一源收口清单(整理 drift)
| 东西 | 现状(多处/漂移)| 收口 |
|---|---|---|
| 病种知识 | 3 字典双跳 + exact/includes 混用(颌骨囊肿 漏)| **1 个 disease-knowledge(subKey-keyed)** |
| 人群 | SKILL.md 把"知识 + 稳健模板"混在一起 | 拆:population-knowledge(共用)+ stable/template.md(独占)|
| 禁词 | `base-system.md` 一份 + `call.ts FORBIDDEN_PHRASES` 一份(**不一致**,base 多了 免费/赠送)| **1 个 safety-rules.ts**;机器闸用它,prompt 从它生成 |
| 4 段结构规则 | `schema.describe` + `base-system.md` + `population SKILL.md` **三处复述** | base 管铁律 / 稳健模板管段内 / schema 管字段,**不互相复读** |
| ≤18 禁拍片 | 仅 prompt,机器闸不查(无 age)| safety context 加 age + 机器规则,**双保险** |
---
## 九、待定 / 下一步
| 项 | 状态 |
|---|---|
| **P0 病种知识单 config 化**(3 字典双跳 → 1 个 subKey-keyed,修 颌骨囊肿)| 🎯 先做(修 bug + 收口 + 为多档铺路)|
| **P0 ScriptContext base 已落地**(剔治疗链 + 正名,本轮完成)| ✅ |
| **P1 人群 SKILL.md 拆分**(知识 → population-knowledge 共用;模板 → stable/template.md 独占)| 🎯 标准档接入时 |
| **P1 禁词单一源**(base-system.md + call.ts 合并 → safety-rules.ts)| 🎯 |
| **P1 ≤18 禁拍片机器闸**(safety context 加 age)| 🎯 |
| **P1 4段规则去三处复述**(base/schema/模板 职责分清)| 🟡 |
| 稳健档 4 段模板 + 标准档 4 段自由 prompt 草稿 | 🎯 起草对比手感 |
| 深度档 3 步(planner schema / writer / verifier 判则)细化 | 🎯 |
| 老人(elderly)人群知识补全 | 🟡 1.5w 老人,语气慢/复述/家属 |
| 档位智能默认阈值 | 🟡 待业务方 |
| 有界事实拉取工具(深度档可选升级) | 🔵 按需 |
| 成功通话 RAG 喂养(W7+ 有 outcome 数据后) | 🔵 增强层 |
---
## ▎一句话归纳
> **一个"投入档"旋钮联动(输入 base→base++ / 输出 4段模板→多段自由 / 步数 1→3),安全接地地板恒定;脊柱固定、策略可插(每档一个 ScriptStrategy);上下文聚焦切片 + 薄画像,治疗链不进(用 treatment_record 看"做过什么");pipeline 步骤定死、话术段数模型自定。**
# PAC 数据库设计 v2(合并整理版)
> **版本:** v2.1(W2 末更新)
> **目的:** 统一收敛 PAC 数据库设计 — 替代历史 [db-review.md](db-review.md) / [db-suggest-after-review.md](db-suggest-after-review.md) / [db-review-confirm.md](db-review-confirm.md) 三份文档,作为后续唯一权威参考。
> **目的:** 统一收敛 PAC 数据库设计 — 替代已删除的 W1 评审过程稿(db-review / db-suggest-after-review / db-review-confirm),作为后续唯一权威参考。
> **状态:** ✅ v2.0 schema 已落地(13 model + 4 MIGRATION);🟡 v2.1 增量未跑 migration(等开 PR 1 一起)
> **真理源:** [`apps/pac-service/prisma/schema.prisma`](../apps/pac-service/prisma/schema.prisma) — 本文档与之保持同步,如有出入以 schema 为准。
>
......@@ -13,7 +13,7 @@
> - **PAC canonical-codes 字典集中化**:`packages/types/src/canonical-codes.ts` 单一真理源
>
> **v2.0 合并说明**(原 3 份文档保留不变,本文为优化合并版):
> - 内容来源:db-review.md(主体设计 + §21 决议)+ db-suggest-after-review.md(评审建议)+ db-review-confirm.md(最终响应单)
> - 内容来源:已合并 W1 三份评审过程稿(主体设计 + 评审建议 + 最终响应单),原稿已删
> - 删除过时章节:6 张已删除镜像表(diagnoses / treatments / appointments / charges / touchpoints / patient_fact_events)、`rulesConfig` 字段、`adapterKind` / `assigneeRole` 等已删字段
> - 收敛重复表述:三份文档中重复的"接口 1 / 接口 2"约束、隔离纪律等
> - 加上交叉引用:[architecture-v2.md](architecture-v2.md) / [design-v2.md](design-v2.md) / [three-layer-model.md](three-layer-model.md) / [host-integration.md](host-integration.md) / [schema.prisma](../apps/pac-service/prisma/schema.prisma)
......@@ -1005,9 +1005,9 @@ CREATE UNIQUE INDEX "followup_plans_one_open_per_scenario"
---
## 十三、决议清单(决策记录 — 从 [db-review-confirm.md](db-review-confirm.md) 整理)
## 十三、决议清单(决策记录)
> 完整 60+ 决议项 closure 检查 — 跟 [db-review-confirm.md](db-review-confirm.md) 等价,这里给出**关键 8 大决议**及其落地状态。
> 完整 60+ 决议项 closure 检查(原 W1 响应单已并入此处),给出**关键 8 大决议**及其落地状态。
### D1:第一层重构为 Transaction + Fact 双表 ✅ 已落地
......@@ -1153,16 +1153,12 @@ CREATE UNIQUE INDEX "followup_plans_one_open_per_scenario"
## 附录:历史文档归档
| 历史文档 | 状态 | 替代位置 |
|---|---|---|
| [db-review.md](db-review.md) | 📜 归档(W1 评审过程记录)| 决议沉淀进本文档 + schema.prisma |
| [db-suggest-after-review.md](db-suggest-after-review.md) | 📜 归档(评审建议)| 采纳的部分进本文档 |
| [db-review-confirm.md](db-review-confirm.md) | 📜 归档(响应单)| 决议清单见本文档 §13 |
> W1 评审过程稿 `db-review.md` / `db-suggest-after-review.md` / `db-review-confirm.md` 已**删除**
> (3 份共 ~3.5k 行 ping-pong 过程记录,决议已全部沉淀进本文档 §13 + `schema.prisma`,无独立价值)。
**真理源优先级**(冲突时以下排序):
1. **`apps/pac-service/prisma/schema.prisma`** — 唯一权威源
2. 本文档 [db-design-v2.md](db-design-v2.md) — 设计意图 + 决策记录
3. [host-integration.md](host-integration.md) — 对外契约
4. [architecture-v2.md](architecture-v2.md) / [design-v2.md](design-v2.md) — 上层设计
5. 历史文档(db-review / suggest / confirm)— 仅供决议溯源参考
# PAC 数据库结构 — 提议响应确认单(对应 db-review §21)
> **状态**:待确认
> **目的**:对建议文档(db-suggest-after-review.md)的所有提议,逐项确认我们的响应(采纳 / 部分采纳 / 不采纳)
> **范围**:仅限提议带来的改动,不含 PAC 已有规则
> **流程**:本文确认 → 敲定 schema 字段 → 出"枚举值确认单"二轮
---
## 一、第三层 Plan 响应
### 1.1 提议 §15:FollowupPlan 需要 `target_clinic_id` + `execution_clinic_id`
- [ ] **采纳**:`followup_plans``target_clinic_id`(nullable,业务字段,创建锁定不更新);`null` = 集团统一客服池(VIP 中心 / 中央营销 / 流失挽回)
- [ ] **采纳**:`plan_executions.clinic_id` 改名 **`executor_clinic_id`**(必填,事实字段)
- [ ] **配套**:两者**允许不一致**(代办场景:A 归属 B 代办)
### 1.2 提议 §15:Plan / PlanExecution 回写 fact 层
- [ ] **拒绝**:Plan 不回写(主语是 PAC 不是患者,创造循环自反馈)
- [ ] **拒绝**:PlanExecution 不回写(算事实但跟同步引擎拉回的 Touchpoint 重复,熔断阈值翻倍风险)
- [ ] **根因澄清**:提议 §16 把 `touchpoints` 错等同于"宿主触达 + PAC 执行 transaction",混了接口 1/2
## 二、第二层 Persona 响应
### 2.1 提议 §14:改名 Persona → Assessment
- [ ] **拒绝改名**:PDF §6.4 钦定术语是 Persona,改名破坏 PDF / 代码 / 文档对齐
### 2.2 提议 §14 + PDF §6.4:feature_key 候选范围
逐项确认(对齐业务边界,枚举值不在 schema 强约束,应用层校验即可):
- [ ] `value`(患者价值)
- [ ] `treatment_chain_status`(治疗链状态)
- [ ] `incomplete_treatment`(未完成治疗)
- [ ] `recall_risk`(复查风险)
- [ ] `engagement`(触达活跃度)
- [ ] `do_not_contact_status`(不打扰状态)
- [ ] `recommended_role`(推荐回访角色)
- [ ] `referral_relation`(推荐关系)
- [ ] `doctor_relation`(医生关系)
- [ ] `communication_preference`(沟通偏好)
- [ ] `customer_experience_risk`(客户体验风险)
- [ ] `treatment_intent`(治疗意向)
- [ ] `family_social_relation`(家庭社会关系)
### 2.3 提议 §14:`assessment_scope` 字段(某诊所视角评价 hook)
- [ ] **拒绝**:v1 集团统一运营,业务上无诊所视角差异;Phase 6+ 真出现"加盟诊所自看自评"再加
## 三、第一层 fact 重构
### 3.1 提议 §6-§10:拆 Transaction + Fact 双表
- [ ] **采纳**:**消灭 5 张事实镜像表**(diagnoses / treatments / appointments / charges / touchpoints)
- [ ] **采纳**:**拆 `patient_transactions`(append-only,操作账本)+ `patient_facts`(版本流,事实单元)双表**
- [ ] **命名**:用 `patient_*` 前缀(不用提议的 `customer_*`)— 跟现有项目定义PAC(不偏运营向) / `patients` 风格统一
- [ ] **`patients` 主档保留**(身份对象,不属事实单元)
### 3.2 提议 §3.1:`patient_identity_refs` / `patient_profile` 拆表
- [ ] **拒绝建 `patient_identity_refs`**(v1 接入契约假设 external_id 唯一,Phase 6+ 跨平台合并再加)
- [ ] **拒绝建 `patient_profile`**(v1 patients 字段可控 ~10 个,Phase 6+ 字段膨胀再拆)
### 3.3 提议 §2:三类 type 严格区分
- [ ] **采纳**:`transaction_type`(动作事实,push 主导)/ `subject_type`(被操作主体,pull 主导)/ `fact_type`(事实单元,parser 解析后下游统一)
- [ ] **采纳 push/pull 摄入路径分工**:transaction_type 跟 push 推送对应 / subject_type 跟 pull 资源对应
### 3.4 提议 §7:Fact 分 Actual / Planned
- [ ] **采纳**:`fact_kind` 字段区分(同表),不拆两张表
### 3.5 提议 §10:`patient_facts` 字段
- [ ] **采纳版本流写入模式**:每版本独立 UUID + `subject_id` 跨版本稳定 + partial UNIQUE active
- [ ] **采纳 `evidence_transaction_ids` 数组**(GIN 索引)
- [ ] **`clinic_id` 立柱但 nullable**(actual / planned 字段统一,**与提议"planned 必填" 差异**;反例:中央客服 400 接咨询 / 集团总部群发触达;必填性 per fact_type 应用层校验)
- [ ] **拒绝 `source_kind` 字段**(只装宿主事实,该字段永远 `host_transaction`)
- [ ] **拒绝 `processor_invocation_ids`**(AI 产物归 `agent_invocations.output`,不进 fact)
- [ ] **拒绝 `evidence_fact_ids`(fact 引用 fact)**(引用关系在 transaction 维度)
- [ ] **拒绝 `confidence`**(AI 置信度,不属宿主事实)
- [ ] **拒绝 `primary_clinic_id` 性能冗余**(nullable 后不需要)
### 3.6 提议 §9:`patient_transactions` 幂等键
- [ ] **采纳 `source_event_id`(nullable)** 作为幂等键 — 宿主原生事件 ID 或 adapter 合成 hash
- [ ] **拒绝提议合成键(组合 5 字段)** — 用 `source_event_id` 单字段更简洁
- [ ] **Push 路径硬要求带 `source_event_id`**,无则拒收
- [ ] **Pull 路径 adapter 合成**:`hash(externalRef, occurredAt, payloadDigest)`
### 3.7 数据流单向 — 已剔除项
- [ ] **采纳剔除**:按"Plan / PlanExecution / AI 产物不回写 fact"决议,从提议 enum 中剔除 `followup_plan_*` / `followup_executed` 4 个 transaction_type、`followup_plan` / `followup_execution` 2 个 subject_type、`followup_plan` / `followup_execution_record` / `image_analysis_result` 3 个 fact_type
### 3.8 保留枚举值逐项确认(对齐业务边界)
> 起步候选,不在 schema 强约束(应用层 zod 校验);本节确认业务范围。
> **起步策略**:transaction_type 14 个最小起步(原版);fact_type actual 12 个 = 原 10 + `treatment_record` + `diagnosis_record`(后两个由 adapter parser 从 encounter / emr / charge 衍生)。
> **后期可补**:`treatment_started/completed/cancelled` / `diagnosis_added` / `patient_registered/updated` 等专门 transaction_type(精度提升时)。
> **触达数据**:PAC 跟宿主回访模块互斥(有回访的宿主不接 PAC),无 touchpoint 类 fact;Persona engagement 应用层显式 join `plan_executions` 消费。
**transaction_type(14 个)**:
- [ ] `consultation_created`(咨询记录创建)
- [ ] `appointment_created`(预约创建)
- [ ] `appointment_changed`(预约变更)
- [ ] `appointment_cancelled`(预约取消)
- [ ] `patient_arrived`(到店签到)
- [ ] `visit_registered`(挂号)
- [ ] `patient_received`(被接诊)
- [ ] `doctor_encounter_started`(医生接诊)
- [ ] `charge_order_submitted`(收费单提交)
- [ ] `payment_received`(收款)
- [ ] `refund_created`(退费)
- [ ] `emr_submitted`(病历提交)
- [ ] `emr_rejected`(病历打回)
- [ ] `image_uploaded`(影像上传)
**subject_type(10 个)**:
- [ ] `consultation`(咨询)
- [ ] `appointment`(预约)
- [ ] `visit_registration`(挂号)
- [ ] `visit_reception`(接诊登记)
- [ ] `clinical_encounter`(医生接诊)
- [ ] `emr_document`(病历文档)
- [ ] `charge_order`(收费单)
- [ ] `payment`(收款)
- [ ] `refund`(退费)
- [ ] `image_study`(影像资料)
**fact_type — Actual Fact(12 个,已发生事实)**:
- [ ] `consultation_record`(咨询记录)
- [ ] `visit_registration_record`(挂号记录)
- [ ] `patient_received_record`(患者被接诊记录)
- [ ] `encounter_record`(医生接诊记录)
- [ ] `diagnosis_record`(诊断记录 — adapter parser 衍生)
- [ ] `treatment_record`(治疗记录 ⭐ 召回核心 — adapter parser 衍生)
- [ ] `charge_order_record`(收费单提交记录)
- [ ] `payment_record`(收款记录)
- [ ] `refund_record`(退费记录)
- [ ] `emr_record`(病历记录)
- [ ] `emr_quality_record`(病历质控记录)
- [ ] `image_record`(影像资料记录)
**fact_type — Planned Fact(4 个,未来安排)**:
- [ ] `appointment_plan`(预约计划)
- [ ] `treatment_plan`(治疗计划,由 encounter parser 衍生)
- [ ] `review_plan`(医嘱复查计划,由 encounter parser 衍生)
- [ ] `payment_plan`(待缴费计划)
> 注:**patient 不进 fact 层**(patient 主档 + transaction 流够);**treatment_plan / review_plan 来自 encounter_record 内容解析**,不增加单独 transaction_type。
## 四、跨表字段响应
### 4.1 evidence 字段是否立柱
- [ ] **维持 JSONB 不立柱**(personas / persona_features / followup_plans / plan_scripts / plan_summaries 上的 `evidence`)— 跟提议 §10 的 `evidence_transaction_ids` 等独立列方向**不同**
- [ ] 形态可演进(Phase 5+ 加 promptVersion / scenarioConfigVersion 等),立柱要持续 migration
- [ ] 真出现"某 fact 影响了哪些 plan"反查热点,按需建 GIN 索引(不进 v1 默认)
## 五、衍生能力 — 影像 AI 识别
### 5.1 提议加 `image_uploaded` / `image_analysis_result` 等影像 fact_type
- [ ] **采纳**`image_uploaded` transaction + `image_record` actual fact(接收影像事实)
- [ ] **拒绝**`image_analysis_result` 作为 fact(AI 产物归 `agent_invocations.output`,见 §3.7)
- [ ] **演进路径**:Phase 5+ 多模态 Agent(GPT-4V / Claude 3 / Qwen-VL)消费宿主侧影像 URL,输出落 `Persona.features` / `PlanSummary`
- [ ] **边界**:PAC 不做主权影像 AI / 不复制存储原文件 / 输出不回写 fact
---
## 六、其他业务枚举字段(可补充 / 后续可调)
> 以下字段 schema 用 String 不强约束,应用层 zod enum 标注。下方为建议起步值 — **能确定的可以直接给,没把握的留空,试点反馈后调整**(改 zod enum 常量即可,不动 schema)。
### 6.1 `scenario`(召回场景 — `plan_reasons.scenario`,v2 移到子表)
PAC v1 两条主战场:
- `treatment_aftercare_recall`(治疗链已完成患者的疗效保障召回 — 主战场,#3 种植年度复查 / #4 牙周维护 已就位)
- `treatment_initiation_recall`(启治召回 — 漏治新链,W3+ 数据足够后启用)
⏳ 待业务拍板:
- 链内中断(#1 根管未戴冠 / #2 正畸保持期)— 算 PAC 还是 host 治疗中提醒?
- 漏诊(影像有医生没看)— W6+ AI 影像才做
将来候选:`sleeping_customer_activation`(沉睡客户激活)/ `vip_followup`(高价值客户跟进)/ `complaint_followup`(投诉后安抚)
### 6.2 `outcome`(执行结果 — `plan_executions`,**强枚举**驱动 Plan 状态机)
建议:
- `abandoned`(客服放弃 → Plan abandoned)
- `no_answer`(无应答 → Plan 仍 active 等下次)
- `scheduled_next`(约定下次回访 → Plan 仍 assigned)
- `success_appointed`(成功转化为新预约 → Plan completed)
### 6.3 `abandon_reasons`(放弃原因多选 — `plan_executions`)
建议(待业务确认):
- `wrong_number`(号码错误)
- `patient_refused`(患者明确拒绝)
- `out_of_service_area`(已搬离服务范围)
- `treated_elsewhere`(已在他处治疗)
- `not_interested`(无意向)
- `do_not_contact_request`(患者要求不再联系 → 自动加 dnc)
- `other`(其他,配合 `abandon_other` 文本)
### 6.4 `script_type`(话术类型 — `plan_scripts`)
建议:
- `opening`(开场白)
- `follow_up`(跟进话术)
- `objection_handling`(异议处理)
将来候选:`closing`(收尾)/ `scheduling_confirm`(预约确认)
### 6.5 `summary_type`(摘要类型 — `plan_summaries`)
建议:
- `one_page`(One Page Summary,客服打电话前快读)
- `medical_record`(病历摘要,引用 EMR 关键内容)
- `treatment_chain`(治疗链概览,跨次治疗梳理)
### 6.6 `agent_kind`(Agent 类型 — `agent_invocations`)
建议:
- `SCRIPT`(话术生成)
- `SUMMARY`(摘要生成)
- `PERSONA_FEATURE`(画像特征推断)
- `RANKING`(召回排序辅助)
- `MEDICAL_SUMMARY`(deprecated,改用 SUMMARY + summary_type)
### 6.7 `recommended_role`(推荐回访角色 — `followup_plans`)
建议(跟 RBAC 角色集对齐):
- `staff`(一线客服)
- `leader`(主管)
- `admin`(管理员)— 极少回访,仅留 hook
将来候选:`doctor_assistant`(医助)/ `vip_specialist`(VIP 客服)
### 6.8 `recommended_channel`(推荐触达渠道 — `followup_plans`)+ `channel`(实际触达渠道 — `touchpoints`)
两字段共用同一组取值。建议:
- `phone`(电话回访)
- `wecom`(企业微信)
- `sms`(短信)
- `other`(其他,配合自由文本)
将来候选:`wechat`(个微)/ `email` / `app_push`
### 6.9 `direction`(触达方向 — `touchpoints`)
建议:
- `inbound`(患者主动来电 / 来消息)
- `outbound`(宿主或 PAC 主动外呼 / 外推)
- `unknown`(无法判定,兜底)
### 6.10 `do_not_contact_reason`(不打扰原因 — `patients`,合规相关)
建议:
- `complaint`(投诉过)
- `opt_out`(主动要求不联系)
- `treated_elsewhere`(已转他处)
- `deceased`(已故,但通常用 `deceased` 字段)
- `legal_hold`(法律纠纷)
- `other`(其他)
---
# 下一步
**本确认单(§1-§5 决议 + §2.2 / §3.8 业务边界 + §6 业务枚举)**:按所有结论敲定 `patient_transactions` / `patient_facts` 等 schema 字段(prisma + 索引)及应用层 zod enum,直接进 Phase 1 重构。
请逐项打勾(§1-§5 / §2.2 / §3.8)或在 §6 各项下直接给出最终业务取值,反馈即可。
This source diff could not be displayed because it is too large. You can view the blob instead.
# PAC 底层事实模型建议
> 版本:v0.5
> 对象:PAC 底层数据模型设计
> 目的:明确 PAC 中 Transaction、Fact、Assessment、Plan 的边界。
> 核心口径:PAC 底座采用 **CustomerTransaction + CustomerFact(Actual / Planned)+ Assessment + Plan**。
---
## 1. 总体模型
PAC 底层模型:
```text
CustomerTransaction // 操作账本:记录操作事实如何发生
CustomerFact // 事实单元:记录患者事实中形成了什么
Assessment / Persona // 评价:基于事实形成患者评价
FollowupPlan // 计划:基于评价形成召回计划
```
四层主语:
```text
Transaction:某个系统/人员做了什么操作
Fact:患者事实中存在什么事实单元
Assessment:PAC 对患者事实形成什么评价
Plan:PAC 形成什么行动计划
```
---
## 2. 三个 type 的定义
这里需要明确区分:
```text
transaction_type
subject_type
fact_type
```
三者不能混用。
---
### 2.1 transaction_type:动作事实
`transaction_type` 描述:
```text
发生了什么操作事实
```
它的主语是:
```text
某个系统 / 某个人 / 某个流程
```
表达方式:
```text
appointment_created
appointment_changed
visit_registered
patient_received
charge_order_submitted
payment_received
emr_submitted
```
示例:
```text
预约被创建
预约被变更
挂号发生
患者被接诊
收费单被提交
收款被确认
病历被提交
```
`transaction_type` 要尽可能贴近宿主真实动作,越细越好。
---
### 2.2 subject_type:被操作主体类型
`subject_type` 描述:
```text
这次操作作用在哪类主体上
```
它不表达动作,只表达对象类型。
示例:
```text
consultation
appointment
visit_registration
visit_reception
clinical_encounter
emr_document
charge_order
payment
refund
image_study
followup_plan
followup_execution
```
例如:
```text
transaction_type = appointment_created
subject_type = appointment
transaction_type = visit_registered
subject_type = visit_registration
transaction_type = patient_received
subject_type = visit_reception
transaction_type = doctor_encounter_started
subject_type = clinical_encounter
```
---
### 2.3 fact_type:患者事实单元类型
`fact_type` 描述:
```text
患者事实中形成了什么事实单元
```
它不表达操作过程,而表达患者事实的稳定单元。
示例:
```text
appointment_plan
visit_registration_record
patient_received_record
encounter_record
charge_order_record
payment_record
emr_record
image_record
followup_plan
followup_execution_record
```
例如:
```text
transaction_type = patient_received
subject_type = visit_reception
解析为:
fact_kind = actual
fact_type = patient_received_record
```
---
### 2.4 三者关系
```text
transaction_type = 动作事实,尽可能细节准确
subject_type = 被操作主体类型,不表达动作
fact_type = 患者事实单元类型,不表达操作过程
```
---
## 3. clinic_id 的字段归属
### 3.1 Patient 相关对象:到租户
Patient 相关对象包括:
```text
patients
patient_identity_refs
patient_profile
assessment / persona
assessment_features
```
数据边界:
```text
platform_id + tenant_id
```
不强制带 `clinic_id`
定义:
```text
患者身份到 tenant。
患者资料到 tenant。
患者画像到 tenant。
患者评价到 tenant。
```
---
### 3.2 Transaction:必须有 clinic_id
Transaction 记录操作发生过程:
```text
谁,在什么时候,通过哪个系统,在哪个 clinic 上下文中,对哪个主体,做了什么操作。
```
Transaction 必须带 `clinic_id`
`clinic_id` 表示:
```text
操作发生地点
操作归属诊所
业务现场上下文
计划创建所在诊所
收费发生诊所
病历提交诊所
预约创建/变更所在诊所
挂号发生诊所
患者接诊诊所
```
---
### 3.3 Actual Fact:到租户
Actual Fact 记录患者事实中已经形成的事实单元。
数据边界:
```text
platform_id + tenant_id + patient_id
```
Actual Fact 不强制带 `clinic_id`
clinic 通过 evidence transaction 反查:
```text
actual_fact.evidence_transaction_ids -> customer_transactions.clinic_id
```
如果为了查询性能需要冗余 clinic,可使用:
```text
primary_clinic_id
```
该字段为派生字段,不作为隔离字段。
---
### 3.4 Planned Fact:必须有 clinic_id
Planned Fact 记录患者事实中已经形成的未来安排。
Planned Fact 必须有 `clinic_id`
`clinic_id` 表示计划落点:
```text
预约目标诊所
回访执行诊所/团队
复查目标诊所
治疗计划执行诊所
待缴费归属诊所
```
---
## 4. clinic_id 字段归属总表
| 对象 | clinic_id | 说明 |
|---|---:|---|
| Patient | 否 | 患者身份到 tenant |
| Patient Identity / Profile | 否 | 患者资料到 tenant |
| Assessment / Persona | 否 | 患者评价到 tenant |
| Assessment Feature | 否 | 评价特征引用 facts |
| CustomerTransaction | 是 | 操作必须记录 clinic 上下文 |
| Actual Fact | 通常否 | 已发生事实到 tenant;clinic 由 evidence transaction 追溯 |
| Planned Fact | 是 | 计划事实必须记录目标诊所/执行诊所 |
| FollowupPlan | 是 | 计划需要目标诊所或执行团队 |
| PlanExecution | 是 | 执行记录需要执行地点/团队上下文 |
| AgentInvocation / ProcessorInvocation | 否或可选 | 可记录 related clinic,不作为主隔离 |
字段归属口径:
```text
Patient 到租户。
Transaction 必须有 clinic。
Actual Fact 到租户。
Planned Fact 必须有 clinic。
```
---
## 5. 宿主业务对象在 PAC 中的归属
以下宿主业务对象在 PAC 中不作为主权事实表:
```text
diagnoses
treatments
appointments
charges
touchpoints
```
这些对象在 PAC 中的承接方式:
```text
1. 作为 CustomerTransaction 的 subject snapshot
2. 作为 CustomerFact 的 fact_content_json
3. 作为 Assessment 的 evidence 来源
```
### 5.1 宿主对象归属表
| 宿主对象 | PAC 中的承接方式 |
|---|---|
| diagnosis | 进入病历/临床类 transaction snapshot;必要时解析为 fact 内容 |
| treatment | 进入治疗计划/治疗动作类 transaction snapshot;必要时解析为 planned / actual fact |
| appointment | 进入预约创建/变更/取消 transaction;解析为 appointment_plan 等 fact |
| charge | 进入收费单/收费/退费 transaction;解析为 charge_order_record / payment_record / refund_record 等 fact |
| touchpoint | 进入触达/回访 transaction;解析为 followup_execution_record 或 followup_plan 等 fact |
PAC 记录的表达方式:
```text
宿主在某个时间提交了一份包含某些内容的记录。
```
PAC 事实表达方式:
```text
患者事实中存在一条由该宿主记录解析形成的事实单元。
```
---
## 6. Transaction 和 Fact 的边界
### 6.1 Transaction 是操作账本
Transaction 回答:
```text
某个系统/人员做了什么操作?
```
示例:
```text
400 创建咨询记录
预约被创建
预约被变更
预约被取消
患者到店签到
挂号发生
患者被接诊
医生开始接诊
收费单被提交
收款被确认
退费被创建
病历被提交
病历被打回
影像被上传
回访计划被创建
回访被执行
```
Transaction 的性质:
```text
不可变操作记录
append-only
```
---
### 6.2 Fact 是事实单元
Fact 回答:
```text
患者事实中存在什么?
```
示例:
```text
患者事实中存在一条咨询记录
患者事实中存在一条预约计划
患者事实中存在一条挂号记录
患者事实中存在一条患者被接诊记录
患者事实中存在一条医生接诊记录
患者事实中存在一条收费单记录
患者事实中存在一条收款记录
患者事实中存在一条退费记录
患者事实中存在一条病历记录
患者事实中存在一条病历质控记录
患者事实中存在一条影像资料记录
患者事实中存在一条影像分析结果
患者事实中存在一条回访计划
患者事实中存在一条回访执行记录
```
Fact 的性质:
```text
由 transaction 解析、切块、归一、AI 处理后形成的可证据化事实单元。
```
---
## 7. Fact 分为 Actual Fact 与 Planned Fact
PAC 中的 Fact 分为两类:
```text
Actual Fact 已发生事实
Planned Fact 计划事实
```
---
### 7.1 Actual Fact:已发生事实
Actual Fact 回答:
```text
患者事实中已经存在什么已发生事实?
```
典型 fact:
```text
患者事实中存在一条咨询记录
患者事实中存在一条挂号记录
患者事实中存在一条患者被接诊记录
患者事实中存在一条医生接诊记录
患者事实中存在一条收费单提交记录
患者事实中存在一条收款记录
患者事实中存在一条退费记录
患者事实中存在一条病历记录
患者事实中存在一条病历质控记录
患者事实中存在一条影像资料记录
患者事实中存在一条回访执行记录
```
Actual Fact 的核心时间字段:
```text
occurred_at
```
Actual Fact 的主边界:
```text
platform_id + tenant_id + patient_id
```
Actual Fact 不强制 `clinic_id`
---
### 7.2 Planned Fact:计划事实
Planned Fact 回答:
```text
患者事实中已经存在什么未来安排?
```
典型 fact:
```text
患者事实中存在一条预约计划
患者事实中存在一条回访计划
患者事实中存在一条治疗计划
患者事实中存在一条医嘱复查计划
患者事实中存在一条待缴费计划
```
Planned Fact 的核心字段:
```text
clinic_id
planned_for
valid_from
valid_until
plan_status
```
Planned Fact 的边界:
```text
platform_id + tenant_id + patient_id + clinic_id
```
这里的 clinic 是计划落点,不是 patient 隔离维度。
定义:
```text
Planned Fact 是某个系统或人员已经创建出的未来安排事实。
```
---
## 8. Appointment / Visit / Follow-up 的模型
Appointment、Visit、Follow-up 都需要区分:
```text
1. 操作:创建、变更、取消、挂号、接诊、执行
2. 事实内容:患者事实中形成的事实单元
```
---
### 8.1 预约创建
#### Transaction:预约被创建
```json
{
"transaction_type": "appointment_created",
"subject_type": "appointment",
"subject_id": "apt_001",
"operation": "created",
"clinic_id": "clinic_A",
"transaction_time": "2026-05-10T10:00:00+08:00",
"after_snapshot": {
"scheduled_at": "2026-05-20T10:00:00+08:00",
"clinic_id": "clinic_A",
"doctor_name": "张医生",
"appointment_type": "复查",
"source": "400",
"purpose": "根管复查"
}
}
```
#### Fact:患者事实中形成预约计划
```json
{
"fact_kind": "planned",
"fact_type": "appointment_plan",
"clinic_id": "clinic_A",
"planned_for": "2026-05-20T10:00:00+08:00",
"status": "active",
"fact_summary": "患者事实中存在一条 2026-05-20 10:00 到 clinic_A 复查的预约计划",
"evidence_transaction_ids": ["tx_001"]
}
```
---
### 8.2 预约变更
#### Transaction:预约被变更
```json
{
"transaction_type": "appointment_changed",
"subject_type": "appointment",
"subject_id": "apt_001",
"operation": "changed",
"clinic_id": "clinic_A",
"before_snapshot": {
"scheduled_at": "2026-05-20T10:00:00+08:00"
},
"after_snapshot": {
"scheduled_at": "2026-05-22T15:00:00+08:00"
}
}
```
#### Fact 变化
```text
旧 appointment_plan fact -> superseded
新 appointment_plan fact -> active
新 fact 保留 clinic_id
```
---
### 8.3 挂号
#### Transaction:挂号发生
```json
{
"transaction_type": "visit_registered",
"subject_type": "visit_registration",
"subject_id": "reg_001",
"operation": "created",
"clinic_id": "clinic_A",
"transaction_time": "2026-05-22T14:40:00+08:00",
"after_snapshot": {
"registration_no": "R20260522001",
"appointment_id": "apt_001",
"department": "口腔综合",
"doctor_name": "张医生"
}
}
```
#### Fact:患者事实中形成挂号记录
```json
{
"fact_kind": "actual",
"fact_type": "visit_registration_record",
"occurred_at": "2026-05-22T14:40:00+08:00",
"fact_summary": "患者事实中存在一条 2026-05-22 14:40 的挂号记录",
"evidence_transaction_ids": ["tx_002"]
}
```
---
### 8.4 患者被接诊
#### Transaction:患者被接诊
```json
{
"transaction_type": "patient_received",
"subject_type": "visit_reception",
"subject_id": "recv_001",
"operation": "received",
"clinic_id": "clinic_A",
"transaction_time": "2026-05-22T14:55:00+08:00",
"after_snapshot": {
"registration_id": "reg_001",
"appointment_id": "apt_001",
"received_by": "前台A"
}
}
```
#### Fact:患者事实中形成被接诊记录
```json
{
"fact_kind": "actual",
"fact_type": "patient_received_record",
"occurred_at": "2026-05-22T14:55:00+08:00",
"fact_summary": "患者事实中存在一条 2026-05-22 14:55 在 clinic_A 被接诊的记录",
"evidence_transaction_ids": ["tx_003"]
}
```
说明:
```text
patient_received_record 是 actual fact。
clinic 可由 tx_003 反查。
```
同时:
```text
appointment_plan fact -> fulfilled
```
---
### 8.5 医生接诊
#### Transaction:医生开始接诊
```json
{
"transaction_type": "doctor_encounter_started",
"subject_type": "clinical_encounter",
"subject_id": "enc_001",
"operation": "started",
"clinic_id": "clinic_A",
"transaction_time": "2026-05-22T15:05:00+08:00",
"after_snapshot": {
"doctor_name": "张医生",
"registration_id": "reg_001",
"appointment_id": "apt_001"
}
}
```
#### Fact:患者事实中形成医生接诊记录
```json
{
"fact_kind": "actual",
"fact_type": "encounter_record",
"occurred_at": "2026-05-22T15:05:00+08:00",
"fact_summary": "患者事实中存在一条 2026-05-22 15:05 的医生接诊记录",
"evidence_transaction_ids": ["tx_004"]
}
```
---
### 8.6 回访计划
Transaction:
```text
followup_plan_created
followup_plan_changed
followup_plan_cancelled
followup_executed
```
Fact:
```text
planned fact: followup_plan,保留 clinic_id
actual fact: followup_execution_record,clinic 可由 evidence transaction 反查
```
定义:
```text
回访计划是 Planned Fact。
回访执行结果是 Actual Fact。
```
---
## 9. CustomerTransaction 建议结构
表名:
```text
customer_transactions
```
建议字段:
```text
id
platform_id
tenant_id
patient_id
transaction_type
transaction_time
received_at
source_system // 5i5ya / 400 / friday / pac / manual
source_module // appointment / visit / emr / charge / followup / image
source_object_type
source_object_id
source_event_id // 如果宿主有事件 ID,优先用于幂等
source_actor_id
source_actor_role
clinic_id // 必填:操作业务现场 / 计划归属诊所 / 来源诊所
subject_type // consultation / appointment / visit_registration / visit_reception / clinical_encounter / emr_document / charge_order / payment / refund / image_study / followup_plan / followup_execution
subject_id // 宿主侧主体 ID
operation // created / changed / cancelled / submitted / rejected / paid / refunded / executed / registered / received / started
before_snapshot // 可空,宿主能给就保存
after_snapshot // 当次操作后的主体内容
raw_payload // 原始入参快照
payload_hash
ingest_id
created_at
```
---
### 9.1 幂等键
优先使用:
```text
platform_id + tenant_id + source_event_id
```
如果宿主没有 source_event_id,再退化为:
```text
platform_id + tenant_id + source_system + subject_type + subject_id + operation + transaction_time + payload_hash
```
同一个业务主体可能被多次修改,因此不使用:
```text
fact_type + external_ref
```
作为唯一幂等依据。
---
## 10. CustomerFact 建议结构
表名:
```text
customer_facts
```
建议字段:
```text
id
platform_id
tenant_id
patient_id
fact_kind // actual | planned
fact_type // appointment_plan / visit_registration_record / patient_received_record / emr_record / charge_record / image_analysis_result ...
fact_status // active / superseded / cancelled / fulfilled / expired / invalidated
version
occurred_at // actual fact 使用
planned_for // planned fact 使用
valid_from
valid_until
clinic_id // planned fact 必填;actual fact 可空
primary_clinic_id // actual fact 如需性能冗余,可用该字段,不作为隔离
fact_title
fact_summary
fact_content_json
source_kind // host_transaction / pac_processor / ai_processor / manual_correction
evidence_transaction_ids
evidence_fact_ids
processor_invocation_ids
confidence
superseded_by
created_at
updated_at
```
---
### 10.1 字段约束
```text
if fact_kind = 'planned':
clinic_id must not be null
planned_for should usually not be null
if fact_kind = 'actual':
occurred_at should not be null
clinic_id may be null
```
---
## 11. 初始 CustomerTransaction 类型
| transaction_type | 中文名 | subject_type | clinic 要求 | 典型结果 |
|---|---|---|---|---|
| `consultation_created` | 咨询记录被创建 | consultation | 必填/可用目标诊所 | actual fact: consultation_record |
| `appointment_created` | 预约被创建 | appointment | 必填 | planned fact: appointment_plan |
| `appointment_changed` | 预约被变更 | appointment | 必填 | 新 planned fact,旧 fact superseded |
| `appointment_cancelled` | 预约被取消 | appointment | 必填 | actual fact: appointment_cancel_record;planned fact cancelled |
| `patient_arrived` | 患者到店/签到 | visit_reception | 必填 | transaction,可参与解析 patient_received_record |
| `visit_registered` | 挂号发生 | visit_registration | 必填 | actual fact: visit_registration_record |
| `patient_received` | 患者被接诊 | visit_reception | 必填 | actual fact: patient_received_record |
| `doctor_encounter_started` | 医生接诊开始 | clinical_encounter | 必填 | actual fact: encounter_record |
| `charge_order_submitted` | 收费单被提交 | charge_order | 必填 | planned fact: payment_plan / actual fact: charge_order_record |
| `payment_received` | 收款被确认 | payment | 必填 | actual fact: payment_record |
| `refund_created` | 退费被创建 | refund | 必填 | actual fact: refund_record |
| `emr_submitted` | 病历被提交 | emr_document | 必填 | actual fact: emr_record |
| `emr_rejected` | 病历被打回 | emr_document | 必填 | actual fact: emr_quality_record |
| `image_uploaded` | 影像被上传 | image_study | 必填 | actual fact: image_record |
| `followup_plan_created` | 回访计划被创建 | followup_plan | 必填 | planned fact: followup_plan |
| `followup_plan_changed` | 回访计划被变更 | followup_plan | 必填 | 新 planned fact,旧 fact superseded |
| `followup_plan_cancelled` | 回访计划被取消 | followup_plan | 必填 | planned fact cancelled |
| `followup_executed` | 回访被执行 | followup_execution | 必填 | actual fact: followup_execution_record |
---
## 12. 初始 CustomerFact 类型
### 12.1 Actual Fact
```text
consultation_record 咨询记录
visit_registration_record 挂号记录
patient_received_record 患者被接诊记录
encounter_record 医生接诊记录
charge_order_record 收费单提交记录
payment_record 收款记录
refund_record 退费记录
emr_record 病历记录
emr_quality_record 病历质控记录
image_record 影像资料记录
image_analysis_result 影像分析结果
followup_execution_record 回访执行记录
```
这些 fact 主边界到 tenant,不强制 clinic_id。
---
### 12.2 Planned Fact
```text
appointment_plan 预约计划
followup_plan 回访计划
treatment_plan 治疗计划
review_plan 医嘱复查计划
payment_plan 待缴费计划
```
这些 fact 必须有 clinic_id。
---
## 13. Image AI / EMR AI 等解析结果的归属
例如:
```text
image_uploaded transaction
```
可触发影像 AI:
```text
processor_invocation
```
然后形成:
```text
fact_kind = actual
fact_type = image_analysis_result
```
该 fact 引用:
```text
evidence_transaction_ids = [image_uploaded_tx]
processor_invocation_ids = [image_ai_call_id]
```
边界:
```text
Fact = 对 transaction / 原始材料的解析结果
Assessment = 对 facts 的业务评价
```
例如:
| 内容 | 层级 |
|---|---|
| 某张片子被上传 | Transaction |
| 影像 AI 对片子的分析结果 | Fact |
| 基于影像、病历、收费形成患者治疗意向评价 | Assessment |
| 形成客服回访计划 | Plan |
---
## 14. Assessment / Persona 的位置
Assessment 基于 facts 生成。
它回答:
```text
PAC 对患者事实形成什么评价?
```
例如:
```text
患者价值评价
复查未完成风险评价
治疗意向评价
投诉/体验风险评价
近期不宜打扰评价
推荐回访角色评价
```
Assessment 消费:
```text
customer_facts
```
每个 assessment feature 引用:
```text
evidence_fact_ids
```
必要时再间接追溯到 transaction。
Assessment 不带 clinic_id 作为主隔离;若需要形成“某诊所视角下的评价”,可使用 assessment_scope 或 feature context。
---
## 15. FollowupPlan 的位置
FollowupPlan 是 PAC 的行动计划。
它可以基于:
```text
customer_facts
assessment_features
rules
agent output
```
生成。
FollowupPlan 需要目标诊所或执行诊所:
```text
target_clinic_id
execution_clinic_id / team_id
```
PAC 生成的 FollowupPlan 可作为 planned fact 回写到 fact 层:
```text
fact_kind = planned
fact_type = followup_plan
```
PlanExecution 可形成 actual fact:
```text
fact_kind = actual
fact_type = followup_execution_record
source_system = pac
```
clinic 来自执行 transaction。
---
## 16. 与原表的对应关系
| 原表/概念 | 建议归属 |
|---|---|
| `patient_fact_events` | `customer_transactions`,操作账本,必须有 clinic_id |
| `diagnoses` | 临床/病历类 transaction snapshot;必要时解析为 fact 内容 |
| `treatments` | 治疗计划/治疗动作类 transaction snapshot;必要时解析为 planned / actual fact |
| `appointments` | 预约创建/变更/取消 transaction;解析为 appointment_plan 等 fact |
| `charges` | 收费单、收费、退费 transaction;解析为 charge_order_record / payment_record / refund_record fact |
| `touchpoints` | 宿主触达和 PAC 执行 transaction;解析为 followup_execution_record 或 followup_plan fact |
| `personas` | Assessment 层,到 tenant,不带 clinic 主隔离 |
| `followup_plans` | Plan 层,带 target_clinic_id / execution context |
| `agent_invocations` | 可扩展为 processor_invocations,覆盖 AI 解析、LLM 生成等 |
---
## 17. 设计约束
### 17.1 Patient 到租户
```text
Patient / Persona / Assessment 只到 platform + tenant。
clinic 不切分患者和画像。
```
### 17.2 Transaction 必须有 clinic
```text
每个 transaction 必须记录 clinic 上下文。
```
### 17.3 transaction_type 表达动作事实
```text
transaction_type 描述操作发生了什么。
命名应尽可能贴近宿主真实动作。
```
### 17.4 subject_type 表达被操作主体
```text
subject_type 只表达对象类型,不表达动作。
```
### 17.5 fact_type 表达患者事实单元
```text
fact_type 表达患者事实中形成的事实单元,不表达操作过程。
```
### 17.6 Actual Fact 到租户
```text
Actual Fact 主边界到 tenant。
clinic 通过 evidence transaction 追溯。
如冗余 clinic,只能作为 derived field,不是隔离字段。
```
### 17.7 Planned Fact 必须有 clinic
```text
预约计划、回访计划、治疗计划、复查计划、待缴费计划都必须有 clinic_id。
```
### 17.8 宿主对象不作为 PAC 主权对象
```text
PAC 不拥有宿主 diagnosis / treatment / appointment / charge。
PAC 记录宿主对这些主体做过什么操作,以及当时的内容快照。
```
### 17.9 Transaction append-only
```text
Transaction 不更新、不删除。
错误通过 correction transaction 修正。
```
### 17.10 Fact 必须可追溯
```text
每个 fact 必须追溯到 transaction、raw payload、processor invocation。
```
### 17.11 Planned Fact 也是事实
```text
预约计划、回访计划、治疗计划、复查计划都是 planned fact。
它们是已经形成的未来安排。
```
### 17.12 Assessment 是评价
```text
患者价值、治疗意向、投诉风险、回访优先级属于 Assessment。
```
---
## 18. 建议下一步
下一步先完成以下设计:
1. 定义 `customer_transactions` 表。
2. 定义 `customer_facts` 表。
3. 梳理首批 transaction_type。
4. 梳理首批 subject_type。
5. 梳理首批 fact_type,并区分 actual / planned。
6. 明确 clinic_id 字段归属规则。
7. 定义 transaction → fact 的解析器协议。
8. 再讨论 Assessment / Plan 如何消费 facts。
首批链路:
```text
400咨询
预约创建 / 变更 / 取消
患者到店 / 挂号 / 被接诊
医生接诊
提交收费单
收费
退费
提交病历
病历打回
影像上传
回访计划创建 / 变更 / 取消
回访执行
```
尘锋 SCRM 可后续作为 external_signal 接入。
---
## 19. 一句话总结
PAC 的底层模型:
```text
CustomerTransaction:操作发生的证据链,必须有 clinic_id
CustomerFact:操作解析出的事实单元,分 Actual Fact 和 Planned Fact
Actual Fact:已发生事实,到 tenant,clinic 从 transaction 追溯
Planned Fact:计划事实,必须有 clinic_id
Assessment:对事实的业务评价,到 tenant
FollowupPlan:基于评价生成的行动计划,必须有执行/目标 clinic
```
核心区分:
```text
transaction_type = 动作事实
subject_type = 被操作主体
fact_type = 患者事实单元
```
目标形态:
```text
Customer Transaction Based Assessment & Follow-up Planning Engine
```
......@@ -152,4 +152,4 @@ priorityScore =
- 启治召回(漏治新链) → `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`
- 通用 schema 决策 → `docs/db-design-v2.md` §十三 决议清单
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