Sfoglia il codice sorgente

docs(v5): fix JSON triplet whitelist filtering + name/age/gender regex + vitamin B1/B12 overlap bug

Fixes: JSON path now filters disease risk/antibiotics/nutrition by known-name whitelists; overview extraction uses stricter patterns (姓名/编号/年龄/性别); _extract_names_from_region uses forward search to avoid B1 vs B12 overlap; all CSVs regenerated clean.
asus 3 mesi fa
parent
commit
9c08ae98e4

+ 4 - 4
docs/参考资料/CSV/报告概述.csv

@@ -1,6 +1,6 @@
 姓名,编号,年龄,性别,肠道预测年龄,肠型,肠道菌群平衡,菌群多样性,有益菌,有害菌,核心菌属,健康总分,菌群健康,慢病控制,营养均衡
 某人,501999942,53岁,男,55.52岁,普雷沃⽒菌型,38,50,22,26,85,57,76,64,30
-侯(快递姓名),530010234,12岁,男,10.68岁,拟杆菌型,31,37,34,78,77,68,53,76,68
-编号,547982403,53岁,⼥,57.71岁,拟杆菌型,39,86,25,41,83,78,81,78,74
-儿童示例,ST2,8岁,男,9.12岁,拟杆菌型,38,72,23,37,65,70,74,60,83
-朱明泽编号590646194年龄8岁备注性别男,590646194年龄8岁备注性别男,8岁备注性别男,男,10.63岁,拟杆菌型肠道菌群平衡37中等菌群多样性77稳定有益菌22中等有害菌34正常核心菌属81正常基本信息,37,77,22,34,81,81,80,80,82
+530010234-侯,530010234,12岁,男,10.68岁,拟杆菌型,31,37,34,78,77,68,53,76,68
+547982403,547982403,53岁,,57.71岁,拟杆菌型,39,86,25,41,83,78,81,78,74
+儿童示例,,8岁,男,9.12岁,拟杆菌型,38,72,23,37,65,70,74,60,83
+朱明泽,590646194,8岁,男,10.63岁,拟杆菌型肠道菌群平衡37中等菌群多样性77稳定有益菌22中等有害菌34正常核心菌属81正常基本信息,37,77,22,34,81,81,80,80,82

+ 1 - 2
docs/参考资料/CSV/氨基酸评估.csv

@@ -1,7 +1,6 @@
 氨基酸,501999942-某人,530010234-侯,547982403,儿童示例,朱评估报告
-丙氨酸,1,9,19,73,50
+丙氨酸,1,9,19,73,
 丝氨酸,9,88,20,99,75
-亮氨酸,,,,,14
 异亮氨酸,,,,,66
 甘氨酸,5,38,24,43,33
 组氨酸,40,74,58,73,68

+ 1 - 1
docs/参考资料/CSV/维生素评估.csv

@@ -1,7 +1,7 @@
 维生素,501999942-某人,530010234-侯,547982403,儿童示例,朱评估报告
 叶酸,35,90,62,13,47
 维生素A,84,74,81,26,54
-维生素B1,2,38,56,77,253
+维生素B1,2,38,56,77,83
 维生素B12,25,14,31,77,53
 维生素B2,77,32,33,77,53
 维生素B5,64,26,48,78,69

+ 241 - 0
docs/参考资料/cross_validate.py

@@ -0,0 +1,241 @@
+"""
+交叉验证:提取所有5份PDF的JSON数据,逐模块比较
+"""
+import sys, os
+sys.path.insert(0, r'D:\workspace\cfc\docs\参考资料')
+import extract_full_report_v5 as m
+
+BASE = r'D:\workspace\cfc\docs\参考资料'
+pdfs = sorted(f for f in os.listdir(BASE) if f.lower().endswith('.pdf'))
+print(f'找到 {len(pdfs)} 份PDF\n')
+
+# 提取所有报告
+results = {}
+for fname in pdfs:
+    label = fname.replace('.pdf', '')
+    print(f'提取 {label}...')
+    results[label] = m.extract_pdf_to_json(os.path.join(BASE, fname))
+
+all_labels = list(results.keys())
+print()
+
+# =============================================
+# 1. 报告概述
+# =============================================
+print('=' * 80)
+print('1. 报告概述')
+print('=' * 80)
+overview_fields = ['姓名', '编号', '年龄', '性别', '肠道预测年龄', '肠型',
+                   '肠道菌群平衡', '菌群多样性', '有益菌', '有害菌', '核心菌属',
+                   '健康总分', '菌群健康', '慢病控制', '营养均衡']
+header = f"{'字段':<14}" + "".join(f"{l[:8]:<10}" for l in all_labels)
+print(header)
+print('-' * len(header))
+for field in overview_fields:
+    vals = []
+    for label in all_labels:
+        v = results[label]['报告概述'].get(field, '—')
+        vals.append(str(v)[:8])
+    print(f"{field:<14}" + "".join(f"{v:<10}" for v in vals))
+
+# =============================================
+# 2. 疾病风险评估
+# =============================================
+print('\n' + '=' * 80)
+print('2. 疾病风险评估')
+print('=' * 80)
+# 收集所有疾病名
+all_diseases = set()
+for label in all_labels:
+    for r in results[label]['疾病风险评估']:
+        all_diseases.add(r['名称'])
+all_diseases = sorted(all_diseases)
+print(f'共 {len(all_diseases)} 个疾病指标')
+for d in all_diseases:
+    vals = []
+    for label in all_labels:
+        found = [r for r in results[label]['疾病风险评估'] if r['名称'] == d]
+        if found:
+            v = found[0].get('数值', found[0].get('状态', '?'))
+        else:
+            v = '—'
+        vals.append(str(v)[:8])
+    print(f"  {d:<14}" + "".join(f"{v:<10}" for v in vals))
+
+# =============================================
+# 3. 主要营养评估
+# =============================================
+print('\n' + '=' * 80)
+print('3. 主要营养评估')
+print('=' * 80)
+all_nutri = set()
+for label in all_labels:
+    for r in results[label]['主要营养评估']:
+        all_nutri.add(r['名称'])
+for n in sorted(all_nutri):
+    vals = []
+    for label in all_labels:
+        found = [r for r in results[label]['主要营养评估'] if r['名称'] == n]
+        vals.append(found[0]['数值'] if found else '—')
+    print(f"  {n:<10}" + "".join(f"{v:<8}" for v in vals))
+
+# =============================================
+# 4. 氨基酸评估
+# =============================================
+print('\n' + '=' * 80)
+print('4. 氨基酸评估')
+print('=' * 80)
+all_amino = set()
+for label in all_labels:
+    for r in results[label]['氨基酸评估']:
+        all_amino.add(r['名称'])
+for a in sorted(all_amino):
+    vals = []
+    for label in all_labels:
+        found = [r for r in results[label]['氨基酸评估'] if r['名称'] == a]
+        vals.append(found[0]['数值'] if found else '—')
+    print(f"  {a:<8}" + "".join(f"{v:<8}" for v in vals))
+
+# =============================================
+# 5. 维生素评估
+# =============================================
+print('\n' + '=' * 80)
+print('5. 维生素评估')
+print('=' * 80)
+all_vit = set()
+for label in all_labels:
+    for r in results[label]['维生素评估']:
+        all_vit.add(r['名称'])
+for v in sorted(all_vit):
+    vals = []
+    for label in all_labels:
+        found = [r for r in results[label]['维生素评估'] if r['名称'] == v]
+        vals.append(found[0]['数值'] if found else '—')
+    print(f"  {v:<10}" + "".join(f"{v:<8}" for v in vals))
+
+# =============================================
+# 6. 微量元素评估
+# =============================================
+print('\n' + '=' * 80)
+print('6. 微量元素评估')
+print('=' * 80)
+for t in ['铁', '锌']:
+    vals = []
+    for label in all_labels:
+        found = [r for r in results[label]['微量元素评估'] if r['名称'] == t]
+        vals.append(found[0]['数值'] if found else '—')
+    print(f"  {t:<8}" + "".join(f"{v:<8}" for v in vals))
+
+# =============================================
+# 7. 主要消化道致病菌
+# =============================================
+print('\n' + '=' * 80)
+print('7. 主要消化道致病菌')
+print('=' * 80)
+all_path = set()
+for label in all_labels:
+    for r in results[label]['主要消化道致病菌']:
+        all_path.add(r['致病菌'])
+for p in sorted(all_path):
+    vals = []
+    for label in all_labels:
+        found = [r for r in results[label]['主要消化道致病菌'] if r['致病菌'] == p]
+        vals.append(found[0]['丰度'] if found else '—')
+    print(f"  {p:<10}" + "".join(f"{v:<8}" for v in vals))
+
+# =============================================
+# 8. 肠道屏障及代谢物
+# =============================================
+print('\n' + '=' * 80)
+print('8. 肠道屏障及代谢物')
+print('=' * 80)
+all_bar = set()
+for label in all_labels:
+    for r in results[label]['肠道屏障及代谢物']:
+        all_bar.add(r['名称'])
+for b in sorted(all_bar):
+    vals = []
+    for label in all_labels:
+        found = [r for r in results[label]['肠道屏障及代谢物'] if r['名称'] == b]
+        v = found[0].get('数值', found[0].get('评估值', '?')) if found else '—'
+        vals.append(v)
+    print(f"  {b:<16}" + "".join(f"{v:<8}" for v in vals))
+
+# =============================================
+# 9. 短链脂肪酸
+# =============================================
+print('\n' + '=' * 80)
+print('9. 短链脂肪酸')
+print('=' * 80)
+all_scfa = set()
+for label in all_labels:
+    for r in results[label]['短链脂肪酸']:
+        all_scfa.add(r['名称'])
+for s in sorted(all_scfa):
+    vals = []
+    for label in all_labels:
+        found = [r for r in results[label]['短链脂肪酸'] if r['名称'] == s]
+        v = found[0].get('数值', found[0].get('评估值', '?')) if found else '—'
+        vals.append(v)
+    print(f"  {s:<20}" + "".join(f"{v:<8}" for v in vals))
+
+# =============================================
+# 10. 神经递质及激素
+# =============================================
+print('\n' + '=' * 80)
+print('10. 神经递质及激素')
+print('=' * 80)
+all_nt = set()
+for label in all_labels:
+    for r in results[label]['神经递质及激素']:
+        all_nt.add(r['名称'])
+for n in sorted(all_nt):
+    vals = []
+    for label in all_labels:
+        found = [r for r in results[label]['神经递质及激素'] if r['名称'] == n]
+        v = found[0].get('数值', found[0].get('评估值', '?')) if found else '—'
+        vals.append(v)
+    print(f"  {n:<20}" + "".join(f"{v:<8}" for v in vals))
+
+# =============================================
+# 11. 抗生素风险评估
+# =============================================
+print('\n' + '=' * 80)
+print('11. 抗生素风险评估')
+print('=' * 80)
+all_abx = set()
+for label in all_labels:
+    for r in results[label]['抗生素风险评估']:
+        all_abx.add(r['名称'])
+for a in sorted(all_abx):
+    vals = []
+    for label in all_labels:
+        found = [r for r in results[label]['抗生素风险评估'] if r['名称'] == a]
+        vals.append(found[0]['数值'] if found else '—')
+    print(f"  {a:<10}" + "".join(f"{v:<8}" for v in vals))
+
+# =============================================
+# 12. 个体化食物推荐表
+# =============================================
+print('\n' + '=' * 80)
+print('12. 个体化食物推荐表')
+print('=' * 80)
+for label in all_labels:
+    food = results[label]['个体化食物推荐表']
+    print(f"  {label}: {food['条目数']} 条, 格式={food['格式']}")
+
+# =============================================
+# 总结
+# =============================================
+print('\n' + '=' * 80)
+print('交叉验证总结')
+print('=' * 80)
+print(f"共验证 {len(all_labels)} 份报告")
+for label in all_labels:
+    r = results[label]
+    total = 0
+    for k in ['疾病风险评估', '主要营养评估', '氨基酸评估', '维生素评估', '微量元素评估',
+              '抗生素风险评估', '主要消化道致病菌', '肠道屏障及代谢物', '短链脂肪酸', '神经递质及激素']:
+        total += len(r.get(k, []))
+    total += r['个体化食物推荐表']['条目数']
+    print(f"  {label}: {total} 总指标项")

+ 73 - 20
docs/参考资料/extract_full_report_v5.py

@@ -193,13 +193,25 @@ def extract_overview(lines_all, label):
     r = {'姓名': label}
     t = '\n'.join(lines_all)
 
-    m = re.search(r'编号\s*(\S+)', t)
+    m = re.search(r'编号[::\s]*(\d+)', t)
     if m: r['编号'] = m.group(1)
-    m = re.search(r'姓名\s*(\S+)', t)
-    if m: r['姓名'] = m.group(1)
+    # 姓名:找"姓名"后的中文字符(遇到"编号""年龄""性别"等字段停止)
+    m = re.search(r'姓名[::\s]*([\u4e00-\u9fff]{2,10})', t)
+    if m:
+        name = m.group(1)
+        # 如果名字后面跟着"编号"等字段,截断到第一个非名字字段
+        name = re.sub(r'(编号|年龄|性别|备注|肠道).*', '', name)
+        # 如果还太长,只取前4个字
+        name = name[:4]
+        if len(name) >= 2:
+            r['姓名'] = name
     for pat in ['年龄', '性别']:
-        m = re.search(rf'{pat}\s*(\S+)', t)
-        if m: r[pat] = m.group(1)
+        if pat == '年龄':
+            m = re.search(r'年龄[::\s]*(\d+)', t)
+            if m: r['年龄'] = m.group(1) + '岁'
+        else:
+            m = re.search(r'性别[::\s]*([\u4e00-\u9fff])', t)
+            if m: r['性别'] = m.group(1)
     m = re.search(r'肠道预测年龄[:\s]*([\d.]+岁?)', t)
     if m: r['肠道预测年龄'] = m.group(1)
     m = re.search(r'肠型[:\s]*(\S+)', t)
@@ -427,16 +439,40 @@ def _extract_names_from_region(region_str, known_names, end_pos):
         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
+        # 找所有匹配位置,选第一个后面有数字且不是头部标记的
+        search_start = 0
+        while True:
+            ii = region_str.find(kn, search_start)
+            if ii == -1:
+                break
+            after = region_str[ii+len(kn):ii+len(kn)+5]
+            if re.match(r'\d', after):
+                positions.append((ii, kn))
+                break  # 找到第一个有效匹配
+            search_start = ii + 1
     if not positions:
         return []
+    # 去重:同名先后取前面(较精准的),不同名重叠取较长的
+    # 按位置排序
+    positions.sort()
+    # 移除被更长名称覆盖的短名匹配
+    filtered = []
+    for i, (pos, name) in enumerate(positions):
+        # 检查同一位置是否有更长的名匹配
+        has_longer = False
+        for j, (pos2, name2) in enumerate(positions):
+            if i != j and pos2 <= pos and pos + len(name) <= pos2 + len(name2) and len(name2) > len(name):
+                has_longer = True
+                break
+        if not has_longer:
+            filtered.append((pos, name))
+    positions = filtered
+    # 再去重:同名只保留位置最前的
+    seen_names = {}
+    for pos, name in positions:
+        if name not in seen_names:
+            seen_names[name] = pos
+    positions = [(pos, name) for name, pos in seen_names.items()]
     positions.sort()
     local = []
     for i, (ii, kn) in enumerate(positions):
@@ -712,21 +748,38 @@ def extract_pdf_to_json(pdf_path):
     if fmt == 'triplet':
         parsed = extract_all_triplet(all_lines)
 
-        # 疾病风险评估
-        result['疾病风险评估'] = parsed.get('疾病风险评估', [])
+        # 疾病风险评估(使用已知疾病名词表)
+        disease_rows = parsed.get('疾病风险评估', [])
+        disease_set = set(KNOWN_DISEASE_RISKS)
+        result['疾病风险评估'] = [r for r in disease_rows if r['名称'] in disease_set]
+
         # 主要营养评估
-        result['主要营养评估'] = parsed.get('主要营养评估', [])
+        nutri_rows = parsed.get('主要营养评估', [])
+        nutri_set = set(KNOWN_MACRO_NUTRIENTS)
+        result['主要营养评估'] = [r for r in nutri_rows if r['名称'] in nutri_set]
+
         # 氨基酸
         amino = parsed.get('氨基酸评估_p2', []) + parsed.get('氨基酸评估_p3', [])
-        result['氨基酸评估'] = amino
+        amino_set = set(KNOWN_AMINO_ACIDS)
+        result['氨基酸评估'] = [r for r in amino if r['名称'] in amino_set]
+
         # 维生素
-        result['维生素评估'] = parsed.get('维生素评估', [])
+        vit_rows = parsed.get('维生素评估', [])
+        vit_set = set(KNOWN_VITAMINS)
+        result['维生素评估'] = [r for r in vit_rows if r['名称'] in vit_set]
+
         # 微量元素
-        result['微量元素评估'] = parsed.get('微量元素评估', [])
+        trace_rows = parsed.get('微量元素评估', [])
+        trace_set = set(KNOWN_TRACE)
+        result['微量元素评估'] = [r for r in trace_rows if r['名称'] in trace_set]
+
         # 主要消化道致病菌
         result['主要消化道致病菌'] = parsed.get('主要消化道致病菌', [])
+
         # 抗生素风险评估
-        result['抗生素风险评估'] = parsed.get('抗生素风险评估', [])
+        abx_rows = parsed.get('抗生素风险评估', [])
+        abx_set = set(KNOWN_ANTIBIOTICS)
+        result['抗生素风险评估'] = [r for r in abx_rows if r['名称'] in abx_set]
 
         # 肠道屏障 + 短链脂肪酸 + 神经递质
         bs = extract_barrier_and_scfa_triplet(all_lines)