实现检查项目名称的智能匹配,能够处理:
将检查项目名称分解为三个维度:
然后针对每个维度分别计算相似度,最后加权汇总。
输入:"CT 上腹部平扫"
↓
【步骤1】规范化处理
- 去除多余空格
- 提取模态:CT
- 提取扫描方式:平扫
- 提取身体部位:上腹部
- 提取关键词:{C, T, 上, 腹, 部, 平扫, 上腹, 腹部}
↓
【步骤2】维度匹配
- 模态匹配:CT == CT → 0.3分
- 扫描方式:平扫 == 平扫 → 0.2分
- 身体部位相似度计算
- 关键词重合度计算
↓
【步骤3】相似度计算
总分 = 0.3(模态) + 0.2(扫描) + 0.3(部位) + 0.2(关键词)
= 0.3 + 0.2 + 0.3*部位相似度 + 0.2*关键词重合度
↓
【步骤4】阈值判断
if (总分 >= 0.7) → 匹配成功 ✅
else → 匹配失败 ❌
相似度 = 0.3 × 模态匹配得分
+ 0.2 × 扫描方式匹配得分
+ 0.3 × 身体部位相似度
+ 0.2 × 关键词重合度
if (modality1.equals(modality2)) {
return 0.3; // 完全匹配
} else {
return -0.5; // 不匹配,大幅降低分数
}
示例:
if (scanMethod1.equals(scanMethod2)) {
return 0.2;
}
示例:
使用多种算法计算:
a. 精确匹配
if (part1.equals(part2)) {
return 1.0;
}
b. 同义词匹配
// 同义词映射
{"头", "颅", "脑", "头部"} // 返回 0.9
{"胸", "胸部", "肺"} // 返回 0.9
{"腹", "腹部", "上腹"} // 返回 0.9
c. 包含关系
if (part1.contains(part2) || part2.contains(part1)) {
return 0.8;
}
d. 编辑距离(Levenshtein Distance)
distance = "上腹" vs "上腹部" → 1
maxLen = 3
similarity = 1 - 1/3 = 0.67
使用Jaccard相似系数:
交集 / 并集
示例:
关键词1 = {C, T, 上, 腹, 平扫}
关键词2 = {C, T, 上, 腹部, 平}
交集 = {C, T, 上} → 3个
并集 = {C, T, 上, 腹, 平扫, 腹部, 平} → 7个
相似度 = 3/7 = 0.43
目标:"CT 上腹部平扫"
候选:"CT上腹部平扫"
规范化:
目标 → CT+上腹部+平扫
候选 → CT+上腹部+平扫
维度匹配:
模态:CT == CT → 0.3
扫描:平扫 == 平扫 → 0.2
部位:上腹部 == 上腹部 → 0.3
关键词:完全重合 → 0.2
总分:0.3 + 0.2 + 0.3 + 0.2 = 1.0 ✅
目标:"CT 上腹部平扫"
候选:"上腹部CT平扫"
规范化:
目标 → {模态:CT, 部位:上腹部, 扫描:平扫}
候选 → {模态:CT, 部位:上腹部, 扫描:平扫}
维度匹配:
模态:CT == CT → 0.3
扫描:平扫 == 平扫 → 0.2
部位:上腹部 == 上腹部 → 0.3
关键词:{C,T,上,腹,平扫} vs {上,腹,C,T,平扫} → 0.2
总分:0.3 + 0.2 + 0.3 + 0.2 = 1.0 ✅
目标:"CT 上腹部平扫"
候选:"CT 腹部平扫"
规范化:
目标 → {模态:CT, 部位:上腹部, 扫描:平扫}
候选 → {模态:CT, 部位:腹部, 扫描:平扫}
维度匹配:
模态:CT == CT → 0.3
扫描:平扫 == 平扫 → 0.2
部位:上腹部 ⊂ 腹部(包含关系)→ 0.3 × 0.8 = 0.24
关键词:部分重合 → 0.2 × 0.7 = 0.14
总分:0.3 + 0.2 + 0.24 + 0.14 = 0.88 ✅
目标:"CT 上腹部平扫"
候选:"MR 头部增强"
规范化:
目标 → {模态:CT, 部位:上腹部, 扫描:平扫}
候选 → {模态:MR, 部位:头部, 扫描:增强}
维度匹配:
模态:CT != MR → -0.5
扫描:平扫 != 增强 → 0.0
部位:上腹部 != 头部 → 0.0
关键词:无重合 → 0.0
总分:-0.5 + 0.0 + 0.0 + 0.0 = -0.5 ❌
// 去除多余空格
text.replaceAll("\\s+", "")
// 提取关键词(支持中文)
- 单字:{C, T, 上, 腹, 部, 平, 扫}
- 双字:{CT, 上腹, 腹部, 平扫}
- 三字:{上腹部, 平扫}
// Levenshtein Distance
int distance = "上腹部" vs "上腹" → 1
int maxLen = 3
similarity = 1 - 1/3 = 0.67
Jaccard(A, B) = |A ∩ B| / |A ∪ B|
示例:
A = {CT, 上, 腹, 平扫}
B = {CT, 上, 腹部, 平}
|A ∩ B| = 3 // {CT, 上, 腹}
|A ∪ B| = 7 // {CT, 上, 腹, 平扫, 腹部, 平}
Jaccard = 3/7 = 0.43
| 维度 | 权重 | 说明 |
|---|---|---|
| 模态匹配 | 0.3 | 最重要,不同模态不匹配 |
| 扫描方式 | 0.2 | 较重要,增强vs平扫区别大 |
| 身体部位 | 0.3 | 最重要,不同部位不匹配 |
| 关键词重合 | 0.2 | 辅助判断,提升准确率 |
| 总计 | 1.0 |
| 阈值 | 含义 | 应用场景 |
|---|---|---|
| 0.9 - 1.0 | 完全匹配 | 几乎相同,可直接使用 |
| 0.7 - 0.9 | 良好匹配 | 可以接受,人工确认后使用 |
| 0.5 - 0.7 | 一般匹配 | 需要人工审核 |
| < 0.5 | 不匹配 | 不推荐使用 |
当前阈值:0.7(相似度 >= 0.7 才匹配成功)
优先级1:study_info.exam_item_name 精确匹配
示例:"CT 上腹部平扫" == "CT 上腹部平扫" → 1.0
触发条件:exam_item完全一致
优先级2:DICOM.examItem 精确匹配
示例:"CT ABDOMEN..." == "CT ABDOMEN..." → 1.0
触发条件:exam_item完全一致
优先级3:study_info.exam_item_name 智能匹配 ← NEW!
示例:"CT 上腹部平扫" ~ "CT上腹部平扫" → 0.95
示例:"CT 上腹部平扫" ~ "上腹部CT平扫" → 0.92
触发条件:相似度 >= 0.7
优先级4:DICOM.examItem 智能匹配 ← NEW!
示例:使用复杂DICOM标签进行智能匹配
触发条件:相似度 >= 0.7
优先级5:bodyPart 模糊匹配
示例:使用"ABDOMEN"等部位信息
触发条件:所有上述都失败
| 序号 | 目标 | 候选 | 预期相似度 | 结果 |
|---|---|---|---|---|
| 1 | CT 上腹部平扫 | CT上腹部平扫 | >= 0.9 | ✅ |
| 2 | CT 上腹部平扫 | 上腹部CT平扫 | >= 0.9 | ✅ |
| 3 | CT 上腹部平扫 | CT腹部平扫 | >= 0.8 | ✅ |
| 4 | CT 上腹部平扫 | CT胸部平扫 | < 0.5 | ❌ |
| 5 | MR 颅脑平扫 | MR头部平扫 | >= 0.9 | ✅ |
| 6 | CT 冠脉CTA | CT冠状动脉增强 | >= 0.8 | ✅ |
| 7 | DR 胸部后前位 | DR胸部PA | >= 0.7 | ✅ |
当前实现:每次匹配都查询所有标准
List<QcStandard> standards = qcStandardMapper.selectList(...);
优化方案:缓存质控标准
@Cacheable("qc_standards")
public List<QcStandard> getAllActiveStandards() {
return qcStandardMapper.selectList(...);
}
增加更多同义词映射
BODY_PART_SYNONYMS.put("冠状动脉", Arrays.asList("冠脉", "冠状动脉", "冠"));
BODY_PART_SYNONYMS.put("脑血管", Arrays.asList("脑血管", "脑血", "颈动脉"));
增加扫描方式识别
SCAN_METHOD_KEYWORDS.addAll(Arrays.asList(
"CTA", "MRA", "SWI", "DWI", "FLAIR", "VR", "MIP"
));
返回匹配详情
public class MatchDetail {
private String candidate;
private double similarity;
private String matchReason; // 匹配原因说明
private Map<String, Double> dimensionScores; // 各维度得分
}
// 注入匹配器
@Resource
private ExamItemMatcher examItemMatcher;
// 使用匹配
String target = "CT 上腹部平扫";
List<String> candidates = Arrays.asList(
"CT上腹部平扫",
"上腹部CT平扫",
"CT胸部平扫"
);
MatchResult result = examItemMatcher.findBestMatch(target, candidates);
if (result != null && result.getSimilarity() >= 0.7) {
System.out.println("匹配成功:" + result.getCandidate());
System.out.println("相似度:" + result.getSimilarity());
}
-- 查看实际的exam_item数据
SELECT exam_item_name, COUNT(*)
FROM study_info
WHERE exam_item_name LIKE '%上腹部%'
GROUP BY exam_item_name;
-- 查看质控标准配置
SELECT exam_item, standard_name
FROM qc_standard
WHERE modality = 'CT'
AND exam_item LIKE '%上腹部%';
算法版本:v1.0 创建时间:2026-01-27 适用场景:质控标准自动匹配