Files
yiliao/backend/medical_intervention_v2.py

1487 lines
59 KiB
Python
Raw 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.

"""
医学干预建议模块 V2
基于医疗干预模板(1).docx重新设计的功能医学健康建议生成器
结构:
1. 总述(核心干预方向+原则)
2. 生物同源性荷尔蒙调理 (BHRT)
3. 静脉营养点滴组合 (IVNT)
4. 细胞再生疗法 (MSC)
每个板块按"关联前期问题→方案细节→价值小结"展开
"""
import json
import re
from typing import Dict, List, Any, Tuple
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
# 三大板块的固定结构
INTERVENTION_SECTIONS = {
'BHRT': {
'title_en': 'Hormone-Centered Improvement Plan',
'title_cn': '生物同源性荷尔蒙调理',
'number': '1',
'subsections': [
{'id': '1.1', 'title_en': 'Thyroid-axis optimization', 'title_cn': '甲状腺轴优化'},
{'id': '1.2', 'title_en': 'Sex-steroid / perimenopause support', 'title_cn': '性激素与围绝经管理'},
]
},
'IVNT': {
'title_en': 'IVNT Drip Selection',
'title_cn': '建议静脉营养点滴组合',
'number': '2',
'subsections': [
{'id': 'detox', 'title_en': 'Liver Detox Protocol', 'title_cn': '肝胆排毒'},
{'id': 'immune', 'title_en': 'Immune Activation Therapy', 'title_cn': '免疫激活疗法'},
{'id': 'ovarian', 'title_en': 'Ovarian Care Formula', 'title_cn': '卵巢呵护配方'},
{'id': 'nad', 'title_en': 'Multi-Nutrient Advanced NAD+', 'title_cn': 'NAD+能量支持'},
]
},
'MSC': {
'title_en': 'Cellular Regeneration',
'title_cn': '细胞再生疗法(干细胞)',
'number': '3',
'subsections': [
{'id': '3.1', 'title_en': 'Clinical positioning', 'title_cn': '临床定位'},
{'id': '3.2', 'title_en': 'Immunomodulatory mechanisms', 'title_cn': '免疫调节机制'},
{'id': '3.3', 'title_en': 'Ovarian function support', 'title_cn': '协助卵巢功能恢复'},
{'id': '3.4', 'title_en': 'Synergy with BHRT', 'title_cn': '与BHRT协同'},
]
}
}
def detect_gender_from_items(all_items: List[Dict]) -> str:
"""
根据检测项目判断性别
检测逻辑:
- 如果有 TPSA/FPSA前列腺特异性抗原→ 男性
- 如果有 AMH/CA125/CA15-3/SCC女性特异性肿瘤标志物→ 女性
- 如果有 E2雌二醇且值 > 50 → 女性男性E2通常 < 50 pg/mL
- 默认:女性
Returns:
'male''female'
"""
male_specific = {'TPSA', 'FPSA', 'PSA'}
female_specific = {'AMH', 'CA125', 'CA15-3', 'SCC'}
for item in all_items:
abb = item.get('abb', '').upper()
# 男性特异性检测项目
if abb in male_specific:
return 'male'
# 女性特异性检测项目
if abb in female_specific:
return 'female'
# E2雌二醇判断
if abb == 'E2':
try:
result = item.get('result', '')
if result:
value = float(str(result).replace('<', '').replace('>', '').strip())
if value > 50:
return 'female'
except:
pass
return 'female' # 默认女性
def categorize_abnormal_items(abnormal_items: List[Dict], api_key: str = None, call_deepseek_api = None, all_items: List[Dict] = None) -> Dict[str, List[Dict]]:
"""
将异常指标按24个模块分类基于abb_mapping_config.json
每个异常项已经有module字段来自matched_data直接使用该字段进行分类。
对于没有module字段的项目使用DeepSeek进行智能分类到24个模块之一。
荷尔蒙项目会根据检测到的性别路由到正确的模块Female Hormone 或 Male Hormone
Args:
abnormal_items: 异常指标列表
api_key: DeepSeek API Key
call_deepseek_api: API调用函数
all_items: 所有检测项目(用于性别检测)
Returns:
Dict[str, List[Dict]]: 模块名称 -> 异常项列表
"""
from config import load_abb_config, normalize_abb, normalize_module_name
# 加载24个模块配置
abb_config = load_abb_config()
modules = abb_config.get('modules', {})
abb_to_module = abb_config.get('abb_to_module', {})
# 检测性别
items_for_detection = all_items if all_items else abnormal_items
detected_gender = detect_gender_from_items(items_for_detection)
print(f" [性别检测] 检测结果: {'男性' if detected_gender == 'male' else '女性'}")
# 根据性别确定荷尔蒙项目应该分配到的模块
hormone_target_module = 'Male Hormone' if detected_gender == 'male' else 'Female Hormone'
hormone_wrong_module = 'Female Hormone' if detected_gender == 'male' else 'Male Hormone'
# 荷尔蒙相关的ABB这些项目在男性和女性荷尔蒙模块中都可能出现
hormone_abbs = {'E2', 'PROG', 'FSH', 'LH', 'PRL', 'T', 'DHEAS', 'COR', 'CORTISOL', 'IGF-1', 'AMH'}
# 初始化所有模块的分类字典
categories = {module_name: [] for module_name in modules.keys()}
unclassified = [] # 无法自动分类的指标
for item in abnormal_items:
abb = item.get('abb', '')
module = item.get('module', '')
abb_upper = abb.upper()
# 特殊处理:荷尔蒙项目根据性别路由
if abb_upper in hormone_abbs:
item['module'] = hormone_target_module
categories[hormone_target_module].append(item)
continue
# 1. 首先检查item自带的module字段
if module:
# 标准化模块名称
normalized_module = normalize_module_name(module, abb_config)
# 如果是错误的荷尔蒙模块,修正为正确的
if normalized_module == hormone_wrong_module:
normalized_module = hormone_target_module
if normalized_module in categories:
categories[normalized_module].append(item)
continue
# 2. 尝试通过ABB查找模块
normalized_abb = normalize_abb(abb, abb_config)
found_module = abb_to_module.get(normalized_abb.upper()) or abb_to_module.get(abb.upper())
# 如果是错误的荷尔蒙模块,修正为正确的
if found_module == hormone_wrong_module:
found_module = hormone_target_module
if found_module and found_module in categories:
item['module'] = found_module # 补充module字段
categories[found_module].append(item)
continue
# 3. 无法自动分类的收集起来
unclassified.append(item)
# 如果有无法分类的指标使用DeepSeek进行分类
if unclassified and api_key and call_deepseek_api:
print(f" 🤖 {len(unclassified)} 个指标需要DeepSeek智能分类...")
deepseek_classified = classify_with_deepseek(unclassified, list(modules.keys()), api_key, call_deepseek_api)
# 合并分类结果
for module_name, items in deepseek_classified.items():
if module_name in categories:
categories[module_name].extend(items)
elif unclassified:
# 没有API时打印警告但不默认归入任何类别
print(f" ⚠️ {len(unclassified)} 个指标无法分类无API Key:")
for item in unclassified:
print(f" - {item.get('name', '')} ({item.get('abb', '')})")
# 移除空的分类
categories = {k: v for k, v in categories.items() if v}
return categories
def classify_with_deepseek(unclassified_items: List[Dict], module_names: List[str], api_key: str, call_deepseek_api) -> Dict[str, List[Dict]]:
"""
使用DeepSeek对无法自动分类的指标进行分类到24个模块之一
Args:
unclassified_items: 无法自动分类的指标列表
module_names: 24个模块名称列表
api_key: DeepSeek API Key
call_deepseek_api: API调用函数
Returns:
分类结果字典 {module_name: [items]}
"""
from config import load_abb_config
if not unclassified_items or not api_key:
return {}
# 加载配置获取模块中文名称
abb_config = load_abb_config()
modules = abb_config.get('modules', {})
# 构建模块列表描述
module_desc = []
for module_name in module_names:
cn_name = modules.get(module_name, {}).get('cn_name', '')
module_desc.append(f"- {module_name} ({cn_name})")
# 构建指标描述
items_desc = []
for item in unclassified_items:
direction = '偏高' if item.get('point') in ['', 'H', ''] else '偏低'
desc = f"- {item.get('name', '')} ({item.get('abb', '')}): {item.get('result', '')} {item.get('unit', '')} ({direction})"
items_desc.append(desc)
prompt = f"""请将以下医学检测指标分类到对应的模块中。
## 待分类指标:
{chr(10).join(items_desc)}
## 可选模块共24个
{chr(10).join(module_desc)}
## 分类规则:
1. 每个指标必须分类到上述24个模块之一
2. 根据指标的医学属性选择最合适的模块
3. 不要创建新的模块只能使用上述24个模块
## 输出格式JSON
```json
{{
"classifications": [
{{"abb": "指标缩写", "module": "模块英文名称"}}
]
}}
```
只返回JSON不要其他内容。"""
try:
response = call_deepseek_api(prompt, api_key, max_tokens=1000, timeout=30)
if response is None:
return {}
# 解析JSON
if '```json' in response:
response = response.split('```json')[1].split('```')[0]
elif '```' in response:
response = response.split('```')[1].split('```')[0]
result = json.loads(response.strip())
classifications = result.get('classifications', [])
# 构建分类结果
classified = {}
# 创建ABB到item的映射
abb_to_item = {item.get('abb', '').upper(): item for item in unclassified_items}
for cls in classifications:
abb = cls.get('abb', '').upper()
module = cls.get('module', '')
if abb in abb_to_item and module in module_names:
if module not in classified:
classified[module] = []
item = abb_to_item[abb]
item['module'] = module # 补充module字段
classified[module].append(item)
print(f"{item.get('name', '')} ({abb}) -> {module}")
return classified
except Exception as e:
print(f" ⚠️ DeepSeek分类失败: {e}")
return {}
def build_problem_summary(categories: Dict[str, List[Dict]]) -> str:
"""
构建问题摘要用于prompt
基于24个模块分类构建摘要
"""
from config import load_abb_config
# 加载配置获取模块中文名称
abb_config = load_abb_config()
modules = abb_config.get('modules', {})
summary_parts = []
for module_name, items in categories.items():
if not items:
continue
# 获取模块中文名称
cn_name = modules.get(module_name, {}).get('cn_name', module_name)
# 构建该模块的描述
desc = f"{cn_name}】({module_name})" + "".join([
f"{item['name']}({item['abb']}) {item['result']}{item.get('unit', '')} {'' if item['point'] in ['', 'H', ''] else ''}"
for item in items[:5]
])
if len(items) > 5:
desc += f"{len(items)}"
summary_parts.append(desc)
return "\n".join(summary_parts) if summary_parts else "暂无明显异常"
def build_intervention_prompt(abnormal_items: List[Dict], categories: Dict[str, List[Dict]]) -> str:
"""
构建医学干预建议的PromptV2版本 - 基于案例文档优化)
"""
problem_summary = build_problem_summary(categories)
# 构建详细的异常指标列表
abnormal_details = []
for item in abnormal_items:
direction = '偏高' if item['point'] in ['', 'H', ''] else '偏低'
detail = f"- {item['name']} ({item['abb']}): {item['result']} {item.get('unit', '')} ({direction})"
if item.get('reference'):
detail += f" [参考: {item['reference']}]"
abnormal_details.append(detail)
prompt = f"""# 角色设定
你是Be.U Med功能医学团队的资深医学顾问在功能医学、抗衰老医学、血液净化疗法、静脉营养疗法(IVNT)、生物同源性荷尔蒙疗法(BHRT)、干细胞再生医学领域具有丰富的临床经验。
# 任务
根据体检者的异常指标,撰写个性化的「医学干预」建议方案。
# 体检者问题摘要
{problem_summary}
# 异常指标详情
{chr(10).join(abnormal_details)}
# 核心原则(必须严格遵守)
## 1. 段落格式(极其重要!严格遵守字数限制!)
- **每个子板块必须包含2-4个独立的内容段落不要合并成一整段**
- **每个内容段落必须先写英文,再写对应的中文**
- **英文段落50-80词绝对不能超过80词**
- **中文段落70-120字绝对不能超过120字硬性上限140字**
- 不要英中混排,必须分开
- **禁止将所有内容写成一个超长段落**
- **如果内容较多,必须拆分成多个段落,每个段落控制在限制内**
## 2. 逻辑要求
- 不重复指标解读(前期报告分析已完成)
- 紧扣前期问题给出对应解决方案
- 清晰传递产品核心价值与协同作用
- 体现"先稳基础、再精准干预"的功能医学思路
## 3. 语气要求
- 客观陈述方案原理与实际价值
- 使用"建议""支持""助力""帮助""有助于"等引导词
- **严禁使用任何不确定表述**,包括但不限于:
- 中文禁用词:可能、也许、或许、大概、似乎、有概率、有可能、可能会、或可、疑似、倾向于、趋向于、不排除、有待、存在...的可能
- 英文禁用词may, might, could, possibly, probably, perhaps, likely, potentially, tend to, appear to, seem to, it is possible that
- 禁用强硬/绝对化表述(如"必须""一定""保证""治愈"
- 不夸大效果,实事求是
## 4. 模块总结要求(极其重要!必须遵守!)
- **每个区域模块必须包含intro字段放在区域标题后、子标题前**
- **intro字段是JSON中的必填字段格式为{{"en": "英文", "cn": "中文"}}**
- 总结段落约120-140字中文对应英文约80-100词
- 总结内容:概括该模块要解决的核心问题、干预方向、预期价值
- 格式:先英文总结,再中文总结
- 位置:区域标题 → intro模块总结 → 子标题1 → 子标题2...
# 文章结构(必须严格遵循案例格式)
## 总述部分(固定模板+动态填充)
- **标题**: `Medical Intervention 「医学干预」建议方案`
**英文总述(固定模板)**
"The current core medical intervention priorities are: `{{动态:干预方向,如 Vascular health (dyslipidemia + elevated lipoprotein(a)) → Thyroid autoimmune regulation → Iron metabolism balance → Hormonal homeostasis}}`."
"Based on this, the principle guiding your proposed medical intervention plan is to first \"reduce vascular risk and oxidative stress\", followed by \"immune regulation and hormonal optimization\". Your issue does not lie with acute organ dysfunction, but rather with \"`{{动态:具体异常组合,如 elevated lipoprotein(a) + thyroid autoimmune activation + iron overload + mild androgen imbalance}}`\"—a combination of chronic risk factors that synergistically threaten vascular and endocrine health. In functional medicine, this pattern requires a multi-targeted, integrated intervention to address both symptoms and root causes."
**中文总述(固定模板)**
"当前核心的医疗干预方向为:`{{动态:干预方向,如 血管健康 → 荷尔蒙平衡 → 微量元素调节 → 机体抗衰}}`。基于此,针对您的「医学干预」建议方案的原则是先"降低血管风险与氧化应激",再"免疫调节与荷尔蒙优化"。`{{动态:问题定性,如 您的问题并非急性脏器功能障碍,而是"脂蛋白(a)升高 + 甲状腺自身免疫活化 + 铁过载 + 轻度雄激素失衡"的慢性风险组合,这些因素协同威胁血管与内分泌健康}}`。在功能医学上,此类格局需要多靶点、一体化干预,实现标本兼顾。"
**动态部分生成规则**
- 干预方向:根据异常指标所属的系统生成,格式为 `A → B → C → D`
- 具体异常组合:列出主要异常指标的英文描述
- 问题定性:用中文描述异常指标的组合特征
## 板块选择根据异常指标动态选择2-4个板块
### 板块A: Vascular Protection & Metabolic Regulation 血管保护与代谢调控
适用于:血脂异常、脂蛋白(a)升高、胆固醇异常、同型半胱氨酸升高
A.1 Blood Purification Therapy (DFPP + Ozone) 血液净化疗法
- 疗法定位(英文→中文)
- DFPP深度血液净化每6个月1次共2次每次120分钟英文→中文
- Ozone轻盈血液净化每月1次共6次每次30分钟英文→中文
- 干预意义(英文→中文)
A.2 IVNT Nutritional Intervention 静脉营养点滴干预
- IVNT定位说明英文→中文
- IVNT 肝胆排毒 (3次/疗程)维生素C、谷胱甘肽、α-硫辛酸(英文→中文)
- IVNT 免疫激活疗法 (3次/疗程)B族维生素、辅酶Q10、NAC英文→中文
- IVNT 血管保护配方 (3次/疗程)叶酸、B6、B12、Omega-3英文→中文
- 干预意义(英文→中文)
### 板块B: Immune Regulation & Thyroid Protection 免疫调节与甲状腺保护
适用于:甲状腺抗体升高(TgAb/TPOAb)、自身免疫活化、甲状腺功能异常
B.1 Lymphocyte Therapy 淋巴细胞疗法
- 疗法定位血液净化与IVNT干预1个月后启动每年1次英文→中文
- 免疫调节机制Treg细胞作用英文→中文
- 干预意义(英文→中文)
B.2 BHRT Bioidentical Hormone Therapy 生物同源性荷尔蒙调理
- 方案定位:靶向调节而非外源替代(英文→中文)
- 甲状腺轴优化硒200μg/日、维生素D3 5000IU/日(英文→中文)
- 性激素轴调节(英文→中文)
- 干预意义(英文→中文)
### 板块C: Iron Metabolism Balance & Lifestyle Optimization 铁代谢平衡与生活方式优化
适用于:铁蛋白异常、铁过载、需要生活方式干预
C.1 ONS (Oral Nutritional Support) 口服营养支持
- 方案定位定制6个月英文→中文
- 铁代谢调节:乳铁蛋白拮抗剂+维生素E 400IU/日(英文→中文)
- 血管保护辅助辅酶Q10 200mg/日+植物甾醇2g/日(英文→中文)
C.2 Lifestyle Optimization 生活方式优化
- 饮食建议:具体食物推荐和禁忌(英文→中文)
- 运动建议每周3次有氧+每周2次阻力训练英文→中文
- 作息与压力管理7-8小时睡眠+15分钟冥想英文→中文
- 干预意义(英文→中文)
### 板块D: Cellular Regeneration 细胞再生疗法(干细胞)
适用于:多系统慢性问题、作为其他干预的协同增效
D.1 Clinical Positioning 临床定位
- MSC疗法定位再生修复+免疫调节核心干预3个月后启动每6个月1次共2次英文→中文
- MSC作用机制英文→中文
D.2 Synergy with Other Interventions 与其他干预的协同
- 血管保护协同(英文→中文)
- 甲状腺支持协同(英文→中文)
- 荷尔蒙优化协同(英文→中文)
## 签名部分(右对齐,空一行后显示)
Functional Medical Team from Be.U Med
Be.U Med 功能医学团队
[当前日期格式YYYY年MM月DD日]
# 输出格式JSON
```json
{{
"overview": {{
"title_en": "Medical Intervention",
"title_cn": "「医学干预」建议方案",
"content_en": "使用固定模板填充动态部分。第一句The current core medical intervention priorities are: [动态:干预方向]。第二句Based on this, the principle guiding your proposed medical intervention plan is to first \"reduce vascular risk and oxidative stress\", followed by \"immune regulation and hormonal optimization\". Your issue does not lie with acute organ dysfunction, but rather with \"[动态:具体异常组合]\"—a combination of chronic risk factors that synergistically threaten vascular and endocrine health. In functional medicine, this pattern requires a multi-targeted, integrated intervention to address both symptoms and root causes.",
"content_cn": "使用固定模板填充动态部分。当前核心的医疗干预方向为:[动态:干预方向]。基于此,针对您的「医学干预」建议方案的原则是先"降低血管风险与氧化应激",再"免疫调节与荷尔蒙优化"。[动态:问题定性]。在功能医学上,此类格局需要多靶点、一体化干预,实现标本兼顾。"
}},
"sections": [
{{
"number": "1",
"title_en": "Vascular Protection & Metabolic Regulation",
"title_cn": "血管保护与代谢调控",
"intro": {{
"en": "模块总结英文80-100词概括该模块要解决的核心问题、干预方向、预期价值...",
"cn": "模块总结中文120-140字概括该模块要解决的核心问题、干预方向、预期价值..."
}},
"subsections": [
{{
"id": "1.1",
"title_en": "Blood Purification Therapy (DFPP + Ozone)",
"title_cn": "血液净化疗法DFPP + Ozone",
"paragraphs": [
{{
"en": "英文段落1疗法定位和概述50-80词...",
"cn": "中文段落1对应翻译70-120字不超过140字..."
}},
{{
"en": "英文段落2DFPP疗法详情包含频次、疗程、时长、作用机制...",
"cn": "中文段落2对应翻译..."
}},
{{
"en": "英文段落3Ozone疗法详情...",
"cn": "中文段落3对应翻译..."
}},
{{
"en": "Intervention Significance: 干预意义英文...",
"cn": "干预意义: 中文..."
}}
]
}},
{{
"id": "1.2",
"title_en": "IVNT Nutritional Intervention",
"title_cn": "静脉营养点滴干预",
"paragraphs": [...]
}}
]
}},
{{
"number": "2",
"title_en": "Immune Regulation & Thyroid Protection",
"title_cn": "免疫调节与甲状腺保护",
"intro": {{
"en": "模块总结英文...",
"cn": "模块总结中文..."
}},
"subsections": [...]
}}
],
"signature": {{
"team_en": "Functional Medical Team from Be.U Med",
"team_cn": "Be.U Med 功能医学团队",
"date": "YYYY年MM月DD日使用当前实际日期"
}}
}}
```
# 重要提示
1. **每个子板块必须包含2-4个独立段落禁止合并成一整段**
2. **中文段落严格控制在70-120字绝对不能超过140字**
3. **英文段落严格控制在50-80词绝对不能超过80词**
4. **内容必须与体检者的具体异常指标关联**
5. **根据异常指标类型选择合适的板块**不是所有板块都需要选择2-4个最相关的
6. **每个疗法都要说明具体的频次、疗程、核心成分**
7. **每个内容点先英文后中文,不要混排**
8. **强调各干预之间的协同作用**
9. **语气始终保持专业、客观、温和**
10. **【极其重要】每个sections数组中的区域模块必须包含intro字段这是必填字段**
11. **【极其重要】intro字段格式{{"en": "英文总结80-100词", "cn": "中文总结120-140字"}}**
12. **【极其重要】intro内容概括该模块要解决的核心问题、干预方向、预期价值**
13. **只返回JSON不要其他内容**"""
return prompt
def generate_medical_intervention_v2(abnormal_items: List[Dict], api_key: str, call_deepseek_api, all_items: List[Dict] = None) -> dict:
"""
生成医学干预建议内容V2版本
Args:
abnormal_items: 异常项列表
api_key: DeepSeek API Key
call_deepseek_api: API调用函数
all_items: 所有检测项目(用于性别检测)
Returns:
医学干预建议内容字典
"""
if not api_key:
print(" ⚠️ 未提供API Key跳过医学干预建议生成")
return {}
if not abnormal_items:
print(" ⚠️ 没有异常指标,跳过医学干预建议生成")
return {}
# 分类异常指标传入API参数以支持DeepSeek智能分类传入all_items用于性别检测
categories = categorize_abnormal_items(abnormal_items, api_key, call_deepseek_api, all_items)
# 打印分类统计
print(f" 📊 问题分类统计(共 {len(categories)} 个模块有异常):")
for module_name, items in categories.items():
if items:
print(f" {module_name}: {len(items)}")
# 构建prompt
prompt = build_intervention_prompt(abnormal_items, categories)
def parse_json_response(response_text):
"""解析JSON响应"""
if '```json' in response_text:
response_text = response_text.split('```json')[1].split('```')[0]
elif '```' in response_text:
response_text = response_text.split('```')[1].split('```')[0]
response_text = response_text.strip()
try:
return json.loads(response_text)
except json.JSONDecodeError:
pass
# 尝试修复
if response_text.count('"') % 2 != 0:
response_text += '"'
open_braces = response_text.count('{') - response_text.count('}')
open_brackets = response_text.count('[') - response_text.count(']')
if open_brackets > 0:
if open_braces > 0:
response_text += '}' * open_braces
response_text += ']' * open_brackets
elif open_braces > 0:
response_text += '}' * open_braces
try:
return json.loads(response_text)
except json.JSONDecodeError:
return None
# 最多重试3次
for attempt in range(3):
try:
print(f" 🤖 调用DeepSeek生成医学干预建议... (第{attempt+1}次)")
response = call_deepseek_api(prompt, api_key, max_tokens=8000, timeout=240)
if response is None:
if attempt < 2:
print(f" ⚠️ API请求失败重试中...")
import time
time.sleep(3)
continue
result = parse_json_response(response)
if result and (result.get('overview') or result.get('sections') or result.get('bhrt') or result.get('ivnt') or result.get('msc')):
# 检查并补充缺失的intro字段
if result.get('sections'):
for section in result['sections']:
if not section.get('intro'):
title_en = section.get('title_en', '')
title_cn = section.get('title_cn', '')
print(f" ⚠️ 区域 '{title_en}' 缺少intro将自动生成")
# 生成默认intro
section['intro'] = {
'en': f"This section focuses on {title_en.lower()}, addressing the specific abnormalities identified in your test results and providing targeted intervention strategies.",
'cn': f"本模块聚焦于{title_cn},针对您检测结果中发现的具体异常指标,提供靶向干预策略。"
}
print(f" ✓ 成功生成医学干预建议")
return result
if attempt < 2:
print(f" ⚠️ 响应格式不完整,重试中...")
except Exception as e:
if attempt < 2:
print(f" ⚠️ 生成失败: {e},重试中...")
print(f" ✗ 生成医学干预建议失败")
return {}
def convert_intervention_to_sections(intervention_result: dict) -> dict:
"""
将V2格式转换为sections格式以便复用现有的填充函数
支持两种JSON格式
1. 新格式带sections数组
2. 旧格式bhrt/ivnt/msc分开
"""
sections = []
# 1. 总述部分
overview = intervention_result.get('overview', {})
if overview:
paragraphs = []
# 新格式content_en/content_cn
if overview.get('content_en') or overview.get('content_cn'):
paragraphs.append({
'en': overview.get('content_en', ''),
'cn': overview.get('content_cn', '')
})
else:
# 旧格式direction + principle
if overview.get('direction_en') or overview.get('direction_cn'):
paragraphs.append({
'en': overview.get('direction_en', ''),
'cn': overview.get('direction_cn', '')
})
if overview.get('principle_en') or overview.get('principle_cn'):
paragraphs.append({
'en': overview.get('principle_en', ''),
'cn': overview.get('principle_cn', '')
})
if paragraphs:
sections.append({
'title_en': overview.get('title_en', 'Medical Intervention'),
'title_cn': overview.get('title_cn', '「医学干预」建议方案'),
'paragraphs': paragraphs,
'is_main_title': True
})
# 2. 检查是否使用新格式sections数组
if intervention_result.get('sections'):
for section in intervention_result['sections']:
section_paragraphs = []
# 处理模块总结intro- 在子标题之前
intro = section.get('intro', {})
if intro and (intro.get('en') or intro.get('cn')):
section_paragraphs.append({
'en': intro.get('en', ''),
'cn': intro.get('cn', ''),
'is_intro': True
})
else:
# 如果没有intro打印警告
title = section.get('title_en', section.get('title_cn', ''))
print(f" ⚠️ 区域 '{title}' 缺少intro字段")
# 处理子板块
for subsection in section.get('subsections', []):
# 子标题 - 英文带标号,中文不带标号
# 格式1.2 Sex-steroid / early perimenopause support 性激素与"早衰/围绝经提前"管理
sub_id = subsection.get('id', '')
sub_title_en = f"{sub_id} {subsection.get('title_en', '')}".strip()
sub_title_cn = subsection.get('title_cn', '') # 中文不加标号
section_paragraphs.append({
'en': sub_title_en,
'cn': sub_title_cn,
'is_subtitle': True
})
# 处理段落新格式使用paragraphs数组
for para in subsection.get('paragraphs', []):
if para.get('en') or para.get('cn'):
section_paragraphs.append({
'en': para.get('en', ''),
'cn': para.get('cn', '')
})
# 兼容旧格式content_en/content_cn
if subsection.get('content_en') or subsection.get('content_cn'):
section_paragraphs.append({
'en': subsection.get('content_en', ''),
'cn': subsection.get('content_cn', '')
})
if section_paragraphs:
number = section.get('number', '')
title_en = f"{number}) {section.get('title_en', '')}".strip() if number else section.get('title_en', '')
title_cn = section.get('title_cn', '')
sections.append({
'title_en': title_en,
'title_cn': title_cn,
'paragraphs': section_paragraphs
})
else:
# 3. 旧格式BHRT板块
bhrt = intervention_result.get('bhrt', {})
if bhrt:
paragraphs = []
if bhrt.get('intro_en') or bhrt.get('intro_cn'):
paragraphs.append({
'en': bhrt.get('intro_en', ''),
'cn': bhrt.get('intro_cn', '')
})
for sub in bhrt.get('subsections', []):
sub_title_en = f"{sub.get('id', '')} {sub.get('title_en', '')}"
sub_title_cn = f"{sub.get('id', '')} {sub.get('title_cn', '')}"
paragraphs.append({
'en': sub_title_en,
'cn': sub_title_cn,
'is_subtitle': True
})
if sub.get('content_en') or sub.get('content_cn'):
paragraphs.append({
'en': sub.get('content_en', ''),
'cn': sub.get('content_cn', '')
})
if bhrt.get('value_summary_en') or bhrt.get('value_summary_cn'):
paragraphs.append({
'en': bhrt.get('value_summary_en', ''),
'cn': bhrt.get('value_summary_cn', '')
})
if paragraphs:
sections.append({
'title_en': bhrt.get('title_en', '1) Hormone-Centered Improvement Plan'),
'title_cn': bhrt.get('title_cn', '生物同源性荷尔蒙调理'),
'paragraphs': paragraphs
})
# 4. 旧格式IVNT板块
ivnt = intervention_result.get('ivnt', {})
if ivnt:
paragraphs = []
if ivnt.get('intro_en') or ivnt.get('intro_cn'):
paragraphs.append({
'en': ivnt.get('intro_en', ''),
'cn': ivnt.get('intro_cn', '')
})
for formula in ivnt.get('formulas', []):
freq = formula.get('frequency', '')
title_en = f"IVNT {formula.get('name_en', '')}"
title_cn = f"IVNT {formula.get('name_cn', '')} {freq}"
paragraphs.append({
'en': title_en,
'cn': title_cn,
'is_formula_title': True
})
if formula.get('content_en') or formula.get('content_cn'):
paragraphs.append({
'en': formula.get('content_en', ''),
'cn': formula.get('content_cn', '')
})
if ivnt.get('value_summary_en') or ivnt.get('value_summary_cn'):
paragraphs.append({
'en': ivnt.get('value_summary_en', ''),
'cn': ivnt.get('value_summary_cn', '')
})
if paragraphs:
sections.append({
'title_en': ivnt.get('title_en', '2) IVNT Drip Selection'),
'title_cn': ivnt.get('title_cn', '建议静脉营养点滴组合'),
'paragraphs': paragraphs
})
# 5. 旧格式MSC板块
msc = intervention_result.get('msc', {})
if msc:
paragraphs = []
for sub in msc.get('subsections', []):
sub_title_en = f"{sub.get('id', '')} {sub.get('title_en', '')}"
sub_title_cn = f"{sub.get('id', '')} {sub.get('title_cn', '')}"
paragraphs.append({
'en': sub_title_en,
'cn': sub_title_cn,
'is_subtitle': True
})
if sub.get('content_en') or sub.get('content_cn'):
paragraphs.append({
'en': sub.get('content_en', ''),
'cn': sub.get('content_cn', '')
})
if msc.get('value_summary_en') or msc.get('value_summary_cn'):
paragraphs.append({
'en': msc.get('value_summary_en', ''),
'cn': msc.get('value_summary_cn', '')
})
if paragraphs:
sections.append({
'title_en': msc.get('title_en', '3) Cellular Regeneration'),
'title_cn': msc.get('title_cn', '细胞再生疗法(干细胞)'),
'paragraphs': paragraphs
})
# 6. 签名(右对齐,包含日期)- 始终添加签名即使API没有返回
from datetime import datetime
signature = intervention_result.get('signature', {})
# 获取日期优先使用API返回的日期否则使用当前日期
date_str = signature.get('date', '') if signature else ''
if not date_str or 'YYYY' in date_str:
date_str = datetime.now().strftime('%Y年%m月%d')
# 获取团队名称,使用默认值
team_en = signature.get('team_en', 'Functional Medical Team from Be.U Med') if signature else 'Functional Medical Team from Be.U Med'
team_cn = signature.get('team_cn', 'Be.U Med 功能医学团队') if signature else 'Be.U Med 功能医学团队'
sections.append({
'title_en': '',
'title_cn': '',
'paragraphs': [{
'en': team_en,
'cn': team_cn,
'date': date_str,
'is_signature': True
}],
'is_signature_section': True
})
return {'sections': sections}
# ============================================================
# 文档填充函数
# ============================================================
def clean_markdown_formatting(text: str) -> str:
"""清理Markdown格式"""
if not text:
return text
text = re.sub(r'\*\*([^*]+)\*\*', r'\1', text)
text = re.sub(r'__([^_]+)__', r'\1', text)
text = re.sub(r'(?<!\*)\*([^*]+)\*(?!\*)', r'\1', text)
text = re.sub(r'(?<!_)_([^_]+)_(?!_)', r'\1', text)
text = re.sub(r'`([^`]+)`', r'\1', text)
return text
def create_formatted_paragraph_intervention(text: str, is_title: bool = False, is_chinese: bool = False, style_override: str = None):
"""创建带格式的段落(无缩进)
Args:
text: 段落文本
is_title: 是否是标题
is_chinese: 是否是中文
style_override: 样式覆盖
"""
text = clean_markdown_formatting(text)
p = OxmlElement('w:p')
pPr = OxmlElement('w:pPr')
# 设置段落样式
pStyle = OxmlElement('w:pStyle')
if style_override:
pStyle.set(qn('w:val'), style_override)
elif is_chinese:
pStyle.set(qn('w:val'), '0') # 0中文正文
else:
pStyle.set(qn('w:val'), '00') # 0英语正文
pPr.append(pStyle)
# 显式清除所有缩进(覆盖样式中的默认缩进)
ind = OxmlElement('w:ind')
ind.set(qn('w:left'), '0')
ind.set(qn('w:right'), '0')
ind.set(qn('w:firstLine'), '0')
pPr.append(ind)
if is_title:
rPr_para = OxmlElement('w:rPr')
b = OxmlElement('w:b')
rPr_para.append(b)
bCs = OxmlElement('w:bCs')
rPr_para.append(bCs)
pPr.append(rPr_para)
p.append(pPr)
r = OxmlElement('w:r')
rPr = OxmlElement('w:rPr')
if is_title:
b = OxmlElement('w:b')
rPr.append(b)
bCs = OxmlElement('w:bCs')
rPr.append(bCs)
color = OxmlElement('w:color')
if is_chinese:
color.set(qn('w:val'), '000000')
else:
color.set(qn('w:val'), '767171')
color.set(qn('w:themeColor'), 'background2')
color.set(qn('w:themeShade'), '80')
rPr.append(color)
r.append(rPr)
t = OxmlElement('w:t')
t.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
t.text = text
r.append(t)
p.append(r)
return p
def create_empty_paragraph_intervention():
"""创建空段落(无缩进)"""
p = OxmlElement('w:p')
pPr = OxmlElement('w:pPr')
pStyle = OxmlElement('w:pStyle')
pStyle.set(qn('w:val'), '00')
pPr.append(pStyle)
# 显式清除所有缩进
ind = OxmlElement('w:ind')
ind.set(qn('w:left'), '0')
ind.set(qn('w:right'), '0')
ind.set(qn('w:firstLine'), '0')
pPr.append(ind)
p.append(pPr)
return p
def create_signature_paragraph(text: str, is_chinese: bool = False):
"""创建右对齐的签名段落(无缩进)"""
text = clean_markdown_formatting(text)
p = OxmlElement('w:p')
pPr = OxmlElement('w:pPr')
# 设置段落样式
pStyle = OxmlElement('w:pStyle')
if is_chinese:
pStyle.set(qn('w:val'), '0') # 0中文正文
else:
pStyle.set(qn('w:val'), '00') # 0英语正文
pPr.append(pStyle)
# 显式清除所有缩进(覆盖样式中的默认缩进)
ind = OxmlElement('w:ind')
ind.set(qn('w:left'), '0')
ind.set(qn('w:right'), '0')
ind.set(qn('w:firstLine'), '0')
pPr.append(ind)
# 设置右对齐
jc = OxmlElement('w:jc')
jc.set(qn('w:val'), 'right')
pPr.append(jc)
p.append(pPr)
r = OxmlElement('w:r')
rPr = OxmlElement('w:rPr')
color = OxmlElement('w:color')
if is_chinese:
color.set(qn('w:val'), '000000')
else:
color.set(qn('w:val'), '767171')
color.set(qn('w:themeColor'), 'background2')
color.set(qn('w:themeShade'), '80')
rPr.append(color)
r.append(rPr)
t = OxmlElement('w:t')
t.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
t.text = text
r.append(t)
p.append(r)
return p
def create_section_title_intervention(title_en: str, title_cn: str, is_main: bool = False):
"""创建板块标题
参考 Be.U+Wellness+Center功能医学健康报告&定制化方案-+Ming+Wang.docx
- 主标题Medical Intervention使用4二级-标题样式
- 区域标题(如 1) Vascular Protection...使用0中文正文样式加粗英文和中文用竖线分隔
格式1) Hormone-Centered Improvement Plan 生物同源性荷尔蒙调理
"""
if is_main:
# 主标题使用4二级-标题样式
combined = f"{title_en} {title_cn}"
p = OxmlElement('w:p')
pPr = OxmlElement('w:pPr')
pStyle = OxmlElement('w:pStyle')
pStyle.set(qn('w:val'), '4-') # 4二级-标题
pPr.append(pStyle)
# 显式清除所有缩进
ind = OxmlElement('w:ind')
ind.set(qn('w:left'), '0')
ind.set(qn('w:right'), '0')
ind.set(qn('w:firstLine'), '0')
pPr.append(ind)
p.append(pPr)
r = OxmlElement('w:r')
t = OxmlElement('w:t')
t.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
t.text = combined
r.append(t)
p.append(r)
return [p]
else:
# 区域标题英文和中文用竖线分隔使用0中文正文样式加粗
# 格式1) Hormone-Centered Improvement Plan 生物同源性荷尔蒙调理
combined = f"{title_en} {title_cn}"
p = OxmlElement('w:p')
pPr = OxmlElement('w:pPr')
pStyle = OxmlElement('w:pStyle')
pStyle.set(qn('w:val'), '0') # 0中文正文样式
pPr.append(pStyle)
# 显式清除所有缩进
ind = OxmlElement('w:ind')
ind.set(qn('w:left'), '0')
ind.set(qn('w:right'), '0')
ind.set(qn('w:firstLine'), '0')
pPr.append(ind)
# 段落级别加粗
rPr_para = OxmlElement('w:rPr')
b = OxmlElement('w:b')
rPr_para.append(b)
bCs = OxmlElement('w:bCs')
rPr_para.append(bCs)
pPr.append(rPr_para)
p.append(pPr)
r = OxmlElement('w:r')
rPr = OxmlElement('w:rPr')
# run级别加粗
b = OxmlElement('w:b')
rPr.append(b)
bCs = OxmlElement('w:bCs')
rPr.append(bCs)
r.append(rPr)
t = OxmlElement('w:t')
t.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
t.text = combined
r.append(t)
p.append(r)
return [p]
def create_subtitle_intervention(title_en: str, title_cn: str):
"""创建子标题(如 1.2 Sex-steroid / early perimenopause support 性激素与"早衰/围绝经提前"管理)
参考 Be.U+Wellness+Center功能医学健康报告&定制化方案-+Ming+Wang.docx
- 使用0中文正文样式加粗英文和中文用竖线分隔
格式1.2 Sex-steroid / early perimenopause support 性激素与"早衰/围绝经提前"管理
"""
combined = f"{title_en} {title_cn}"
p = OxmlElement('w:p')
pPr = OxmlElement('w:pPr')
pStyle = OxmlElement('w:pStyle')
pStyle.set(qn('w:val'), '0') # 0中文正文样式
pPr.append(pStyle)
# 显式清除所有缩进
ind = OxmlElement('w:ind')
ind.set(qn('w:left'), '0')
ind.set(qn('w:right'), '0')
ind.set(qn('w:firstLine'), '0')
pPr.append(ind)
# 段落级别加粗
rPr_para = OxmlElement('w:rPr')
b = OxmlElement('w:b')
rPr_para.append(b)
bCs = OxmlElement('w:bCs')
rPr_para.append(bCs)
pPr.append(rPr_para)
p.append(pPr)
r = OxmlElement('w:r')
rPr = OxmlElement('w:rPr')
# run级别加粗
b = OxmlElement('w:b')
rPr.append(b)
bCs = OxmlElement('w:bCs')
rPr.append(bCs)
r.append(rPr)
t = OxmlElement('w:t')
t.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
t.text = combined
r.append(t)
p.append(r)
return p
def create_page_break_paragraph():
"""创建分页符段落"""
p = OxmlElement('w:p')
r = OxmlElement('w:r')
br = OxmlElement('w:br')
br.set(qn('w:type'), 'page')
r.append(br)
p.append(r)
return p
def fill_medical_intervention_v2(doc, intervention_result: dict):
"""
将V2版本的医学干预建议填充到文档
查找 "Medical Intervention""Functional Medical Health Advice" 位置并替换
"""
if not intervention_result:
print(" 医学干预内容为空,跳过填充")
return
# 转换为sections格式
sections_data = convert_intervention_to_sections(intervention_result)
sections = sections_data.get('sections', [])
if not sections:
print(" 转换后的sections为空跳过填充")
return
body = doc.element.body
children = list(body)
# 仅查找 "Medical Intervention"(不再用 Functional Medical Health Advice 作为替代目标)
mi_pos = -1
for i, elem in enumerate(children):
text = ''.join(elem.itertext()).strip().lower()
if 'medical intervention' in text or '医学干预' in text:
mi_pos = i
print(f" 找到Medical Intervention位置: {i}")
break
# 结束边界关键词(包含 FHA 以避免覆盖功能医学健康建议区域)
end_keywords = ['functional medical health advice', '功能医学健康建议',
'功能医学检测档案', 'functional medical examination',
'客户功能医学检测档案', '尿液检测', 'urine detection', 'urine test']
if mi_pos >= 0:
# Medical Intervention 已存在,替换其内容(到 FHA 或 Client File 为止)
next_section_pos = len(children)
for i in range(mi_pos + 1, len(children)):
text = ''.join(children[i].itertext()).strip().lower()
if any(kw in text for kw in end_keywords):
next_section_pos = i
print(f" 找到下一区域位置: {i}")
break
# 删除 MI 到下一区域之间的所有内容
children = list(body)
elements_to_remove = []
# 检查MI前面的分页符
for check_pos in range(max(0, mi_pos - 3), mi_pos):
if check_pos < len(children):
prev_elem = children[check_pos]
br_elem = prev_elem.find('.//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}br')
if br_elem is not None:
break_type = br_elem.get('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}type')
if break_type == 'page':
elements_to_remove.append(prev_elem)
print(f" 删除Medical Intervention前的分页符 (位置 {check_pos})")
for i in range(mi_pos, min(next_section_pos, len(children))):
elem = children[i]
if elem.tag.endswith('}sectPr'):
continue
elements_to_remove.append(elem)
for elem in elements_to_remove:
try:
body.remove(elem)
except:
pass
if elements_to_remove:
print(f" 已删除 {len(elements_to_remove)} 个原有内容")
# 重新获取插入位置(在下一区域之前)
children = list(body)
insert_pos = 0
for i, elem in enumerate(children):
text = ''.join(elem.itertext()).strip().lower()
if any(kw in text for kw in end_keywords):
insert_pos = i
break
else:
# Medical Intervention 不存在,在 FHA 或 Client File 之前插入(不删除任何内容)
insert_pos = -1
for i, elem in enumerate(children):
text = ''.join(elem.itertext()).strip().lower()
if any(kw in text for kw in end_keywords):
insert_pos = i
print(f" Medical Intervention不存在在位置 {i} 前插入")
break
if insert_pos < 0:
print(" 未找到插入位置")
return
# 插入新内容
section_idx = 0
for section in sections:
title_en = section.get('title_en', '')
title_cn = section.get('title_cn', '')
paragraphs = section.get('paragraphs', [])
is_main = section.get('is_main_title', False)
is_signature = section.get('is_signature_section', False)
# 跳过签名部分的标题
if not is_signature and (title_en or title_cn):
# 只在第二个及之后的板块标题前插入空段落(第一个板块紧跟前面内容,不需要空段落)
if section_idx > 0:
empty_p = create_empty_paragraph_intervention()
body.insert(insert_pos, empty_p)
insert_pos += 1
# 板块标题
title_paragraphs = create_section_title_intervention(title_en, title_cn, is_main)
for title_p in title_paragraphs:
body.insert(insert_pos, title_p)
insert_pos += 1
section_idx += 1
# 段落内容
for para_idx, para in enumerate(paragraphs):
is_subtitle = para.get('is_subtitle', False)
is_formula = para.get('is_formula_title', False)
is_sig = para.get('is_signature', False)
is_intro = para.get('is_intro', False)
en_text = para.get('en', '')
cn_text = para.get('cn', '')
if is_intro:
# 模块总结样式 - 在区域标题后、子标题前,先英文后中文
if en_text:
p_en = create_formatted_paragraph_intervention(en_text, is_chinese=False)
body.insert(insert_pos, p_en)
insert_pos += 1
if cn_text:
p_cn = create_formatted_paragraph_intervention(cn_text, is_chinese=True)
body.insert(insert_pos, p_cn)
insert_pos += 1
elif is_subtitle or is_formula:
# 子标题样式 - 上面空一行,英文和中文在同一行,加粗
empty_p = create_empty_paragraph_intervention()
body.insert(insert_pos, empty_p)
insert_pos += 1
# 使用新的子标题函数,英文和中文在同一行
p_subtitle = create_subtitle_intervention(en_text, cn_text)
body.insert(insert_pos, p_subtitle)
insert_pos += 1
elif is_sig:
# 签名样式 - 右对齐,空一行后显示
empty_p = create_empty_paragraph_intervention()
body.insert(insert_pos, empty_p)
insert_pos += 1
# 获取日期
date_text = para.get('date', '')
if en_text:
p_en = create_signature_paragraph(en_text, is_chinese=False)
body.insert(insert_pos, p_en)
insert_pos += 1
if cn_text:
p_cn = create_signature_paragraph(cn_text, is_chinese=True)
body.insert(insert_pos, p_cn)
insert_pos += 1
if date_text:
p_date = create_signature_paragraph(date_text, is_chinese=True)
body.insert(insert_pos, p_date)
insert_pos += 1
else:
# 普通段落 - 不需要额外缩进
if en_text:
p_en = create_formatted_paragraph_intervention(en_text, is_chinese=False)
body.insert(insert_pos, p_en)
insert_pos += 1
if cn_text:
p_cn = create_formatted_paragraph_intervention(cn_text, is_chinese=True)
body.insert(insert_pos, p_cn)
insert_pos += 1
print(f" ✓ 已插入 {len(sections)} 个医学干预板块")
# 确保"功能医学健康建议"前面有分页符MI内容插入在FHA前需要在MI和FHA之间加分页符
children = list(body)
for i, elem in enumerate(children):
text = ''.join(elem.itertext()).strip()
text_lower = text.lower()
if ('functional medical health advice' in text_lower or
'功能医学健康建议' in text):
already_has_break = False
if i > 0:
prev_elem = children[i - 1]
br_elem = prev_elem.find('.//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}br')
if br_elem is not None:
break_type = br_elem.get('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}type')
if break_type == 'page':
already_has_break = True
if not already_has_break:
page_break = create_page_break_paragraph()
body.insert(i, page_break)
print(f" 已在'功能医学健康建议'前插入分页符")
break
# 确保"客户功能医学检测档案"前面有分页符
children = list(body)
for i, elem in enumerate(children):
text = ''.join(elem.itertext()).strip()
if '功能医学检测档案' in text or 'Functional Medical Examination File' in text:
# 检查前一个元素是否已经是分页符
already_has_break = False
if i > 0:
prev_elem = children[i - 1]
br_elem = prev_elem.find('.//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}br')
if br_elem is not None:
break_type = br_elem.get('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}type')
if break_type == 'page':
already_has_break = True
if not already_has_break:
# 在该元素前插入分页符
page_break = create_page_break_paragraph()
body.insert(i, page_break)
print(f" 已在'客户功能医学检测档案'前插入分页符")
break
# ============================================================
# 主入口函数
# ============================================================
def generate_and_fill_medical_intervention_v2(doc, abnormal_items: List[Dict], api_key: str, call_deepseek_api):
"""
生成并填充医学干预建议V2版本
替代原有的 generate_functional_health_advice + fill_functional_health_advice_section
"""
if not api_key:
print(" ⚠️ 未提供DeepSeek API Key跳过医学干预建议生成")
return None
if not abnormal_items:
print(" ⚠️ 没有异常指标,跳过医学干预建议生成")
return None
print("\n" + "=" * 60)
print("医学干预建议 V2")
print("=" * 60)
# 生成内容
intervention_result = generate_medical_intervention_v2(abnormal_items, api_key, call_deepseek_api)
if intervention_result:
# 填充到文档
print("\n 📝 正在填充医学干预建议...")
fill_medical_intervention_v2(doc, intervention_result)
print(" ✓ 医学干预建议完成")
else:
print(" ✗ 医学干预建议生成失败")
return intervention_result
# ============================================================
# 测试
# ============================================================
if __name__ == '__main__':
# 测试分类功能
test_items = [
{'abb': 'TSH', 'name': '促甲状腺激素', 'result': '16.879', 'unit': 'μIU/mL', 'point': '', 'module': 'Thyroid Function'},
{'abb': 'AMH', 'name': '抗缪勒管激素', 'result': '0.17', 'unit': 'ng/mL', 'point': '', 'module': 'Female Hormone'},
{'abb': 'CRP', 'name': 'C反应蛋白', 'result': '5.2', 'unit': 'mg/L', 'point': '', 'module': 'Inflammatory Reaction'},
{'abb': 'Hb', 'name': '血红蛋白', 'result': '105', 'unit': 'g/L', 'point': '', 'module': 'Complete Blood Count'},
{'abb': 'DHEAS', 'name': '硫酸脱氢表雄酮', 'result': '45', 'unit': 'μg/dL', 'point': '', 'module': 'Female Hormone'},
{'abb': 'FBS', 'name': '空腹血糖', 'result': '6.5', 'unit': 'mmol/L', 'point': '', 'module': 'Blood Sugar'},
{'abb': 'TC', 'name': '总胆固醇', 'result': '6.2', 'unit': 'mmol/L', 'point': '', 'module': 'Lipid Profile'},
]
categories = categorize_abnormal_items(test_items)
print("=" * 60)
print("异常指标分类测试24模块分类")
print("=" * 60)
for module_name, items in categories.items():
if items:
print(f"\n{module_name}: {len(items)}")
for item in items:
print(f" - {item['name']} ({item['abb']})")
print("\n" + "=" * 60)
print("问题摘要:")
print("=" * 60)
print(build_problem_summary(categories))