from typing import List, Dict, Any, Union import networkx as nx import logging import re from .utils.unit_parser import parse_time_input, parse_volume_input logger = logging.getLogger(__name__) def debug_print(message): """调试输出""" logger.info(f"[WASH_SOLID] {message}") def find_solvent_source(G: nx.DiGraph, solvent: str) -> str: """查找溶剂源(精简版)""" debug_print(f"🔍 查找溶剂源: {solvent}") # 简化搜索列表 search_patterns = [ f"flask_{solvent}", f"bottle_{solvent}", f"reagent_{solvent}", "liquid_reagent_bottle_1", "flask_1", "solvent_bottle" ] for pattern in search_patterns: if pattern in G.nodes(): debug_print(f"🎉 找到溶剂源: {pattern}") return pattern debug_print(f"⚠️ 使用默认溶剂源: flask_{solvent}") return f"flask_{solvent}" def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str: """查找滤液容器(精简版)""" debug_print(f"🔍 查找滤液容器: {filtrate_vessel}") # 如果指定了且存在,直接使用 if filtrate_vessel and filtrate_vessel in G.nodes(): debug_print(f"✅ 使用指定容器: {filtrate_vessel}") return filtrate_vessel # 简化搜索列表 default_vessels = ["waste_workup", "filtrate_vessel", "flask_1", "collection_bottle_1"] for vessel in default_vessels: if vessel in G.nodes(): debug_print(f"🎉 找到滤液容器: {vessel}") return vessel debug_print(f"⚠️ 使用默认滤液容器: waste_workup") return "waste_workup" def extract_vessel_id(vessel: Union[str, dict]) -> str: """ 从vessel参数中提取vessel_id Args: vessel: vessel字典或vessel_id字符串 Returns: str: vessel_id """ if isinstance(vessel, dict): vessel_id = list(vessel.values())[0].get("id", "") debug_print(f"🔧 从vessel字典提取ID: {vessel_id}") return vessel_id elif isinstance(vessel, str): debug_print(f"🔧 vessel参数为字符串: {vessel}") return vessel else: debug_print(f"⚠️ 无效的vessel参数类型: {type(vessel)}") return "" def get_vessel_display_info(vessel: Union[str, dict]) -> str: """ 获取容器的显示信息(用于日志) Args: vessel: vessel字典或vessel_id字符串 Returns: str: 显示信息 """ if isinstance(vessel, dict): vessel_id = vessel.get("id", "unknown") vessel_name = vessel.get("name", "") if vessel_name: return f"{vessel_id} ({vessel_name})" else: return vessel_id else: return str(vessel) def get_vessel_liquid_volume(vessel: dict) -> float: """ 获取容器中的液体体积 - 支持vessel字典 Args: vessel: 容器字典 Returns: float: 液体体积(mL) """ if not vessel or "data" not in vessel: debug_print(f"⚠️ 容器数据为空,返回 0.0mL") return 0.0 vessel_data = vessel["data"] vessel_id = vessel.get("id", "unknown") debug_print(f"🔍 读取容器 '{vessel_id}' 体积数据: {vessel_data}") # 检查liquid_volume字段 if "liquid_volume" in vessel_data: liquid_volume = vessel_data["liquid_volume"] # 处理列表格式 if isinstance(liquid_volume, list): if len(liquid_volume) > 0: volume = liquid_volume[0] if isinstance(volume, (int, float)): debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (列表格式)") return float(volume) # 处理直接数值格式 elif isinstance(liquid_volume, (int, float)): debug_print(f"✅ 容器 '{vessel_id}' 体积: {liquid_volume}mL (数值格式)") return float(liquid_volume) # 检查其他可能的体积字段 volume_keys = ['current_volume', 'total_volume', 'volume'] for key in volume_keys: if key in vessel_data: try: volume = float(vessel_data[key]) if volume > 0: debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (字段: {key})") return volume except (ValueError, TypeError): continue debug_print(f"⚠️ 无法获取容器 '{vessel_id}' 的体积,返回默认值 0.0mL") return 0.0 def update_vessel_volume(vessel: dict, G: nx.DiGraph, new_volume: float, description: str = "") -> None: """ 更新容器体积(同时更新vessel字典和图节点) Args: vessel: 容器字典 G: 网络图 new_volume: 新体积 description: 更新描述 """ vessel_id = vessel.get("id", "unknown") if description: debug_print(f"🔧 更新容器体积 - {description}") # 更新vessel字典中的体积 if "data" in vessel: if "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] if isinstance(current_volume, list): if len(current_volume) > 0: vessel["data"]["liquid_volume"][0] = new_volume else: vessel["data"]["liquid_volume"] = [new_volume] else: vessel["data"]["liquid_volume"] = new_volume else: vessel["data"]["liquid_volume"] = new_volume else: vessel["data"] = {"liquid_volume": new_volume} # 同时更新图中的容器数据 if vessel_id in G.nodes(): if 'data' not in G.nodes[vessel_id]: G.nodes[vessel_id]['data'] = {} vessel_node_data = G.nodes[vessel_id]['data'] current_node_volume = vessel_node_data.get('liquid_volume', 0.0) if isinstance(current_node_volume, list): if len(current_node_volume) > 0: G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume else: G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume] else: G.nodes[vessel_id]['data']['liquid_volume'] = new_volume debug_print(f"📊 容器 '{vessel_id}' 体积已更新为: {new_volume:.2f}mL") def generate_wash_solid_protocol( G: nx.DiGraph, vessel: Union[str, dict], # 🔧 修改:支持vessel字典 solvent: str, volume: Union[float, str] = "50", filtrate_vessel: Union[str, dict] = "", # 🔧 修改:支持vessel字典 temp: float = 25.0, stir: bool = False, stir_speed: float = 0.0, time: Union[str, float] = "0", repeats: int = 1, volume_spec: str = "", repeats_spec: str = "", mass: str = "", event: str = "", **kwargs ) -> List[Dict[str, Any]]: """ 生成固体清洗协议 - 支持vessel字典和体积运算 Args: G: 有向图,节点为设备和容器,边为流体管道 vessel: 清洗容器字典(从XDL传入)或容器ID字符串 solvent: 清洗溶剂名称 volume: 溶剂体积(每次清洗) filtrate_vessel: 滤液收集容器字典或容器ID字符串 temp: 清洗温度(°C) stir: 是否搅拌 stir_speed: 搅拌速度(RPM) time: 搅拌时间 repeats: 清洗重复次数 volume_spec: 体积规格(small/medium/large) repeats_spec: 重复次数规格(few/several/many) mass: 固体质量(用于计算溶剂用量) event: 事件描述 **kwargs: 其他可选参数 Returns: List[Dict[str, Any]]: 固体清洗操作的动作序列 """ # 🔧 核心修改:从vessel参数中提取vessel_id vessel_id = extract_vessel_id(vessel) vessel_display = get_vessel_display_info(vessel) # 🔧 处理filtrate_vessel参数 filtrate_vessel_id = extract_vessel_id(filtrate_vessel) if filtrate_vessel else "" debug_print("🧼" * 20) debug_print("🚀 开始生成固体清洗协议(支持vessel字典和体积运算)✨") debug_print(f"📝 输入参数:") debug_print(f" 🥽 vessel: {vessel_display} (ID: {vessel_id})") debug_print(f" 🧪 solvent: {solvent}") debug_print(f" 💧 volume: {volume}") debug_print(f" 🗑️ filtrate_vessel: {filtrate_vessel_id}") debug_print(f" ⏰ time: {time}") debug_print(f" 🔄 repeats: {repeats}") debug_print("🧼" * 20) # 🔧 新增:记录清洗前的容器状态 debug_print("🔍 记录清洗前容器状态...") if isinstance(vessel, dict): original_volume = get_vessel_liquid_volume(vessel) debug_print(f"📊 清洗前液体体积: {original_volume:.2f}mL") else: original_volume = 0.0 debug_print(f"📊 vessel为字符串格式,无法获取体积信息") # 📋 快速验证 if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id debug_print("❌ 容器验证失败! 😱") raise ValueError("vessel 参数无效") if not solvent: debug_print("❌ 溶剂不能为空! 😱") raise ValueError("solvent 参数不能为空") debug_print("✅ 基础验证通过 🎯") # 🔄 参数解析 debug_print("📍 步骤1: 参数解析... ⚡") final_volume = parse_volume_input(volume, volume_spec, mass) final_time = parse_time_input(time) # 重复次数处理(简化) if repeats_spec: spec_map = {'few': 2, 'several': 3, 'many': 4, 'thorough': 5} final_repeats = next((v for k, v in spec_map.items() if k in repeats_spec.lower()), repeats) else: final_repeats = max(1, min(repeats, 5)) # 限制1-5次 # 🕐 模拟时间优化 debug_print(" ⏱️ 模拟时间优化...") original_time = final_time if final_time > 60.0: final_time = 60.0 # 限制最长60秒 debug_print(f" 🎮 时间优化: {original_time}s → {final_time}s ⚡") # 参数修正 temp = max(25.0, min(temp, 80.0)) # 温度范围25-80°C stir_speed = max(0.0, min(stir_speed, 300.0)) if stir else 0.0 # 速度范围0-300 debug_print(f"🎯 最终参数: 体积={final_volume}mL, 时间={final_time}s, 重复={final_repeats}次") # 🔍 查找设备 debug_print("📍 步骤2: 查找设备... 🔍") try: solvent_source = find_solvent_source(G, solvent) actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel_id) debug_print(f"🎉 设备配置完成 ✨") debug_print(f" 🧪 溶剂源: {solvent_source}") debug_print(f" 🗑️ 滤液容器: {actual_filtrate_vessel}") except Exception as e: debug_print(f"❌ 设备查找失败: {str(e)} 😭") raise ValueError(f"设备查找失败: {str(e)}") # 🚀 生成动作序列 debug_print("📍 步骤3: 生成清洗动作... 🧼") action_sequence = [] # 🔧 新增:体积变化跟踪变量 current_volume = original_volume total_solvent_used = 0.0 for cycle in range(final_repeats): debug_print(f" 🔄 第{cycle+1}/{final_repeats}次清洗...") # 1. 转移溶剂 try: from .pump_protocol import generate_pump_protocol_with_rinsing debug_print(f" 💧 添加溶剂: {final_volume}mL {solvent}") transfer_actions = generate_pump_protocol_with_rinsing( G=G, from_vessel=solvent_source, to_vessel=vessel_id, # 🔧 使用 vessel_id volume=final_volume, amount="", time=0.0, viscous=False, rinsing_solvent="", rinsing_volume=0.0, rinsing_repeats=0, solid=False, flowrate=2.5, transfer_flowrate=0.5 ) if transfer_actions: action_sequence.extend(transfer_actions) debug_print(f" ✅ 转移动作: {len(transfer_actions)}个 🚚") # 🔧 新增:更新体积 - 添加溶剂后 current_volume += final_volume total_solvent_used += final_volume if isinstance(vessel, dict): update_vessel_volume(vessel, G, current_volume, f"第{cycle+1}次清洗添加{final_volume}mL溶剂后") except Exception as e: debug_print(f" ❌ 转移失败: {str(e)} 😞") # 2. 搅拌(如果需要) if stir and final_time > 0: debug_print(f" 🌪️ 搅拌: {final_time}s @ {stir_speed}RPM") stir_action = { "device_id": "stirrer_1", "action_name": "stir", "action_kwargs": { "vessel": {"id": vessel_id}, # 🔧 使用 vessel_id "time": str(time), "stir_time": final_time, "stir_speed": stir_speed, "settling_time": 10.0 # 🕐 缩短沉降时间 } } action_sequence.append(stir_action) debug_print(f" ✅ 搅拌动作: {final_time}s, {stir_speed}RPM 🌪️") # 3. 过滤 debug_print(f" 🌊 过滤到: {actual_filtrate_vessel}") filter_action = { "device_id": "filter_1", "action_name": "filter", "action_kwargs": { "vessel": {"id": vessel_id}, # 🔧 使用 vessel_id "filtrate_vessel": actual_filtrate_vessel, "temp": temp, "volume": final_volume } } action_sequence.append(filter_action) debug_print(f" ✅ 过滤动作: → {actual_filtrate_vessel} 🌊") # 🔧 新增:更新体积 - 过滤后(液体被滤除) # 假设滤液完全被移除,固体残留在容器中 filtered_volume = current_volume * 0.9 # 假设90%的液体被过滤掉 current_volume = current_volume - filtered_volume if isinstance(vessel, dict): update_vessel_volume(vessel, G, current_volume, f"第{cycle+1}次清洗过滤后") # 4. 等待(缩短时间) wait_time = 5.0 # 🕐 缩短等待时间:10s → 5s action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": wait_time} }) debug_print(f" ✅ 等待: {wait_time}s ⏰") # 🔧 新增:清洗完成后的最终状态报告 if isinstance(vessel, dict): final_volume_vessel = get_vessel_liquid_volume(vessel) else: final_volume_vessel = current_volume # 🎊 总结 debug_print("🧼" * 20) debug_print(f"🎉 固体清洗协议生成完成! ✨") debug_print(f"📊 协议统计:") debug_print(f" 📋 总动作数: {len(action_sequence)} 个") debug_print(f" 🥽 清洗容器: {vessel_display}") debug_print(f" 🧪 使用溶剂: {solvent}") debug_print(f" 💧 单次体积: {final_volume}mL") debug_print(f" 🔄 清洗次数: {final_repeats}次") debug_print(f" 💧 总溶剂用量: {total_solvent_used:.2f}mL") debug_print(f"📊 体积变化统计:") debug_print(f" - 清洗前体积: {original_volume:.2f}mL") debug_print(f" - 清洗后体积: {final_volume_vessel:.2f}mL") debug_print(f" - 溶剂总用量: {total_solvent_used:.2f}mL") debug_print(f"⏱️ 预计总时间: {(final_time + 5) * final_repeats / 60:.1f} 分钟") debug_print("🧼" * 20) return action_sequence # 🔧 新增:便捷函数 def wash_with_water(G: nx.DiGraph, vessel: Union[str, dict], volume: Union[float, str] = "50", repeats: int = 2) -> List[Dict[str, Any]]: """用水清洗固体""" vessel_display = get_vessel_display_info(vessel) debug_print(f"💧 水洗固体: {vessel_display} ({repeats} 次)") return generate_wash_solid_protocol(G, vessel, "water", volume=volume, repeats=repeats) def wash_with_ethanol(G: nx.DiGraph, vessel: Union[str, dict], volume: Union[float, str] = "30", repeats: int = 1) -> List[Dict[str, Any]]: """用乙醇清洗固体""" vessel_display = get_vessel_display_info(vessel) debug_print(f"🍺 乙醇洗固体: {vessel_display} ({repeats} 次)") return generate_wash_solid_protocol(G, vessel, "ethanol", volume=volume, repeats=repeats) def wash_with_acetone(G: nx.DiGraph, vessel: Union[str, dict], volume: Union[float, str] = "25", repeats: int = 1) -> List[Dict[str, Any]]: """用丙酮清洗固体""" vessel_display = get_vessel_display_info(vessel) debug_print(f"💨 丙酮洗固体: {vessel_display} ({repeats} 次)") return generate_wash_solid_protocol(G, vessel, "acetone", volume=volume, repeats=repeats) def wash_with_ether(G: nx.DiGraph, vessel: Union[str, dict], volume: Union[float, str] = "40", repeats: int = 2) -> List[Dict[str, Any]]: """用乙醚清洗固体""" vessel_display = get_vessel_display_info(vessel) debug_print(f"🌬️ 乙醚洗固体: {vessel_display} ({repeats} 次)") return generate_wash_solid_protocol(G, vessel, "diethyl_ether", volume=volume, repeats=repeats) def wash_with_cold_solvent(G: nx.DiGraph, vessel: Union[str, dict], solvent: str, volume: Union[float, str] = "30", repeats: int = 1) -> List[Dict[str, Any]]: """用冷溶剂清洗固体""" vessel_display = get_vessel_display_info(vessel) debug_print(f"❄️ 冷{solvent}洗固体: {vessel_display} ({repeats} 次)") return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, temp=5.0, repeats=repeats) def wash_with_hot_solvent(G: nx.DiGraph, vessel: Union[str, dict], solvent: str, volume: Union[float, str] = "50", repeats: int = 1) -> List[Dict[str, Any]]: """用热溶剂清洗固体""" vessel_display = get_vessel_display_info(vessel) debug_print(f"🔥 热{solvent}洗固体: {vessel_display} ({repeats} 次)") return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, temp=60.0, repeats=repeats) def wash_with_stirring(G: nx.DiGraph, vessel: Union[str, dict], solvent: str, volume: Union[float, str] = "50", stir_time: Union[str, float] = "5 min", repeats: int = 1) -> List[Dict[str, Any]]: """带搅拌的溶剂清洗""" vessel_display = get_vessel_display_info(vessel) debug_print(f"🌪️ 搅拌清洗: {vessel_display} with {solvent} ({repeats} 次)") return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, stir=True, stir_speed=200.0, time=stir_time, repeats=repeats) def thorough_wash(G: nx.DiGraph, vessel: Union[str, dict], solvent: str, volume: Union[float, str] = "50") -> List[Dict[str, Any]]: """彻底清洗(多次重复)""" vessel_display = get_vessel_display_info(vessel) debug_print(f"🔄 彻底清洗: {vessel_display} with {solvent} (5 次)") return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, repeats=5) def quick_rinse(G: nx.DiGraph, vessel: Union[str, dict], solvent: str, volume: Union[float, str] = "20") -> List[Dict[str, Any]]: """快速冲洗(单次,小体积)""" vessel_display = get_vessel_display_info(vessel) debug_print(f"⚡ 快速冲洗: {vessel_display} with {solvent}") return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, repeats=1) def sequential_wash(G: nx.DiGraph, vessel: Union[str, dict], solvents: list, volume: Union[float, str] = "40") -> List[Dict[str, Any]]: """连续多溶剂清洗""" vessel_display = get_vessel_display_info(vessel) debug_print(f"📝 连续清洗: {vessel_display} with {' → '.join(solvents)}") action_sequence = [] for solvent in solvents: wash_actions = generate_wash_solid_protocol(G, vessel, solvent, volume=volume, repeats=1) action_sequence.extend(wash_actions) return action_sequence # 测试函数 def test_wash_solid_protocol(): """测试固体清洗协议""" debug_print("🧪 === WASH SOLID PROTOCOL 测试 === ✨") # 测试vessel参数处理 debug_print("🔧 测试vessel参数处理...") # 测试字典格式 vessel_dict = {"id": "filter_flask_1", "name": "过滤瓶1", "data": {"liquid_volume": 25.0}} vessel_id = extract_vessel_id(vessel_dict) vessel_display = get_vessel_display_info(vessel_dict) volume = get_vessel_liquid_volume(vessel_dict) debug_print(f" 字典格式: {vessel_dict}") debug_print(f" → ID: {vessel_id}, 显示: {vessel_display}, 体积: {volume}mL") # 测试字符串格式 vessel_str = "filter_flask_2" vessel_id = extract_vessel_id(vessel_str) vessel_display = get_vessel_display_info(vessel_str) debug_print(f" 字符串格式: {vessel_str}") debug_print(f" → ID: {vessel_id}, 显示: {vessel_display}") debug_print("✅ 测试完成 🎉") if __name__ == "__main__": test_wash_solid_protocol()