Files
bj_power/bj_power_mes/docs/superpowers/plans/2026-05-16-operation-step-plan.md
T

673 lines
20 KiB
Markdown
Raw Normal View History

# OPERATION 复合步骤实现计划
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将 LOAD/EXECUTE/UNLOAD 三元组合并为 OPERATION 步骤,24 步 → 12 步
**Architecture:** 新增 OPERATION step_typeGenerator 根据 job.RawStatus 分派 Load/MachineWait/Unload 候选;事件循环中 Load/MachineDone 不再调 AdvanceStep,改为 SetJobProcessing/SetJobWaitingUnload 原地改状态;Unload 完成后才 AdvanceStep 推进
**Tech Stack:** Go 1.25, ent ORM, PostgreSQL
**Spec:** `docs/superpowers/specs/2026-05-16-operation-step-design.md`
---
## Chunk 1: 常量与 Schema 数据
### Task 1: 更新 step_type 常量
**Files:**
- Modify: `constants/constants.go:88-102`
- [ ] **Step 1: 新增 OPERATION,删除 LOAD/MACHINING/WASH/DEBURR/RUST_WASH/INSPECTION**
```go
// constants.go StepType 枚举
StepType_Operation StepType = "OPERATION"
StepType_BufferStage StepType = "BUFFER_STAGE"
StepType_Decision StepType = "DECISION"
StepType_FinalScan StepType = "FINAL_SCAN"
StepType_Inspection StepType = "INSPECTION" // 保留:SAMPLING 用
StepType_Judge StepType = "JUDGE"
StepType_LaserMark StepType = "LASER_MARK"
StepType_Unload StepType = "UNLOAD"
```
注意:`StepType_Inspection` 保留——OP110 SAMPLING 的 resourceType 通过 `EquipmentTypeCode_Sampling` 区分,但 step_type 仍可为 OPERATION。`loadActionFor("SAMPLING")` 返回 `ActionLoadSampling`
- [ ] **Step 2: 更新 `ValidStepTypes()` 集合**
```go
func ValidStepTypes() []string {
return []string{
string(StepType_Operation),
string(StepType_BufferStage),
string(StepType_Decision),
string(StepType_FinalScan),
string(StepType_Judge),
string(StepType_LaserMark),
string(StepType_Unload),
}
}
```
移除 `StepType_Load``StepType_Machining``StepType_Wash``StepType_Deburr``StepType_RustWash`
- [ ] **Step 3: 编译验证**
```bash
go build ./...
```
预期:constants 包编译通过;references to deleted constants 的编译错误由后续 task 修复。
- [ ] **Step 4: Commit**
```bash
git add constants/constants.go
git commit -m "refactor: 新增 StepType_Operation,删除 LOAD/MACHINING/WASH/DEBURR/RUST_WASH"
```
---
### Task 2: 重写 recipe step 数据
**Files:**
- Modify: `schema/tools/data.go` (recipe step 部分)
- [ ] **Step 1: 删除旧 24 步,插入新 12 步**
定位 `schema/tools/data.go` 中 recipe step 的 bulk insert 代码,替换为:
```go
steps := []struct {
stepID, stepName, stepType, resourceType, nextStepDefault string
stepIndex int
nextStepBranches map[string]any
processingParams map[string]any
}{
{1, "OP10", "CNC加工", "OPERATION", string(constants.EquipmentTypeCode_CNC), "OP20"},
{2, "OP20", "高压清洗", "OPERATION", string(constants.EquipmentTypeCode_WashingMachine), "OP30"},
{3, "OP30", "内窥镜检测", "OPERATION", string(constants.EquipmentTypeCode_Inspection), "OP40"},
{4, "OP40", "检测判断", "JUDGE", "", "OP50",
map[string]any{"MEASURE_NG": "OP120"},
map[string]any{"decisionType": "negate_bool", "setKey": "MEASURE_NG", "inputKey": "inspectionPass"},
},
{5, "OP50", "热能去毛刺", "OPERATION", string(constants.EquipmentTypeCode_Deburr), "OP60"},
{6, "OP60", "除锈清洗", "OPERATION", string(constants.EquipmentTypeCode_CleaningLine), "OP70"},
{7, "OP70", "终检扫码", "FINAL_SCAN", "", "OP80"},
{8, "OP80", "打标判断", "DECISION", "", "OP90",
map[string]any{"SKIP_MARK": "OP120", "NEED_MARKING": "OP90"}, // NEED_MARKING 为 truthy → OP90, else → OP120
nil, // decisionType 由 FINAL_SCAN 步骤的 scan_mismatch 结果驱动
},
{9, "OP90", "激光打标", "LASER_MARK", "", "OP100"},
{10, "OP100", "抽检判断", "DECISION", "", "OP120",
map[string]any{"NEED_SAMPLING": "OP110"},
map[string]any{"decisionType": "noop"}, // 规则由前置步骤 context 决定
},
{11, "OP110", "抽检", "OPERATION", string(constants.EquipmentTypeCode_Sampling), "OP120"},
{12, "OP120", "下料接驳台", "UNLOAD", "", ""},
}
```
**分支逻辑说明:**
- OP40 (JUDGE): `inspectionPass=false` → nextStepBranches 匹配 `MEASURE_NG` → OP120(跳过后续,直接下料)。`inspectionPass=true` → nextStepDefault → OP50。
- OP80 (DECISION): `NEED_MARKING` 由 OP70 FINAL_SCAN 的 scan_mismatch 判定设置。存在 → OP90,不存在(skip)→ OP120。
- OP100 (DECISION): `NEED_SAMPLING` 由外部判定设置。存在 → OP110,不存在 → OP120。
- [ ] **Step 2: 重新生成 ent 代码**
```bash
cd schema && go generate
```
- [ ] **Step 3: 清空 job 表(dev 环境无旧数据兼容)**
```sql
DELETE FROM job;
```
- [ ] **Step 4: Commit**
```bash
git add schema/tools/data.go schema/ ent/
git commit -m "refactor: recipe 步骤改为 12 步 OPERATION 模型"
```
---
## Chunk 2: DBState 新增方法
### Task 3: 新增 SetJobProcessing / SetJobWaitingUnload
**Files:**
- Modify: `internal/eventloop/dbstate.go`
- [ ] **Step 1: 添加 SetJobProcessing**
`DBState` 上新增方法——设置 job 为 PROCESSING 状态,记录位置:
```go
// SetJobProcessing 标记工件已上料到设备,开始加工
func (d *DBState) SetJobProcessing(ctx context.Context, jobID, equipmentID, slotNo int) error {
_, err := d.client.Job.UpdateOneID(jobID).
SetStatus(constants.JobStatus_Processing).
SetPositionType(constants.PositionType_OnEquipment).
SetPositionRefId(fmt.Sprintf("%d:%d", equipmentID, slotNo)).
AddVersion(1).
Save(ctx)
if err != nil {
return fmt.Errorf("set job %d processing: %w", jobID, err)
}
return nil
}
```
- [ ] **Step 2: 添加 SetJobWaitingUnload**
```go
// SetJobWaitingUnload 标记设备加工完成,等待机器人卸料
func (d *DBState) SetJobWaitingUnload(ctx context.Context, jobID int) error {
_, err := d.client.Job.UpdateOneID(jobID).
SetStatus(constants.JobStatus_WaitingUnload).
AddVersion(1).
Save(ctx)
if err != nil {
return fmt.Errorf("set job %d waiting unload: %w", jobID, err)
}
return nil
}
```
- [ ] **Step 3: Commit**
```bash
git add internal/eventloop/dbstate.go
git commit -m "feat: 新增 SetJobProcessing / SetJobWaitingUnload"
```
---
## Chunk 3: 调度器
### Task 4: JobView 新增 RawStatusbuildJobViews 填充
**Files:**
- Modify: `internal/scheduler/types.go:14-21`
- Modify: `internal/eventloop/scheduler_bridge.go:buildJobViews`
- [ ] **Step 1: JobView 新增字段**
```go
type JobView struct {
JobID int
OrderID int
ProductID int
CurrentStep StepView
State JobViewState
RawStatus string // "ON_BUFFER" / "PROCESSING" / "WAITING_UNLOAD"
Context map[string]any
}
```
- [ ] **Step 2: buildJobViews 填充 RawStatus**
`scheduler_bridge.go``buildJobViews` 中,构建 JobView 时填充:
```go
views = append(views, scheduler.JobView{
// ...existing...
State: state,
RawStatus: snap.Status, // 新增
Context: snap.Context,
})
```
- [ ] **Step 3: Commit**
```bash
git add internal/scheduler/types.go internal/eventloop/scheduler_bridge.go
git commit -m "feat: JobView 新增 RawStatusbuildJobViews 填充"
```
---
### Task 5: Generator 新增 OPERATION case
**Files:**
- Modify: `internal/scheduler/generator.go:66-86`
- [ ] **Step 1: 删除旧的 LOAD/UNLOAD/MACHINING/WASH/... case,新增 OPERATION**
替换 `generateFromStep` 中的 switch 分支:
```go
case constants.StepType_Operation:
switch job.RawStatus {
case string(constants.JobStatus_OnBuffer):
candidates = append(candidates, CandidateTask{
JobID: job.JobID,
OrderID: job.OrderID,
StepIndex: step.Index,
Action: g.loadActionFor(step.ResourceType),
TargetID: 0, // trySchedule 中 findIdleMachine 分配
Priority: PriorityNormal,
})
case string(constants.JobStatus_Processing):
candidates = append(candidates, CandidateTask{
JobID: job.JobID,
OrderID: job.OrderID,
StepIndex: step.Index,
Action: ActionLoadCNC, // 占位
TargetID: step.TargetID,
Priority: PriorityNormal,
Meta: map[string]any{"machineWait": true},
})
case string(constants.JobStatus_WaitingUnload):
candidates = append(candidates, CandidateTask{
JobID: job.JobID,
OrderID: job.OrderID,
StepIndex: step.Index,
Action: g.unloadActionFor(step.ResourceType),
TargetID: step.TargetID,
Priority: PriorityHigh,
})
}
```
删除旧 case`StepType_Load``StepType_Unload``StepType_Machining`/`StepType_Wash`/`StepType_Deburr`/`StepType_RustWash`
保留其他 case`StepType_BufferStage``StepType_FinalScan``StepType_Decision`/`StepType_Judge``StepType_LaserMark`
- [ ] **Step 2: 编译验证**
```bash
go build ./internal/scheduler/...
```
- [ ] **Step 3: Commit**
```bash
git add internal/scheduler/generator.go
git commit -m "feat: Generator 新增 OPERATION case,按 RawStatus 分派"
```
---
### Task 6: findExchangePair 更新
**Files:**
- Modify: `internal/eventloop/scheduler_bridge.go:332-353` (findExchangePair)
- [ ] **Step 1: 将 `StepType_Load` 改为 `StepType_Operation && ON_BUFFER`**
```go
func (l *ProductionEventLoop) findExchangePair(unloadSnap *RuntimeSnapshot, excludeLoadJobs ...map[int]bool) int {
if unloadSnap == nil {
return 0
}
if unloadSnap.ResourceType != string(constants.EquipmentTypeCode_CNC) &&
unloadSnap.ResourceType != string(constants.EquipmentTypeCode_WashingMachine) &&
unloadSnap.ResourceType != string(constants.EquipmentTypeCode_CleaningLine) {
return 0
}
var excluded map[int]bool
if len(excludeLoadJobs) > 0 {
excluded = excludeLoadJobs[0]
}
for _, bufSnap := range l.jobRuntimes {
if bufSnap.StepType == constants.StepType_Operation &&
bufSnap.Status == string(constants.JobStatus_OnBuffer) &&
bufSnap.PositionType == string(constants.PositionType_OnBuffer) &&
bufSnap.ResourceType == unloadSnap.ResourceType {
if excluded != nil && excluded[bufSnap.JobID] {
continue
}
return bufSnap.JobID
}
}
return 0
}
```
- [ ] **Step 2: Commit**
```bash
git add internal/eventloop/scheduler_bridge.go
git commit -m "refactor: findExchangePair 改为匹配 StepType_Operation + ON_BUFFER"
```
---
## Chunk 4: 事件循环 — Load/MachineDone 分支
### Task 7: dispatchWorker Load 完成后分流
**Files:**
- Modify: `internal/eventloop/worker_dispatch.go:100-160` (dispatchWorker Load 完成部分)
- Modify: `internal/eventloop/loop.go:130-152` (handleWorkerResult Load 完成部分)
- Modify: `internal/eventloop/worker_dispatch.go:313-350` (handleMultiActionResult load_to_machine case)
在三个位置,Load 完成后检测 stepType
- `dispatchWorker`: Load 成功后 `l.registry` 查槽位 → 如果是 OPERATION → `l.db.SetJobProcessing`
- `handleWorkerResult`: 成功路径中 `act.IsLoad()` → 如果 stepType==OPERATION → 只改状态不调 AdvanceStep
- `handleMultiActionResult`: `load_to_machine` case → 同样分流
- [ ] **Step 1: dispatchWorker 中 Load 成功后的槽位同步 + 状态更新**
`dispatchWorker` 中 Load 成功后已经有从 Station 查槽位的逻辑。在此逻辑之后,检测 stepType:
```go
// dispatchWorker 中,Load 成功后:
if err == nil && l.registry != nil && ct.TargetID > 0 && ct.Action.IsLoad() {
// ...existing slot query...
if assignedSlot > 0 {
l.db.SetEquipmentSlot(ctx, ct.TargetID, assignedSlot, Empty, Occupied, ct.JobID)
}
}
// 新增:OPERATION 步骤不调 AdvanceStep
snap := l.jobRuntimes[ct.JobID]
if snap != nil && snap.StepType == constants.StepType_Operation && ct.Action.IsLoad() {
l.db.SetJobProcessing(ctx, ct.JobID, ct.TargetID, assignedSlot)
// 不调 AdvanceStep
} else {
// 原有 AdvanceStep 逻辑
}
```
但注意:`dispatchWorker` 只是派遣,不更新 job 状态。状态更新在 `handleWorkerResult` 中。所以关键在于 `handleWorkerResult` 中 Load 成功的分支。
- [ ] **Step 2: handleWorkerResult 中 Load 成功分流**
```go
// handleWorkerResult: success 路径中 Load 分支
case act.IsLoad():
if l.isOperationStep(jobID) {
// OPERATION: 不调 AdvanceStep,只改状态
slot := intFromPayload(msg.Payload, "assignedSlot")
if slot < 1 { slot = 1 }
l.db.SetJobProcessing(ctx, jobID, targetID, slot)
} else {
// 原有逻辑:AdvanceStep
newPosType = constants.PositionType_OnEquipment
// ...
l.db.AdvanceStep(ctx, jobID, ...)
}
```
需要在 `ProductionEventLoop` 上加一个辅助方法:
```go
func (l *ProductionEventLoop) isOperationStep(jobID int) bool {
snap := l.jobRuntimes[jobID]
return snap != nil && snap.StepType == constants.StepType_Operation
}
```
- [ ] **Step 3: handleMultiActionResult load_to_machine 分流**
```go
case "load_to_machine":
if snap != nil && snap.StepType == constants.StepType_Operation {
l.db.SetJobProcessing(ctx, jobID, targetID, assignedSlot)
} else {
// 原有 AdvanceStep 逻辑
l.db.AdvanceStep(ctx, jobID, nil, OnEquipment, fmt.Sprintf("%d:%d", targetID, assignedSlot))
}
```
- [ ] **Step 4: 编译验证**
```bash
go build ./internal/eventloop/...
```
- [ ] **Step 5: Commit**
```bash
git add internal/eventloop/
git commit -m "feat: Load 完成后 OPERATION 步骤调用 SetJobProcessing"
```
---
### Task 8: MachineDone 分流
**Files:**
- Modify: `internal/eventloop/candidate_handlers.go:34-44` (handleMachineWaitCandidate)
- Modify: `internal/eventloop/loop.go:327-341` (handleMachineDone 步骤推进部分)
- [ ] **Step 1: handleMachineWaitCandidate OPERATION 分支**
```go
func (l *ProductionEventLoop) handleMachineWaitCandidate(ctx context.Context, ct scheduler.CandidateTask) {
// 检测设备不变
if l.inspectionMachines[ct.TargetID] {
l.entClient.Job.UpdateOneID(ct.JobID).SetStatus(WaitingUnload).Save(ctx)
return
}
// 检查 slot Done
if ct.TargetID > 0 {
slot, _ := l.entClient.EquipmentSlot.Query().
Where(equipmentslot.EquipmentIdEQ(ct.TargetID),
equipmentslot.CurrentJobIdEQ(ct.JobID)).First(ctx)
if slot == nil || slot.Status != constants.SlotStatus_Done {
return
}
}
// 分流
if l.isOperationStep(ct.JobID) {
l.db.SetJobWaitingUnload(ctx, ct.JobID)
} else {
finished, err := l.db.AdvanceStep(ctx, ct.JobID, nil, "", "")
if err != nil {
slog.Error(...)
return
}
if finished {
l.checkOrderCompletion(ctx, ct.JobID)
return
}
l.SendScheduleTick()
}
}
```
- [ ] **Step 2: handleMachineDone 中推进步骤分流**
```go
// handleMachineDone 中 doneJobs 处理循环:
for _, jobID := range doneJobs {
job, _ := l.entClient.Job.Get(ctx, jobID)
if job != nil && job.Status == constants.JobStatus_Processing {
if l.isOperationStep(jobID) {
l.db.SetJobWaitingUnload(ctx, jobID)
} else {
l.db.AdvanceStep(ctx, jobID, nil, "", "")
l.entClient.Job.UpdateOneID(jobID).SetStatus(WaitingUnload).Save(ctx)
}
}
if l.jobOps != nil {
l.jobOps.WakeJob(jobID)
}
}
```
- [ ] **Step 3: Commit**
```bash
git add internal/eventloop/
git commit -m "feat: MachineDone 时 OPERATION 步骤调用 SetJobWaitingUnload"
```
---
## Chunk 5: 事件循环 — InspectionResult / Unload
### Task 9: handleInspectionDone OPERATION 分流
**Files:**
- Modify: `internal/eventloop/loop.go:377-413`
- [ ] **Step 1: 分流 pass 路径**
```go
func (l *ProductionEventLoop) handleInspectionDone(ctx context.Context, machineID int, pass bool) {
// ... slot/job 查询不变 ...
if pass {
if l.isOperationStep(jobID) {
l.db.CompleteStep(ctx, jobID, job.CurrentStepIndex,
map[string]any{"inspectionPass": true})
l.db.SetJobWaitingUnload(ctx, jobID)
} else {
finished, err := l.db.AdvanceStep(ctx, jobID,
map[string]any{"inspectionPass": true}, "", "")
if err != nil { ... }
if finished { l.checkOrderCompletion(ctx, jobID) }
}
} else {
// 不合格路径不变
...
}
}
```
注意:pass=true 时需要用 `CompleteStep` 先记录 `inspectionPass` 上下文(不推进 stepIndex),因为 `SetJobWaitingUnload` 不更新 context。
- [ ] **Step 2: Commit**
```bash
git add internal/eventloop/loop.go
git commit -m "feat: handleInspectionDone OPERATION 步骤 pass→SetJobWaitingUnload"
```
---
### Task 10: Unload 完成后保持 AdvanceStep
Unload 是 OPERATION 的出口,`handleWorkerResult``handleMultiActionResult` 中 Unload 成功的路径继续调用 `AdvanceStep`,不需要修改。确认现有代码正确即可。
- [ ] **Step 1: 验证 handleWorkerResult 中 Unload 路径不变**
```bash
grep -n "IsUnload\|ActionExchange\|ActionUnloadDock" internal/eventloop/worker_dispatch.go
```
确认这些路径的 `AdvanceStep` 调用不受影响。
- [ ] **Step 2: 验证 handleMultiActionResult 中 unload_to_buffer/exchange_full/unload_to_dock 路径不变**
同上。
- [ ] **Step 3: Commit (no-op 确认)**
```bash
git commit --allow-empty -m "chore: 确认 Unload 完成后 AdvanceStep 路径不受 OPERATION 影响"
```
---
## Chunk 6: 清洗机批量编排适配
### Task 11: Washer batch 中 Load/MachineDone 分流
**Files:**
- Modify: `internal/eventloop/washer_orchestrator.go`
- Modify: `internal/eventloop/worker_dispatch.go:handleMultiActionResult`
- [ ] **Step 1: 确认 buildWasherLoadActions 不变**
清洗机 Load 编排逻辑不变——仍然一次机器人行程上两个工件。变化在 Load 完成后的状态更新(已在 Task 7 handleMultiActionResult 中处理)。
- [ ] **Step 2: 确认 buildWasherUnloadActions 不变**
清洗机 Unload 编排不变。Unload 完成后 AdvanceStep(已在 Task 10 确认不受影响)。
- [ ] **Step 3: Commit**
```bash
git commit --allow-empty -m "chore: 确认清洗机批量编排兼容 OPERATION"
```
---
## Chunk 7: 编译、测试、清理
### Task 12: 全量编译和修复编译错误
- [ ] **Step 1: 全量编译**
```bash
go build ./...
```
预期:按 chunk 1-6 顺序实施后,此时应无编译错误。如有,逐个修复(主要是遗漏的常量引用)。
- [ ] **Step 2: 运行 eventloop 测试**
```bash
go test ./internal/eventloop/ -count=1 -v
```
- [ ] **Step 3: 运行 scheduler 测试**
```bash
go test ./internal/scheduler/ -count=1 -v
```
- [ ] **Step 4: Commit**
```bash
git add -A
git commit -m "chore: 修复遗漏的编译错误,测试通过"
```
---
### Task 13: Mock 运行联调
- [ ] **Step 1: 运行 mockrun 验证端到端流程**
```bash
go run cmd/mockrun/main.go
```
观察:
- OPERATION 步骤 Load → PROCESSING → MachineDone → WAITING_UNLOAD → Unload → AdvanceStep
- 检测步骤 InspectionResult 路径
- 清洗机批量编排
- [ ] **Step 2: 验证 Exchange 配对**
观察 mockrun 日志中 CNC 换料是否正常。
- [ ] **Step 3: Commit 任何 mockrun 发现的问题修复**
---
### Task 14: 清理死代码
- [ ] **Step 1: 搜索残留的已删除常量引用**
```bash
rg "StepType_Load|StepType_Machining|StepType_Wash|StepType_Deburr|StepType_RustWash" --type go
```
确认全部清理干净或只在测试 mock 中有意保留。
- [ ] **Step 2: 删除未使用的 import 和函数**
```bash
go vet ./...
```
- [ ] **Step 3: Commit**
```bash
git add -A
git commit -m "chore: 清理死代码和未使用 import"
```