Files
zkxw/docs/superpowers/specs/2026-07-02-donation-import-multi-sample-design.md
T
2026-07-02 12:25:00 +08:00

294 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 科研捐赠协议单 · 一键导入增强(单样本 / 多样本)
> 日期: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`(或新增子类型):
```typescript
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 | 多样本以采集时间字段为准,首行标题仅作回退 | 避免标题歧义 |