protocol完整修复版本& bump version to 0.9.10

This commit is contained in:
KCFeng425
2025-07-10 16:48:09 +08:00
parent d82ccd5cf1
commit 7b93332bf5
25 changed files with 3760 additions and 1843 deletions

View File

@@ -1,58 +1,238 @@
import networkx as nx
from typing import List, Dict, Any
import re
import logging
from typing import List, Dict, Any, Union
from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
print(f"[ADD] {message}", flush=True)
logger.info(f"[ADD] {message}")
def parse_volume_input(volume_input: Union[str, float]) -> float:
"""
解析体积输入,支持带单位的字符串
Args:
volume_input: 体积输入(如 "2.7 mL", "2.67 mL", "?", 10.0
Returns:
float: 体积(毫升)
"""
if isinstance(volume_input, (int, float)):
return float(volume_input)
if not volume_input or not str(volume_input).strip():
return 0.0
volume_str = str(volume_input).lower().strip()
debug_print(f"解析体积输入: '{volume_str}'")
# 处理未知体积
if volume_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_volume = 10.0 # 默认10mL
debug_print(f"检测到未知体积,使用默认值: {default_volume}mL")
return default_volume
# 移除空格并提取数字和单位
volume_clean = re.sub(r'\s+', '', volume_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean)
if not match:
debug_print(f"⚠️ 无法解析体积: '{volume_str}'使用默认值10mL")
return 10.0
value = float(match.group(1))
unit = match.group(2) or 'ml' # 默认单位为毫升
# 转换为毫升
if unit in ['l', 'liter']:
volume = value * 1000.0 # L -> mL
elif unit in ['μl', 'ul', 'microliter']:
volume = value / 1000.0 # μL -> mL
else: # ml, milliliter 或默认
volume = value # 已经是mL
debug_print(f"体积转换: {value}{unit}{volume}mL")
return volume
def parse_mass_input(mass_input: Union[str, float]) -> float:
"""
解析质量输入,支持带单位的字符串
Args:
mass_input: 质量输入(如 "19.3 g", "4.5 g", 2.5
Returns:
float: 质量(克)
"""
if isinstance(mass_input, (int, float)):
return float(mass_input)
if not mass_input or not str(mass_input).strip():
return 0.0
mass_str = str(mass_input).lower().strip()
debug_print(f"解析质量输入: '{mass_str}'")
# 移除空格并提取数字和单位
mass_clean = re.sub(r'\s+', '', mass_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(g|mg|kg|gram|milligram|kilogram)?', mass_clean)
if not match:
debug_print(f"⚠️ 无法解析质量: '{mass_str}'返回0.0g")
return 0.0
value = float(match.group(1))
unit = match.group(2) or 'g' # 默认单位为克
# 转换为克
if unit in ['mg', 'milligram']:
mass = value / 1000.0 # mg -> g
elif unit in ['kg', 'kilogram']:
mass = value * 1000.0 # kg -> g
else: # g, gram 或默认
mass = value # 已经是g
debug_print(f"质量转换: {value}{unit}{mass}g")
return mass
def parse_time_input(time_input: Union[str, float]) -> float:
"""
解析时间输入,支持带单位的字符串
Args:
time_input: 时间输入(如 "1 h", "20 min", "30 s", 60.0
Returns:
float: 时间(秒)
"""
if isinstance(time_input, (int, float)):
return float(time_input)
if not time_input or not str(time_input).strip():
return 0.0
time_str = str(time_input).lower().strip()
debug_print(f"解析时间输入: '{time_str}'")
# 处理未知时间
if time_str in ['?', 'unknown', 'tbd']:
default_time = 60.0 # 默认1分钟
debug_print(f"检测到未知时间,使用默认值: {default_time}s")
return default_time
# 移除空格并提取数字和单位
time_clean = re.sub(r'\s+', '', time_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(s|sec|second|min|minute|h|hr|hour|d|day)?', time_clean)
if not match:
debug_print(f"⚠️ 无法解析时间: '{time_str}'返回0s")
return 0.0
value = float(match.group(1))
unit = match.group(2) or 's' # 默认单位为秒
# 转换为秒
if unit in ['min', 'minute']:
time_sec = value * 60.0 # min -> s
elif unit in ['h', 'hr', 'hour']:
time_sec = value * 3600.0 # h -> s
elif unit in ['d', 'day']:
time_sec = value * 86400.0 # d -> s
else: # s, sec, second 或默认
time_sec = value # 已经是s
debug_print(f"时间转换: {value}{unit}{time_sec}s")
return time_sec
def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
"""增强版试剂容器查找,支持固体和液体"""
print(f"ADD_PROTOCOL: 查找试剂 '{reagent}' 的容器...")
debug_print(f"查找试剂 '{reagent}' 的容器...")
# 1. 直接名称匹配
# 🔧 方法1直接搜索 data.reagent_name 和 config.reagent
for node in G.nodes():
node_data = G.nodes[node].get('data', {})
node_type = G.nodes[node].get('type', '')
config_data = G.nodes[node].get('config', {})
# 只搜索容器类型的节点
if node_type == 'container':
reagent_name = node_data.get('reagent_name', '').lower()
config_reagent = config_data.get('reagent', '').lower()
# 精确匹配
if reagent_name == reagent.lower() or config_reagent == reagent.lower():
debug_print(f"✅ 通过reagent字段找到容器: {node}")
return node
# 模糊匹配
if (reagent.lower() in reagent_name and reagent_name) or \
(reagent.lower() in config_reagent and config_reagent):
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node}")
return node
# 🔧 方法2常见的容器命名规则
reagent_clean = reagent.lower().replace(' ', '_').replace('-', '_')
possible_names = [
reagent,
f"flask_{reagent}",
f"bottle_{reagent}",
f"vessel_{reagent}",
f"{reagent}_flask",
f"{reagent}_bottle",
f"reagent_{reagent}",
f"reagent_bottle_{reagent}",
f"solid_reagent_bottle_{reagent}", # 🔧 添加固体试剂瓶匹配
reagent_clean,
f"flask_{reagent_clean}",
f"bottle_{reagent_clean}",
f"vessel_{reagent_clean}",
f"{reagent_clean}_flask",
f"{reagent_clean}_bottle",
f"reagent_{reagent_clean}",
f"reagent_bottle_{reagent_clean}",
f"solid_reagent_bottle_{reagent_clean}",
f"reagent_bottle_1", # 通用试剂瓶
f"reagent_bottle_2",
f"reagent_bottle_3"
]
for name in possible_names:
if name in G.nodes():
print(f"ADD_PROTOCOL: 找到容器: {name}")
return name
node_type = G.nodes[name].get('type', '')
if node_type == 'container':
debug_print(f"✅ 通过命名规则找到容器: {name}")
return name
# 2. 模糊匹配 - 检查容器数据
# 🔧 方法3节点名称模糊匹配
for node_id in G.nodes():
node_data = G.nodes[node_id]
if node_data.get('type') == 'container':
# 检查配置中的试剂名称
config_reagent = node_data.get('config', {}).get('reagent', '')
data_reagent = node_data.get('data', {}).get('reagent_name', '')
# 名称匹配
if (config_reagent.lower() == reagent.lower() or
data_reagent.lower() == reagent.lower() or
reagent.lower() in node_id.lower()):
print(f"ADD_PROTOCOL: 模糊匹配到容器: {node_id}")
# 检查节点名称是否包含试剂名称
if reagent_clean in node_id.lower():
debug_print(f"✅ 通过节点名称模糊匹配到容器: {node_id}")
return node_id
# 液体类型匹配(保持原有逻辑)
# 检查液体类型匹配
vessel_data = node_data.get('data', {})
liquids = vessel_data.get('liquid', [])
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
if liquid_type.lower() == reagent.lower():
print(f"ADD_PROTOCOL: 液体类型匹配到容器: {node_id}")
debug_print(f"✅ 通过液体类型匹配到容器: {node_id}")
return node_id
# 🔧 方法4使用第一个试剂瓶作为备选
for node_id in G.nodes():
node_data = G.nodes[node_id]
if (node_data.get('type') == 'container' and
('reagent' in node_id.lower() or 'bottle' in node_id.lower())):
debug_print(f"⚠️ 未找到专用容器,使用备选试剂瓶: {node_id}")
return node_id
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器")
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找连接到指定容器的搅拌器"""
stirrer_nodes = []
@@ -64,40 +244,41 @@ def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
# 查找连接到容器的搅拌器
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
print(f"ADD_PROTOCOL: 找到连接的搅拌器: {stirrer}")
debug_print(f"找到连接的搅拌器: {stirrer}")
return stirrer
# 返回第一个搅拌器
if stirrer_nodes:
print(f"ADD_PROTOCOL: 使用第一个搅拌器: {stirrer_nodes[0]}")
debug_print(f"使用第一个搅拌器: {stirrer_nodes[0]}")
return stirrer_nodes[0]
return None
return ""
def find_solid_dispenser(G: nx.DiGraph) -> str:
"""查找固体加样器"""
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'solid_dispenser' in node_class:
print(f"ADD_PROTOCOL: 找到固体加样器: {node}")
if 'solid_dispenser' in node_class or 'dispenser' in node_class:
debug_print(f"找到固体加样器: {node}")
return node
return None
debug_print("⚠️ 未找到固体加样器")
return ""
def generate_add_protocol(
G: nx.DiGraph,
vessel: str,
reagent: str,
volume: float = 0.0,
mass: float = 0.0,
# 🔧 修复:所有参数都用 Union 类型,支持字符串和数值
volume: Union[str, float] = 0.0,
mass: Union[str, float] = 0.0,
amount: str = "",
time: float = 0.0,
time: Union[str, float] = 0.0,
stir: bool = False,
stir_speed: float = 300.0,
viscous: bool = False,
purpose: str = "添加试剂",
# 新增XDL参数
# XDL扩展参数
mol: str = "",
event: str = "",
rate_spec: str = "",
@@ -106,133 +287,191 @@ def generate_add_protocol(
**kwargs
) -> List[Dict[str, Any]]:
"""
生成添加试剂协议
生成添加试剂协议 - 修复版
智能判断
- 有 mass 或 mol → 固体加样器
- 有 volume → 液体转移
- 都没有 → 默认液体 1mL
支持所有XDL参数和单位
- volume: "2.7 mL", "2.67 mL", "?" 或数值
- mass: "19.3 g", "4.5 g" 或数值
- time: "1 h", "20 min" 或数值(秒)
- mol: "0.28 mol", "16.2 mmol", "25.2 mmol"
- rate_spec: "portionwise", "dropwise"
- event: "A", "B"
- equiv: "1.1"
- ratio: "?", "1:1"
"""
print(f"ADD_PROTOCOL: 添加 {reagent}{vessel}")
print(f" - 体积: {volume} mL, 质量: {mass} g, 摩尔: {mol}")
print(f" - 时间: {time} s, 事件: {event}, 速率: {rate_spec}")
# 1. 验证容器
if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在")
# 2. 判断固体 vs 液体
is_solid = (mass > 0 or mol.strip() != "")
debug_print("=" * 60)
debug_print("开始生成添加试剂协议")
debug_print(f"原始参数:")
debug_print(f" - vessel: '{vessel}'")
debug_print(f" - reagent: '{reagent}'")
debug_print(f" - volume: {volume} (类型: {type(volume)})")
debug_print(f" - mass: {mass} (类型: {type(mass)})")
debug_print(f" - time: {time} (类型: {type(time)})")
debug_print(f" - mol: '{mol}'")
debug_print(f" - event: '{event}'")
debug_print(f" - rate_spec: '{rate_spec}'")
debug_print("=" * 60)
action_sequence = []
if is_solid:
# === 固体加样路径 ===
print(f"ADD_PROTOCOL: 使用固体加样器")
solid_dispenser = find_solid_dispenser(G)
if not solid_dispenser:
raise ValueError("未找到固体加样器")
# 启动搅拌(如果需要)
if stir:
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"准备添加固体 {reagent}"
}
})
# 等待搅拌稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 3}
})
# 固体加样
action_sequence.append({
"device_id": solid_dispenser,
"action_name": "add_solid",
"action_kwargs": {
"vessel": vessel,
"reagent": reagent,
"mass": str(mass) if mass > 0 else "",
"mol": mol,
"purpose": purpose,
"event": event
}
})
else:
# === 液体转移路径 ===
print(f"ADD_PROTOCOL: 使用液体转移")
# 默认体积
if volume <= 0:
volume = 1.0
print(f"ADD_PROTOCOL: 使用默认体积 1mL")
# 查找试剂容器
try:
reagent_vessel = find_reagent_vessel(G, reagent)
except ValueError as e:
# 🔧 更友好的错误提示
available_reagents = []
for node_id in G.nodes():
node_data = G.nodes[node_id]
if node_data.get('type') == 'container':
config_reagent = node_data.get('config', {}).get('reagent', '')
data_reagent = node_data.get('data', {}).get('reagent_name', '')
if config_reagent:
available_reagents.append(f"{node_id}({config_reagent})")
elif data_reagent:
available_reagents.append(f"{node_id}({data_reagent})")
# === 参数验证 ===
debug_print("步骤1: 参数验证...")
if not vessel:
raise ValueError("vessel 参数不能为空")
if not reagent:
raise ValueError("reagent 参数不能为空")
if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
debug_print("✅ 基本参数验证通过")
# === 🔧 关键修复:参数解析 ===
debug_print("步骤2: 参数解析...")
# 解析各种参数为数值
final_volume = parse_volume_input(volume)
final_mass = parse_mass_input(mass)
final_time = parse_time_input(time)
debug_print(f"解析结果:")
debug_print(f" - 体积: {final_volume}mL")
debug_print(f" - 质量: {final_mass}g")
debug_print(f" - 时间: {final_time}s")
debug_print(f" - 摩尔: '{mol}'")
debug_print(f" - 事件: '{event}'")
debug_print(f" - 速率: '{rate_spec}'")
# === 判断添加类型 ===
debug_print("步骤3: 判断添加类型...")
# 🔧 修复:现在使用解析后的数值进行比较
is_solid = (final_mass > 0 or (mol and mol.strip() != ""))
is_liquid = (final_volume > 0)
if not is_solid and not is_liquid:
# 默认为液体10mL
is_liquid = True
final_volume = 10.0
debug_print("⚠️ 未指定体积或质量默认为10mL液体")
debug_print(f"添加类型: {'固体' if is_solid else '液体'}")
# === 执行添加流程 ===
debug_print("步骤4: 执行添加流程...")
try:
if is_solid:
# === 固体添加路径 ===
debug_print(f"使用固体添加路径")
error_msg = f"找不到试剂 '{reagent}'。可用试剂: {', '.join(available_reagents)}"
print(f"ADD_PROTOCOL: {error_msg}")
raise ValueError(error_msg)
# 启动搅拌
if stir:
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
solid_dispenser = find_solid_dispenser(G)
if solid_dispenser:
# 启动搅拌
if stir:
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"准备添加固体 {reagent}"
}
})
# 等待搅拌稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 3}
})
# 固体加样
add_kwargs = {
"vessel": vessel,
"reagent": reagent,
"purpose": purpose,
"event": event,
"rate_spec": rate_spec
}
if final_mass > 0:
add_kwargs["mass"] = str(final_mass)
if mol and mol.strip():
add_kwargs["mol"] = mol
if equiv and equiv.strip():
add_kwargs["equiv"] = equiv
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"准备添加液体 {reagent}"
}
})
# 等待搅拌稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
"device_id": solid_dispenser,
"action_name": "add_solid",
"action_kwargs": add_kwargs
})
# 添加后等待
if final_time > 0:
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": final_time}
})
debug_print(f"✅ 固体添加完成")
else:
debug_print("⚠️ 未找到固体加样器,跳过固体添加")
# 计算流速
if time > 0:
flowrate = volume / time
transfer_flowrate = flowrate
else:
flowrate = 1.0 if viscous else 2.5
transfer_flowrate = 0.3 if viscous else 0.5
# 🔧 调用 pump_protocol 时使用正确的参数
try:
# === 液体添加路径 ===
debug_print(f"使用液体添加路径")
# 查找试剂容器
reagent_vessel = find_reagent_vessel(G, reagent)
# 启动搅拌
if stir:
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"准备添加液体 {reagent}"
}
})
# 等待搅拌稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
# 计算流速
if final_time > 0:
flowrate = final_volume / final_time * 60 # mL/min
transfer_flowrate = flowrate
else:
if rate_spec == "dropwise":
flowrate = 0.5 # 滴加,很慢
transfer_flowrate = 0.2
elif viscous:
flowrate = 1.0 # 粘性液体
transfer_flowrate = 0.3
else:
flowrate = 2.5 # 正常流速
transfer_flowrate = 0.5
debug_print(f"流速设置: {flowrate} mL/min")
# 调用pump protocol
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=reagent_vessel,
to_vessel=vessel,
volume=volume,
volume=final_volume,
amount=amount,
duration=time, # 🔧 使用 duration 而不是 time
time=final_time,
viscous=viscous,
rinsing_solvent="",
rinsing_volume=0.0,
@@ -243,33 +482,44 @@ def generate_add_protocol(
rate_spec=rate_spec,
event=event,
through="",
equiv=equiv,
ratio=ratio,
**kwargs
)
action_sequence.extend(pump_actions)
except Exception as e:
raise ValueError(f"液体转移失败: {str(e)}")
debug_print(f"✅ 液体转移完成,添加了 {len(pump_actions)} 个动作")
except Exception as e:
debug_print(f"⚠️ 试剂添加失败: {str(e)}")
# 添加错误日志
action_sequence.append({
"device_id": "system",
"action_name": "log_message",
"action_kwargs": {
"message": f"试剂 '{reagent}' 添加失败: {str(e)}"
}
})
# === 最终结果 ===
debug_print("=" * 60)
debug_print(f"✅ 添加试剂协议生成完成")
debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"📋 处理总结:")
debug_print(f" - 试剂: {reagent}")
debug_print(f" - 添加类型: {'固体' if is_solid else '液体'}")
debug_print(f" - 目标容器: {vessel}")
if is_liquid:
debug_print(f" - 体积: {final_volume}mL")
if is_solid:
debug_print(f" - 质量: {final_mass}g")
debug_print(f" - 摩尔: {mol}")
debug_print("=" * 60)
print(f"ADD_PROTOCOL: 生成 {len(action_sequence)} 个动作")
return action_sequence
# === 便捷函数 ===
# 处理 wait 动作
def process_wait_action(action_kwargs: Dict[str, Any]) -> Dict[str, Any]:
"""处理等待动作"""
wait_time = action_kwargs.get('time', 1.0)
return {
"action_name": "wait",
"action_kwargs": {"time": wait_time},
"description": f"等待 {wait_time}"
}
# 便捷函数
def add_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: float,
time: float = 0.0, rate_spec: str = "") -> List[Dict[str, Any]]:
"""添加液体试剂"""
def add_liquid_volume(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float],
time: Union[str, float] = 0.0, rate_spec: str = "") -> List[Dict[str, Any]]:
"""添加指定体积的液体试剂"""
return generate_add_protocol(
G, vessel, reagent,
volume=volume,
@@ -277,19 +527,17 @@ def add_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: float,
rate_spec=rate_spec
)
def add_solid(G: nx.DiGraph, vessel: str, reagent: str, mass: float,
event: str = "") -> List[Dict[str, Any]]:
"""添加固体试剂"""
def add_solid_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
event: str = "") -> List[Dict[str, Any]]:
"""添加指定质量的固体试剂"""
return generate_add_protocol(
G, vessel, reagent,
mass=mass,
event=event
)
def add_solid_mol(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
event: str = "") -> List[Dict[str, Any]]:
def add_solid_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
event: str = "") -> List[Dict[str, Any]]:
"""按摩尔数添加固体试剂"""
return generate_add_protocol(
G, vessel, reagent,
@@ -297,9 +545,8 @@ def add_solid_mol(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
event=event
)
def add_dropwise(G: nx.DiGraph, vessel: str, reagent: str, volume: float,
time: float = 0.0, event: str = "") -> List[Dict[str, Any]]:
def add_dropwise_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float],
time: Union[str, float] = "20 min", event: str = "") -> List[Dict[str, Any]]:
"""滴加液体试剂"""
return generate_add_protocol(
G, vessel, reagent,
@@ -309,9 +556,8 @@ def add_dropwise(G: nx.DiGraph, vessel: str, reagent: str, volume: float,
event=event
)
def add_portionwise(G: nx.DiGraph, vessel: str, reagent: str, mass: float,
time: float = 0.0, event: str = "") -> List[Dict[str, Any]]:
def add_portionwise_solid(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
time: Union[str, float] = "1 h", event: str = "") -> List[Dict[str, Any]]:
"""分批添加固体试剂"""
return generate_add_protocol(
G, vessel, reagent,
@@ -321,16 +567,30 @@ def add_portionwise(G: nx.DiGraph, vessel: str, reagent: str, mass: float,
event=event
)
# 测试函数
def test_add_protocol():
"""测试添加协议"""
print("=== ADD PROTOCOL 修复版测试 ===")
print("✅ 已修复设备查找逻辑")
print("✅ 已添加固体试剂瓶支持")
print("✅ 已修复错误处理")
"""测试添加协议的各种参数解析"""
print("=== ADD PROTOCOL 增强版测试 ===")
# 测试体积解析
volumes = ["2.7 mL", "2.67 mL", "?", 10.0, "1 L", "500 μL"]
for vol in volumes:
result = parse_volume_input(vol)
print(f"体积解析: {vol}{result}mL")
# 测试质量解析
masses = ["19.3 g", "4.5 g", 2.5, "500 mg", "1 kg"]
for mass in masses:
result = parse_mass_input(mass)
print(f"质量解析: {mass}{result}g")
# 测试时间解析
times = ["1 h", "20 min", "30 s", 60.0, "?"]
for time in times:
result = parse_time_input(time)
print(f"时间解析: {time}{result}s")
print("✅ 测试完成")
if __name__ == "__main__":
test_add_protocol()