|
|
@@ -48,6 +48,15 @@ KNOWN_TRACE = ['铁', '锌']
|
|
|
KNOWN_ANTIBIOTICS = ['β-内酰胺酶类', '氨基糖苷类', '大环内酯类', '呋喃类',
|
|
|
'喹诺酮类', '磺胺类', '甲氧苄啶类', '氯霉素类', '四环素类']
|
|
|
|
|
|
+# 已知疾病风险指标
|
|
|
+KNOWN_DISEASE_RISKS = ['炎症性肠炎', '肠易激综合征', '感染性腹泻', '自闭症',
|
|
|
+ '抑郁症', '甲状腺疾病', '肺部感染或疾病', '肺部疾病', '自体免疫病',
|
|
|
+ '自体免疫疾病', '结直肠癌', '肥胖', '便秘', '过敏', '失眠',
|
|
|
+ '肝病', '肾病', '胃病', '胆病', '心脑血管疾病', 'II型糖尿病',
|
|
|
+ '心脑血管疾病风险', '甲状腺疾病风险', '肠易激综合征风险', '感染性腹泻风险',
|
|
|
+ '炎症性肠炎风险', '自闭症风险', '抑郁症风险', '过敏风险',
|
|
|
+ '便秘风险', '腹胀风险', '肥胖风险']
|
|
|
+
|
|
|
# 已知肠道屏障指标
|
|
|
KNOWN_BARRIER = ['肠道炎症水平', '肠道产气', '肠道屏障', '脂多糖LPS',
|
|
|
'次级胆汁酸', '对甲酚(p-Cresol)', '吲哚', '苯酚', '腐胺',
|
|
|
@@ -396,53 +405,136 @@ def extract_barrier_and_scfa_triplet(all_lines):
|
|
|
# ==========================================
|
|
|
# inline 格式中各模块提取(v5:使用已知名称词表)
|
|
|
# ==========================================
|
|
|
-def extract_inline_module(text, start_marker, end_markers, known_names):
|
|
|
- """从 inline 文本中提取已知名称的指标(密集同行格式专用)
|
|
|
-
|
|
|
- inline 格式示例:
|
|
|
- 维生素A54正常维生素B183正常维生素B253正常
|
|
|
- 或:β-内酰胺酶类76正常氨基糖苷类97注意
|
|
|
-
|
|
|
- 已知名称在文本中是紧挨着的,没有空格分隔。
|
|
|
- 找到名称后,紧跟着的数字就是数值,之后到下一个名称之间的文本就是状态。
|
|
|
+def _extract_names_from_region(region_str, known_names, end_pos):
|
|
|
+ """从区域文本中提取已知名称+数值+状态"""
|
|
|
+ # 先去掉已知的头部标记
|
|
|
+ for h in ['肠道屏障及菌群代谢物名称评估值正常范围过量 / 缺乏相关症状',
|
|
|
+ '名称评估值正常范围过量 / 缺乏相关症状',
|
|
|
+ '短链脂肪酸评估值正常范围过量 / 缺乏相关症状',
|
|
|
+ '神经递质及激素评估值正常范围过量 / 缺乏相关症状',
|
|
|
+ '过量 / 缺乏相关症状']:
|
|
|
+ region_str = region_str.replace(h, '')
|
|
|
+ positions = []
|
|
|
+ for kn in known_names:
|
|
|
+ # 从后往前找(避免被头部标记中的子串命中)
|
|
|
+ for ii in range(len(region_str) - len(kn), -1, -1):
|
|
|
+ if region_str[ii:ii+len(kn)] == kn:
|
|
|
+ # 确认后面有数字
|
|
|
+ after = region_str[ii+len(kn):ii+len(kn)+5]
|
|
|
+ if re.match(r'\d', after):
|
|
|
+ positions.append((ii, kn))
|
|
|
+ break
|
|
|
+ if not positions:
|
|
|
+ return []
|
|
|
+ positions.sort()
|
|
|
+ local = []
|
|
|
+ for i, (ii, kn) in enumerate(positions):
|
|
|
+ start_after = ii + len(kn)
|
|
|
+ vm = re.match(r'(\d+(?:\.\d+)?)', region_str[start_after:])
|
|
|
+ if not vm:
|
|
|
+ continue
|
|
|
+ val = vm.group(1)
|
|
|
+ val_end = start_after + len(val)
|
|
|
+ if i + 1 < len(positions):
|
|
|
+ next_idx = positions[i + 1][0]
|
|
|
+ status = region_str[val_end:next_idx].strip()
|
|
|
+ else:
|
|
|
+ status = region_str[val_end:end_pos].strip()
|
|
|
+ # 清理状态中的残留标记
|
|
|
+ status = re.sub(
|
|
|
+ r'注意大环内酯类|注意呋喃类|注意氯霉素类|注意喹诺酮类|注意磺胺类|'
|
|
|
+ r'注意甲氧苄啶类|注意四环素类|正常氨基酸评估|正常维生素评估|'
|
|
|
+ r'正常微量元素评估|指标范围|疾病风险评估|营养状况评估|'
|
|
|
+ r'抗生素风险评估|抗生素耐药风险|主要消化道致病菌',
|
|
|
+ '', status).strip()
|
|
|
+ if len(status) > 20:
|
|
|
+ status = status[:20]
|
|
|
+ local.append({'名称': kn, '数值': val, '状态': status})
|
|
|
+ return local
|
|
|
+
|
|
|
+
|
|
|
+def extract_inline_module(all_text, start_marker, end_markers, known_names):
|
|
|
+ """从 inline 文本中提取已知名称的指标
|
|
|
+
|
|
|
+ 支持两种布局:
|
|
|
+ A) 标题在前、数据在后(标准):
|
|
|
+ 抗生素风险评估
|
|
|
+ β-内酰胺酶类76正常...四环素类96注意
|
|
|
+ 抗生素耐药风险
|
|
|
+
|
|
|
+ B) 标题在后、数据在前(朱报告格式):
|
|
|
+ 指标范围
|
|
|
+ 维生素A54正常...锌33正常
|
|
|
+ 维生素评估
|
|
|
+ 微量元素评估
|
|
|
"""
|
|
|
- results = []
|
|
|
- sidx = text.find(start_marker)
|
|
|
- if sidx == -1:
|
|
|
- return results
|
|
|
- end_pos = len(text)
|
|
|
+ # 找标题位置
|
|
|
+ sm_idx = all_text.find(start_marker)
|
|
|
+ if sm_idx == -1:
|
|
|
+ return []
|
|
|
+
|
|
|
+ # 找 end_markers(从标题后开始找)
|
|
|
+ end_pos = len(all_text)
|
|
|
for em in end_markers:
|
|
|
- ei = text.find(em, sidx + len(start_marker))
|
|
|
+ ei = all_text.find(em, sm_idx + len(start_marker))
|
|
|
if ei != -1 and ei < end_pos:
|
|
|
end_pos = ei
|
|
|
- region = text[sidx:end_pos]
|
|
|
|
|
|
- # 按名称在区域中的位置排序
|
|
|
- name_positions = []
|
|
|
- for kn in known_names:
|
|
|
- idx = region.find(kn)
|
|
|
- if idx != -1:
|
|
|
- name_positions.append((idx, kn))
|
|
|
- name_positions.sort()
|
|
|
-
|
|
|
- for i, (idx, kn) in enumerate(name_positions):
|
|
|
- start_after = idx + len(kn)
|
|
|
- # 取数字
|
|
|
- val_match = re.match(r'(\d+(?:\.\d+)?)', region[start_after:])
|
|
|
- if not val_match:
|
|
|
- continue
|
|
|
- val = val_match.group(1)
|
|
|
- val_end = start_after + len(val)
|
|
|
- # 状态:从数值结束到下一个已知名称开始
|
|
|
- if i + 1 < len(name_positions):
|
|
|
- next_idx = name_positions[i + 1][0]
|
|
|
- status = region[val_end:next_idx].strip()
|
|
|
- else:
|
|
|
- status = region[val_end:end_pos].strip()
|
|
|
- # 剪掉状态中的后续标记(如"正常氨基酸评估"→"正常")
|
|
|
- status = re.sub(r'注意大环内酯类|注意呋喃类|注意氯霉素类|注意喹诺酮类|注意磺胺类|注意甲氧苄啶类|注意四环素类|正常氨基酸评估|正常维生素评估|正常微量元素评估', '', status).strip()
|
|
|
- results.append({'名称': kn, '数值': val, '状态': status})
|
|
|
+ # Case A: 标题在前,数据在标题和 end_marker 之间
|
|
|
+ region_a = all_text[sm_idx:end_pos]
|
|
|
+ result_a = _extract_names_from_region(region_a, known_names, len(region_a))
|
|
|
+
|
|
|
+ # Case B: 标题在后,数据在"指标范围"和标题之间
|
|
|
+ # 找标题前的"指标范围"
|
|
|
+ zb_idx = all_text.rfind('指标范围', 0, sm_idx)
|
|
|
+ if zb_idx == -1:
|
|
|
+ zb_idx = max(0, sm_idx - 500)
|
|
|
+ region_b = all_text[zb_idx:sm_idx]
|
|
|
+ result_b = _extract_names_from_region(region_b, known_names, len(region_b))
|
|
|
+
|
|
|
+ # 取匹配多的那个
|
|
|
+ if len(result_a) >= len(result_b):
|
|
|
+ return result_a
|
|
|
+ return result_b
|
|
|
+
|
|
|
|
|
|
+# ==========================================
|
|
|
+# inline 格式致病菌提取
|
|
|
+# ==========================================
|
|
|
+# 已知致病菌名
|
|
|
+KNOWN_PATHOGENS = ['幽门螺杆菌', '艰难梭菌', '沙门氏菌', '志贺氏菌', '弯曲杆菌']
|
|
|
+
|
|
|
+def extract_pathogens_inline(text):
|
|
|
+ """提取 inline 格式中主要消化道致病菌数据(幽门螺杆菌0%未检出...)"""
|
|
|
+ results = []
|
|
|
+ sidx = text.find('主要消化道致病菌')
|
|
|
+ if sidx == -1:
|
|
|
+ return results
|
|
|
+ eidx = text.find('肠道屏障', sidx)
|
|
|
+ if eidx == -1:
|
|
|
+ eidx = len(text)
|
|
|
+ region = text[sidx:eidx]
|
|
|
+ # 对每个已知致病菌名逐一提取
|
|
|
+ for name in KNOWN_PATHOGENS:
|
|
|
+ idx = region.find(name)
|
|
|
+ if idx == -1:
|
|
|
+ continue
|
|
|
+ after = region[idx + len(name):]
|
|
|
+ m = re.match(r'(\d+%)', after)
|
|
|
+ if not m:
|
|
|
+ continue
|
|
|
+ abundance = m.group(1)
|
|
|
+ assessment_start = idx + len(name) + len(abundance)
|
|
|
+ # 评估词:到下一个菌名或区域结束
|
|
|
+ assessment_end = len(region)
|
|
|
+ for next_name in KNOWN_PATHOGENS:
|
|
|
+ if next_name == name:
|
|
|
+ continue
|
|
|
+ ni = region.find(next_name, assessment_start)
|
|
|
+ if ni != -1 and ni < assessment_end:
|
|
|
+ assessment_end = ni
|
|
|
+ assessment = region[assessment_start:assessment_end].strip()
|
|
|
+ results.append({'致病菌': name, '丰度': abundance, '评估': assessment})
|
|
|
return results
|
|
|
|
|
|
|
|
|
@@ -627,16 +719,24 @@ def extract_pdf_to_json(pdf_path):
|
|
|
|
|
|
else:
|
|
|
# inline 格式
|
|
|
- result['疾病风险评估'] = parse_inline_region(all_full_text, '疾病风险评估',
|
|
|
- ['主要营养评估', '主要消化道致病菌'])[0]
|
|
|
+ # 疾病风险评估(使用已知疾病名词表)
|
|
|
+ result['疾病风险评估'] = extract_inline_module(all_full_text, '疾病风险评估',
|
|
|
+ ['主要营养评估', '主要消化道致病菌'], KNOWN_DISEASE_RISKS)
|
|
|
|
|
|
# 主要营养评估:使用精确区域+已知5项
|
|
|
result['主要营养评估'] = extract_inline_module(all_full_text, '营养状况评估',
|
|
|
['主要营养评估', '氨基酸评估'], KNOWN_MACRO_NUTRIENTS)
|
|
|
|
|
|
- # 氨基酸评估
|
|
|
- result['氨基酸评估'] = extract_inline_module(all_full_text, '氨基酸评估',
|
|
|
+ # 氨基酸评估(从两个区域合并:营养状况区和氨基酸区)
|
|
|
+ # 朱报告前5个氨基酸在营养状况区,其余在氨基酸区
|
|
|
+ result['氨基酸评估'] = extract_inline_module(all_full_text, '营养状况评估',
|
|
|
+ ['主要营养评估', '氨基酸评估'], KNOWN_AMINO_ACIDS)
|
|
|
+ # 补上从氨基酸评估区域提取的(第二页)
|
|
|
+ amino_p2 = extract_inline_module(all_full_text, '氨基酸评估',
|
|
|
['维生素评估', '主要消化道致病菌'], KNOWN_AMINO_ACIDS)
|
|
|
+ for a in amino_p2:
|
|
|
+ if a['名称'] not in {x['名称'] for x in result['氨基酸评估']}:
|
|
|
+ result['氨基酸评估'].append(a)
|
|
|
|
|
|
# 维生素评估
|
|
|
result['维生素评估'] = extract_inline_module(all_full_text, '维生素评估',
|
|
|
@@ -646,9 +746,8 @@ def extract_pdf_to_json(pdf_path):
|
|
|
result['微量元素评估'] = extract_inline_module(all_full_text, '微量元素评估',
|
|
|
['主要消化道致病菌'], KNOWN_TRACE)
|
|
|
|
|
|
- # 主要消化道致病菌
|
|
|
- path_rows, _ = parse_inline_region(all_full_text, '主要消化道致病菌', ['肠道屏障'])
|
|
|
- result['主要消化道致病菌'] = path_rows
|
|
|
+ # 主要消化道致病菌(inline 特殊格式)
|
|
|
+ result['主要消化道致病菌'] = extract_pathogens_inline(all_full_text)
|
|
|
|
|
|
# 抗生素风险评估(使用已知抗生素词表)
|
|
|
result['抗生素风险评估'] = extract_inline_module(all_full_text, '抗生素风险评估',
|
|
|
@@ -744,7 +843,7 @@ def main_csv():
|
|
|
|
|
|
# ── 2-9. 模块数据(v5:使用精确词表过滤 inline) ──
|
|
|
modules = [
|
|
|
- ('疾病风险评估', '疾病', '疾病风险评估.csv', '疾病风险评估', ['主要营养评估', '主要消化道致病菌'], None),
|
|
|
+ ('疾病风险评估', '疾病', '疾病风险评估.csv', '疾病风险评估', ['主要营养评估', '主要消化道致病菌'], KNOWN_DISEASE_RISKS),
|
|
|
('主要营养评估', '指标', '主要营养评估.csv', '营养状况评估', ['主要营养评估', '氨基酸评估'], KNOWN_MACRO_NUTRIENTS),
|
|
|
('氨基酸评估', '氨基酸', '氨基酸评估.csv', '氨基酸评估', ['维生素评估', '主要消化道致病菌'], KNOWN_AMINO_ACIDS),
|
|
|
('维生素评估', '维生素', '维生素评估.csv', '维生素评估', ['微量元素评估', '主要消化道致病菌'], KNOWN_VITAMINS),
|
|
|
@@ -765,6 +864,7 @@ def main_csv():
|
|
|
data.setdefault(r['名称'], {})[label] = r['数值']
|
|
|
|
|
|
for label in inline_labels:
|
|
|
+ # 使用精确词表的模块用 extract_inline_module
|
|
|
if whitelist:
|
|
|
rows = extract_inline_module(all_full_text[label], start_mk, end_mks, whitelist)
|
|
|
else:
|
|
|
@@ -772,6 +872,14 @@ def main_csv():
|
|
|
for r in rows:
|
|
|
data.setdefault(r['名称'], {})[label] = r['数值']
|
|
|
|
|
|
+ # 氨基酸评估特殊处理:inline 格式前5个氨基酸在营养状况区
|
|
|
+ if mod_name == '氨基酸评估' and inline_labels:
|
|
|
+ for label in inline_labels:
|
|
|
+ extra_rows = extract_inline_module(all_full_text[label], '营养状况评估',
|
|
|
+ ['主要营养评估', '氨基酸评估'], KNOWN_AMINO_ACIDS)
|
|
|
+ for r in extra_rows:
|
|
|
+ data.setdefault(r['名称'], {})[label] = r['数值']
|
|
|
+
|
|
|
if data:
|
|
|
labels = all_labels
|
|
|
with open(os.path.join(OUTDIR, fname), 'w', newline='', encoding='utf-8-sig') as f:
|
|
|
@@ -794,9 +902,9 @@ def main_csv():
|
|
|
path_data.setdefault(r['致病菌'], {})[label] = r['丰度']
|
|
|
|
|
|
for label in inline_labels:
|
|
|
- rows, _ = parse_inline_region(all_full_text[label], '主要消化道致病菌', ['肠道屏障'])
|
|
|
+ rows = extract_pathogens_inline(all_full_text[label])
|
|
|
for r in rows:
|
|
|
- path_data.setdefault(r['名称'], {})[label] = r['数值'] if '%' in r['数值'] else r['数值'] + '%'
|
|
|
+ path_data.setdefault(r['致病菌'], {})[label] = r['丰度']
|
|
|
|
|
|
if path_data:
|
|
|
labels = all_labels
|