mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-05-23 00:49:53 +00:00
更新扣电组装驱动 coin_cell_assembly.py
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
483
.cursor/skills/yibin-electrolyte-submit/SKILL.md
Normal file
483
.cursor/skills/yibin-electrolyte-submit/SKILL.md
Normal file
@@ -0,0 +1,483 @@
|
||||
---
|
||||
name: yibin-electrolyte-submit
|
||||
description: >-
|
||||
通过 Uni-Lab Notebook API 向宜宾电解液工站提交实验,覆盖配液分液(Bioyond LIMS)、
|
||||
扣电组装(CoinCellAssembly)、扣电测试全流程。
|
||||
包含 Excel 解析、formulation 构建、工作流节点参数填写、notebook 提交与状态轮询。
|
||||
Use when the user wants to submit electrolyte experiments, assemble or test coin cells,
|
||||
parse experiment Excel files, build notebook payloads, or mentions
|
||||
宜宾/配液/分液/扣电/电解液实验/notebook提交/CoinCell/BioyondLIMS.
|
||||
---
|
||||
|
||||
# 宜宾电解液产线 API 操作指南
|
||||
|
||||
本 skill 覆盖两个设备的完整操作流程:
|
||||
1. **配液分液工站** (`bioyond_cell_workstation`) — Bioyond LIMS 配液/分液/转运
|
||||
2. **扣电组装站** (`BatteryStation`) — Modbus PLC 扣电组装/数据采集
|
||||
|
||||
## 设备信息
|
||||
|
||||
| 属性 | 配液分液工站 | 扣电组装站 |
|
||||
|------|------------|-----------|
|
||||
| device_id | `bioyond_cell_workstation` | `BatteryStation` |
|
||||
| 显示名 | 配液分液工站 | 扣电工作站 |
|
||||
| 源码 | `unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py` | `unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py` |
|
||||
| 类名 | `BioyondCellWorkstation` | `CoinCellAssemblyWorkstation` |
|
||||
| 通讯 | HTTP REST (Bioyond LIMS API) | Modbus TCP (PLC 寄存器) |
|
||||
|
||||
## 前置条件
|
||||
|
||||
### 认证信息
|
||||
|
||||
```
|
||||
AUTH="Authorization: Lab OTdlY2FkNmUtZmZmMi00YjhiLThhOWEtNWM5ODAyOTJmOTUxOmU0OGM2YWJkLTA4ZmEtNDFjMy04NzhhLTc4M2FiODlhZjYxMw=="
|
||||
BASE="https://uni-lab.test.bohrium.com"
|
||||
```
|
||||
|
||||
来源:`--ak 97ecad6e-fff2-4b8b-8a9a-5c980292f951 --sk e48c6abd-08fa-41c3-878a-783ab89af613 --addr test`
|
||||
|
||||
### 启动 unilab(云端模式)
|
||||
|
||||
> **重要**:提交实验前必须确保 unilab 正在运行且已连接云端 WebSocket。
|
||||
|
||||
```powershell
|
||||
$env:PYTHONIOENCODING="utf-8"
|
||||
conda activate newunilab2603
|
||||
cd D:\UniLabdev\Uni-Lab-OS\unilabos\devices\workstation
|
||||
unilab -g D:\UniLabdev\Uni-Lab-OS\yibin_electrolyte_config.json --ak 97ecad6e-fff2-4b8b-8a9a-5c980292f951 --sk e48c6abd-08fa-41c3-878a-783ab89af613 --upload_registry --addr test --disable_browser --skip_env_check
|
||||
```
|
||||
|
||||
**启动要点**:
|
||||
1. 必须先激活虚拟环境 `newunilab2603`
|
||||
2. 工作目录切到 `unilabos/devices/workstation`(设备驱动所在目录)
|
||||
3. `--upload_registry` 将 64 个设备 + 142 个资源注册到云端
|
||||
4. `--skip_env_check` + `PYTHONIOENCODING=utf-8` 避免 Windows GBK 编码崩溃
|
||||
5. 启动后后台运行,等待日志出现 `Application startup complete` 和 `Host node ready signal published with 3 devices`
|
||||
|
||||
**验证连接成功的标志**:
|
||||
- 日志出现 `[MessageProcessor] ... wss://uni-lab.test.bohrium.com/api/v1/ws/schedule`
|
||||
- 日志出现 `[WebSocketClient] Host node ready signal published with 3 devices`
|
||||
- 日志出现 `Resource tree add completed`(资源树同步完成)
|
||||
|
||||
### 云端物料上架与入库(启动后必做)
|
||||
|
||||
> **在提交实验之前,必须提醒用户完成以下云端操作,否则实验会因物料缺失而失败。**
|
||||
|
||||
1. **拖拽上料**:在云端 UI(`$BASE/laboratory/<lab_uuid>`)的资源树视图中,将物料拖拽到对应的仓库/库位上。unilab 启动后资源树会自动同步到云端,但物料的**上架位置**需要用户在 UI 上手动确认或调整。
|
||||
|
||||
2. **确认配液物料入库**:确保所有配液实验需要的试剂(如 LiPF6、EC、DMC、EMC 等)已在 LIMS 系统中完成入库。可通过以下方式验证:
|
||||
- 云端 UI 资源树中对应仓库(如"粉末加样头堆栈"、"配液站内试剂仓库")下有物料节点
|
||||
- 或通过 API #8 获取资源树后检查物料节点是否存在
|
||||
|
||||
3. **告知 AI 可以提交**:用户完成上述操作后,告知 AI "物料已上架,可以提交实验",AI 再执行 notebook 提交流程。
|
||||
|
||||
**提醒话术模板**(AI 应在启动成功后发送给用户):
|
||||
```
|
||||
unilab 已成功启动并连接云端。提交实验前请完成以下操作:
|
||||
1. 在云端 UI 上确认资源树中的物料位置,必要时拖拽调整上料位
|
||||
2. 确保配液所需的试剂(粉末、液体)已在 LIMS 中完成入库
|
||||
3. 完成后告诉我,我将为您提交实验
|
||||
```
|
||||
|
||||
### 生成 Action Schema(首次使用)
|
||||
|
||||
启动 unilab 后,在 `unilabos_data/` 目录下会生成 `req_device_registry_upload.json`。运行以下命令提取两个设备的 action JSON:
|
||||
|
||||
```bash
|
||||
python .cursor/skills/create-device-skill/scripts/extract_device_actions.py --registry unilabos_data/req_device_registry_upload.json bioyond_cell_workstation .cursor/skills/yibin-electrolyte-submit/actions/
|
||||
python .cursor/skills/create-device-skill/scripts/extract_device_actions.py --registry unilabos_data/req_device_registry_upload.json BatteryStation .cursor/skills/yibin-electrolyte-submit/actions/
|
||||
```
|
||||
|
||||
## 请求约定
|
||||
|
||||
- Windows 平台**必须用 `curl.exe`**(非 PowerShell 的 curl 别名)
|
||||
- 所有请求带 `$AUTH` 头
|
||||
- URL 格式:`$BASE/api/v1/<endpoint>`
|
||||
- POST/PATCH 请求体写入临时 JSON 文件后用 `-d '@tmp.json'` 传参(避免 PowerShell 转义问题)
|
||||
- 本地 API 基址:`http://127.0.0.1:8002/api/v1/`
|
||||
|
||||
## Session State
|
||||
|
||||
每次会话开始时,依次获取以下信息:
|
||||
|
||||
```bash
|
||||
# 1. lab_uuid
|
||||
curl.exe -s -X GET "$BASE/api/v1/edge/lab/info" -H "$AUTH"
|
||||
# → data.uuid → $lab_uuid
|
||||
|
||||
# 2. project_uuid
|
||||
curl.exe -s -X GET "$BASE/api/v1/lab/project/list?lab_uuid=$lab_uuid" -H "$AUTH"
|
||||
# → data.items[].uuid/name → 让用户选择或取唯一项 → $project_uuid
|
||||
```
|
||||
|
||||
## 工作流模板(重要)
|
||||
|
||||
> **必须向用户索要已有的工作流模板 UUID 或 URL,不要自行创建。**
|
||||
>
|
||||
> 原因:通过 `edge/workflow/node` API 创建节点会报 `resource_node_template not found`——
|
||||
> 云端的工作流节点模板系统和设备注册表是独立的,需要用户在云端 UI 上预先配置好工作流模板。
|
||||
|
||||
**获取方式**:
|
||||
- 用户提供工作流页面 URL,如 `$BASE/laboratory/<lab_uuid>/workflow/<workflow_uuid>`
|
||||
- 从 URL 中提取 `workflow_uuid`
|
||||
- 用 API 获取模板详情:
|
||||
|
||||
```
|
||||
GET /api/v1/lab/workflow/template/detail/<workflow_uuid>
|
||||
```
|
||||
|
||||
返回 `data.nodes[]`:每个节点的 uuid、name、param、device_name、handles、disabled。
|
||||
|
||||
**示例**:
|
||||
```
|
||||
工作流 URL: https://uni-lab.test.bohrium.com/laboratory/e9ed9102-d709-4741-b7a0-d1e8578e2065/workflow/b49f80d9-58d6-4456-a521-56f4dd39cda0
|
||||
→ workflow_uuid = b49f80d9-58d6-4456-a521-56f4dd39cda0
|
||||
```
|
||||
|
||||
从模板详情中提取**未 disabled** 的节点的 `uuid` 和 `name`,后续提交 notebook 时使用。
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### #1 获取 lab_uuid
|
||||
|
||||
```
|
||||
GET /api/v1/edge/lab/info
|
||||
```
|
||||
|
||||
### #2 列出项目
|
||||
|
||||
```
|
||||
GET /api/v1/lab/project/list?lab_uuid=$lab_uuid
|
||||
```
|
||||
|
||||
返回 `data.items[]`,取 `uuid` 和 `name`。
|
||||
|
||||
### #3 获取工作流模板详情
|
||||
|
||||
```
|
||||
GET /api/v1/lab/workflow/template/detail/<workflow_uuid>
|
||||
```
|
||||
|
||||
返回 `data.nodes[]`:每个节点的 uuid、name、param、device_name、handles。
|
||||
提取活跃节点(`disabled != true`)的 `uuid` 用于构建 notebook 请求。
|
||||
|
||||
### #4 提交实验(创建 notebook)— 核心 API
|
||||
|
||||
```
|
||||
POST /api/v1/lab/notebook
|
||||
Body: {
|
||||
"lab_uuid": "<lab_uuid>",
|
||||
"project_uuid": "<project_uuid>",
|
||||
"workflow_uuid": "<workflow_uuid>",
|
||||
"name": "<实验名称>",
|
||||
"node_params": [
|
||||
{
|
||||
"sample_uuids": [],
|
||||
"datas": [
|
||||
{
|
||||
"node_uuid": "<模板中的节点UUID>",
|
||||
"param": { <参数键值对> },
|
||||
"sample_params": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**关键注意事项**:
|
||||
- `node_params` 是数组,每个元素代表一轮实验
|
||||
- `datas` 中每个节点对应模板中的一个活跃节点
|
||||
- `param` 中的字段名**必须使用 Python 函数参数名**,不能用模板中存储的 LIMS 字段名(见下方映射表)
|
||||
|
||||
### #5 查询 notebook 状态
|
||||
|
||||
```
|
||||
GET /api/v1/lab/notebook/status?uuid=<notebook_uuid>
|
||||
```
|
||||
|
||||
| status | 含义 |
|
||||
|--------|------|
|
||||
| `running` | 执行中 |
|
||||
| `success` | 成功 |
|
||||
| `fail` | 失败 |
|
||||
|
||||
### #6 运行设备单动作(本地 API)
|
||||
|
||||
```
|
||||
POST http://127.0.0.1:8002/api/v1/job/add
|
||||
Body: {
|
||||
"device_id": "<device_id>",
|
||||
"action": "<action_name>",
|
||||
"action_args": { <参数键值对> },
|
||||
"sample_material": {}
|
||||
}
|
||||
```
|
||||
|
||||
本地 API 可自动解析 `action_type`,无需手动指定。适用于快速调试或云端未连接时。
|
||||
|
||||
### #7 查询本地任务状态
|
||||
|
||||
```
|
||||
GET http://127.0.0.1:8002/api/v1/job/<job_id>/status
|
||||
```
|
||||
|
||||
| status | 含义 |
|
||||
|--------|------|
|
||||
| 0 | UNKNOWN |
|
||||
| 1 | ACCEPTED |
|
||||
| 2 | EXECUTING |
|
||||
| 4 | SUCCEEDED |
|
||||
| 5 | CANCELED |
|
||||
| 6 | ABORTED |
|
||||
|
||||
### #8 获取资源树
|
||||
|
||||
```
|
||||
GET /api/v1/lab/material/download/<lab_uuid>
|
||||
```
|
||||
|
||||
返回所有节点(`id`, `name`, `uuid`, `type`, `parent`)。填写 Slot 字段时用此接口筛选节点。
|
||||
|
||||
## Placeholder Slot 填写规则
|
||||
|
||||
action JSON 中 `placeholder_keys` 标记了哪些字段需要填 Slot:
|
||||
|
||||
| placeholder 值 | Slot 类型 | 填写格式 |
|
||||
|---------------|-----------|---------|
|
||||
| `unilabos_resources` | ResourceSlot | `{"id": "/path/name", "name": "name", "uuid": "xxx"}` |
|
||||
| `unilabos_devices` | DeviceSlot | `"/parent/device_name"` 路径字符串 |
|
||||
| `unilabos_nodes` | NodeSlot | `"/parent/node_name"` 路径字符串 |
|
||||
| `unilabos_class` | ClassSlot | `"class_name"` 字符串 |
|
||||
| `unilabos_formulation` | FormulationSlot | `[{well_name, liquids: [{name, volume}]}]` |
|
||||
|
||||
### ResourceSlot 填写
|
||||
|
||||
从 API #8 资源树中筛选**物料**节点:
|
||||
|
||||
```json
|
||||
{"id": "/bioyond_cell_workstation/YB_Bioyond_Deck/自动堆栈-左", "name": "自动堆栈-左", "uuid": "3a19debc-..."}
|
||||
```
|
||||
|
||||
数组字段:`[{id, name, uuid}, ...]`
|
||||
特例:`create_resource` 的 `res_id` 允许填不存在的路径。
|
||||
|
||||
### DeviceSlot 填写
|
||||
|
||||
从资源树筛选 `type=device` 的节点,填路径字符串:
|
||||
|
||||
```
|
||||
"/BatteryStation"
|
||||
"/bioyond_cell_workstation"
|
||||
```
|
||||
|
||||
### FormulationSlot 填写
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"sample_uuid": "",
|
||||
"well_name": "YB_PrepBottle_15mL_Carrier_bottle_A1",
|
||||
"liquids": [
|
||||
{ "name": "LiPF6", "mass": 12.5 },
|
||||
{ "name": "EC", "mass": 50.0 }
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
`well_name` 从资源树中取物料节点的 `name`。
|
||||
|
||||
## 参数名映射(重要的坑)
|
||||
|
||||
> 工作流模板中存储的参数名和 Python 函数实际接受的参数名**不一定相同**。
|
||||
> 提交 notebook 时必须使用 **Python 函数参数名**。
|
||||
|
||||
### `create_orders_formulation` 参数映射
|
||||
|
||||
| 模板中的 param 键 | 实际 Python 参数名 | 说明 |
|
||||
|-------------------|-------------------|------|
|
||||
| `pouch_cell_info` | `pouch_cell_volume` | 软包组装分液体积 (mL) |
|
||||
| `conductivity_info` | `conductivity_volume` | 电导测试分液体积 (mL) |
|
||||
| `load_shedding_info` | `coin_cell_volume` | 扣电组装分液体积 (mL) |
|
||||
| `formulation` | `formulation` | 配方数组(名称一致) |
|
||||
| `batch_id` | `batch_id` | 批次号(名称一致) |
|
||||
| `bottle_type` | `bottle_type` | 配液瓶类型(名称一致) |
|
||||
| `mix_time` | `mix_time` | 混匀时间(秒)(名称一致) |
|
||||
| `conductivity_bottle_count` | `conductivity_bottle_count` | 电导瓶数(名称一致) |
|
||||
|
||||
当从模板中读到 `param` 包含 `pouch_cell_info` 等 LIMS 字段名时,提交 notebook 时要用右列的 Python 函数参数名。否则会报 `TypeError: got an unexpected keyword argument`。
|
||||
|
||||
## 典型工作流
|
||||
|
||||
### 方式一:通过 Notebook API 批量提交(推荐)
|
||||
|
||||
**适用场景**:多组配方的批量实验,云端管理实验记录
|
||||
|
||||
```
|
||||
1. 向用户索要工作流模板 URL(不要自行创建)
|
||||
2. 获取 lab_uuid(API #1)和 project_uuid(API #2)
|
||||
3. 获取工作流模板详情(API #3),提取活跃节点 UUID
|
||||
4. 解析用户提供的 Excel 文件,构建 formulation 数组
|
||||
5. 提交 notebook(API #4)
|
||||
6. 轮询 notebook 状态(API #5)直到完成
|
||||
```
|
||||
|
||||
**Excel 解析规则**:
|
||||
- 全局参数在第一个数据行:`batch_id`、`bottle_type`、`mix_time`、`coin_cell_volume`、`pouch_cell_volume`、`conductivity_volume`、`conductivity_bottle_count`
|
||||
- 配方列从"试剂名1"开始,交替排列:试剂名列 + 质量列(以 `(g)` 结尾)
|
||||
- 每行一个配方,`order_name` = 配方ID列
|
||||
- formulation 中每个配方的 materials 数组只包含 `mass > 0` 的试剂
|
||||
|
||||
**node_params 构建**:所有配方放入同一个 round 的同一个 datas 条目中,因为只有一个节点(`create_orders_formulation`)。
|
||||
|
||||
### 方式二:设备单步操作(本地 API)
|
||||
|
||||
**适用场景**:调试、快速测试
|
||||
|
||||
```
|
||||
1. 确保 unilab 已在本地启动
|
||||
2. 通过 POST http://127.0.0.1:8002/api/v1/job/add 提交任务
|
||||
3. 通过 GET /api/v1/job/<job_id>/status 查询状态
|
||||
```
|
||||
|
||||
### 设备操作流程:配液 → 转运 → 扣电
|
||||
|
||||
```
|
||||
1. [配液站] scheduler_start_and_auto_feeding → 启动调度 + 上料
|
||||
2. [配液站] create_orders_formulation → 创建配液实验(配方输入)
|
||||
3. [配液站] transfer_3_to_2_to_1_auto → 分液瓶板转运到扣电站
|
||||
4. [扣电站] func_pack_device_init_auto_start_combined → 初始化+自动+启动
|
||||
5. [扣电站] func_sendbottle_allpack_multi → 发送瓶数+批量组装
|
||||
```
|
||||
|
||||
## 云端使用心得
|
||||
|
||||
### 环境准备
|
||||
- Windows 必须设置 `$env:PYTHONIOENCODING="utf-8"` 防止编码崩溃
|
||||
- 使用 `--skip_env_check` 跳过依赖检查,加快启动
|
||||
- 工作目录建议在 `unilabos/devices/workstation` 下启动
|
||||
|
||||
### 连接与注册
|
||||
- `--upload_registry` 会自动将设备和资源注册到云端
|
||||
- WebSocket 连接建立后,本地和云端的资源树会自动同步
|
||||
- 注册成功后用户需在云端 UI 完成**物料拖放上架**操作
|
||||
- 如果 unilab 断开重连,资源树会重新同步
|
||||
|
||||
### 工作流模板
|
||||
- **不要自行调用 API 创建工作流或节点**——云端工作流节点模板需要预配置
|
||||
- 始终向用户索要已有的工作流模板 URL
|
||||
- 从 URL 中提取 `workflow_uuid`,通过 API #3 获取详情
|
||||
- 模板中 `disabled: true` 的节点跳过,只处理活跃节点
|
||||
|
||||
### Notebook 实验提交
|
||||
- Notebook 是云端管理实验的标准方式
|
||||
- 一个 notebook 可包含多轮(`node_params` 数组),每轮可包含多组参数
|
||||
- 提交后通过 API #5 轮询状态,LIMS 配液流程通常需要较长时间(8 个配方约 30-60 分钟)
|
||||
- 实验进度可在云端 UI 和本地 unilab 日志中同步查看
|
||||
|
||||
### 常见错误
|
||||
| 错误 | 原因 | 解决 |
|
||||
|------|------|------|
|
||||
| `edge not started error` | unilab 未连接云端 WebSocket | 检查 unilab 是否在运行、重启 |
|
||||
| `resource_node_template not found` | 云端没有该设备的工作流模板 | 向用户索要已有模板,不要自行创建 |
|
||||
| `got an unexpected keyword argument` | 参数名用了模板字段名而非 Python 函数参数名 | 参照上方映射表转换 |
|
||||
| `UnicodeEncodeError: 'gbk'` | Windows 默认编码不支持特殊字符 | 设置 `PYTHONIOENCODING=utf-8` |
|
||||
| `parse parameter error` | 云端 API 字段名错误 | `device_id` (非 `device_name`)、`action` (非 `action_name`)、必须带 `action_type` |
|
||||
|
||||
## 渐进加载策略
|
||||
|
||||
1. 先读本文件了解 API 端点、参数映射和云端注意事项
|
||||
2. 需要具体 action 参数时,读 [action-index.md](action-index.md) 查找 action 名称和核心参数
|
||||
3. 需要完整 schema 时,读 `actions/<action_name>.json`(需先运行提取命令生成)
|
||||
4. 需要理解参数含义时,读设备源码
|
||||
|
||||
## 完整 Notebook 提交 Checklist
|
||||
|
||||
```
|
||||
- [ ] 确认 unilab 已在本地启动并连接云端 WebSocket
|
||||
- [ ] 提醒用户在云端 UI 拖拽上料、确认物料位置
|
||||
- [ ] 提醒用户确认配液所需试剂已在 LIMS 完成入库
|
||||
- [ ] 等待用户确认物料就绪后再继续
|
||||
- [ ] 向用户索要工作流模板 URL → 提取 workflow_uuid
|
||||
- [ ] 获取 lab_uuid(API #1)
|
||||
- [ ] 获取 project_uuid(API #2)
|
||||
- [ ] 获取工作流模板详情(API #3),提取活跃节点 UUID
|
||||
- [ ] 解析用户 Excel 文件 → 构建 formulation + 全局参数
|
||||
- [ ] 注意参数名映射(模板字段名 → Python 函数参数名)
|
||||
- [ ] 提交 notebook(API #4)
|
||||
- [ ] 轮询 notebook 状态(API #5)直到完成
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 真实场景:宜宾产线 Excel 提交提示词模板
|
||||
|
||||
> 以下为已验证可用的标准提示词,适用于配液-分液-扣电全流程。
|
||||
|
||||
### 场景说明
|
||||
|
||||
- unilab 运行在本地 Windows 机器(miniforge 环境),连接云端 WebSocket
|
||||
- AI(Cursor / OpenClaw)在任意设备上,通过云端 API 操作,**不需要本地 127.0.0.1**
|
||||
- 工作流为 5 节点串联:`create_orders_formulation` → `transfer_3_to_2_to_1_auto` → `func_pack_device_init_auto_start_combined` → `func_sendbottle_allpack_multi` → `transfer_1_to_2`
|
||||
|
||||
### 已知固定参数(宜宾产线)
|
||||
|
||||
```
|
||||
BASE = https://uni-lab.test.bohrium.com
|
||||
lab_uuid = e9ed9102-d709-4741-b7a0-d1e8578e2065
|
||||
project = YiBinElectrolyte (bc5224b4-8120-4765-9961-9dfc1802a1f6)
|
||||
workflow = 配液分液formulation全流程 (2bc59938-db79-4415-ac2d-9897ef125f2f)
|
||||
```
|
||||
|
||||
#### 工作流节点 UUID(固定,无需重新查询)
|
||||
|
||||
| 顺序 | action | node_uuid |
|
||||
|------|--------|-----------|
|
||||
| Step1 | auto-create_orders_formulation | `ece6744a-81ac-4ae4-8cd1-1c8eeda1dab6` |
|
||||
| Step2 | auto-transfer_3_to_2_to_1_auto | `1c37a8dd-5ba0-413d-81db-94b9c936a171` |
|
||||
| Step3 | auto-func_pack_device_init_auto_start_combined | `97a676a2-d257-4479-9096-073b40300970` |
|
||||
| Step4 | auto-func_sendbottle_allpack_multi | `cf69017a-d29c-4aad-a63b-309d63dac2e9` |
|
||||
| Step5 | auto-transfer_1_to_2 | `80d1c1aa-dbc3-4601-86b7-5c22a992dd9e` |
|
||||
|
||||
### 标准提示词
|
||||
|
||||
```
|
||||
请使用 yibin-electrolyte-submit skill,提交以下实验:
|
||||
|
||||
工作流模板 URL:https://uni-lab.test.bohrium.com/laboratory/e9ed9102-d709-4741-b7a0-d1e8578e2065/workflow/2bc59938-db79-4415-ac2d-9897ef125f2f
|
||||
Excel 文件路径:<粘贴或上传 xlsx 路径>
|
||||
|
||||
注意事项:
|
||||
- lab_uuid、project_uuid、workflow节点UUID均已固定,无需重新查询
|
||||
- 直接解析 Excel → 构建 payload → 提交
|
||||
- mix_time 传标量整数即可(已兼容)
|
||||
- 试剂名以 Excel 为准,注意区分 LiDFOB / LiDOFB 等拼写
|
||||
- csv_export_path 取 Excel 中 csv_export_path 列的值
|
||||
- 提交后告知 notebook UUID,无需自动轮询(实验耗时较长)
|
||||
```
|
||||
|
||||
### Excel 列结构说明(experment_template_0415sim-*.xlsx)
|
||||
|
||||
| 列范围 | 内容 |
|
||||
|--------|------|
|
||||
| C | batch_id |
|
||||
| D | bottle_type |
|
||||
| E-H | coin_cell_volume / conductivity_bottle_count / conductivity_volume / csv_export_path |
|
||||
| I-T | 试剂名+质量 交替排列(最多6对)|
|
||||
| U | mix_time |
|
||||
| V | order_name(每行配方的订单号)|
|
||||
| W | pouch_cell_volume |
|
||||
| X-Y | target_device / target_location(Step2参数)|
|
||||
| AA | material_search_enable(Step3参数)|
|
||||
| AB-AS | 扣电站参数(Step4)|
|
||||
|
||||
### CSV 导出说明
|
||||
|
||||
每次 `create_orders_formulation` 完成后,在 `csv_export_path` 目录下生成:
|
||||
```
|
||||
electrolyte_orders_<YYYYMMDD_HHMMSS>.csv
|
||||
```
|
||||
列:`orderCode, orderName, 配液瓶类型, 配液瓶二维码, 分液瓶类型, 分液瓶二维码, 目标配液质量比, 真实配液质量比, 时间`
|
||||
|
||||
> **注意**:barCode 为 `null` 或 `"nullBarCode123456"` 是正常现象,表示 LIMS 中该物料尚未扫码。配液瓶缺失通常是因为物料未放在手动传递窗(`locationId` 前缀 `3a19deae-2c7a-`)。
|
||||
295
.cursor/skills/yibin-electrolyte-submit/action-index.md
Normal file
295
.cursor/skills/yibin-electrolyte-submit/action-index.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# Action 索引
|
||||
|
||||
> Action JSON 文件需运行提取命令生成,详见 [SKILL.md](SKILL.md) 中「生成 Action Schema」。
|
||||
> 以下描述和参数信息基于源码分析。
|
||||
|
||||
---
|
||||
|
||||
## 配液分液工站 (`bioyond_cell_workstation`)
|
||||
|
||||
源码:`unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py`
|
||||
|
||||
### 调度控制
|
||||
|
||||
#### `scheduler_start`
|
||||
|
||||
启动 Bioyond LIMS 调度系统
|
||||
|
||||
- **核心参数**: 无(仅需 apiKey/requestTime,由设备内部处理)
|
||||
- **返回**: LIMS 响应 `{code, message, data}`
|
||||
|
||||
#### `scheduler_stop`
|
||||
|
||||
停止调度
|
||||
|
||||
- **核心参数**: 无
|
||||
|
||||
#### `scheduler_continue`
|
||||
|
||||
继续调度(从暂停状态恢复)
|
||||
|
||||
- **核心参数**: 无
|
||||
|
||||
#### `scheduler_reset`
|
||||
|
||||
复位调度
|
||||
|
||||
- **核心参数**: 无
|
||||
|
||||
#### `scheduler_start_and_auto_feeding`
|
||||
|
||||
**组合操作**:启动调度 + 自动化上料(4号→3号手套箱)
|
||||
|
||||
- **核心参数**: `xlsx_path`(Excel 物料模板路径,可选)
|
||||
- **可选参数**: WH4 加样头面 12 个点位(materialName + quantity)、WH4 原液瓶面 9 个点位(materialName + quantity + materialType + targetWH)、WH3 人工堆栈 15 个点位(materialType + materialId + quantity)
|
||||
- **流程**: 先 `scheduler_start()`,成功后执行 `auto_feeding4to3()`
|
||||
- **备注**: 支持 Excel 模式和手动参数模式,Excel 路径存在时优先使用 Excel
|
||||
|
||||
### 物料上料/下料
|
||||
|
||||
#### `auto_feeding4to3`
|
||||
|
||||
自动化上料:从 4 号手套箱转运物料到 3 号手套箱
|
||||
|
||||
- **核心参数**: `xlsx_path`(Excel 物料模板路径)
|
||||
- **可选参数**: 同 `scheduler_start_and_auto_feeding` 的 WH4/WH3 点位参数
|
||||
- **返回**: 等待上料任务完成后返回结果
|
||||
|
||||
#### `auto_batch_outbound_from_xlsx`
|
||||
|
||||
自动化下料(从 Excel 读取下料信息)
|
||||
|
||||
- **核心参数**: `xlsx_path`(Excel 下料模板)
|
||||
- **Excel 列**: locationId, warehouseId, 数量, x, y, z
|
||||
|
||||
### 物料管理
|
||||
|
||||
#### `create_and_inbound_materials`
|
||||
|
||||
批量创建固体物料并入库
|
||||
|
||||
- **核心参数**: `material_names`(物料名称列表,默认 `["LiPF6", "LiDFOB", "DTD", "LiFSI", "LiPO2F2"]`)
|
||||
- **可选参数**: `type_id`(物料类型ID), `warehouse_name`(目标仓库,默认 "粉末加样头堆栈")
|
||||
- **流程**: 创建物料 → 批量入库 → 同步
|
||||
|
||||
#### `create_material`
|
||||
|
||||
创建单个物料并可选入库
|
||||
|
||||
- **核心参数**: `material_name`, `type_id`, `warehouse_name`
|
||||
- **可选参数**: `location_name_or_id`(库位编号如 "A01" 或 UUID)
|
||||
|
||||
#### `create_sample`
|
||||
|
||||
创建配液板物料(含子瓶)并入库
|
||||
|
||||
- **核心参数**: `name`, `board_type`(如 "5ml分液瓶板"), `bottle_type`(如 "5ml分液瓶"), `location_code`(如 "A01")
|
||||
- **可选参数**: `warehouse_name`(默认 "手动堆栈")
|
||||
- **备注**: 自动创建 2x4=8 个子瓶
|
||||
|
||||
#### `storage_inbound`
|
||||
|
||||
单个物料入库
|
||||
|
||||
- **核心参数**: `material_id`, `location_id`
|
||||
|
||||
#### `storage_batch_inbound`
|
||||
|
||||
批量物料入库
|
||||
|
||||
- **核心参数**: `items`(`[{materialId, locationId}, ...]`)
|
||||
|
||||
### 配液实验
|
||||
|
||||
#### `create_orders`
|
||||
|
||||
从 Excel 文件创建配液实验订单
|
||||
|
||||
- **核心参数**: `xlsx_path`(Excel 文件路径)
|
||||
- **Excel 列**: 配方ID, 创建日期, 配液瓶类型, 混匀时间(s), 扣电组装分液体积, 软包组装分液体积, 电导测试分液体积, 电导测试分液瓶数, 以及所有以 `(g)` 结尾的物料列
|
||||
- **流程**: 解析 Excel → 提交订单 → 等待全部完成 → 计算质量比 → 提取分液瓶板 → 创建资源树对象
|
||||
- **返回**: `{status, total_orders, bottle_count, reports, mass_ratios, vial_plates}`
|
||||
|
||||
#### `create_orders_formulation`
|
||||
|
||||
从配方列表创建配液实验订单(前端/API 输入版本)
|
||||
|
||||
- **核心参数**: `formulation`(配方数组)
|
||||
- **可选参数**: `batch_id`, `bottle_type`(默认 "配液小瓶"), `mix_time`(秒,列表), `coin_cell_volume`, `pouch_cell_volume`, `conductivity_volume`, `conductivity_bottle_count`
|
||||
- **formulation 格式**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"order_name": "配方A",
|
||||
"materials": [
|
||||
{"name": "LiPF6", "mass": 12.5},
|
||||
{"name": "EC", "mass": 50.0},
|
||||
{"name": "DMC", "mass": 37.5}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
- **返回**: 同 `create_orders`
|
||||
|
||||
### 物料转运
|
||||
|
||||
#### `transfer_3_to_2_to_1_auto`
|
||||
|
||||
**自动转运**:从 create_orders 结果中自动定位分液瓶板并转运到目标设备
|
||||
|
||||
- **核心参数**: `vial_plates`(分液瓶板列表,来自 create_orders 返回的 `vial_plates`)
|
||||
- **可选参数**: `target_device`(默认 "BatteryStation"), `target_location`(默认 "bottle_rack_6x2"), `mass_ratios`(配方信息)
|
||||
- **流程**: 遍历瓶板 → 解析 locationId → 调用 LIMS 转运 API → 更新资源树
|
||||
- **返回**: `{total, success, failed, results}`
|
||||
|
||||
#### `transfer_3_to_2_to_1`
|
||||
|
||||
3→2→1 物料转运(手动指定坐标)
|
||||
|
||||
- **核心参数**: `source_wh_id`, `source_x`, `source_y`, `source_z`
|
||||
|
||||
#### `transfer_3_to_2`
|
||||
|
||||
3→2 物料转运
|
||||
|
||||
- **核心参数**: `source_wh_id`, `source_x`, `source_y`, `source_z`
|
||||
|
||||
#### `transfer_1_to_2`
|
||||
|
||||
1→2 物料转运
|
||||
|
||||
- **核心参数**: 无
|
||||
|
||||
### 查询
|
||||
|
||||
#### `order_list_v2`
|
||||
|
||||
批量查询实验报告
|
||||
|
||||
- **可选参数**: `timeType`, `beginTime`, `endTime`, `status`(60=运行中, 80=完成, 90=失败), `filter`, `skipCount`, `pageCount`, `sorting`
|
||||
|
||||
---
|
||||
|
||||
## 扣电组装站 (`BatteryStation`)
|
||||
|
||||
源码:`unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py`
|
||||
|
||||
### 设备控制(组合操作)
|
||||
|
||||
#### `func_pack_device_init_auto_start_combined`
|
||||
|
||||
**组合操作**:设备初始化 → 物料搜寻确认 → 切换自动模式 → 启动
|
||||
|
||||
- **核心参数**: `material_search_enable`(是否启用物料搜寻,默认 `False`)
|
||||
- **前置检查**: REG_UNILAB_INTERACT=False, COIL_GB_L_IGNORE_CMD=False, 所有握手寄存器无残留
|
||||
- **流程**: 手动模式 → 初始化命令 → 监测物料搜寻弹窗并自动处理 → 自动模式 → 启动
|
||||
- **返回**: `True`/`False`
|
||||
- **备注**: 第一次运行必须调用此函数;后续批次调用 `func_sendbottle_allpack_multi`
|
||||
|
||||
### 批量组装
|
||||
|
||||
#### `func_sendbottle_allpack_multi`
|
||||
|
||||
**发送瓶数 + 批量组装**(适用于第二批次及后续批次)
|
||||
|
||||
- **核心参数**: `elec_num`(电解液瓶数), `elec_use_num`(每瓶组装电池数), `elec_vol`(电解液吸液量 μL,默认 50)
|
||||
- **可选参数**:
|
||||
- 双滴模式:`dual_drop_mode`(bool), `dual_drop_first_volume`(μL), `dual_drop_suction_timing`(bool), `dual_drop_start_timing`(bool)
|
||||
- 组装参数:`assembly_type`(7=不用铝箔垫/8=用), `assembly_pressure`(N,默认 4200)
|
||||
- 物料参数:`fujipian_panshu`, `fujipian_juzhendianwei`, `gemopanshu`, `gemo_juzhendianwei`, `qiangtou_juzhendianwei`
|
||||
- 开关:`lvbodian`(铝箔垫片), `battery_pressure_mode`(压力模式), `battery_clean_ignore`(忽略清洁)
|
||||
- 其他:`file_path`(CSV保存路径), `formulations`(配方信息,用于CSV追溯)
|
||||
- **流程**: 发送瓶数触发物料搬运 → 设置PLC参数 → 循环(等待PLC请求→下发参数→读取电池数据→写入CSV→更新资源树)→ 完成握手
|
||||
- **返回**: `{success, total_batteries, batteries, summary}`
|
||||
- **备注**: 设备已初始化后直接调用;`formulations` 来自 create_orders 的 `mass_ratios`
|
||||
|
||||
#### `func_allpack_cmd`
|
||||
|
||||
全套组装(基础版本,含断点续传)
|
||||
|
||||
- **核心参数**: `elec_num`, `elec_use_num`, `elec_vol`, `assembly_type`, `assembly_pressure`, `file_path`
|
||||
- **返回**: `{success, total_batteries, batteries, summary}`
|
||||
|
||||
#### `func_allpack_cmd_simp`
|
||||
|
||||
增强版组装(含双滴模式 + 负极片/隔膜/枪头参数)
|
||||
|
||||
- **核心参数**: 同 `func_sendbottle_allpack_multi`
|
||||
- **备注**: 被 `func_sendbottle_allpack_multi` 内部调用
|
||||
|
||||
### 设备控制(单步操作)
|
||||
|
||||
#### `func_pack_device_init`
|
||||
|
||||
设备初始化(手动模式 → 初始化 → 复位标志)
|
||||
|
||||
#### `func_pack_device_auto`
|
||||
|
||||
切换自动模式
|
||||
|
||||
#### `func_pack_device_start`
|
||||
|
||||
启动设备
|
||||
|
||||
#### `func_pack_device_stop`
|
||||
|
||||
设备停止
|
||||
|
||||
#### `func_pack_send_bottle_num`
|
||||
|
||||
发送电解液瓶数(触发物料搬运)
|
||||
|
||||
- **核心参数**: `bottle_num`(瓶数)
|
||||
|
||||
### PLC 参数设置
|
||||
|
||||
#### `qiming_coin_cell_code`
|
||||
|
||||
设置组装物料参数
|
||||
|
||||
- **核心参数**: `fujipian_panshu`(负极片盘数)
|
||||
- **可选参数**: `fujipian_juzhendianwei`, `gemopanshu`, `gemo_juzhendianwei`, `lvbodian`, `battery_pressure_mode`, `battery_pressure`, `battery_clean_ignore`
|
||||
|
||||
### 数据采集
|
||||
|
||||
#### `func_read_data_and_output`
|
||||
|
||||
持续数据采集并导出 CSV(后台循环运行)
|
||||
|
||||
- **核心参数**: `file_path`(CSV 保存目录)
|
||||
- **采集字段**: 开路电压, 极片质量, 组装时间, 压制力, 电解液加注量, 电池类型, 电解液二维码, 电池二维码
|
||||
|
||||
#### `func_stop_read_data`
|
||||
|
||||
停止 CSV 数据采集
|
||||
|
||||
### 设备状态属性(只读)
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `sys_status` | str | 设备状态(启动中/停止中/复位中/初始化中) |
|
||||
| `sys_mode` | str | 设备模式(手动/自动) |
|
||||
| `data_assembly_coin_cell_num` | int | 已完成电池数量 |
|
||||
| `data_assembly_time` | float | 单颗电池组装时间(秒) |
|
||||
| `data_open_circuit_voltage` | float | 开路电压(V) |
|
||||
| `data_pole_weight` | float | 正极片称重(g) |
|
||||
| `data_glove_box_pressure` | float | 手套箱压力(mbar) |
|
||||
| `data_glove_box_o2_content` | float | 手套箱氧含量(ppm) |
|
||||
| `data_glove_box_water_content` | float | 手套箱水含量(ppm) |
|
||||
| `data_coin_cell_code` | str | 电池二维码 |
|
||||
| `data_electrolyte_code` | str | 电解液二维码 |
|
||||
|
||||
---
|
||||
|
||||
## 配置参考
|
||||
|
||||
设备图文件 `yibin_electrolyte_config.json` 中的仓库映射(`warehouse_mapping`):
|
||||
|
||||
| 仓库名称 | 说明 | 典型操作 |
|
||||
|---------|------|---------|
|
||||
| 粉末加样头堆栈 | 20 个点位 (A01-T01) | `create_and_inbound_materials` 入库目标 |
|
||||
| 配液站内试剂仓库 | 9 个点位 (A01-C03) | 试剂存储 |
|
||||
| 自动堆栈-左 | 4 个点位 | 分液瓶板存放,`transfer_3_to_2_to_1_auto` 的源位置 |
|
||||
| 自动堆栈-右 | 4 个点位 | 分液瓶板存放 |
|
||||
| 手动传递窗左/右 | 各 15 个点位 | 人工上料/下料 |
|
||||
| 4号手套箱内部堆栈 | 12 个点位 | `auto_feeding4to3` 的源位置 |
|
||||
@@ -6,7 +6,7 @@ import threading
|
||||
import time
|
||||
import types
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
from functools import wraps
|
||||
from pylabrobot.resources import Deck, Resource as PLRResource
|
||||
from unilabos_msgs.msg import Resource
|
||||
@@ -1406,12 +1406,45 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
self._formulations_map = {
|
||||
f["orderCode"]: f for f in formulations
|
||||
} if formulations else {}
|
||||
# ✅ 新增:存储配方列表(按接收顺序),用于索引访问
|
||||
# ✅ 新增:存储配方列表(按接收顺序),用于索引访问(兜底用)
|
||||
self._formulations_list = formulations
|
||||
# ✅ 新增:按分液瓶条码(vial_bottle_barcodes)反向索引配方
|
||||
# 配液站夹爪取放顺序与扣电站夹取顺序可能不同,所以不能再依赖位置序号,
|
||||
# 必须用扣电站扫码得到的 data_electrolyte_code 去对齐配液站登记的瓶条码。
|
||||
# vial_bottle_barcodes 字段可能形如 "LG100114"(单瓶)或 '["LG100114","LG100115"]'(多瓶)。
|
||||
self._formulations_by_vial_barcode: Dict[str, Dict] = {}
|
||||
for f in formulations:
|
||||
raw_barcodes = f.get("vial_bottle_barcodes", "")
|
||||
if not raw_barcodes:
|
||||
continue
|
||||
barcodes: List[str] = []
|
||||
if isinstance(raw_barcodes, list):
|
||||
barcodes = [str(b).strip() for b in raw_barcodes if b]
|
||||
else:
|
||||
s = str(raw_barcodes).strip()
|
||||
if s.startswith("[") and s.endswith("]"):
|
||||
try:
|
||||
parsed = json.loads(s)
|
||||
if isinstance(parsed, list):
|
||||
barcodes = [str(b).strip() for b in parsed if b]
|
||||
else:
|
||||
barcodes = [str(parsed).strip()]
|
||||
except Exception:
|
||||
barcodes = [s]
|
||||
else:
|
||||
barcodes = [s]
|
||||
for bc in barcodes:
|
||||
if bc and bc not in self._formulations_by_vial_barcode:
|
||||
self._formulations_by_vial_barcode[bc] = f
|
||||
logger.info(
|
||||
f"已建立分液瓶条码 → 配方索引: {len(self._formulations_by_vial_barcode)} 条 "
|
||||
f"(条码: {list(self._formulations_by_vial_barcode.keys())})"
|
||||
)
|
||||
else:
|
||||
logger.warning("未接收到配方信息,CSV将不包含配方字段")
|
||||
self._formulations_map = {}
|
||||
self._formulations_list = []
|
||||
self._formulations_by_vial_barcode = {}
|
||||
|
||||
# ✅ 新增:存储每瓶电池数,用于计算当前使用的瓶号
|
||||
# ⚠️ 确保转换为整数(前端可能传递字符串)
|
||||
@@ -1680,50 +1713,78 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
|
||||
# 从 self._formulations_list 获取配方信息
|
||||
if hasattr(self, '_formulations_list') and self._formulations_list:
|
||||
# ✅ 新方案:根据电池编号和每瓶电池数计算当前瓶号
|
||||
# 例如:elec_use_num=2时,电池1-2用瓶0,电池3-4用瓶1
|
||||
if hasattr(self, '_elec_use_num') and self._elec_use_num:
|
||||
# ⚠️ 确保转换为整数(防御性编程)
|
||||
elec_use_num_int = int(self._elec_use_num) if self._elec_use_num else 1
|
||||
if elec_use_num_int > 0:
|
||||
current_bottle_index = (data_battery_number - 1) // elec_use_num_int
|
||||
# ============================================================
|
||||
# ✅ 主方案:用扣电站扫码得到的电解液瓶条码 (data_electrolyte_code)
|
||||
# 反查配液站登记的 vial_bottle_barcodes,避免依赖夹爪取放顺序。
|
||||
# 配液站和扣电站的瓶子顺序往往不一致(不同自动化设备的取放策略不同),
|
||||
# 按位置序号匹配会错位;但每个瓶子的条码是唯一的,按条码匹配最可靠。
|
||||
# ============================================================
|
||||
formulation = None
|
||||
match_method = ""
|
||||
|
||||
barcode_map = getattr(self, "_formulations_by_vial_barcode", {}) or {}
|
||||
scan_code = (data_electrolyte_code or "").strip()
|
||||
if scan_code and scan_code != "N/A" and barcode_map:
|
||||
formulation = barcode_map.get(scan_code)
|
||||
if formulation is not None:
|
||||
match_method = f"按条码({scan_code})精确匹配"
|
||||
else:
|
||||
current_bottle_index = 0
|
||||
|
||||
logger.debug(
|
||||
f"[CSV写入] 电池 {data_battery_number}: 计算瓶号索引={current_bottle_index} "
|
||||
f"(每瓶{self._elec_use_num}颗电池)"
|
||||
)
|
||||
else:
|
||||
# 降级方案:尝试从二维码解析(仅当参数未设置时)
|
||||
current_bottle_index = int(data_electrolyte_code.split('-')[-1]) if '-' in str(data_electrolyte_code) else 0
|
||||
logger.debug(
|
||||
f"[CSV写入] 电池 {data_battery_number}: 从二维码解析瓶号索引={current_bottle_index}"
|
||||
)
|
||||
|
||||
# 从配方列表中获取对应配方
|
||||
if 0 <= current_bottle_index < len(self._formulations_list):
|
||||
formulation = self._formulations_list[current_bottle_index]
|
||||
logger.warning(
|
||||
f"[CSV写入] 电池 {data_battery_number}: 扫码条码 {scan_code} "
|
||||
f"在配方索引中找不到 (已登记条码: {list(barcode_map.keys())})"
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# 🔁 降级方案:扫码失败 / 条码缺失时按瓶号位置兜底
|
||||
# 保留原有"每瓶电池数"或"二维码尾号"的位置推断逻辑,
|
||||
# 确保在异常路径下仍能落盘(位置推断的结果可能不准,仅供回溯)。
|
||||
# ============================================================
|
||||
if formulation is None:
|
||||
if hasattr(self, '_elec_use_num') and self._elec_use_num:
|
||||
elec_use_num_int = int(self._elec_use_num) if self._elec_use_num else 1
|
||||
if elec_use_num_int > 0:
|
||||
current_bottle_index = (data_battery_number - 1) // elec_use_num_int
|
||||
else:
|
||||
current_bottle_index = 0
|
||||
|
||||
logger.debug(
|
||||
f"[CSV写入] 电池 {data_battery_number}: 降级按瓶号索引={current_bottle_index} "
|
||||
f"(每瓶{self._elec_use_num}颗电池)"
|
||||
)
|
||||
else:
|
||||
current_bottle_index = (
|
||||
int(data_electrolyte_code.split('-')[-1])
|
||||
if '-' in str(data_electrolyte_code)
|
||||
else 0
|
||||
)
|
||||
logger.debug(
|
||||
f"[CSV写入] 电池 {data_battery_number}: 降级按二维码尾号瓶号索引={current_bottle_index}"
|
||||
)
|
||||
|
||||
if 0 <= current_bottle_index < len(self._formulations_list):
|
||||
formulation = self._formulations_list[current_bottle_index]
|
||||
match_method = f"按位置兜底匹配[{current_bottle_index}]"
|
||||
else:
|
||||
logger.warning(
|
||||
f"[CSV写入] 电池 {data_battery_number}: 瓶号索引 {current_bottle_index} "
|
||||
f"超出配方列表范围 (共{len(self._formulations_list)}个配方)"
|
||||
)
|
||||
|
||||
if formulation is not None:
|
||||
formulation_order_name = formulation.get("orderName", "")
|
||||
prep_bottle_barcode = formulation.get("prep_bottle_barcode", "")
|
||||
vial_bottle_barcodes = formulation.get("vial_bottle_barcodes", "")
|
||||
|
||||
|
||||
real_ratio = formulation.get("real_mass_ratio", {})
|
||||
target_ratio = formulation.get("target_mass_ratio", {})
|
||||
|
||||
# 将配方比例转为JSON字符串
|
||||
import json
|
||||
|
||||
target_ratio_str = json.dumps(target_ratio, ensure_ascii=False) if target_ratio else ""
|
||||
real_ratio_str = json.dumps(real_ratio, ensure_ascii=False) if real_ratio else ""
|
||||
|
||||
|
||||
logger.info(
|
||||
f"[CSV写入] 电池 {data_battery_number}: 使用配方[{current_bottle_index}] "
|
||||
f"orderName={formulation_order_name}, 配液瓶={prep_bottle_barcode}, 分液瓶={vial_bottle_barcodes}"
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"[CSV写入] 电池 {data_battery_number}: 瓶号索引 {current_bottle_index} "
|
||||
f"超出配方列表范围 (共{len(self._formulations_list)}个配方)"
|
||||
f"[CSV写入] 电池 {data_battery_number} ({match_method}): "
|
||||
f"orderName={formulation_order_name}, 配液瓶={prep_bottle_barcode}, "
|
||||
f"分液瓶={vial_bottle_barcodes}"
|
||||
)
|
||||
else:
|
||||
logger.debug(f"[CSV写入] 电池 {data_battery_number}: 未找到配方信息数据")
|
||||
|
||||
Reference in New Issue
Block a user