mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 05:15:10 +00:00
241 lines
5.8 KiB
Plaintext
241 lines
5.8 KiB
Plaintext
---
|
||
description: 协议编译器开发规范
|
||
globs: ["unilabos/compile/**/*.py"]
|
||
---
|
||
|
||
# 协议编译器开发规范
|
||
|
||
## 概述
|
||
|
||
协议编译器负责将高级实验操作(如 Stir、Add、Filter)编译为设备可执行的动作序列。
|
||
|
||
## 文件命名
|
||
|
||
- 位置: `unilabos/compile/`
|
||
- 命名: `{operation}_protocol.py`
|
||
- 示例: `stir_protocol.py`, `add_protocol.py`, `filter_protocol.py`
|
||
|
||
## 协议函数模板
|
||
|
||
```python
|
||
from typing import List, Dict, Any, Union
|
||
import networkx as nx
|
||
import logging
|
||
|
||
from .utils.unit_parser import parse_time_input
|
||
from .utils.vessel_parser import extract_vessel_id
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def generate_{operation}_protocol(
|
||
G: nx.DiGraph,
|
||
vessel: Union[str, dict],
|
||
param1: Union[str, float] = "0",
|
||
param2: float = 0.0,
|
||
**kwargs
|
||
) -> List[Dict[str, Any]]:
|
||
"""
|
||
生成{操作}协议序列
|
||
|
||
Args:
|
||
G: 物理拓扑图 (NetworkX DiGraph)
|
||
vessel: 容器ID或Resource字典
|
||
param1: 参数1(支持字符串单位,如 "5 min")
|
||
param2: 参数2
|
||
**kwargs: 其他参数
|
||
|
||
Returns:
|
||
List[Dict]: 动作序列
|
||
|
||
Raises:
|
||
ValueError: 参数无效时
|
||
"""
|
||
# 1. 提取 vessel_id
|
||
vessel_id = extract_vessel_id(vessel)
|
||
|
||
# 2. 验证参数
|
||
if not vessel_id:
|
||
raise ValueError("vessel 参数不能为空")
|
||
|
||
if vessel_id not in G.nodes():
|
||
raise ValueError(f"容器 '{vessel_id}' 不存在于系统中")
|
||
|
||
# 3. 解析参数(支持单位)
|
||
parsed_param1 = parse_time_input(param1) # "5 min" -> 300.0
|
||
|
||
# 4. 查找设备
|
||
device_id = find_connected_device(G, vessel_id, device_type="my_device")
|
||
|
||
# 5. 生成动作序列
|
||
action_sequence = []
|
||
|
||
action = {
|
||
"device_id": device_id,
|
||
"action_name": "my_action",
|
||
"action_kwargs": {
|
||
"vessel": {"id": vessel_id}, # 始终使用字典格式
|
||
"param1": float(parsed_param1),
|
||
"param2": float(param2),
|
||
}
|
||
}
|
||
action_sequence.append(action)
|
||
|
||
logger.info(f"生成协议: {len(action_sequence)} 个动作")
|
||
return action_sequence
|
||
|
||
|
||
def find_connected_device(
|
||
G: nx.DiGraph,
|
||
vessel_id: str,
|
||
device_type: str = ""
|
||
) -> str:
|
||
"""
|
||
查找与容器相连的设备
|
||
|
||
Args:
|
||
G: 拓扑图
|
||
vessel_id: 容器ID
|
||
device_type: 设备类型关键字
|
||
|
||
Returns:
|
||
str: 设备ID
|
||
"""
|
||
# 查找所有匹配类型的设备
|
||
device_nodes = []
|
||
for node in G.nodes():
|
||
node_class = G.nodes[node].get('class', '') or ''
|
||
if device_type.lower() in node_class.lower():
|
||
device_nodes.append(node)
|
||
|
||
# 检查连接
|
||
if vessel_id and device_nodes:
|
||
for device in device_nodes:
|
||
if G.has_edge(device, vessel_id) or G.has_edge(vessel_id, device):
|
||
return device
|
||
|
||
# 返回第一个可用设备
|
||
if device_nodes:
|
||
return device_nodes[0]
|
||
|
||
# 默认设备
|
||
return f"{device_type}_1"
|
||
```
|
||
|
||
## 关键规则
|
||
|
||
### 1. vessel 参数处理
|
||
|
||
vessel 参数可能是字符串或字典,需要统一处理:
|
||
|
||
```python
|
||
def extract_vessel_id(vessel: Union[str, dict]) -> str:
|
||
"""提取vessel_id"""
|
||
if isinstance(vessel, dict):
|
||
# 可能是 {"id": "xxx"} 或完整 Resource 对象
|
||
return vessel.get("id", list(vessel.values())[0].get("id", ""))
|
||
return str(vessel) if vessel else ""
|
||
```
|
||
|
||
### 2. action_kwargs 中的 vessel
|
||
|
||
始终使用 `{"id": vessel_id}` 格式传递 vessel:
|
||
|
||
```python
|
||
# 正确
|
||
"action_kwargs": {
|
||
"vessel": {"id": vessel_id}, # 字符串ID包装为字典
|
||
}
|
||
|
||
# 避免
|
||
"action_kwargs": {
|
||
"vessel": vessel_resource, # 不要传递完整 Resource 对象
|
||
}
|
||
```
|
||
|
||
### 3. 单位解析
|
||
|
||
使用 `parse_time_input` 解析时间参数:
|
||
|
||
```python
|
||
from .utils.unit_parser import parse_time_input
|
||
|
||
# 支持格式: "5 min", "1 h", "300", "1.5 hours"
|
||
time_seconds = parse_time_input("5 min") # -> 300.0
|
||
time_seconds = parse_time_input(120) # -> 120.0
|
||
time_seconds = parse_time_input("1 h") # -> 3600.0
|
||
```
|
||
|
||
### 4. 参数验证
|
||
|
||
所有参数必须进行验证和类型转换:
|
||
|
||
```python
|
||
# 验证范围
|
||
if speed < 10.0 or speed > 1500.0:
|
||
logger.warning(f"速度 {speed} 超出范围,修正为 300")
|
||
speed = 300.0
|
||
|
||
# 类型转换
|
||
param = float(param) if not isinstance(param, (int, float)) else param
|
||
```
|
||
|
||
### 5. 日志记录
|
||
|
||
使用项目日志记录器:
|
||
|
||
```python
|
||
logger = logging.getLogger(__name__)
|
||
|
||
def generate_protocol(...):
|
||
logger.info(f"开始生成协议...")
|
||
logger.debug(f"参数: vessel={vessel_id}, time={time}")
|
||
logger.warning(f"参数修正: {old_value} -> {new_value}")
|
||
```
|
||
|
||
## 便捷函数
|
||
|
||
为常用操作提供便捷函数:
|
||
|
||
```python
|
||
def stir_briefly(G: nx.DiGraph, vessel: Union[str, dict],
|
||
speed: float = 300.0) -> List[Dict[str, Any]]:
|
||
"""短时间搅拌(30秒)"""
|
||
return generate_stir_protocol(G, vessel, time="30", stir_speed=speed)
|
||
|
||
def stir_vigorously(G: nx.DiGraph, vessel: Union[str, dict],
|
||
time: str = "5 min") -> List[Dict[str, Any]]:
|
||
"""剧烈搅拌"""
|
||
return generate_stir_protocol(G, vessel, time=time, stir_speed=800.0)
|
||
```
|
||
|
||
## 测试函数
|
||
|
||
每个协议文件应包含测试函数:
|
||
|
||
```python
|
||
def test_{operation}_protocol():
|
||
"""测试协议生成"""
|
||
# 测试参数处理
|
||
vessel_dict = {"id": "flask_1", "name": "反应瓶1"}
|
||
vessel_id = extract_vessel_id(vessel_dict)
|
||
assert vessel_id == "flask_1"
|
||
|
||
# 测试单位解析
|
||
time_s = parse_time_input("5 min")
|
||
assert time_s == 300.0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
test_{operation}_protocol()
|
||
```
|
||
|
||
## 现有协议参考
|
||
|
||
- `stir_protocol.py` - 搅拌操作
|
||
- `add_protocol.py` - 添加物料
|
||
- `filter_protocol.py` - 过滤操作
|
||
- `heatchill_protocol.py` - 加热/冷却
|
||
- `separate_protocol.py` - 分离操作
|
||
- `evaporate_protocol.py` - 蒸发操作
|