|
|
@@ -224,9 +224,9 @@ def extract_overview(lines_all, label):
|
|
|
else:
|
|
|
m = re.search(r'性别[::\s]*([\u4e00-\u9fff])', t)
|
|
|
if m: r['性别'] = m.group(1)
|
|
|
- m = re.search(r'肠道预测年龄[:\s]*([\d.]+岁?)', t)
|
|
|
+ m = re.search(r'肠道预测年龄[::\s]*([\d.]+岁?)', t)
|
|
|
if m: r['肠道预测年龄'] = m.group(1)
|
|
|
- m = re.search(r'肠型[:\s]*(\S+)', t)
|
|
|
+ m = re.search(r'肠型[::\s]*(\S+)', t)
|
|
|
if m: r['肠型'] = m.group(1)
|
|
|
for kw in ['肠道菌群平衡', '菌群多样性', '有益菌', '有害菌', '核心菌属']:
|
|
|
m = re.search(rf'{kw}\s*(\d+)', t)
|
|
|
@@ -236,6 +236,14 @@ def extract_overview(lines_all, label):
|
|
|
for kw in ['菌群健康', '慢病控制', '营养均衡']:
|
|
|
m = re.search(rf'{kw}\s*(\d+)', t)
|
|
|
if m: r[kw] = m.group(1)
|
|
|
+ # 健康总分详细说明:如"76分 良好 64分 一般 30分 需关注"
|
|
|
+ detail = re.search(r'健康总分.*?(?:(\d+)分\s*(\S+)\s*(\d+)分\s*(\S+)\s*(\d+)分\s*(\S+))', t)
|
|
|
+ if detail:
|
|
|
+ r['健康总分详情'] = {
|
|
|
+ '菌群健康': {'分数': detail.group(1), '评级': detail.group(2)},
|
|
|
+ '慢病控制': {'分数': detail.group(3), '评级': detail.group(4)},
|
|
|
+ '营养均衡': {'分数': detail.group(5), '评级': detail.group(6)},
|
|
|
+ }
|
|
|
return r
|
|
|
|
|
|
|
|
|
@@ -605,6 +613,11 @@ BACTERIA_TABLE_TITLES = [
|
|
|
'其它重要菌属构成表', '病原菌属构成表', '病原菌',
|
|
|
]
|
|
|
PHYLUM_TABLE_TITLES = ['菌门构成表', '菌群门水平构成表', '门水平菌群构成']
|
|
|
+CLASS_TABLE_TITLES = ['菌纲构成表', '菌群纲水平构成表', '纲水平菌群构成']
|
|
|
+ORDER_TABLE_TITLES = ['菌目构成表', '菌群目水平构成表', '目水平菌群构成']
|
|
|
+FAMILY_TABLE_TITLES = ['菌科构成表', '菌群科水平构成表', '科水平菌群构成']
|
|
|
+GENUS_TABLE_TITLES = ['菌属构成表', '菌群属水平构成表', '属水平菌群构成']
|
|
|
+SPECIES_TABLE_TITLES = ['菌种构成表', '菌群种水平构成表', '种水平菌群构成']
|
|
|
DISEASE_BACTERIA_TITLES = [
|
|
|
'肥胖相关菌', '便秘相关菌', '抑郁相关菌', '过敏相关菌',
|
|
|
'腹胀相关菌', '失眠相关菌', '肠道健康相关菌',
|
|
|
@@ -667,6 +680,31 @@ def extract_bacteria_tables(pdf_path):
|
|
|
if phylum_rows:
|
|
|
all_tables['菌门构成'] = phylum_rows
|
|
|
|
|
|
+ # 菌纲构成表
|
|
|
+ class_rows = _parse_phylum_tables(reader, full_text, fmt, CLASS_TABLE_TITLES)
|
|
|
+ if class_rows:
|
|
|
+ all_tables['菌纲构成'] = class_rows
|
|
|
+
|
|
|
+ # 菌目构成表
|
|
|
+ order_rows = _parse_phylum_tables(reader, full_text, fmt, ORDER_TABLE_TITLES)
|
|
|
+ if order_rows:
|
|
|
+ all_tables['菌目构成'] = order_rows
|
|
|
+
|
|
|
+ # 菌科构成表
|
|
|
+ family_rows = _parse_phylum_tables(reader, full_text, fmt, FAMILY_TABLE_TITLES)
|
|
|
+ if family_rows:
|
|
|
+ all_tables['菌科构成'] = family_rows
|
|
|
+
|
|
|
+ # 菌属构成表
|
|
|
+ genus_rows = _parse_phylum_tables(reader, full_text, fmt, GENUS_TABLE_TITLES)
|
|
|
+ if genus_rows:
|
|
|
+ all_tables['菌属构成'] = genus_rows
|
|
|
+
|
|
|
+ # 菌种构成表
|
|
|
+ species_rows = _parse_phylum_tables(reader, full_text, fmt, SPECIES_TABLE_TITLES)
|
|
|
+ if species_rows:
|
|
|
+ all_tables['菌种构成'] = species_rows
|
|
|
+
|
|
|
# 疾病相关菌表(disease-related)- 已移到知识库,不再从PDF提取
|
|
|
# 这些是静态的菌-疾病关联数据,属于公共知识库,非个性化数据
|
|
|
# 参见: extract_knowledge.py 中的疾病菌表处理
|
|
|
@@ -678,10 +716,12 @@ def extract_bacteria_tables(pdf_path):
|
|
|
return all_tables
|
|
|
|
|
|
|
|
|
-def _parse_phylum_tables(reader, full_text, fmt):
|
|
|
- """提取菌门构成表(phylum level)"""
|
|
|
+def _parse_phylum_tables(reader, full_text, fmt, title_list=None):
|
|
|
+ """提取菌群层级构成表(门/纲/目/科/属/种 level)"""
|
|
|
+ if title_list is None:
|
|
|
+ title_list = PHYLUM_TABLE_TITLES
|
|
|
results = []
|
|
|
- for phylum_title in PHYLUM_TABLE_TITLES:
|
|
|
+ for phylum_title in title_list:
|
|
|
rows = _parse_bacteria_table(reader, None, full_text, phylum_title, fmt)
|
|
|
results.extend(rows)
|
|
|
return results
|
|
|
@@ -706,7 +746,7 @@ def _parse_bacteria_table(reader, pages_text, full_text, title, fmt, skip_header
|
|
|
|
|
|
# 找表格结束位置(下一个标题或页尾)
|
|
|
end_pos = len(full_text)
|
|
|
- for t in BACTERIA_TABLE_TITLES + PHYLUM_TABLE_TITLES + DISEASE_BACTERIA_TITLES + ['指标范围', '个体化食物推荐表', '报告总结', '健康总分']:
|
|
|
+ for t in BACTERIA_TABLE_TITLES + PHYLUM_TABLE_TITLES + CLASS_TABLE_TITLES + ORDER_TABLE_TITLES + FAMILY_TABLE_TITLES + GENUS_TABLE_TITLES + SPECIES_TABLE_TITLES + DISEASE_BACTERIA_TITLES + ['指标范围', '个体化食物推荐表', '报告总结', '健康总分']:
|
|
|
if t == title: continue
|
|
|
ei = full_text.find(t, sidx + len(title))
|
|
|
if ei != -1 and ei < end_pos:
|
|
|
@@ -744,19 +784,19 @@ def _parse_bacteria_table(reader, pages_text, full_text, title, fmt, skip_header
|
|
|
continue
|
|
|
|
|
|
# 找丰度%
|
|
|
- if i + 1 < len(lines) and re.match(r'^[\d.]+%?$|^ND$', lines[i+1]):
|
|
|
+ if i + 1 < len(lines) and re.match(r'^[\d]+\.?[\d]*%?$|^ND$', lines[i+1]):
|
|
|
pct = lines[i+1]
|
|
|
normal_range = ''
|
|
|
pop_level = ''
|
|
|
detection_rate = ''
|
|
|
desc = ''
|
|
|
j = i + 2
|
|
|
- # 正常范围
|
|
|
- if j < len(lines) and re.match(r'^[\d.]+-[\d.]+$', lines[j]):
|
|
|
+ # 正常范围 (允许小数,如 0.06-6.96, 0.03-3.07)
|
|
|
+ if j < len(lines) and re.match(r'^[\d]+\.?[\d]*-[\d]+\.?[\d]*$', lines[j]):
|
|
|
normal_range = lines[j]
|
|
|
j += 1
|
|
|
- # 人群水平%
|
|
|
- if j < len(lines) and re.match(r'^\d+%$', lines[j]):
|
|
|
+ # 人群水平% (允许小数,如 15.66%)
|
|
|
+ if j < len(lines) and re.match(r'^\d+\.?\d*%$', lines[j]):
|
|
|
pop_level = lines[j]
|
|
|
j += 1
|
|
|
# 检出率%
|
|
|
@@ -946,13 +986,15 @@ def extract_pdf_to_json(pdf_path):
|
|
|
|
|
|
# 1. 报告概述
|
|
|
overview = extract_overview(clean_lines(pages_text[0]), label)
|
|
|
- # 如果第一页没找到关键信息,搜其他页
|
|
|
- found_info = any(k in str(overview) for k in ['肠道预测年龄', '核心菌属'])
|
|
|
+ # 如果第一页没找到关键信息,搜所有页
|
|
|
+ found_info = any(k in str(overview) for k in ['肠道预测年龄', '核心菌属', '健康总分'])
|
|
|
if not found_info:
|
|
|
for pt in pages_text:
|
|
|
- if '基本信息' in norm(pt) and '肠道预测年龄' in norm(pt):
|
|
|
+ nt = norm(pt)
|
|
|
+ if '肠道预测年龄' in nt or '健康总分' in nt:
|
|
|
overview = extract_overview(clean_lines(pt), label)
|
|
|
- break
|
|
|
+ if '肠道预测年龄' in str(overview) and '健康总分' in str(overview):
|
|
|
+ break
|
|
|
result['报告概述'] = overview
|
|
|
|
|
|
if fmt == 'triplet':
|
|
|
@@ -963,6 +1005,15 @@ def extract_pdf_to_json(pdf_path):
|
|
|
disease_set = set(KNOWN_DISEASE_RISKS)
|
|
|
result['疾病风险评估'] = [r for r in disease_rows if r['名称'] in disease_set]
|
|
|
|
|
|
+ # 回退:用 inline 方式扫描全文,补充 triplet 漏掉的项目
|
|
|
+ missing_disease = extract_inline_module(all_full_text, '疾病风险评估',
|
|
|
+ ['主要营养评估', '主要消化道致病菌'], KNOWN_DISEASE_RISKS)
|
|
|
+ existing_names = {r['名称'] for r in result['疾病风险评估']}
|
|
|
+ for r in missing_disease:
|
|
|
+ if r['名称'] not in existing_names:
|
|
|
+ result['疾病风险评估'].append(r)
|
|
|
+ existing_names.add(r['名称'])
|
|
|
+
|
|
|
# 主要营养评估
|
|
|
nutri_rows = parsed.get('主要营养评估', [])
|
|
|
nutri_set = set(KNOWN_MACRO_NUTRIENTS)
|
|
|
@@ -972,6 +1023,14 @@ def extract_pdf_to_json(pdf_path):
|
|
|
amino = parsed.get('氨基酸评估_p2', []) + parsed.get('氨基酸评估_p3', [])
|
|
|
amino_set = set(KNOWN_AMINO_ACIDS)
|
|
|
result['氨基酸评估'] = [r for r in amino if r['名称'] in amino_set]
|
|
|
+ # 回退:inline 方式补充 triplet 漏掉的氨基酸
|
|
|
+ missing_amino = extract_inline_module(all_full_text, '氨基酸评估',
|
|
|
+ ['维生素评估', '主要消化道致病菌'], KNOWN_AMINO_ACIDS)
|
|
|
+ existing_amino = {r['名称'] for r in result['氨基酸评估']}
|
|
|
+ for r in missing_amino:
|
|
|
+ if r['名称'] not in existing_amino:
|
|
|
+ result['氨基酸评估'].append(r)
|
|
|
+ existing_amino.add(r['名称'])
|
|
|
|
|
|
# 维生素
|
|
|
vit_rows = parsed.get('维生素评估', [])
|
|
|
@@ -997,6 +1056,22 @@ def extract_pdf_to_json(pdf_path):
|
|
|
result['短链脂肪酸'] = bs['scfa']
|
|
|
result['神经递质及激素'] = bs['neurotransmitter']
|
|
|
|
|
|
+ # 回退:inline 方式补充肠道屏障/短链脂肪酸/神经递质漏掉的项目
|
|
|
+ for section_key, start_mk, end_mks, known_list in [
|
|
|
+ ('肠道屏障及代谢物', '肠道屏障及菌群代谢物',
|
|
|
+ ['短链脂肪酸', '神经递质', '抗生素风险'], KNOWN_BARRIER),
|
|
|
+ ('短链脂肪酸', '短链脂肪酸',
|
|
|
+ ['神经递质', '抗生素风险'], KNOWN_SCFA),
|
|
|
+ ('神经递质及激素', '神经递质',
|
|
|
+ ['抗生素风险', '个体化食物推荐表'], KNOWN_NEUROTRANSMITTER),
|
|
|
+ ]:
|
|
|
+ extra = extract_inline_module(all_full_text, start_mk, end_mks, known_list)
|
|
|
+ existing = {r['名称'] for r in result.get(section_key, [])}
|
|
|
+ for r in extra:
|
|
|
+ if r['名称'] not in existing:
|
|
|
+ result.setdefault(section_key, []).append(r)
|
|
|
+ existing.add(r['名称'])
|
|
|
+
|
|
|
else:
|
|
|
# inline 格式
|
|
|
# 疾病风险评估(使用已知疾病名词表)
|
|
|
@@ -1335,7 +1410,7 @@ def main_csv():
|
|
|
for label in triplet_labels:
|
|
|
pdf_path = os.path.join(BASE, label + '.pdf')
|
|
|
all_bacteria[label] = extract_bacteria_tables(pdf_path)
|
|
|
- BACTERIA_CSV_TABLES = ['核心菌属', '益生菌', '有害菌属', '其它重要菌属', '病原菌属', '病原菌检出', '菌门构成']
|
|
|
+ BACTERIA_CSV_TABLES = ['核心菌属', '益生菌', '有害菌属', '其它重要菌属', '病原菌属', '病原菌检出', '菌门构成', '菌纲构成', '菌目构成', '菌科构成', '菌属构成', '菌种构成']
|
|
|
# 疾病相关菌表(肥胖/便秘等)已移出知识库,不再从报告中提取,详见 extract_knowledge.py
|
|
|
for table_name in BACTERIA_CSV_TABLES:
|
|
|
combined = {}
|