---
title: "分镜状态流转说明"
description: "面向开发者说明 shot.status、skip_extraction 与 shot_extracted_candidates 的职责和流转规则。"
weight: 7
---

## 背景

为了让“分镜是否具备生成视频条件”有统一、可追溯的判定来源，当前后端已经将 `shots.status` 收敛为**系统流程状态**，并引入 `shot_extracted_candidates` 记录镜头提取确认过程中的中间状态。

这次调整的目标不是增加更多前端判断，而是把正式状态统一交给后端维护，前端只消费结果。

## 核心结论

- `shots.status`：系统流程状态，只由后端更新
- `shots.skip_extraction`：用户明确声明“当前镜头无需提取”
- `shot_extracted_candidates`：记录每一条提取候选项的处理状态

也就是说：

```text
shots.status
  = 系统流程状态

skip_extraction
  = 是否跳过提取

shot_extracted_candidates
  = 提取候选的确认明细
```

## `shot.status` 的语义

当前只保留三种正式状态：

- `pending`
  - 当前镜头还没有完成视频生成前的确认流程
- `generating`
  - 当前镜头存在进行中的生成类任务
- `ready`
  - 当前镜头已经具备进入视频生成的前置条件

这里的 `ready` 不再表示“看起来差不多了”，而是明确表示：

> 当前分镜已经完成信息提取确认，因此具备进入视频生成的条件。

## `ready` 的判定规则

后端统一按以下规则重算：

1. 如果镜头存在进行中的关键生成任务，状态优先为 `generating`
2. 如果 `skip_extraction = true`，状态为 `ready`
3. 如果从未提取过，状态为 `pending`
4. 如果提取过但没有任何候选项，状态为 `ready`
5. 如果所有候选项都已经处理完，状态为 `ready`
6. 其他情况为 `pending`

其中“所有候选项都处理完”指的是：

- `candidate_status = linked`
- 或 `candidate_status = ignored`

只要还有任意一条候选项处于 `pending`，镜头就不能进入 `ready`。

## `shot_extracted_candidates` 表结构职责

这张表记录的是**镜头级提取确认明细**，而不是最终资产本身。

核心字段包括：

- `shot_id`
- `candidate_type`
  - `character / scene / prop / costume`
- `candidate_name`
- `candidate_status`
  - `pending / linked / ignored`
- `linked_entity_id`
- `source`
- `payload`
- `confirmed_at`

建议理解为：

```text
一条提取候选
→ 先进入 pending
→ 用户确认后变成 linked 或 ignored
```

## 典型流转

### 路径 A：正常提取确认

```text
extract
→ shot_extracted_candidates 写入 pending
→ 用户执行关联
→ candidate 变成 linked
→ 或用户执行忽略
→ candidate 变成 ignored
→ 全部候选已 resolved
→ shot.status = ready
```

### 路径 B：明确无需提取

```text
用户设置 skip_extraction = true
→ shot.status = ready
```

### 路径 C：取消关联 / 替换

```text
原 candidate = linked
→ 用户删除关联 / 替换关联对象 / 清空 scene
→ candidate 回退到 pending
→ shot.status 重新计算
```

## 已接入的自动回写点

当前后端已经接入这些回写动作：

- 提取接口完成后，按镜头同步 `shot_extracted_candidates`
- 角色关联成功后，匹配角色候选回写为 `linked`
- 场景 / 道具 / 服装关联成功后，匹配候选回写为 `linked`
- `ShotDetail.scene_id` 设置成功后，场景候选回写为 `linked`
- 删除场景 / 道具 / 服装关联后，对应候选回退为 `pending`
- 同 index 角色被替换时，被顶掉的旧角色候选回退为 `pending`
- `scene_id` 从 A 切到 B，或清空时，旧场景候选回退为 `pending`

## 前端约束

前端页面现在必须遵守下面这条规则：

> 不再自行推导正式 `shot.status`，也不再手动把 `pending/ready` 写回本地状态。

正确做法是：

- 调后端接口
- 使用后端返回的最新 `ShotRead`
- 用 `shot_extracted_candidates` 展示待确认项

目前 `ChapterStudio` 与 `ChapterShotEditPage` 已经接入这一约束。

## OpenAPI 同步约定

这次改动新增了：

- `GET /api/v1/studio/shots/{shot_id}/extracted-candidates`
- `PATCH /api/v1/studio/shots/{shot_id}/skip-extraction`
- `PATCH /api/v1/studio/shots/extracted-candidates/{candidate_id}/link`
- `PATCH /api/v1/studio/shots/extracted-candidates/{candidate_id}/ignore`

前端在接口更新后，应主动执行：

```bash
cd front
pnpm run openapi:update
```

不要继续手写一层额外的 request 封装去复制 generated client。

## 开发建议

后续如果继续扩这条链路，优先级建议是：

1. 继续补“取消关联 -> candidate 回退”的更多边界场景
2. 让章节页、分镜列表页更多消费正式 `status`
3. 如果后续真的出现高频人工语义，再单独引入“人工标记”字段

当前阶段，不建议把人工返工、优先级、跳过等语义混进 `shots.status`。
