Просмотр исходного кода

fix: comprehensive bacteria table & indicator extraction fixes

- Add taxonomy level extraction (class/order/family/genus/species)
- Fix regex patterns for abundance, normal range, population level
- Add inline fallback for missing disease risks, amino acids, barrier/scfa/nt
- Fix overview extraction for gut age, health score details
asus 2 месяцев назад
Родитель
Сommit
f1de08129d
1 измененных файлов с 91 добавлено и 16 удалено
  1. 91 16
      docs/参考资料/extract_full_report_v5.py

+ 91 - 16
docs/参考资料/extract_full_report_v5.py

@@ -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 = {}