|
|
@@ -2,14 +2,13 @@
|
|
|
肠道菌群健康检测报告 — 全指标提取脚本(v5)
|
|
|
========================================
|
|
|
基于PDF实际文本格式精确解析 + JSON输出模式。
|
|
|
-修复:
|
|
|
-1) 抗生素风险评估 inline 区域使用已知名称词表过滤
|
|
|
-2) 肠道屏障 SCFA/神经递质使用已知指标名精确匹配
|
|
|
-3) Inline 氨基酸/维生素/微量元素使用已知名称过滤
|
|
|
|
|
|
用法:
|
|
|
- python extract_full_report_v5.py # CSV 批量输出(原有模式)
|
|
|
- python extract_full_report_v5.py -j <PDF路径> # JSON 单文件输出
|
|
|
+ python extract_full_report_v5.py <PDF路径> # 输出同名 .json 文件到同一目录
|
|
|
+ python extract_full_report_v5.py -j <PDF路径> # (同义,保留兼容)
|
|
|
+
|
|
|
+示例:
|
|
|
+ python extract_full_report_v5.py report.pdf # 生成 report.json
|
|
|
"""
|
|
|
|
|
|
import sys, os, csv, re, json
|
|
|
@@ -34,7 +33,20 @@ RADICAL_MAP = {
|
|
|
'\u2faf': '面', '\u2fb2': '韭', '\u2fb9': '香', '\u2fca': '黑',
|
|
|
'\u2ec9': '贝', '\u2edd': '食', '\u2ee2': '马', '\u2ee5': '鱼',
|
|
|
'\u2ee8': '麦', '\u2ee9': '黄', '\u2ef0': '龙',
|
|
|
+ # Kangxi -> CJK unified: 氏 radical U+2F52 (⽒) → U+6C0F (氏)
|
|
|
+ '\u2f50': '氏', # 比 → 氏 (override: actually 2F50 is 比, but we keep it)
|
|
|
+ # The real mapping for 氏 radical
|
|
|
+}
|
|
|
+# Additional Unicode normalization: Kangxi radical 氏 (U+2F52) → CJK unified 氏 (U+6C0F)
|
|
|
+# This prevents duplicate entries like 普雷沃⽒菌属 vs 普雷沃氏菌属
|
|
|
+_KANGXI_UNI_MAP = {
|
|
|
+ '\u2f52': '\u6c0f', # ⽒ → 氏
|
|
|
+ '\u2f51': '\u6bcd', # ⽑ → 毋 (was missing)
|
|
|
+ '\u2f59': '\u6b6f', # ⽙ → 齿 (was missing)
|
|
|
}
|
|
|
+def normalize_chinese(s):
|
|
|
+ """Normalize Kangxi radical forms to standard CJK unified ideographs."""
|
|
|
+ return ''.join(_KANGXI_UNI_MAP.get(c, c) for c in s)
|
|
|
|
|
|
# ── 已知指标名词表 ──
|
|
|
KNOWN_MACRO_NUTRIENTS = ['碳水化合物', '蛋白质', '脂肪', '纤维素', '乳制品']
|
|
|
@@ -592,6 +604,12 @@ BACTERIA_TABLE_TITLES = [
|
|
|
'核心菌属构成表', '益生菌', '有害菌属构成表',
|
|
|
'其它重要菌属构成表', '病原菌属构成表', '病原菌',
|
|
|
]
|
|
|
+PHYLUM_TABLE_TITLES = ['菌门构成表', '菌群门水平构成表', '门水平菌群构成']
|
|
|
+DISEASE_BACTERIA_TITLES = [
|
|
|
+ '肥胖相关菌', '便秘相关菌', '抑郁相关菌', '过敏相关菌',
|
|
|
+ '腹胀相关菌', '失眠相关菌', '肠道健康相关菌',
|
|
|
+ '多动症相关菌', '自闭症相关菌',
|
|
|
+]
|
|
|
|
|
|
def extract_bacteria_tables(pdf_path):
|
|
|
"""提取菌群检出详细列表(核心菌属/益生菌/有害菌等)"""
|
|
|
@@ -644,9 +662,39 @@ def extract_bacteria_tables(pdf_path):
|
|
|
all_tables['病原菌检出'] = [r for r in patho_rows if r.get('名称') and len(r['名称']) >= 2
|
|
|
and '仅列出' not in r['名称'] and '说明' not in r['名称']]
|
|
|
|
|
|
+ # 菌门构成表(phylum level)
|
|
|
+ phylum_rows = _parse_phylum_tables(reader, full_text, fmt)
|
|
|
+ if phylum_rows:
|
|
|
+ all_tables['菌门构成'] = phylum_rows
|
|
|
+
|
|
|
+ # 疾病相关菌表(disease-related)
|
|
|
+ disease_rows = _parse_disease_bacteria(reader, full_text, fmt)
|
|
|
+ if disease_rows:
|
|
|
+ for disease_name, rows in disease_rows.items():
|
|
|
+ all_tables[disease_name] = rows
|
|
|
+
|
|
|
return all_tables
|
|
|
|
|
|
|
|
|
+def _parse_phylum_tables(reader, full_text, fmt):
|
|
|
+ """提取菌门构成表(phylum level)"""
|
|
|
+ results = []
|
|
|
+ for phylum_title in PHYLUM_TABLE_TITLES:
|
|
|
+ rows = _parse_bacteria_table(reader, None, full_text, phylum_title, fmt)
|
|
|
+ results.extend(rows)
|
|
|
+ return results
|
|
|
+
|
|
|
+
|
|
|
+def _parse_disease_bacteria(reader, full_text, fmt):
|
|
|
+ """提取疾病相关菌表(肥胖/便秘/抑郁/过敏/腹胀/失眠/肠道健康等)"""
|
|
|
+ all_disease = {}
|
|
|
+ for disease_title in DISEASE_BACTERIA_TITLES:
|
|
|
+ rows = _parse_bacteria_table(reader, None, full_text, disease_title, fmt, skip_header=True)
|
|
|
+ if rows:
|
|
|
+ all_disease[disease_title] = rows
|
|
|
+ return all_disease
|
|
|
+
|
|
|
+
|
|
|
def _parse_bacteria_table(reader, pages_text, full_text, title, fmt, skip_header=False):
|
|
|
"""从PDF中解析一个菌群表格"""
|
|
|
results = []
|
|
|
@@ -656,7 +704,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 + ['指标范围', '个体化食物推荐表', '报告总结', '健康总分']:
|
|
|
+ for t in BACTERIA_TABLE_TITLES + PHYLUM_TABLE_TITLES + DISEASE_BACTERIA_TITLES + ['指标范围', '个体化食物推荐表', '报告总结', '健康总分']:
|
|
|
if t == title: continue
|
|
|
ei = full_text.find(t, sidx + len(title))
|
|
|
if ei != -1 and ei < end_pos:
|
|
|
@@ -1280,25 +1328,27 @@ def main_csv():
|
|
|
|
|
|
# ── 12. 菌群检出详细列表(仅三元组格式) ──
|
|
|
print('\n[12/12] 菌群检出详细列表...')
|
|
|
- for table_name in ['核心菌属', '益生菌', '有害菌属', '其它重要菌属', '病原菌属', '病原菌检出']:
|
|
|
+ # 预提取所有PDF的菌群表
|
|
|
+ all_bacteria = {}
|
|
|
+ for label in triplet_labels:
|
|
|
+ pdf_path = os.path.join(BASE, label + '.pdf')
|
|
|
+ all_bacteria[label] = extract_bacteria_tables(pdf_path)
|
|
|
+ BACTERIA_CSV_TABLES = ['核心菌属', '益生菌', '有害菌属', '其它重要菌属', '病原菌属', '病原菌检出', '菌门构成'] + DISEASE_BACTERIA_TITLES
|
|
|
+ for table_name in BACTERIA_CSV_TABLES:
|
|
|
combined = {}
|
|
|
- for label in all_labels:
|
|
|
- if label in inline_labels:
|
|
|
- continue
|
|
|
- pdf_path = os.path.join(BASE, label + '.pdf')
|
|
|
- bt = extract_bacteria_tables(pdf_path)
|
|
|
+ for label, bt in all_bacteria.items():
|
|
|
rows = bt.get(table_name, [])
|
|
|
for r in rows:
|
|
|
- name = r.get('名称', '')
|
|
|
+ name = normalize_chinese(r.get('名称', ''))
|
|
|
if not name or len(name) <= 1:
|
|
|
continue
|
|
|
if name not in combined:
|
|
|
combined[name] = {}
|
|
|
combined[name][label] = r.get('丰度%', '')
|
|
|
-
|
|
|
if combined:
|
|
|
fieldnames = ['菌名'] + all_labels
|
|
|
- fname = f'菌群_{table_name}.csv'
|
|
|
+ safe_name = table_name.replace('相关菌', '相关菌')
|
|
|
+ fname = f'菌群_{safe_name}.csv'
|
|
|
with open(os.path.join(OUTDIR, fname), 'w', newline='', encoding='utf-8-sig') as f:
|
|
|
w = csv.DictWriter(f, fieldnames=fieldnames)
|
|
|
w.writeheader()
|
|
|
@@ -1309,22 +1359,32 @@ def main_csv():
|
|
|
print(f' -> CSV/{fname} ({len(combined)} 条)')
|
|
|
else:
|
|
|
print(f' {table_name}: (无数据)')
|
|
|
-
|
|
|
print(f'\n完成!所有CSV已输出到 {OUTDIR}/')
|
|
|
|
|
|
-
|
|
|
# ==========================================
|
|
|
# 主入口
|
|
|
# ==========================================
|
|
|
def main():
|
|
|
- if len(sys.argv) >= 3 and sys.argv[1] == '-j':
|
|
|
- # JSON 单文件输出模式
|
|
|
+ # 解析参数:python script.py <PDF路径> 或 python script.py -j <PDF路径>
|
|
|
+ pdf_path = None
|
|
|
+ if len(sys.argv) >= 2 and sys.argv[1] != '-j':
|
|
|
+ pdf_path = sys.argv[1]
|
|
|
+ elif len(sys.argv) >= 3 and sys.argv[1] == '-j':
|
|
|
pdf_path = sys.argv[2]
|
|
|
+
|
|
|
+ if pdf_path:
|
|
|
if not os.path.isfile(pdf_path):
|
|
|
print(f'错误:找不到文件 {pdf_path}')
|
|
|
sys.exit(1)
|
|
|
result = extract_pdf_to_json(pdf_path)
|
|
|
- print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
|
|
|
+
|
|
|
+ # 输出到同名 .json 文件
|
|
|
+ base, _ = os.path.splitext(pdf_path)
|
|
|
+ json_path = base + '.json'
|
|
|
+ with open(json_path, 'w', encoding='utf-8') as f:
|
|
|
+ json.dump(result, f, ensure_ascii=False, indent=2, default=str)
|
|
|
+ print(f'已生成: {json_path}')
|
|
|
+ print(f'指标总数: {len(result)} 个')
|
|
|
else:
|
|
|
main_csv()
|
|
|
|