Files
yiliao/backend/config.py

413 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
配置文件 - 统一管理路径和参数
"""
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()