""" 配置文件 - 统一管理路径和参数 """ from pathlib import Path import os # 项目根目录 PROJECT_ROOT = Path(__file__).parent.parent BACKEND_ROOT = Path(__file__).parent # ==================== 路径配置 ==================== # PDF输入目录(存放原始医疗报告PDF) PDF_INPUT_DIR = Path(r"c:\Users\UI\Desktop\医疗报告\医疗报告智能体") # Word模板文件 TEMPLATE_COMPLETE = BACKEND_ROOT / "template_complete.docx" # 用于 extract_and_fill_report.py TEMPLATE_DOCXTPL = PROJECT_ROOT / "template_docxtpl.docx" # 用于 fill_with_docxtpl.py # 配置文件 ABB_MAPPING_CONFIG = BACKEND_ROOT / "abb_mapping_config.json" # 输出目录 REPORTS_OUTPUT_DIR = BACKEND_ROOT / "reports" REPORTS_OUTPUT_DIR.mkdir(exist_ok=True) # 缓存文件 EXTRACTED_DATA_FILE = BACKEND_ROOT / "extracted_medical_data.json" ANALYZED_DATA_FILE = BACKEND_ROOT / "analyzed_medical_data.json" DEEPSEEK_PROCESSED_DATA_FILE = BACKEND_ROOT / "deepseek_processed_data.json" DEEPSEEK_CACHE_FILE = BACKEND_ROOT / "deepseek_cache.json" # ==================== OCR配置 ==================== # 百度OCR配置(从环境变量读取) BAIDU_OCR_APP_ID = os.getenv("BAIDU_OCR_APP_ID", "") BAIDU_OCR_API_KEY = os.getenv("BAIDU_OCR_API_KEY", "") BAIDU_OCR_SECRET_KEY = os.getenv("BAIDU_OCR_SECRET_KEY", "") # ==================== LLM配置 ==================== # DeepSeek配置 DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY", "") DEEPSEEK_API_BASE = os.getenv("DEEPSEEK_API_BASE", "https://api.deepseek.com") # OpenAI配置 OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "") OPENAI_API_BASE = os.getenv("OPENAI_API_BASE", "") OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-3.5-turbo") # Ollama配置 OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://localhost:11434") OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "qwen2.5:7b") # Coze配置 COZE_API_KEY = os.getenv("COZE_API_KEY", "") COZE_WORKFLOW_ID = os.getenv("COZE_WORKFLOW_ID", "") # ==================== 功能开关 ==================== # 是否启用DeepSeek分析(在extract_and_fill_report.py中使用) ENABLE_DEEPSEEK_ANALYSIS = os.getenv("ENABLE_DEEPSEEK_ANALYSIS", "false").lower() == "true" # ==================== 辅助函数 ==================== def load_abb_config() -> dict: """ 加载ABB映射配置文件 Returns: dict: 包含以下键的字典: - modules: 模块配置字典 - abb_list: 所有ABB列表 - abb_to_module: ABB到模块的映射 - abb_to_info: ABB到详细信息的映射 - abb_aliases: ABB别名映射 - module_aliases: 模块名称别名映射 """ import json result = { 'modules': {}, 'abb_list': [], 'abb_to_module': {}, 'abb_to_info': {}, 'abb_aliases': {}, 'module_aliases': {} } if not ABB_MAPPING_CONFIG.exists(): return result with open(ABB_MAPPING_CONFIG, 'r', encoding='utf-8') as f: config = json.load(f) # 新格式:基于模块的配置 if 'modules' in config and isinstance(config['modules'], dict): result['modules'] = config['modules'] result['abb_aliases'] = config.get('abb_aliases', {}) result['module_aliases'] = config.get('module_aliases', {}) # 定义大小写敏感的ABB(这些ABB有大小写冲突,必须精确匹配) case_sensitive_abbs = {'TG', 'Tg'} # TG=甘油三酯, Tg=甲状腺球蛋白 for module_name, module_data in config['modules'].items(): items = module_data.get('items', []) for item in items: abb = item.get('abb', '') if abb: result['abb_list'].append(abb) info = { 'abb': abb, 'project': item.get('project', ''), 'project_cn': item.get('project_cn', ''), 'module': module_name, 'module_cn': module_data.get('cn_name', '') } # 对于大小写敏感的ABB,使用原始大小写作为key if abb in case_sensitive_abbs: result['abb_to_module'][abb] = module_name result['abb_to_info'][abb] = info else: # 其他ABB使用大写作为key(保持向后兼容) result['abb_to_module'][abb.upper()] = module_name result['abb_to_info'][abb.upper()] = info # 旧格式:items列表 elif 'items' in config: for item in config['items']: abb = item.get('abb', '') if abb: result['abb_list'].append(abb) module = item.get('module', '') result['abb_to_module'][abb.upper()] = module result['abb_to_info'][abb.upper()] = { 'abb': abb, 'project': item.get('project', ''), 'project_cn': item.get('project_cn', ''), 'module': module } return result def normalize_abb(abb: str, config: dict = None) -> str: """ 标准化ABB名称(处理别名) Args: abb: 原始ABB名称 config: ABB配置(可选,如果不提供则自动加载) Returns: 标准化后的ABB名称 """ if config is None: config = load_abb_config() aliases = config.get('abb_aliases', {}) abb_upper = abb.upper() # 检查是否有别名 if abb in aliases: return aliases[abb] if abb_upper in aliases: return aliases[abb_upper] return abb def normalize_module_name(module: str, config: dict = None) -> str: """ 标准化模块名称(处理DeepSeek返回的不同名称) Args: module: 原始模块名称 config: ABB配置(可选,如果不提供则自动加载) Returns: 标准化后的模块名称 """ if config is None: config = load_abb_config() module_aliases = config.get('module_aliases', {}) # 检查是否有别名 if module in module_aliases: return module_aliases[module] # 尝试不区分大小写匹配 module_lower = module.lower() for alias, standard in module_aliases.items(): if alias.lower() == module_lower: return standard return module def get_standard_module_order() -> list: """ 获取标准模块顺序(基于2.pdf模板) 优先从配置文件读取order字段,确保与2.pdf一致 Returns: 模块名称列表,按标准顺序排列 """ config = load_abb_config() modules = config.get('modules', {}) # 如果配置中有order字段,按order排序 if modules and any('order' in m for m in modules.values()): sorted_modules = sorted( modules.items(), key=lambda x: x[1].get('order', 999) ) return [name for name, _ in sorted_modules] # 默认顺序(与2.pdf一致) return [ 'Urine Test', # 1. 尿液检测 (第16-19页) 'Complete Blood Count', # 2. 血常规 (第20-26页) 'Blood Sugar', # 3. 血糖 (第27-28页) 'Lipid Profile', # 4. 血脂 (第29-31页) 'Blood Type', # 5. 血型 (第32-33页) 'Blood Coagulation', # 6. 凝血功能 (第34-36页) 'Four Infectious Diseases', # 7. 传染病四项 (第37-40页) 'Serum Electrolytes', # 8. 血电解质 (第41-43页) 'Liver Function', # 9. 肝功能 (第44-47页) 'Kidney Function', # 10. 肾功能 (第48-49页) 'Myocardial Enzyme', # 11. 心肌酶谱 (第50-51页) 'Thyroid Function', # 12. 甲状腺功能 (第52-54页) 'Thromboembolism', # 13. 心脑血管风险因子 (第55-56页) 'Bone Metabolism', # 14. 骨代谢 (第57-59页) 'Microelement', # 15. 微量元素 (第60-62页) 'Lymphocyte Subpopulation', # 16. 淋巴细胞亚群 (第63-64页) 'Humoral Immunity', # 17. 体液免疫 (第65-67页) 'Inflammatory Reaction', # 18. 炎症反应 (第68-69页) 'Autoantibody', # 19. 自身抗体 (第70-71页) 'Female Hormone', # 20. 女性荷尔蒙 (第72-75页) 'Male Hormone', # 21. 男性荷尔蒙 (第76-79页) 'Tumor Markers', # 22. 肿瘤标记物 (第80-84页) 'Imaging', # 23. 影像学检查 (第85-88页) 'Female-specific', # 24. 女性专项检查 (第89-91页) ] def get_standard_item_order(module_name: str, config: dict = None) -> list: """ 获取指定模块的标准项目顺序 Args: module_name: 模块名称 config: ABB配置(可选) Returns: 该模块的ABB列表,按标准顺序排列 """ if config is None: config = load_abb_config() modules = config.get('modules', {}) if module_name in modules: items = modules[module_name].get('items', []) return [item.get('abb', '') for item in items] return [] def sort_items_by_standard_order(items: list, module_name: str, config: dict = None) -> list: """ 按标准顺序排序项目列表 Args: items: [(abb, data), ...] 格式的项目列表 module_name: 模块名称 config: ABB配置(可选) Returns: 排序后的项目列表,标准项目在前,非标准项目在后 """ if config is None: config = load_abb_config() standard_order = get_standard_item_order(module_name, config) abb_aliases = config.get('abb_aliases', {}) # 创建顺序映射(先精确匹配,再大写匹配) # 大小写敏感的ABB(如TG/Tg)需要精确匹配 case_sensitive_abbs = {'TG', 'Tg'} order_map_exact = {abb: i for i, abb in enumerate(standard_order)} order_map_upper = {abb.upper(): i for i, abb in enumerate(standard_order) if abb not in case_sensitive_abbs} # 分离标准项目和非标准项目 standard_items = [] extra_items = [] for abb, data in items: # 先标准化ABB(处理别名) normalized_abb = normalize_abb(abb, config) # 先尝试精确匹配(使用标准化后的ABB) if normalized_abb in order_map_exact: standard_items.append((abb, data, order_map_exact[normalized_abb])) # 再尝试原始ABB精确匹配 elif abb in order_map_exact: standard_items.append((abb, data, order_map_exact[abb])) # 再尝试大写匹配(排除大小写敏感的ABB) elif normalized_abb.upper() in order_map_upper: standard_items.append((abb, data, order_map_upper[normalized_abb.upper()])) elif abb.upper() in order_map_upper: standard_items.append((abb, data, order_map_upper[abb.upper()])) else: extra_items.append((abb, data)) # 标准项目按顺序排序 standard_items.sort(key=lambda x: x[2]) sorted_standard = [(abb, data) for abb, data, _ in standard_items] # 非标准项目按ABB字母顺序排序,添加到末尾 extra_items.sort(key=lambda x: x[0].upper()) return sorted_standard + extra_items def get_output_path(prefix: str = "filled_report", suffix: str = ".docx") -> Path: """ 生成输出文件路径(自动递增版本号) Args: prefix: 文件名前缀 suffix: 文件后缀 Returns: 输出文件路径 """ existing = list(REPORTS_OUTPUT_DIR.glob(f"{prefix}_*.docx")) if not existing: version = 1 else: versions = [] for p in existing: name = p.stem try: # 尝试提取版本号(格式: prefix_v1, prefix_20240101_120000等) if name.startswith(prefix): rest = name[len(prefix):] if rest.startswith('_v'): v_str = rest[2:] if v_str.isdigit(): versions.append(int(v_str)) elif rest.startswith('_') and len(rest) > 1: # 尝试提取时间戳后的版本号 parts = rest.split('_') if len(parts) > 1: last_part = parts[-1] if last_part.isdigit(): versions.append(int(last_part)) except: continue version = max(versions) + 1 if versions else 1 return REPORTS_OUTPUT_DIR / f"{prefix}_v{version}{suffix}" def check_required_files() -> dict: """ 检查必需文件是否存在 Returns: dict: {文件路径: 是否存在} """ return { "template_complete": TEMPLATE_COMPLETE.exists(), "template_docxtpl": TEMPLATE_DOCXTPL.exists(), "config_file": ABB_MAPPING_CONFIG.exists(), "pdf_input_dir": PDF_INPUT_DIR.exists(), } def print_config_summary(): """打印配置摘要""" print("\n" + "=" * 70) print("配置摘要") print("=" * 70) print("\n[路径配置]") print(f" PDF输入目录: {PDF_INPUT_DIR}") print(f" 模板文件 (extract): {TEMPLATE_COMPLETE}") print(f" 模板文件 (docxtpl): {TEMPLATE_DOCXTPL}") print(f" 配置文件: {ABB_MAPPING_CONFIG}") print(f" 输出目录: {REPORTS_OUTPUT_DIR}") print("\n[文件检查]") files_status = check_required_files() for name, exists in files_status.items(): status = "[OK] 存在" if exists else "[X] 缺失" print(f" {name}: {status}") print("\n[API配置]") print(f" 百度OCR: {'[OK] 已配置' if BAIDU_OCR_API_KEY else '[X] 未配置'}") print(f" DeepSeek: {'[OK] 已配置' if DEEPSEEK_API_KEY else '[X] 未配置'}") print(f" OpenAI: {'[OK] 已配置' if OPENAI_API_KEY else '[X] 未配置'}") print(f" Ollama: {'[OK] 已配置' if OLLAMA_HOST else '[X] 未配置'}") print(f" Coze: {'[OK] 已配置' if COZE_API_KEY else '[X] 未配置'}") print("=" * 70 + "\n") if __name__ == '__main__': # 测试配置 print_config_summary()