2026-07-02-donation-import-multi-sample-design.md 12 KB

科研捐赠协议单 · 一键导入增强(单样本 / 多样本)

日期:2026-07-02
状态:已确认 / 已实现
关联模块:web-admin 捐赠协议单新增弹窗(DonationFormDialog.vue)
关联常量:SAMPLE_TYPE_PERINATAL_HSC、SAMPLE_TYPE_UC_MSC(sample-production-reception-fields.ts)


1. 背景与目标

1.1 业务诉求

科研捐赠协议单新增时,左侧「一键导入」支持粘贴客户提供的标准文本,解析后自动填充右侧表单。客户文本存在两种典型场景:

  1. 单样本:如围产期造血干细胞捐献,仅一种样本类型。
  2. 多样本:如同一产妇同时采集「围产期造血干细胞」与「脐带间充质干细胞」,文本中含多条不同样本的采集时间字段。

当前实现仅支持首行标题推断一个样本类型、固定快递运输、胎次仅识别阿拉伯数字、时间格式不支持「点」,无法正确导入多样本数据。

1.2 目标

  1. 根据采集时间字段名推断应创建的样本类型与行数(1 行或 N 行)。
  2. 正确映射运输方式:快递 → kdys,顺风车等 → ryxd(人员携带)。
  3. 胎次支持中文数字(如「二胎」→ 2)。
  4. 中文时间支持 18点05分 格式(与 12时17分 等价)。
  5. 保持「仅填充空白字段、不覆盖已有值」策略。
  6. 不改后端表结构;协议级 sampleCollectTime 取各样本采集时间中的最早值(已确认:选项 A)。

1.3 非目标(本期不做)

  • 样本表增加独立 sample_collect_time 字段(方案 B,后续若产品要求再立项)。
  • 配置化规则引擎(JSON/YAML 运营可改规则)。
  • 细胞存储协议单一键导入(本模块无此功能)。
  • 一键导入 UI 布局变更(仍为新增弹窗左侧粘贴区 + 右侧表单)。

2. 客户文本样例

2.1 单样本(捐献)

围产期干细胞(捐献)
产妇姓名:李会
新生儿性别:男
新生儿体重:3900g
新生儿评分: 9
分娩时间:2026年6月22日12时17分
造血干细胞采集时间:2026年6月22日12时18分
母血采集时间:未采集
分娩方式:顺产
胎次:3
采集医护:王天琪
采集医院:利辛县人民医院
运输方式:顺丰快递
销售人员:赵文静
采集医院:利辛县人民医院

期望结果:1 条样本(围产期造血干细胞);运输 kdys + 顺丰;胎次 3。

2.2 多样本(科研脐带)

围产期干细胞(科研脐带)
产妇姓名:陆玲
新生儿性别:男孩
新生儿体重:3700g
分娩时间:2026年3月14日 18点05分
造血干细胞样本采集时间:2026年3月14日 18点06分
脐带采集时间:2026年3月14日  18点08分
母血采集时间:2026年3月14日  19点40分
分娩方式:顺产
胎次:二胎
运输方式:顺风车
销售人员:张慧茹
采集医院:涡阳县中医院

期望结果:2 条样本(围产期造血干细胞、脐带间充质干细胞);协议级 sampleCollectTime = 2026-03-14 18:06(最早);运输 ryxd,不填快递公司;胎次 2。


3. 业务规则

3.1 样本类型推断(优先级)

优先级 规则 说明
1 采集时间字段 存在且值可解析 → 创建对应样本行(可多条)
2 首行标题 无采集时间字段时,回退现有 matchCellSampleType(header) 逻辑

采集时间字段 → 样本类型映射:

字段名(含别名) 目标类型名 typeKey
造血干细胞样本采集时间、造血干细胞采集时间、样本采集时间 围产期造血干细胞 hsc
脐带采集时间 脐带间充质干细胞 uc_msc
  • 同一 typeKey 在文本中多次出现:以首次有效时间为准。
  • 字段值为空或「未采集」等占位:不创建该样本行。
  • 多样本场景下,首行标题不参与样本类型决定(避免「围产期干细胞(科研脐带)」歧义)。

3.2 协议级样本采集时间(已确认)

  • 存储位置:formData.sampleCollectTime → biz_research_donation_collection.sample_collect_time。
  • 多样本:取所有已识别样本采集时间中的最早时间(minute 精度 YYYY-MM-DD HH:mm)。
  • 单样本:该样本采集时间写入协议级字段(与现逻辑一致)。
  • 若协议级字段已有值:不覆盖(空白才填)。

3.3 运输方式映射

客户文本字段仍为 运输方式;解析结果区分 模式 与 原始标签:

客户文本特征 transportType 后续处理
含「快递」「顺丰」「圆通」「中通」「韵达」「EMS」等 kdys 匹配 expressCompanyId(现有 matchExpressCompany)
含「顺风车」「顺丰车」(后者为笔误)、「自行」「人员携带」「自带」「自送」等 ryxd 不填 expressCompanyId;可选后续人工填携带人
无法识别 — warning,不填运输
  • 多样本时:各样本行复制同一套运输配置(客户数据中通常一致)。
  • 仅当样本行 transport.transportType 为空时写入。

3.4 胎次

输入示例 解析结果
3 3
二胎、三胎 2、3
第二胎 2
无法解析 warning,不填

实现:parseBirthOrder(text) — 先 extractNumericPart;若无阿拉伯数字,再解析中文数字(一~十及「N胎」模式)。

3.5 其他字段(保持 / 微调)

字段 规则
新生儿性别 扩展识别 男孩→1、女孩→0
母血采集时间 「未采集」等 → 清空(已有)
采集医院重复行 首次有效值为准(已有)
母亲 / 医院 / 销售 远程匹配(已有)
样本编码 每行样本 assignDonationSampleCodeIfMissing(已有)

3.6 填充策略(不变)

  • 仅填空:目标字段为空才写入。
  • 样本行:若 formData.samples 已有同类型样本且 sampleTypeId 非空,不重复添加;若为空行可复用。
  • 导入完成后:ElMessage.success 列出已填充项;ElMessage.warning 列出未匹配项。

4. 技术设计

4.1 解析层结构变更

文件:web-admin/src/utils/donation-standard-import-parse.ts

扩展 DonationStandardImportParsed(或新增子类型):

export type DonationSampleImportHint = {
  typeKey: 'hsc' | 'uc_msc'
  typeName: string
  collectTime?: string // 已解析为标准 datetime-minute
}

export type DonationStandardImportParsed = {
  sampleTypeHeader: string
  sampleHints: DonationSampleImportHint[] // 新增:0..N
  transportModeText?: string // 原始运输方式文本
  transportType?: 'kdys' | 'ryxd' // 新增:解析后的系统码
  // ... 现有 common 字段保持不变
}

新增 / 导出函数:

函数 职责
parseBirthOrder(text) 阿拉伯 + 中文胎次
resolveTransportType(text) 运输文本 → kdys | ryxd | null
matchSampleTypeByExactName(name, types) 按 typeName 精确匹配(优先于 segment 评分)

采集时间字段识别:在 KEY_ALIASES 之外,对 key 做 pattern 匹配(支持「造血干细胞样本采集时间」等变体),命中后写入 sampleHints 而非单一 sampleCollectTime。

回退:sampleHints.length === 0 时,保留现有 sampleCollectTime + 首行 matchCellSampleType 行为。

4.2 时间解析增强

文件:web-admin/src/utils/chinese-datetime-parse.ts

  • 正则扩展:[时::点] 均视为小时分隔符。
  • 例:2026年3月14日 18点06分 → 2026-03-14 18:06。

可选新文件:web-admin/src/utils/chinese-numeral-parse.ts

  • parseChineseNumeral(text: string): number | null — 支持「二」「二胎」「三胎」等;供 parseBirthOrder 调用。

4.3 填充层变更

文件:web-admin/src/views/research-donation/list/DonationFormDialog.vue — handleStandardImport()

1. parseDonationStandardImportText(text)
2. 并行:母亲 / 医院 / 销售 / 字典预加载(现有)
3. common 字段填空(性别、体重、评分、分娩时间、母血、分娩方式、胎次、医护…)
4. 样本行:
   if sampleHints.length > 0:
     for each hint:
       matchSampleTypeByExactName(hint.typeName)
       获取或创建对应样本行
       assignDonationSampleCodeIfMissing
     sampleCollectTime = min(hint.collectTime)  // 最早
   else:
     现有单样本逻辑(首行 + 一行样本)
5. 运输:
   if parsed.transportType === 'kdys': 匹配快递公司
   if parsed.transportType === 'ryxd': transportType=ryxd,跳过 expressCompanyId
6. hydrateSampleLookupOptions + 消息反馈

4.4 数据模型(无变更)

实体 字段 说明
biz_research_donation_collection sample_collect_time 协议级;多样本取最早采集时间
biz_research_donation_sample — 无 per-sample 采集时间;本期不加
biz_research_donation_sample_transport transport_type kdys / ryxd

5. 测试

新建:web-admin/src/utils/donation-standard-import-parse.test.ts(或项目现有 vitest 路径)

用例 断言
单样本完整文本(§2.1) 1 个 hint(hsc);transportType=kdys;birthOrder=3
多样本完整文本(§2.2) 2 个 hint(hsc+uc_msc);最早时间 18:06;transportType=ryxd;birthOrder=2
胎次「二胎」「3」 2、3
时间「18点06分」「12时18分」 等价解析
仅首行无采集时间 sampleHints 空,回退首行匹配
运输「顺风车」「顺丰快递」 ryxd / kdys

手工验收(/#/market/research-donation/audit 或 list 页新增):

  1. 粘贴 §2.1 → 1 样本、顺丰、胎次 3。
  2. 粘贴 §2.2 → 2 样本、人员携带、协议采集时间 18:06、胎次 2。
  3. 先手动填部分字段再导入 → 已填字段不被覆盖。

6. 改动文件清单

文件 改动类型
web-admin/src/utils/donation-standard-import-parse.ts 扩展解析:多样本 hints、运输、胎次
web-admin/src/utils/chinese-datetime-parse.ts 支持「点」
web-admin/src/utils/chinese-numeral-parse.ts 新建(可选,也可内联于 parse 文件)
web-admin/src/views/research-donation/list/DonationFormDialog.vue handleStandardImport 多样本 + 运输
web-admin/src/utils/donation-standard-import-parse.test.ts 新建单元测试

不改:backend/、docs/sql/、OpenSpec(除非评审要求补充 ui-conventions 条目)。


7. 风险与限制

  1. 协议级采集时间:多样本各时间不同,列表/采集信息页仅展示一个时间;业务接受「最早时间」代表协议(已确认)。
  2. 运输携带人:ryxd 模式下客户文本通常不含携带人姓名;导入后可能需人工补全。
  3. 未知样本类型:若未来出现第三种采集时间字段,需扩展 SAMPLE_COLLECT_TIME_RULES;本期仅 hsc / uc_msc。

8. 验收标准

  • §2.1 单样本文本一键导入后,样本 Tab 有 1 行「围产期造血干细胞」,运输为快递运输 + 顺丰。
  • §2.2 多样本文本一键导入后,样本 Tab 有 2 行(造血 + 脐带),运输为人员携带,协议采集时间为 18:06。
  • 胎次「二胎」解析为 2;「3」解析为 3。
  • 18点06分 时间正确解析。
  • 已有字段不被覆盖;单元测试通过。

9. 决策记录

日期 决策 理由
2026-07-02 方案 A:纯前端增强,不改表 最小改动、与现架构一致
2026-07-02 协议级 sampleCollectTime 取最早采集时间 用户确认选项 A
2026-07-02 多样本以采集时间字段为准,首行标题仅作回退 避免标题歧义