---
title: 架构总览
description: PAC 的整体架构 —— 多宿主可插拔、集团级、三层推断(事实 → 画像 → 触达)的事件驱动系统。
icon: Layers
---

> PAC 是一个**多宿主可插拔、集团级**的患者数据中枢。
> 它把任何宿主的原始数据,经统一管道**标准化**成不可变的**事实账本**,在其上事件驱动地派生**画像**与**召回计划**,
> 再通过 REST / MCP / 工作台对外服务,并把客服执行结果回填。

---

## 组件 / 模块地图

| 分组 | 模块 | 职责 | 状态 |
|---|---|---|---|
| **摄入** | `sync` | 统一摄入管道(冷启 / 增量 / 推送 + transforms/assembler/parser) | ✅ |
| **标准化核心** | `clinical-gap` | 潜在治疗 gap 的 SQL 单一真理源(召回 + 画像共用,无 HTTP) | ✅ |
| **画像** | `persona` | Layer 2:确定性特征提取器(RFM / 生命周期 / 禁忌 / 潜在治疗 …) | ✅ |
| **召回** | `plan` · `plan-aggregate` | Layer 3:召回计划 + 执行状态机 + 规则引擎;详情聚合 + AI 重生成入口 | ✅ |
| **AI** | `ai` | LLM harness:AiCall 注册表 + 单一收口 runner + 多供应商(见下) | ✅ |
| **接口** | `auth` · `mcp` · `patient` · `admin` | 鉴权换票;MCP 工具(对外 agent 只读);患者档案/时间线;后台自助 | ✅ |
| **客服助手** | `assistant` · `weixin-aibot` · `realtime-coach` | 工作台 AI 助手(MCP 工具)+ 企微问答 + 实时语音教练(WS) | ✅(企微按配置启用) |

---

## 整体数据流

```mermaid
flowchart TB
  H["🏥 宿主系统 / 数仓<br/>就诊 · 治疗 · 缴费 · 影像 …"]

  subgraph ING["摄入 · 标准化（sync）"]
    direction LR
    E["Pull(直连 / 文件 / API)<br/>Push(事件)"] --> S["transforms → assembler → parser"]
  end

  subgraph CORE["三层推断（事件驱动）"]
    direction LR
    F["① 事实账本<br/>transactions / facts"] --> P["② 画像<br/>persona"] --> PL["③ 召回计划<br/>followup_plans"]
  end

  subgraph SVC["服务"]
    direction LR
    A["REST / MCP"]
    AI["AI 话术 · 摘要 / 客服助手"]
  end

  EX["执行回填"]

  H --> ING --> F
  PL --> SVC --> EX
  EX -.->|"驱动状态机 · 不回写事实"| PL
```

**事件驱动主路径**:事实变更 → 画像重算 → 计划重算 → 话术 / 摘要生成，三条队列依次推进。
按患者 / 计划串行(同一主体不并发)、跨主体并行;每步靠**水位**(单调序列 `event_seq` + 消费方 `eventWatermark`)判幂等,已处理过的直接跳过。

---

## 数据摄入

摄入只有**两种传输方向**,叠加正交的**存量 / 增量**;所有入口取回 raw 后,汇入同一条标准化管道(`transforms → assembler → 事务合成 → parser → fact 落地`),口径一致,`fact.content` 是下游唯一真理源。

- **Pull(PAC 主动取)**:源有 数据库直连(ClickHouse)/ 文件(CSV·JSON)/ API(待实现);存量↔增量靠游标切换。
- **Push(宿主主动推)**:webhook 推事件(`POST /push/events`,HMAC 验签)。

> 细节(各源、双源等价、管道各层、幂等机制)见 [数据摄入](/docs/architecture/data-ingestion)。

---

## 三层模型

PAC 的骨架是一条**不可变事件流**,其上派生两层读模型(详见 [三层模型](/docs/architecture/three-layer-model)):

| 层 | 表 | 性质 |
|---|---|---|
| **① 事实** | `patient_transactions`(操作账本)+ `patient_facts`(事实版本流) | 不可变、可追溯,标准化收口 |
| **② 画像** | `persona` + `persona_features` | 在标准事实上自动评价(16 个规则特征提取器,候选 33) |
| **③ 触达** | `followup_plans` + `plan_executions` | 规则打分生成召回计划,执行结果回填 |

---

## 多租户与权限

权限分两条正交轴:**功能权限**(能做什么操作)+ **数据权限**(能看哪些数据)。

- **PAC 不拥有用户**:无账号 / 用户表 —— 客服、管理员身份来自宿主,经换票(`auth/exchange-token`)签发 PAC 自己的 JWT(带角色 + `orgScope`);落库的指派人 / 执行人只是宿主侧 user id,展示名由宿主接口解析。
- **功能权限(RBAC)= 能做什么**:角色 `staff / leader / admin` 随 JWT 带入,端点用 `@RequirePermission` 守卫操作级权限 —— staff 限自己的工单(查看 / 执行 / 认领),leader 增全池查看 / 回收 / 统计,admin 全量(含平台管理)。
- **`tenant_id` = 集团**(硬隔离基线,进每张业务表唯一键 + 每条查询):宿主是 SaaS 系统 / 数仓,一套往往横跨多个集团 —— 故一个 host 可含多个 tenant;集团才是数据归属与隔离的自然边界。
- **`source_unit` = 源组织命名空间(发证来源)**:进唯一键,给 `external_id` 在集团内消歧(同号不同源 = 不同人)。取哪一层由 manifest `identity_namespace_field` 声明,单命名空间宿主留空串。语义是发证命名空间(历史不可变),非当前归属 —— 当前归属由 `orgScope` 解析。
- **`clinic_id` = 业务字段**(RBAC 用,不是 DB 隔离边界)。
- **`orgScope` = 数据权限**:JWT 只装通用 org 节点引用,每请求按 org 树 `expandOrgScope` 展开成 `sourceUnits + clinicIds`。

**三层强制(纵深防御)**:
1. **每请求拦截器** —— 展开 orgScope,放进 AsyncLocalStorage 上下文。
2. **Prisma 扩展兜底** —— 对受限模型的列表 / 聚合读自动注入 `tenant_id`(+ `source_unit`),漏写也不泄漏。
3. **DB 唯一键** —— 最后一道。
4. **fail-closed**:需 `source_unit` 消歧的租户遇到无法解析的范围 → 落 `DENY_SOURCE_UNIT` 哨兵,绝不退化成「不限范围」。

详见 [登录授权](/docs/integration/auth-login)。

---

## AI 层

- **自建 in-house harness**(数据不出境)。
- **多供应商**:**DeepSeek**(默认,国内端点) / **Qwen·DashScope**,按 modelId 路由。
- **单一收口** `ai-call-runner`:任何 LLM 调用都过它 —— 输入哈希缓存、`agent_invocations` 全量审计、安全闸、成本估算、超时 + 重试。
- **失败降级**:LLM 失败或安全闸拦截 → `template_fallback`,产出标记来源、流式照常收尾。
- LLM **只产自然语言**(话术 / 摘要);数值、决策、状态机全是确定性规则。

---

## 技术栈

| 层 | 选型 |
|---|---|
| 服务 | NestJS 11 · TypeScript · SWC |
| 存储 | PostgreSQL(单一 OLTP,经 Prisma 6)· Redis |
| 任务 | BullMQ(persona / plan / asset 三队列)+ Bull-Board |
| 数仓读取 | ClickHouse(仅冷启 / 增量数据源) |
| AI | Vercel `ai` SDK + `@ai-sdk/{deepseek,google,openai-compatible}` |
| 对外 | REST(nestjs-zod 校验 + 统一信封)· MCP(Streamable-HTTP)· Scalar API 文档 |
| 实时 | socket.io / ws |
| 前端 | Next.js(App Router) |

---

## 关键不变量

这些是 PAC 区别于普通 CRUD 系统的硬约束,**代码层强制**:

- **事件只追加**:`patient_transactions` 只插不改不删,错了补一条修正事件。
- **事实版本流**:fact 新版 supersede 旧版,任一 subject 只有一个 active 版本。
- **单向数据流**:事实 → 画像 → 计划单向;执行结果 / AI 产物**不回写事实**(归 `plan_executions` / `agent_invocations`)。
- **证据链**:画像、计划、话术都带 `factIds / agentInvocationIds / ruleIds`,任何结论可回溯「为什么」。
- **水位幂等**:`event_seq` 单调 + 消费方 `eventWatermark`,断点续传、不重算。
- **规则为骨、AI 为肉**:主路径确定性,LLM 仅在生成自然语言时出场且必有兜底。
- **宿主差异只在 yaml**:接新宿主只写 `manifest + assembler yaml`(取数 / 形态 / 字段枚举翻译);标准化之后 —— fact 及其下游 —— 全集团一套代码、零 per-host 分支。

---

延伸:[数据摄入](/docs/architecture/data-ingestion) · [三层模型](/docs/architecture/three-layer-model) · [数据模型](/docs/architecture/data-model) · [接入](/docs/integration/overview) · [登录授权](/docs/integration/auth-login)
