Commit c4636e36 by luoqi

feat(friday): 摄入契约收敛为 9 张自洽表 — 宿主 inline 自己的引用(全路径一致)

manifest 共用于所有摄入路径(cold-import/file/pull/push/reparse),改它一处全生效。
删 5 处跨表 lookup + 5 张源表,宿主导出时 within-host join 后 inline:
- 私有字典(std_diag/std_check_class)→ diag[].stdCode / med_check.class_name(删 lookup + 源表)
- 联系方式 1:N(customer_contacts)→ customer_basic_info.phone/phone_relationship(挑默认号)
- 计划头(customer_treat_plan)→ 计划行 organization_id/plan_name(删 lookup + 头源表 + group)
- 支付通道(settlement_modes)→ 不摄入(金额在结算头 net_receipts_this;payment 去掉 method)
- incremental 清废弃表名(patient_settlement_refund/spec_refund)+ 补 patient_settlement_spec

export.sh 同步改成自洽形态(within-host join;含 diag stdCode 跨库预取 map);
image.yaml primary image_rows→med_check;payment.yaml 去 method。

验证:元和王永 16472 患者 dry-run 逐资源计数与 inline 前**完全一致**
(patient/diagnosis 4458/treatment 5781/image 6170/payment 20995/refund 190+308);
中间表 41→33;typecheck 干净;全量 299 测试绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parent 08c515b6
# diagnosis — FRIDAY 诊断 fact(med_emr_info.diag[] 拆行)
# 消费 transforms 输出表 diagnosis_rows(split + std_diag lookup + normalize/substring/coalesce)
# 消费 transforms 输出表 diagnosis_rows(split + normalize/substring/coalesce;std_code 由宿主 inline 进 diag 元素)
#
# code 解析规则(用户定,2026-07-18):
# linkCode 非空 → std_diag.std_code K 开头 → 截 K 大类(std_code_k 直通下方共享字典 passthrough);
......
# image — FRIDAY 影像 metadata(MySQL emr.med_check;PAC 不持文件本体)
# 影像不在 Mongo 病历文档内(全字段枚举证实),挂 emr_id(10 位病历号)回病历。
# 消费 transforms 输出表 image_rows(med_check lookup std_check_class 附 class_name)
# 直接消费 med_check(class_name 由宿主 inline:class_code→std_check_class 已解析);modality 经共享字典翻
canonical: image
emits:
......@@ -9,7 +9,7 @@ emits:
occurredAtField: uploadedAt
primary:
table: image_rows
table: med_check
key: id
dedup_by: id
......@@ -21,7 +21,7 @@ field_mapping:
clinicId: organization_id
uploadedAt: shooting_time # 拍摄时间
encounterExternalId: emr_id # 病历号级挂载(= med_emr_info.emr_id,非 emr_sub_id)
modality: class_name # lookup 自 std_check_class 字典
modality: class_name # 宿主 inline(class_code→std_check_class 已解析)→ 共享字典翻
fileUrl: file_url # OSS 相对路径(passthrough)
fileName: file_name
......
# patient — 主档 upsert(不进 transaction,无 emits)
# 源:customer.customer_basic_info(原始表);phone 由 manifest.transforms.lookup
# 从 customer_contacts 挑默认号附加进来,故这里直接映射标量 phone
# 源:customer.customer_basic_info;phone / phone_relationship 由宿主导出时挑默认号 inline 进主档
# (不再 PAC 侧 lookup customer_contacts),故这里直接映射标量
canonical: patient
primary:
......@@ -10,7 +10,7 @@ primary:
field_mapping:
externalId: id # customer_basic_info 主键 = host 内部患者唯一 id(→ String 化)
name: name
phone: phone # 由 transforms.lookup 从 customer_contacts 挑默认号附加
phone: phone # 宿主 inline(默认号:非空→is_default↓→contacts_type↑→id↑)
# 该号关系码(lookup 同源带出)→ preferences.contactPhone{relationshipCode, relationship, isSelf}。
# 官方枚举(源码实锤 friday-saas/customer …/enums/PhoneRelationshipEnum.java,2026-07-20):
# 1本人 2爸爸 3妈妈 4爷爷 5奶奶 6朋友 7配偶 8子女 9其他(语义=持号人是患者的X)
......
......@@ -26,7 +26,8 @@ field_mapping:
# 应收降级 receivableAmount(折扣率分母/权益客识别)。
amount: net_receipts_this
receivableAmount: receivable_this
method: method # lookup 主导支付通道(现金/微信/刷卡/医保…原样入 fact)
# method 暂不接:支付通道明细(settlement_modes)不摄入,金额取结算头 net_receipts_this。
# 将来要"支付方式分析"(医保占比/分次支付)再摄入 settlement_modes、恢复 method。
doctorId: doctor_id
encounterExternalId: registration_id # 关联接诊
settlementSerialNum: settlement_serial_num # 结算流水号(passthrough,对账用)
# treatment_planned — FRIDAY 治疗计划(customer_treat_plan + _item 独立表;用户确认此表为治疗计划)
# 消费 transforms 输出表 treatment_planned_rows(行表 lookup 头表拿 organization_id/plan_name)
# treatment_planned — FRIDAY 治疗计划(消费 customer_treat_plan_item;头属性 organization_id/plan_name 由宿主 inline)
# 消费 transforms 输出表 treatment_planned_rows(item 过滤无诊所行 + normalize 术式名)
# 行粒度 = 计划行项(牙位 + 术式 mode_name + 价格区间);externalId = 行表主键(host 稳定)
canonical: treatment
......@@ -19,7 +19,7 @@ field_mapping:
updatedAt: updated_gmt_at
createdAt: created_gmt_at
patientExternalId: customer_id
clinicId: organization_id # lookup 自头表(行表无诊所)
clinicId: organization_id # 宿主 inline 自计划头(行表原生无诊所)
occurredAt: created_gmt_at # 计划制定时点
category: mode_norm # 归一术式名 → 共享字典翻 11 类闭集
subtype: mode_name # 原始术式名
......
......@@ -47,55 +47,32 @@ incremental:
med_emr_info: { cursor_column: updated_gmt_at }
med_check: { cursor_column: updated_gmt_at }
patient_settlement: { cursor_column: updated_gmt_at }
patient_settlement_refund: { cursor_column: updated_gmt_at }
patient_settlement_spec_refund: { cursor_column: updated_gmt_at }
settlement_modes: { cursor_column: updated_gmt_at }
customer_treat_plan: { cursor_column: updated_gmt_at }
patient_settlement_spec: { cursor_column: updated_gmt_at }
customer_treat_plan_item: { cursor_column: updated_gmt_at }
customer_referee_circle: { cursor_column: updated_gmt_at }
customer_consult: { cursor_column: updated_gmt_at }
# ⭐ 宿主推「自洽的业务表」:私有字典码 / 1:N 派生 / 头-行属性 都由宿主 within-host join 后 inline 进相关行
# (契约见 docs/integration/friday-push-payload)。故不再摄入纯字典表(std_diag/std_check_class)、
# 联系方式 1:N(customer_contacts→inline 默认号进主档)、计划头(customer_treat_plan→inline 进行)、
# 支付通道(settlement_modes,金额已在结算头 net_receipts_this)。所有摄入路径(push/file/pull/reparse)同此形态。
tables:
- { table: customer_basic_info, file: customer_basic_info.csv } # 患者主档(原始)
- { table: customer_contacts, file: customer_contacts.csv } # 联系方式 1:N(原始)
- { table: customer_basic_info, file: customer_basic_info.csv } # 患者主档(含 inline phone/phone_relationship)
- { table: appointment_base, file: appointment_base.csv } # 预约(原始,全状态)
# ── EMR 病历链(W FRIDAY-EMR:Mongo med_emr_info status∈{3,4} 正式病历;export.sh 平铺导出)──
# 数组字段(diag/treat/dispose/examine)导出保留原生数组(split_json_array 原生支持);
# dispose_text/examine_text 是同数据的 JSON 字符串形态(emr_record 自由文本位要 string)。
# diag[] 元素含 inline stdCode(宿主解析 linkCode→std_diag.std_code);treat/dispose/examine 原生数组。
- { table: med_emr_info, file: med_emr_info.json } # 病历正文(Mongo,含 diag/treat 数组)
- { table: med_check, file: med_check.csv } # 影像档案(挂 emr_id 病历号)
- { table: std_check_class, file: std_check_class.csv } # 影像类型字典(class_code→class_name)
- { table: std_diag, file: std_diag.csv } # 诊断字典(linkCode→std_code;K 码用)
# 治疗计划 = 头+行强成组(item 是 primary,lookup 头表拿诊所/方案名;缺任一 → planned 产 0 行)。
# group 只约束网页上传(要么全在要么整组跳);CLI 全量不受此限。
- { table: customer_treat_plan, file: customer_treat_plan.csv, group: treatment_plan } # 治疗计划头(方案名/期望)
- { table: customer_treat_plan_item, file: customer_treat_plan_item.csv, group: treatment_plan } # 治疗计划行(牙位/术式/价格)
- { 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)
# 结算原表(全 status,导出侧不做业务 WHERE)—— 消费/退费/明细全部由 PAC transforms 切分(单一真理源)
- { table: patient_settlement, file: patient_settlement.csv } # 结算头单原表(全 status)
- { table: patient_settlement_spec, file: patient_settlement_spec.csv } # 结算明细原表(全量,含 is_refund)
- { table: settlement_modes, file: settlement_modes.csv } # 支付通道长表
- { table: customer_referee_circle, file: customer_referee_circle.csv } # 转介绍圈(患者-患者边)
- { table: customer_consult, file: customer_consult.csv } # 咨询(意向/未成交原因)
transforms:
# ── 患者:把 1:N 联系方式归约成 patient.phone 标量(无需源侧 join)──
- kind: lookup
input: customer_basic_info
output: customer_basic_info # in-place 附加 phone 列
from: customer_contacts
left_key: id # 患者主键
right_key: customer_id # 联系方式指向患者的外键
where:
contacts_tel: { not_empty: true } # 空号候选剔除
order_by: # 挑一条:默认号 → 本人(type=1) → 最早 id
- { field: is_default, dir: desc }
- { field: contacts_type, dir: asc }
- { field: id, dir: asc }
select:
phone: contacts_tel # patient.phone ← 选中候选的 contacts_tel
# 该号与患者的关系码(host sys_dict 编码;实测 1=本人 78.7%(成人),儿童过半非本人号)。
# 先落原码(preferences.contactPhone,isSelf=码=='1');完整标签字典待 sys_dict 可读后补。
phone_relationship: relationship
# ── 患者 phone:宿主已按「默认号→本人→最早」挑好、inline 进 customer_basic_info 的
# phone / phone_relationship 两列(不再 lookup customer_contacts)。patient.yaml 直接读。──
# ── 预约:丢草稿 → 全部预约(拼 scheduledAt)+ 已到诊拆成接诊事件(→ encounter)──
# 与 jvs-dw 同款设计:一个预约源喂两张 canonical(appointment / encounter)。
......@@ -154,19 +131,9 @@ transforms:
element_fields:
value: value
tooth_position: toothPosition
link_code: linkCode
std_code: stdCode # 宿主已解析 linkCode→std_diag.std_code、inline 进元素(不再 PAC 侧 lookup)
where:
value: { not_empty: true } # 只要求有诊断名;linkCode 可空(仅 0.3% 有)
# E.2.1 linkCode → std_diag 字典(diag_code UUID 全局唯一,LEFT JOIN 未命中 → std_code=null)
- kind: lookup
input: _diag_raw
output: _diag_raw # in-place 附加 std_code
from: std_diag
left_key: link_code
right_key: diag_code
select:
std_code: std_code
value: { not_empty: true } # 只要求有诊断名;stdCode 可空(非 K/未解析 → 走自由文本)
# E.2.2 归一 + K 码截取 + coalesce
# 规则(用户定):std_code K 开头 → 截 K 大类;非 K(DA0E.50 等 ICD-11)/join 不上/linkCode 空
......@@ -305,30 +272,13 @@ transforms:
op: concat
parts: ['${emr_sub_id}', '|rec|', '${value}', '|', '${tooth_position}']
# ── E.4 影像:med_check + 类型字典 → class_name(modality 经共享字典翻)──
- kind: lookup
input: med_check
output: image_rows
from: std_check_class
left_key: class_code
right_key: class_code
select:
class_name: class_name
# ── E.4 影像:med_check 已含 inline class_name(宿主解析 class_code→std_check_class);
# image.yaml 直接以 med_check 为 primary、读 class_name 翻 modality(不再 PAC 侧 lookup)。──
# ── E.5 治疗计划:行表 join 头表拿 诊所/方案名 → planned 候选 ──
- kind: lookup
input: customer_treat_plan_item
output: _plan_item_joined
from: customer_treat_plan
left_key: treat_plan_id
right_key: id
select:
organization_id: organization_id
plan_name: plan_name
# 头表 lookup 未命中 / 头表无诊所 → 计划行无法归属诊所,显式丢弃(canonical clinicId 必填;
# 留着只会变 failed 噪音。测试库实测 75/187 行如此)
# ── E.5 治疗计划:计划行已含 inline organization_id/plan_name(宿主取自计划头);
# 仅丢弃无诊所归属的行(canonical clinicId 必填;头无诊所 → 该行产 failed 噪音,提前剔除)。──
- kind: filter
input: _plan_item_joined
input: customer_treat_plan_item
output: _plan_item_with_org
where:
organization_id: { not_empty: true }
......@@ -345,23 +295,14 @@ transforms:
# 消费 = status∈{1,3};退费双轨 = ①status=4 整单(ref_settlement_id 挂原单)②spec is_refund=1 行级。
# LTV = Σpayment − Σrefund 自然对账;不从 settlement 反推治疗(财务≠临床,jvs-dw 已验证的教训)。
# ── S.1 消费:status 1/3 → 主导支付通道 lookup(金额最大者)──
# ── S.1 消费:status∈{1,3} 且应收≥0 → payment(金额取结算头 net_receipts_this)。
# 支付通道(settlement_modes)暂不摄入 → payment 无 method(总值在结算头,无需通道拆分)。──
- kind: filter
input: patient_settlement
output: _pay_hdr
output: payment_rows
where:
status: { in: ['1', '3'] }
receivable_this: { gte: 0 } # 应收≥0(迁自导出侧 WHERE;数值算子)
- kind: lookup
input: _pay_hdr
output: payment_rows
from: settlement_modes
left_key: uuid
right_key: settlement_id
order_by:
- { field: money, dir: desc }
select:
method: modes_name # 主导支付通道(现金/微信/刷卡/医保…干净中文名,原样入 fact)
# ── S.2 退费①:整单冲减 —— PAC 侧切分(不再靠导出 WHERE)。两种表达 union:
# ① status=4 整单反向;② status=3 且 receivable_this<0(2ac96f6d 品牌用负额表达退费)。
......
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