Commit c90e1e7e by luoqi

Merge branch 'feat/friday-ingestion' into test

parents 1a924b2d 96d37e38
Pipeline #3430 failed in 0 seconds
...@@ -35,6 +35,11 @@ icon: FileJson ...@@ -35,6 +35,11 @@ icon: FileJson
> ③ 头-行属性(计划头的诊所/方案名下放到计划行)。这些**都是宿主自己的 within-host join**,数据在它手里、成本极低。 > ③ 头-行属性(计划头的诊所/方案名下放到计划行)。这些**都是宿主自己的 within-host join**,数据在它手里、成本极低。
> PAC 只做**跨宿主的临床归一**(共享 K 码/modality/治疗类别字典、金额单位、时区、结算 status 切分、变更检测/版本)。 > PAC 只做**跨宿主的临床归一**(共享 K 码/modality/治疗类别字典、金额单位、时区、结算 status 切分、变更检测/版本)。
> 好处:每张 source 自洽,增量推变更行天然成立,无跨表依赖。 > 好处:每张 source 自洽,增量推变更行天然成立,无跨表依赖。
>
> **两条配套纪律**:① **整表照推,宿主不做任何行过滤**——status/金额范围等切分全部由 PAC 完成
> (单一真理源,与 file / cold-import 同口径),故本文各表**不再规定「推送范围」**;
> ② **列名一律沿用宿主原表列名**——包括 inline 进来的列(`contacts_tel`/`relationship`/`std_code`/
> `class_name`/`organization_id`/`plan_name`),PAC 不发明新名,宿主无需为 PAC 做字段改名。
--- ---
...@@ -45,7 +50,7 @@ icon: FileJson ...@@ -45,7 +50,7 @@ icon: FileJson
> **部分更新语义**:只更新本次提供的字段,未提供的不覆盖存量;push 无"清空字段"语义,需清空走全量通道。 > **部分更新语义**:只更新本次提供的字段,未提供的不覆盖存量;push 无"清空字段"语义,需清空走全量通道。
> >
> **联系电话已 inline(不再单独推 customer_contacts)**:宿主按 `is_default↓ → contacts_type↑(1=本人优先) → id↑` > **联系电话已 inline(不再单独推 customer_contacts)**:宿主按 `is_default↓ → contacts_type↑(1=本人优先) → id↑`
> **挑出默认号**,把号码与归属 inline 进本表的 `phone` / `phone_relationship` 两列。PAC 现只消费这一个有效号 + 归属 > **挑出默认号**,把号码与归属 inline 进本表的 `contacts_tel` / `relationship` 两列(沿用 customer_contacts 原列名)。PAC 现只消费这一个有效号 + 归属
> (存 `patient.phone` 与 `contactPhone.isSelf`),不需要完整通讯录 → 联系方式 1:N 表无需摄入。改号 = 重推本表。 > (存 `patient.phone` 与 `contactPhone.isSelf`),不需要完整通讯录 → 联系方式 1:N 表无需摄入。改号 = 重推本表。
| 字段 | 类型 | 必填 | 说明 | | 字段 | 类型 | 必填 | 说明 |
...@@ -57,8 +62,8 @@ icon: FileJson ...@@ -57,8 +62,8 @@ icon: FileJson
| `file_number` | string | | 病历号/档案号(客服沟通用) | | `file_number` | string | | 病历号/档案号(客服沟通用) |
| `tenant_id` | string | ✅ | 品牌 GUID(宿主原生列) | | `tenant_id` | string | ✅ | 品牌 GUID(宿主原生列) |
| `organization_id` | string | | 建档诊所 | | `organization_id` | string | | 建档诊所 |
| `phone` | string | | **默认联系号(宿主 inline)**——按上述规则挑出的有效号码 | | `contacts_tel` | string | | **默认联系号(宿主 inline)**——按上述规则挑出的有效号码(= customer_contacts 原列名) |
| `phone_relationship` | string/number | | **该号归属(宿主 inline)**——`PhoneRelationshipEnum`:`1`本人 `2`爸爸 `3`妈妈 `4`爷爷 `5`奶奶 `6`朋友 `7`配偶 `8`子女 `9`其他(PAC 存 `isSelf`=码=='1') | | `relationship` | string/number | | **该号归属(宿主 inline,= customer_contacts 原列名)**——`PhoneRelationshipEnum`:`1`本人 `2`爸爸 `3`妈妈 `4`爷爷 `5`奶奶 `6`朋友 `7`配偶 `8`子女 `9`其他(PAC 存 `isSelf`=码=='1') |
| `created_gmt_at` / `updated_gmt_at` | string(datetime) | ✅ | 建档/更新时间 | | `created_gmt_at` / `updated_gmt_at` | string(datetime) | ✅ | 建档/更新时间 |
### `customer_referee_circle` — 转介绍圈(患者-患者关系) ### `customer_referee_circle` — 转介绍圈(患者-患者关系)
...@@ -100,8 +105,6 @@ icon: FileJson ...@@ -100,8 +105,6 @@ icon: FileJson
### `med_emr_info` — 病历正文(Mongo 平铺;一张表喂诊断/治疗/建议/复查/病历全链) ### `med_emr_info` — 病历正文(Mongo 平铺;一张表喂诊断/治疗/建议/复查/病历全链)
**推送范围:`status ∈ {3, 4}`(已完成/归档);`status=2` 未完成草稿不推。**
| 字段 | 类型 | 必填 | 说明 | | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---| |---|---|---|---|
| `emr_sub_id` | string | ✅ | 小病历号 uuid(病历主键;临床事实同诊聚合锚) | | `emr_sub_id` | string | ✅ | 小病历号 uuid(病历主键;临床事实同诊聚合锚) |
...@@ -114,8 +117,8 @@ icon: FileJson ...@@ -114,8 +117,8 @@ icon: FileJson
| `user_id` / `user_name` | string | | 接诊医生 id / 姓名 | | `user_id` / `user_name` | string | | 接诊医生 id / 姓名 |
| `clinic_time` | string(datetime) | ✅ | 就诊时刻 | | `clinic_time` | string(datetime) | ✅ | 就诊时刻 |
| `visit_indicator` | number | | `1`初诊 `2`复诊 | | `visit_indicator` | number | | `1`初诊 `2`复诊 |
| `status` | number | ✅ | `3`已完成 `4`归档(推送范围) | | `status` | number | ✅ | `3`已完成 `4`归档 `2`未完成草稿(整表照推,PAC 侧只取 3/4) |
| `diag` | array | | 诊断数组,元素 `{ "toothPosition": "41;42", "value": "慢性牙髓炎", "stdCode": "<宿主已解析的标准码或空>" }`。**`stdCode` 由宿主 inline**(原 `linkCode` → 自家 `std_diag.std_code` 解析好);K 开头 PAC 截 K 大类,非 K/空按自由文本 | | `diag` | array | | 诊断数组,元素 `{ "toothPosition": "41;42", "value": "慢性牙髓炎", "std_code": "<宿主已解析的标准码或空>" }`。**`std_code` 由宿主 inline**——原 `linkCode` 经自家 `std_diag` 解析所得,沿用 std_diag 原列名;K 开头 PAC 截 K 大类,非 K/空按自由文本 |
| `treat` | array | | 本次治疗数组,元素同上结构(`value`=治疗名) | | `treat` | array | | 本次治疗数组,元素同上结构(`value`=治疗名) |
| `dispose` | array | | 处置叙述数组,元素 `{ "toothPosition", "value" }` | | `dispose` | array | | 处置叙述数组,元素 `{ "toothPosition", "value" }` |
| `examine` | array | | 检查所见数组,元素同上 | | `examine` | array | | 检查所见数组,元素同上 |
...@@ -129,7 +132,7 @@ icon: FileJson ...@@ -129,7 +132,7 @@ icon: FileJson
> 牙位格式:FDI 牙位号,多牙分号分隔,可带牙面字母(`"42 L;43 D"` / `"11;12;13"`)。 > 牙位格式:FDI 牙位号,多牙分号分隔,可带牙面字母(`"42 L;43 D"` / `"11;12;13"`)。
> **`std_diag` 不再单独推**——它是纯翻译字典(`linkCode→std_code`),离开诊断行无独立意义。 > **`std_diag` 不再单独推**——它是纯翻译字典(`linkCode→std_code`),离开诊断行无独立意义。
> 宿主在导出/推 `med_emr_info` 时,把 `diag[].linkCode` 用自家 `std_diag` 解析成 `stdCode` **inline 进 diag 元素**即可。 > 宿主在导出/推 `med_emr_info` 时,把 `diag[].linkCode` 用自家 `std_diag` 解析成 `std_code` **inline 进 diag 元素**即可。
### `med_check` — 影像档案(metadata,不含文件本体) ### `med_check` — 影像档案(metadata,不含文件本体)
...@@ -169,8 +172,6 @@ icon: FileJson ...@@ -169,8 +172,6 @@ icon: FileJson
### `patient_settlement` — 结算头原表(全 status 照推,含消费与整单退费) ### `patient_settlement` — 结算头原表(全 status 照推,含消费与整单退费)
**推送范围:全部 `status` 照推**(含退费的 `status=4`、`receivable_this` 负值行);消费/整单退费的切分由 PAC 完成。
| 字段 | 类型 | 必填 | 说明 | | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---| |---|---|---|---|
| `uuid` | string | ✅ | 结算单主键 | | `uuid` | string | ✅ | 结算单主键 |
...@@ -190,8 +191,6 @@ icon: FileJson ...@@ -190,8 +191,6 @@ icon: FileJson
### `patient_settlement_spec` — 结算明细原表(全量照推,含行级退费) ### `patient_settlement_spec` — 结算明细原表(全量照推,含行级退费)
**推送范围:结算明细全量照推**(含 `is_refund` 各值);行级退费(`is_refund=1`)由 PAC 侧切分。
| 字段 | 类型 | 必填 | 说明 | | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---| |---|---|---|---|
| `id` | string | ✅ | 明细行主键 | | `id` | string | ✅ | 明细行主键 |
...@@ -254,7 +253,7 @@ icon: FileJson ...@@ -254,7 +253,7 @@ icon: FileJson
|---|---|---| |---|---|---|
| 时间口径 | MySQL `datetime` = 北京墙钟(`_gmt_` 命名系惯例误导);**Mongo `Date` 是北京墙钟伪装成 UTC** | `clinicTime`/`createdGmtAt` 小时分布呈营业双峰;按真 UTC 解释则半数病历落深夜 | | 时间口径 | MySQL `datetime` = 北京墙钟(`_gmt_` 命名系惯例误导);**Mongo `Date` 是北京墙钟伪装成 UTC** | `clinicTime`/`createdGmtAt` 小时分布呈营业双峰;按真 UTC 解释则半数病历落深夜 |
| 金额单位 | 元(`decimal(9,2)`) | 洁治单价均值 ¥310、已结算客单均值 ¥1,730 | | 金额单位 | 元(`decimal(9,2)`) | 洁治单价均值 ¥310、已结算客单均值 ¥1,730 |
| `phone_relationship`(默认号归属) | `PhoneRelationshipEnum` 1-9(见 §2;宿主挑默认号后 inline) | customer 服务枚举类 | | `relationship`(默认号归属) | `PhoneRelationshipEnum` 1-9(见 §2;宿主挑默认号后 inline) | customer 服务枚举类 |
| `referee_relationship` | `RecommendRelationshipEnum` 17 码,**语义 = 本行 customer 是 referee 的 X**(PAC 侧按逆关系映射) | 枚举类 `reverseValue()` + 双方年龄差数据 | | `referee_relationship` | `RecommendRelationshipEnum` 17 码,**语义 = 本行 customer 是 referee 的 X**(PAC 侧按逆关系映射) | 枚举类 `reverseValue()` + 双方年龄差数据 |
| `med_emr_info.treat` vs `dispose` | `treat` = 本次治疗(→ 结构化治疗事实);`dispose` = 处置叙述(→ 病历自由文本) | EMR 接口 DTO 字段注释 | | `med_emr_info.treat` vs `dispose` | `treat` = 本次治疗(→ 结构化治疗事实);`dispose` = 处置叙述(→ 病历自由文本) | EMR 接口 DTO 字段注释 |
| `patient_settlement.status` 全 9 值 | `0`未结算 `1`已结算 `2`只生成uuid `3`含退费行 `4`整单反向冲减 `5`重新结算(废弃历史) `6`医生未提交 `7`流程结束 `8`欠款补缴克隆单 | 实体注释 + `savePatientSettlementQk()` 实现 | | `patient_settlement.status` 全 9 值 | `0`未结算 `1`已结算 `2`只生成uuid `3`含退费行 `4`整单反向冲减 `5`重新结算(废弃历史) `6`医生未提交 `7`流程结束 `8`欠款补缴克隆单 | 实体注释 + `savePatientSettlementQk()` 实现 |
...@@ -266,6 +265,6 @@ icon: FileJson ...@@ -266,6 +265,6 @@ icon: FileJson
|---|---| |---|---|
| `organization_name` 覆盖 | 目前仅病历/影像表带诊所名,**只覆盖有病历记录的诊所**;其余诊所前端回退显示 GUID。若贵方有组织树接口(如 `regional_nodes_structure`),提供 `诊所 id → 名称` 全量表可补齐 | | `organization_name` 覆盖 | 目前仅病历/影像表带诊所名,**只覆盖有病历记录的诊所**;其余诊所前端回退显示 GUID。若贵方有组织树接口(如 `regional_nodes_structure`),提供 `诊所 id → 名称` 全量表可补齐 |
| 品牌名 | PAC 当前**不需要**品牌中文名(无展示位,内部按品牌 id 标识)。将来若有展示需求,再约定推品牌主档(`tenant_apply.tenant_info` 的 `tenant_id`/`name`)——贵方 `tenant_name` 未冗余进业务表,只能走主档 | | 品牌名 | PAC 当前**不需要**品牌中文名(无展示位,内部按品牌 id 标识)。将来若有展示需求,再约定推品牌主档(`tenant_apply.tenant_info` 的 `tenant_id`/`name`)——贵方 `tenant_name` 未冗余进业务表,只能走主档 |
| 私有字典不单独推 | `std_diag`(生产多品牌合计 1.5万~3万+行)/ `std_check_class`:纯翻译字典,**由宿主解析好 inline** 进 `diag[].stdCode` / `med_check.class_name`。既省掉大表全量重推,也省掉 PAC 侧持久化字典存储——PAC 只留跨宿主的 `_shared` 临床字典 | | 私有字典不单独推 | `std_diag`(生产多品牌合计 1.5万~3万+行)/ `std_check_class`:纯翻译字典,**由宿主解析好 inline** 进 `diag[].std_code` / `med_check.class_name`。既省掉大表全量重推,也省掉 PAC 侧持久化字典存储——PAC 只留跨宿主的 `_shared` 临床字典 |
| 支付通道 | `settlement_modes`(约 24% 结算单多通道拆付)暂不摄入:总值已在结算头 `net_receipts_this`,PAC 当前无支付方式分析需求。将来要"医保占比/分次支付"再摄入通道明细 | | 支付通道 | `settlement_modes`(约 24% 结算单多通道拆付)暂不摄入:总值已在结算头 `net_receipts_this`,PAC 当前无支付方式分析需求。将来要"医保占比/分次支付"再摄入通道明细 |
| 测试环境数据特征 | 部分租户为开发沙盒(诊所名如"XX专用诊所勿动"),字典类映射(治疗类别关键词等)待**生产数据**回流后再校准一轮 | | 测试环境数据特征 | 部分租户为开发沙盒(诊所名如"XX专用诊所勿动"),字典类映射(治疗类别关键词等)待**生产数据**回流后再校准一轮 |
# patient — 主档 upsert(不进 transaction,无 emits) # patient — 主档 upsert(不进 transaction,无 emits)
# 源:customer.customer_basic_info;phone / phone_relationship 由宿主导出时挑默认号 inline 进主档 # 源:customer.customer_basic_info;contacts_tel / relationship 由宿主导出时挑默认号 inline 进主档
# (不再 PAC 侧 lookup customer_contacts),故这里直接映射标量。 # (不再 PAC 侧 lookup customer_contacts),故这里直接映射标量。
canonical: patient canonical: patient
...@@ -10,12 +10,12 @@ primary: ...@@ -10,12 +10,12 @@ primary:
field_mapping: field_mapping:
externalId: id # customer_basic_info 主键 = host 内部患者唯一 id(→ String 化) externalId: id # customer_basic_info 主键 = host 内部患者唯一 id(→ String 化)
name: name name: name
phone: phone # 宿主 inline(默认号:非空→is_default↓→contacts_type↑→id↑) phone: contacts_tel # 宿主 inline 的默认号(沿用 customer_contacts 原列名)
# 该号关系码(lookup 同源带出)→ preferences.contactPhone{relationshipCode, relationship, isSelf}。 # 该号关系码(lookup 同源带出)→ preferences.contactPhone{relationshipCode, relationship, isSelf}。
# 官方枚举(源码实锤 friday-saas/customer …/enums/PhoneRelationshipEnum.java,2026-07-20): # 官方枚举(源码实锤 friday-saas/customer …/enums/PhoneRelationshipEnum.java,2026-07-20):
# 1本人 2爸爸 3妈妈 4爷爷 5奶奶 6朋友 7配偶 8子女 9其他(语义=持号人是患者的X) # 1本人 2爸爸 3妈妈 4爷爷 5奶奶 6朋友 7配偶 8子女 9其他(语义=持号人是患者的X)
phoneRelationshipCode: phone_relationship phoneRelationshipCode: relationship
phoneRelationship: phone_relationship # 同源列解码成 PAC 关系词(下方 enum_mapping) phoneRelationship: relationship # 同源列解码成 PAC 关系词(下方 enum_mapping)
gender: sex # tinyint 1/2 → enum_mapping 归一;0/空 → 空 gender: sex # tinyint 1/2 → enum_mapping 归一;0/空 → 空
birthDate: birthday # date;空值入库为 null birthDate: birthday # date;空值入库为 null
medicalRecordNumber: file_number # 档案号(病历号,客服沟通用) medicalRecordNumber: file_number # 档案号(病历号,客服沟通用)
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
# - 但**宿主自己的引用**在导出时 within-host join 后 inline 进相关行(PAC 无从做,数据在宿主手里): # - 但**宿主自己的引用**在导出时 within-host join 后 inline 进相关行(PAC 无从做,数据在宿主手里):
# 私有字典码(diag linkCode→std_code、影像 class_code→类型名)、1:N 派生(默认联系号+归属)、 # 私有字典码(diag linkCode→std_code、影像 class_code→类型名)、1:N 派生(默认联系号+归属)、
# 头-行属性(计划头 诊所/方案名 下放到计划行)。故不再导出纯字典表/联系方式/计划头/支付通道。 # 头-行属性(计划头 诊所/方案名 下放到计划行)。故不再导出纯字典表/联系方式/计划头/支付通道。
# - 列名不改(宿主原生字段直出);inline 新增列用契约约定名(phone/phone_relationship/class_name/…)。 # - **列名一律沿用宿主原表列名**(含 inline 进来的:contacts_tel/relationship/std_code/class_name/
# organization_id/plan_name)—— PAC 不发明新名,宿主无需为 PAC 做字段改名。
# #
# 用法(cohort 过滤,语义与 jvs-dw cold-import --clinics/--since 完全一致): # 用法(cohort 过滤,语义与 jvs-dw cold-import --clinics/--since 完全一致):
# ./export.sh # 全量(默认行为不变) # ./export.sh # 全量(默认行为不变)
...@@ -131,8 +132,8 @@ echo "── MySQL 导出 ──" ...@@ -131,8 +132,8 @@ echo "── MySQL 导出 ──"
# 不再单独导出成文件——PAC 只摄入自洽业务行(所有摄入路径同形态)。 # 不再单独导出成文件——PAC 只摄入自洽业务行(所有摄入路径同形态)。
# 患者主档 + inline 默认联系号(挑:非空号 → is_default↓ → contacts_type↑ → id↑;phone 与归属同源同一条) # 患者主档 + inline 默认联系号(挑:非空号 → is_default↓ → contacts_type↑ → id↑;phone 与归属同源同一条)
mysql_csv "SELECT cbi.id,cbi.name,cbi.sex,cbi.birthday,cbi.file_number,cbi.tenant_id,cbi.organization_id,cbi.created_gmt_at,cbi.updated_gmt_at, mysql_csv "SELECT cbi.id,cbi.name,cbi.sex,cbi.birthday,cbi.file_number,cbi.tenant_id,cbi.organization_id,cbi.created_gmt_at,cbi.updated_gmt_at,
(SELECT c.contacts_tel FROM customer.customer_contacts c WHERE c.customer_id=cbi.id AND c.contacts_tel<>'' ORDER BY c.is_default DESC,c.contacts_type ASC,c.id ASC LIMIT 1) AS phone, (SELECT c.contacts_tel FROM customer.customer_contacts c WHERE c.customer_id=cbi.id AND c.contacts_tel<>'' ORDER BY c.is_default DESC,c.contacts_type ASC,c.id ASC LIMIT 1) AS contacts_tel,
(SELECT c.relationship FROM customer.customer_contacts c WHERE c.customer_id=cbi.id AND c.contacts_tel<>'' ORDER BY c.is_default DESC,c.contacts_type ASC,c.id ASC LIMIT 1) AS phone_relationship (SELECT c.relationship FROM customer.customer_contacts c WHERE c.customer_id=cbi.id AND c.contacts_tel<>'' ORDER BY c.is_default DESC,c.contacts_type ASC,c.id ASC LIMIT 1) AS relationship
FROM customer.customer_basic_info cbi $(pf cbi.id)" customer_basic_info.csv FROM customer.customer_basic_info cbi $(pf cbi.id)" customer_basic_info.csv
# 预约(全状态;transforms 丢草稿 10) # 预约(全状态;transforms 丢草稿 10)
mysql_csv "SELECT id,patient_appointment_id,organization_id,tenant_id,appointment_date,appointment_start,appointment_status,doctor_user_id,appointment_time_length,in_time,created_gmt_at,updated_gmt_at FROM \`arrail-appointment-server\`.appointment_base $(pf patient_appointment_id)" appointment_base.csv mysql_csv "SELECT id,patient_appointment_id,organization_id,tenant_id,appointment_date,appointment_start,appointment_status,doctor_user_id,appointment_time_length,in_time,created_gmt_at,updated_gmt_at FROM \`arrail-appointment-server\`.appointment_base $(pf patient_appointment_id)" appointment_base.csv
...@@ -160,7 +161,7 @@ echo "── Mongo 导出(med_emr_info,status∈{3,4} 正式病历)──" ...@@ -160,7 +161,7 @@ echo "── Mongo 导出(med_emr_info,status∈{3,4} 正式病历)──"
# 按真 UTC 解释则一半病历落深夜——荒谬)。故导出**不能** toISOString 带 Z(会被 PAC 当真 # 按真 UTC 解释则一半病历落深夜——荒谬)。故导出**不能** toISOString 带 Z(会被 PAC 当真
# UTC 再 +8,EMR 链时间全偏 8 小时,已踩):把 UTC 字段值直读为墙钟,输出 naive # UTC 再 +8,EMR 链时间全偏 8 小时,已踩):把 UTC 字段值直读为墙钟,输出 naive
# "YYYY-MM-DD HH:mm:ss",交给 manifest timezone=Asia/Shanghai 解释 → 瞬间正确。 # "YYYY-MM-DD HH:mm:ss",交给 manifest timezone=Asia/Shanghai 解释 → 瞬间正确。
# 诊断字典 map(diag_code→std_code)—— diag[] inline stdCode 的 within-host join;linkCode 在 Mongo、 # 诊断字典 map(diag_code→std_code)—— diag[] inline std_code 的 within-host join;linkCode 在 Mongo、
# 字典在 MySQL,故预取成 map 挂进 mongo 容器,eval 里逐元素解析(不再 PAC 侧 lookup std_diag)。 # 字典在 MySQL,故预取成 map 挂进 mongo 容器,eval 里逐元素解析(不再 PAC 侧 lookup std_diag)。
printf 'SELECT diag_code,std_code FROM emr.std_diag\n' | docker run -i --rm mysql:8 mysql \ printf 'SELECT diag_code,std_code FROM emr.std_diag\n' | docker run -i --rm mysql:8 mysql \
-h"$FRIDAY_MYSQL_HOST" -P"$FRIDAY_MYSQL_PORT" -u"$FRIDAY_MYSQL_USER" -p"$FRIDAY_MYSQL_PASSWORD" \ -h"$FRIDAY_MYSQL_HOST" -P"$FRIDAY_MYSQL_PORT" -u"$FRIDAY_MYSQL_USER" -p"$FRIDAY_MYSQL_PASSWORD" \
...@@ -183,7 +184,7 @@ docker run --rm ${MONGO_ARGS[@]+"${MONGO_ARGS[@]}"} mongo:7 mongosh "$FRIDAY_MON ...@@ -183,7 +184,7 @@ docker run --rm ${MONGO_ARGS[@]+"${MONGO_ARGS[@]}"} mongo:7 mongosh "$FRIDAY_MON
// id 类字段字符串化:Mongo 存的是 number/Long 混型,canonical 契约要 string // id 类字段字符串化:Mongo 存的是 number/Long 混型,canonical 契约要 string
// (CSV 源天然全字符串;JSON 导出须显式对齐,否则 patientExternalId 等校验失败) // (CSV 源天然全字符串;JSON 导出须显式对齐,否则 patientExternalId 等校验失败)
const sid = (v) => (v === undefined || v === null || v === "") ? null : String(flat(v)); const sid = (v) => (v === undefined || v === null || v === "") ? null : String(flat(v));
// 诊断字典 map(within-host join):diag[].linkCode → std_code inline 进元素 stdCode // 诊断字典 map(within-host join):diag[].linkCode → std_code inline 进元素(沿用宿主列名 std_code)
let DIAG = {}; try { DIAG = JSON.parse(require("fs").readFileSync("/std_diag_map.json", "utf-8")); } catch (e) {} let DIAG = {}; try { DIAG = JSON.parse(require("fs").readFileSync("/std_diag_map.json", "utf-8")); } catch (e) {}
const q = { status: { $in: [3, 4] } }; const q = { status: { $in: [3, 4] } };
try { try {
...@@ -202,9 +203,9 @@ docker run --rm ${MONGO_ARGS[@]+"${MONGO_ARGS[@]}"} mongo:7 mongosh "$FRIDAY_MON ...@@ -202,9 +203,9 @@ docker run --rm ${MONGO_ARGS[@]+"${MONGO_ARGS[@]}"} mongo:7 mongosh "$FRIDAY_MON
organization_name: flat(d.organizationName), // 诊所名(clinic_directory 派生 host.clinicNames) organization_name: flat(d.organizationName), // 诊所名(clinic_directory 派生 host.clinicNames)
patient_id: sid(d.patientId), user_id: sid(d.userId), user_name: flat(d.userName), patient_id: sid(d.patientId), user_id: sid(d.userId), user_name: flat(d.userName),
clinic_time: flat(d.clinicTime), visit_indicator: flat(d.visitIndicator), status: flat(d.status), clinic_time: flat(d.clinicTime), visit_indicator: flat(d.visitIndicator), status: flat(d.status),
// diag[] inline stdCode(宿主 within-host join);treat/dispose/examine 原生数组原样 // diag[] inline std_code(宿主 within-host join);treat/dispose/examine 原生数组原样
diag: (d.diag ?? []).map((el) => Object.assign({}, el, diag: (d.diag ?? []).map((el) => Object.assign({}, el,
{ stdCode: (el && el.linkCode != null && el.linkCode !== "") ? (DIAG[String(el.linkCode)] ?? "") : "" })), { std_code: (el && el.linkCode != null && el.linkCode !== "") ? (DIAG[String(el.linkCode)] ?? "") : "" })),
treat: d.treat ?? [], dispose: d.dispose ?? [], examine: d.examine ?? [], treat: d.treat ?? [], dispose: d.dispose ?? [], examine: d.examine ?? [],
// emr_record 自由文本位要字符串(fact 层 zod nullableString);数组原样给 split_json_array 拆行 // emr_record 自由文本位要字符串(fact 层 zod nullableString);数组原样给 split_json_array 拆行
dispose_text: (d.dispose ?? []).length ? JSON.stringify(d.dispose) : null, dispose_text: (d.dispose ?? []).length ? JSON.stringify(d.dispose) : null,
......
# FRIDAY SaaS Cold Import Manifest(首版 — 仅 patient 试跑) # FRIDAY SaaS Cold Import Manifest(首版 — 仅 patient 试跑)
# #
# 数据源:FRIDAY SaaS MySQL(customer 库)。cold-import 不支持 MySQL 直连, # 数据源:FRIDAY SaaS MySQL(customer 库)。cold-import 不支持 MySQL 直连,
# 走 CSV 文件模式:host 端**纯导原始表**(SELECT * FROM tb),不做任何 join。 # 走 CSV 文件模式。宿主推「自洽业务表」:自己的引用(私有字典码 / 1:N 默认号 / 头-行属性)
# 电话在 1:N 的 customer_contacts 里 → 由 transforms.lookup 按 customer_id join、 # 在导出时 within-host join 后 inline 进相关行,**列名沿用宿主原表列名**(见 tables 上方说明);
# 挑默认号(is_default→本人→最早)填成 patient.phone 标量。宿主零特殊处理 # 业务 WHERE / status 切分等全部留在 PAC transforms(单一真理源)
# #
# ── 方案 B:合成"市场"集团 tenant;品牌降为 source_unit 命名空间 ── # ── 方案 B:合成"市场"集团 tenant;品牌降为 source_unit 命名空间 ──
# FRIDAY 是多品牌 SaaS(30+ 独立品牌 tenant_id,各含 1-8 诊所 organization_id)。 # FRIDAY 是多品牌 SaaS(30+ 独立品牌 tenant_id,各含 1-8 诊所 organization_id)。
...@@ -39,7 +39,7 @@ clinic_directory: ...@@ -39,7 +39,7 @@ clinic_directory:
# ── 增量水位声明(2026-07 与 jvs-dw 统一;file 源放 manifest 顶层)── # ── 增量水位声明(2026-07 与 jvs-dw 统一;file 源放 manifest 顶层)──
# file 装载不消费水位(每轮全量装载,幂等去重兜底);每轮跑完与 jvs-dw 走同一段 finally # file 装载不消费水位(每轮全量装载,幂等去重兜底);每轮跑完与 jvs-dw 走同一段 finally
# 记账 cursor_after = run_start(sync_logs)。用途:delta 导出 WHERE 模板 / push 回放起点。 # 记账 cursor_after = run_start(sync_logs)。用途:delta 导出 WHERE 模板 / push 回放起点。
# 只列有 updated_gmt_at 的事件表;字典表(std_*)与 customer_contacts(无更新列)恒全量 # 只列有 updated_gmt_at 的事件表(字典/联系方式/计划头等已 inline,不再单独摄入)
incremental: incremental:
per_query: per_query:
customer_basic_info: { cursor_column: updated_gmt_at } customer_basic_info: { cursor_column: updated_gmt_at }
...@@ -57,10 +57,10 @@ incremental: ...@@ -57,10 +57,10 @@ incremental:
# 联系方式 1:N(customer_contacts→inline 默认号进主档)、计划头(customer_treat_plan→inline 进行)、 # 联系方式 1:N(customer_contacts→inline 默认号进主档)、计划头(customer_treat_plan→inline 进行)、
# 支付通道(settlement_modes,金额已在结算头 net_receipts_this)。所有摄入路径(push/file/pull/reparse)同此形态。 # 支付通道(settlement_modes,金额已在结算头 net_receipts_this)。所有摄入路径(push/file/pull/reparse)同此形态。
tables: tables:
- { table: customer_basic_info, file: customer_basic_info.csv } # 患者主档(含 inline phone/phone_relationship) - { table: customer_basic_info, file: customer_basic_info.csv } # 患者主档(含 inline contacts_tel/relationship)
- { table: appointment_base, file: appointment_base.csv } # 预约(原始,全状态) - { table: appointment_base, file: appointment_base.csv } # 预约(原始,全状态)
# ── EMR 病历链(W FRIDAY-EMR:Mongo med_emr_info status∈{3,4} 正式病历;export.sh 平铺导出)── # ── EMR 病历链(W FRIDAY-EMR:Mongo med_emr_info status∈{3,4} 正式病历;export.sh 平铺导出)──
# diag[] 元素含 inline stdCode(宿主解析 linkCode→std_diag.std_code);treat/dispose/examine 原生数组。 # diag[] 元素含 inline std_code(宿主解析 linkCode→std_diag.std_code);treat/dispose/examine 原生数组。
- { table: med_emr_info, file: med_emr_info.json } # 病历正文(Mongo,含 diag/treat 数组) - { table: med_emr_info, file: med_emr_info.json } # 病历正文(Mongo,含 diag/treat 数组)
- { table: med_check, file: med_check.csv } # 影像档案(含 inline class_name;挂 emr_id) - { table: med_check, file: med_check.csv } # 影像档案(含 inline class_name;挂 emr_id)
- { table: customer_treat_plan_item, file: customer_treat_plan_item.csv } # 治疗计划行(含 inline organization_id/plan_name) - { table: customer_treat_plan_item, file: customer_treat_plan_item.csv } # 治疗计划行(含 inline organization_id/plan_name)
...@@ -72,7 +72,7 @@ tables: ...@@ -72,7 +72,7 @@ tables:
transforms: transforms:
# ── 患者 phone:宿主已按「默认号→本人→最早」挑好、inline 进 customer_basic_info 的 # ── 患者 phone:宿主已按「默认号→本人→最早」挑好、inline 进 customer_basic_info 的
# phone / phone_relationship 两列(不再 lookup customer_contacts)。patient.yaml 直接读。── # contacts_tel / relationship 两列(沿用宿主源列名;不再 lookup customer_contacts)。patient.yaml 直接读。──
# ── 预约:丢草稿 → 全部预约(拼 scheduledAt)+ 已到诊拆成接诊事件(→ encounter)── # ── 预约:丢草稿 → 全部预约(拼 scheduledAt)+ 已到诊拆成接诊事件(→ encounter)──
# 与 jvs-dw 同款设计:一个预约源喂两张 canonical(appointment / encounter)。 # 与 jvs-dw 同款设计:一个预约源喂两张 canonical(appointment / encounter)。
...@@ -131,9 +131,9 @@ transforms: ...@@ -131,9 +131,9 @@ transforms:
element_fields: element_fields:
value: value value: value
tooth_position: toothPosition tooth_position: toothPosition
std_code: stdCode # 宿主已解析 linkCode→std_diag.std_code、inline 进元素(不再 PAC 侧 lookup) std_code: std_code # 宿主已解析 linkCode→std_diag.std_code、inline 进元素(沿用宿主列名;不再 PAC 侧 lookup)
where: where:
value: { not_empty: true } # 只要求有诊断名;stdCode 可空(非 K/未解析 → 走自由文本) value: { not_empty: true } # 只要求有诊断名;std_code 可空(非 K/未解析 → 走自由文本)
# E.2.2 归一 + K 码截取 + coalesce # E.2.2 归一 + K 码截取 + coalesce
# 规则(用户定):std_code K 开头 → 截 K 大类;非 K(DA0E.50 等 ICD-11)/join 不上/linkCode 空 # 规则(用户定):std_code K 开头 → 截 K 大类;非 K(DA0E.50 等 ICD-11)/join 不上/linkCode 空
......
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