Commit fb52cf5e by luoqi

docs(push): 金额/时间改为「宿主原样发、PAC 按接入配置(amountUnit+timezone)归一」

不再要求宿主自己转整数分/ISO8601带时区:接入时配置 amountUnit(fen/yuan)+timezone(IANA),
宿主按自身系统原样发,PAC 的 normalize 归一成 分+UTC(同数仓口径)。带 offset 的时间直接用。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 48610164
......@@ -2,19 +2,19 @@
> **给宿主开发**:把患者事实**实时推**给 PAC。你只发业务系统里**本来就有的字段**;
> 标准码映射 / 幂等键 / 内部 ID 合成 / 外键关联 / 时区归一 **全部 PAC 内部做**。
> **必须 HTTPS**。配套 Pull(PAC 主动拉)见 `host-integration.md`
> **必须 HTTPS**。配套 Pull(PAC 主动拉)。
---
## 0. 两种接入形态(二选一)
| | **形态 A:推原生行(推荐)** | **形态 C:推结构化事件(备选)** |
|---|---|---|
| 你发什么 | 你导出给数仓的那种**原生表行,照抄** | PAC 约定的事件(envelope + payload)|
| ------------- | ------------------------ | ----------------------------- |
| 你发什么 | 你导出给数仓的那种**原生表行,照抄** | PAC 约定的事件(envelope + payload) |
| 你要懂 | 只懂**自己的数据字典** | 还要懂 PAC 的字段/类型约定 |
| 拆分 / 关联 / 码映射 | **全 PAC** | payload 的码仍 PAC 映射,其余你排好 |
| 一次性成本 | 给一份**数据字典**,PAC 写映射 yaml | 对一遍字段约定 |
| 适合 | **绝大多数**(诊所 / HIS / 老系统)| 有数据中台、愿稳定吐标准格式的强宿主 |
| 适合 | **绝大多数**(诊所 / HIS / 老系统) | 有数据中台、愿稳定吐标准格式的强宿主 |
**默认选 A**:字段最少、口径与数仓一致;PAC 以后改映射字典能**自愈历史**(无需你重发)。
鉴权(§1)两形态通用;防重(§4)两形态都保证。
......@@ -24,9 +24,9 @@
## 1. 鉴权(HMAC-SHA256,两形态通用)
| Header | 值 |
|---|---|
| `X-PAC-Host-Id` | PAC 分配的 host UUID(也表明"你是谁")|
| `X-PAC-Timestamp` | 当前 Unix 秒(容忍 ±5 分钟,防重放)|
| ----------------- | ------------------------------------------------------- |
| `X-PAC-Host-Id` | PAC 分配的 host UUID(也表明"你是谁") |
| `X-PAC-Timestamp` | 当前 Unix 秒(容忍 ±5 分钟,防重放) |
| `X-PAC-Signature` | `hex( HMAC-SHA256( secret, "{timestamp}.{rawBody}" ) )` |
- `secret`:接入时 PAC 一次性安全下发,双方各存原文,永不上网。
......@@ -47,7 +47,7 @@ const sig = require('crypto').createHmac('sha256', SECRET).update(`${ts}.${body
```jsonc
POST /pac/v1/push/rows
{
"tenantId": "ruier", // 硬隔离边界;多品牌必填,单租户可由 PAC 配置兜底
"tenantId": "123456", // 硬隔离边界;多品牌必填,单租户可由 PAC 配置兜底
"source": "fact_emr_treatment_out", // 哪张源表(= 你给数据字典里的表名)
"rows": [
{ /* 你那张表的一行,原生字段照抄,含嵌套结构(如 diag 数组)都不用拆 */ }
......@@ -60,13 +60,11 @@ POST /pac/v1/push/rows
-**末次修改时间**列(如 `updated_date`)——驱动幂等/版本;
- 有该行的自然键(如 `id`)。
PAC 收到后(用你一次性提供的数据字典生成的 yaml):**拆分**(一行 EMR → 病历 + 多条诊断 + 治疗,外键自动挂)→ **诊断名映射标准码****合成幂等键去重** → 落库。**跟数仓那条管线一模一样,已在生产运行。**
> 你**不需要**发 `subjectId` / `emrId` / 标准码 / 幂等键——原生行里有什么 PAC 用什么,关系和 ID 它自己补。
PAC 收到后(用你一次性提供的数据字典生成的 yaml):**拆分**(一行 EMR → 病历 + 多条诊断 + 治疗,外键自动挂)→ **诊断名映射标准码****合成幂等键去重** → 落库。
---
## 3. 形态 C:推结构化事件(备选)
## 3. 形态 C:推结构化事件
```jsonc
POST /pac/v1/push/events
......@@ -78,15 +76,15 @@ POST /pac/v1/push/events
### 3.1 envelope 公共字段
| 字段 | 必填 | 说明 |
|---|---|---|
| ------------- | ---- | -------------------------------------------------------------------- |
| `subjectType` | ✅ | 资源类型:`diagnosis`/`treatment`/`appointment`/`payment`/`emr`/… |
| `action` | ✅ | 动作:`*_recorded` / `*_created` / `*_changed` / `*_cancelled`(撤回,见 §5)|
| `tenantId` | ✅ | 硬隔离(多品牌必填;`clinicId` 替代不了)|
| `action` | ✅ | 动作:`*_recorded` / `*_created` / `*_changed` / `*_cancelled`(撤回,见 §5) |
| `tenantId` | ✅ | 硬隔离(多品牌必填;`clinicId` 替代不了) |
| `patientId` | ✅ | 患者号 |
| `occurredAt` | ✅ | 发生时间(ISO8601 带时区;**没有独立发生时间就用记录创建时间**)|
| `updatedAt` | ✅ | 末次修改时间(内容变则变、重试不变 → 幂等/版本)|
| `occurredAt` | ✅ | 发生时间(**本地时间即可**,PAC 按你配置的时区归一;带 offset 也行;**无独立发生时间就用创建时间**) |
| `updatedAt` | ✅ | 末次修改时间(内容变则变、重试不变 → 幂等/版本) |
| `subjectId` | ⭕ 选填 | 该记录自己的 id。**有就给(去重更准);没有 PAC 用自然键自己合成** |
| `clinicId` | ⭕ 条件 | 发生诊所(operational 事件给;patient 主档无)|
| `clinicId` | ⭕ 条件 | 发生诊所(operational 事件给;patient 主档无) |
### 3.2 payload(按 subjectType)
......@@ -94,7 +92,7 @@ POST /pac/v1/push/events
```jsonc
{
"subjectType":"diagnosis", "action":"diagnosis_recorded",
"tenantId":"ruier", "patientId":"1328199", "clinicId":"7d49539c",
"tenantId":"123456", "patientId":"1328199", "clinicId":"7d49539c",
"occurredAt":"2025-12-06T13:51:44+08:00", "updatedAt":"2025-12-25T18:38:21+08:00",
"payload": {
"emrId": "16bee63f...", // 关联电子病历(诊断/治疗/建议有;预约/收费无)
......@@ -110,14 +108,15 @@ POST /pac/v1/push/events
**其它资源 payload 速查**:
| subjectType | action 例 | payload 关键字段 |
|---|---|---|
| `emr`(病历本体)| `emr_recorded` | `doctorId/doctorName, illnessDesc, examFindings, disposal, doctorAdvice` |
| `emr`| `emr_recorded` | `doctorId/doctorName, illnessDesc, examFindings, disposal, doctorAdvice` |
| `treatment` | `treatment_recorded` | `emrId, items[]:{ category, subtype, tooth, status, startedAt, completedAt }` |
| `recommendation` | `recommendation_recorded` | `emrId, items[]:{ name, tooth, windowDays }` |
| `appointment` | `appointment_created/changed/cancelled` | `scheduledAt, status, complaintCategory`(建议带 `subjectId`=预约 id)|
| `payment`/`recharge`/`refund` | `*_recorded` | `amountCents, currency`(建议带 `subjectId`)|
| `consultation` | `consultation_recorded` | `intentCategories[]` |
> 金额一律**整数分**(`amountCents`)+ `currency`;时间一律 **ISO8601 带时区**。
> **金额、时间按你系统原样发,不用自己转**:接入时配置 `amountUnit`(fen/yuan)+ `timezone`(IANA),
> PAC 自动归一成「整数分 + UTC」(同数仓口径)。金额带 `currency`;时间本地值即可(带 offset 也接受)。
---
......@@ -125,7 +124,7 @@ POST /pac/v1/push/events
PAC **自己合成幂等键**(你**不发** `source_event_id`)。你只要:
1. **身份稳定**:有 `subjectId` 就给;没有,保证自然键稳定(诊断 = `emrId+name+tooth`);
2. **`updatedAt` 正确**:内容变它变、重试不变。
2. **`updatedAt` (更新时间字段)正确**:内容变它变、重试不变。
| 你的行为 | PAC 结果 |
|---|---|
......@@ -154,7 +153,7 @@ PAC 靠你**发事件**才知道——**物理删除永远感知不到**。所
| 项 | 约定 |
|---|---|
| 批量 | 形态 C ≤ 500 events;形态 A 按批推荐 ≤ 数百行/请求 |
| 金额 / 时间 | 整数分 + currency;ISO8601 带时区(无时区拒收)|
| 金额 / 时间 | **按你系统原样发**;PAC 按接入配置(`amountUnit`+`timezone`)归一成 分/UTC。带 offset 的时间直接用 |
| 编码 | UTF-8 |
| 重试 | 失败可安全重试(幂等);建议指数退避 |
......
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