瀏覽代碼

docs(v5): fix inline extraction for vitamins/barrier/pathogens + add disease risk known names and amino acid merge

Key fixes: extract_inline_module now handles start-marker-after-data layout (朱 report); barrier extraction skips header substring false match; pathogens use known-name whitelist; disease risk uses KNOWN_DISEASE_RISKS; amino acids merged from two inline regions
asus 2 月之前
父節點
當前提交
9c2ebee3b4
共有 1 個文件被更改,包括 159 次插入51 次删除
  1. 159 51
      docs/参考资料/extract_full_report_v5.py

+ 159 - 51
docs/参考资料/extract_full_report_v5.py

@@ -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