---
document_type: "architecture_design"
service: "MOMENTRY_CORE"
title: "Job Worker 實作計畫"
date: "2026-04-27"
version: "V1.2"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
  - "實作計畫"
  - "worker"
  - "processing_status"
ai_query_hints:
  - "查詢 Job Worker 實作計畫 的內容"
  - "Job Worker 實作計畫 的主要目的是什麼？"
  - "如何操作或實施 Job Worker 實作計畫？"
  - "processing_status 字段設計"
---

# Job Worker 實作計畫

| 項目 | 內容 |
|------|------|
| 建立者 | Warren / OpenCode |
| 建立時間 | 2026-03-24 |
| 文件版本 | V1.2 |
| 狀態 | ✅ 已實作 |

---

## 版本歷史

| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.0 | 2026-03-24 | 建立實作計畫 | OpenCode |
| V1.1 | 2026-03-25 | 實作完成，更新狀態 | OpenCode |
| V1.2 | 2026-04-27 | 添加 processing_status 字段設計說明 | OpenCode |

---

## 實作狀態

### ✅ 已完成

| 元件 | 檔案 | 狀態 |
|------|------|------|
| MonitorJob 結構 | `src/core/db/postgres_db.rs` | ✅ |
| ProcessorResult 結構 | `src/core/db/postgres_db.rs` | ✅ |
| Worker 配置 | `src/worker/config.rs` | ✅ |
| Job Worker | `src/worker/job_worker.rs` | ✅ |
| Processor Pool | `src/worker/processor.rs` | ✅ |
| Worker 模組 | `src/worker/mod.rs` | ✅ |
| PostgreSQL 表格 | `monitor_jobs`, `processor_results` | ✅ |
| 類型修復 | `i32`, `NaiveDateTime` | ✅ |

### 待整合

| 項目 | 說明 |
|------|------|
| Worker 服務啟動 | 需要加入 launchd plist |
| 監控整合 | 需要加入 MOMENTRY_CORE_MONITORING.md |
| 備份涵蓋 | 需要確認備份包含新表格 |

---

## 1. 設計決策

### 1.1 確認的設計決策

| 項目 | 決策 | 理由 |
|------|------|------|
| 觸發方式 | 輪詢（Job Worker） | 暫無可靠的 API 觸發機制 |
| 並行處理 | 最多 2 個 | 可根據 CPU/GPU 能力調整 |
| 失敗處理 | 獨立模組，部分完成可接續 | 任何模組失敗都產出狀態記錄 |
| Worker 啟動 | 獨立進程 | 隔離、易管理 |
| 並行上限調整 | 環境變數 + 預設值 | 靈活、可調整 |
| 狀態同步 | PostgreSQL + Redis | 可靠 + 即時 |

### 1.2 環境變數

| 變數 | 預設值 | 說明 |
|------|--------|------|
| `MOMENTRY_MAX_CONCURRENT` | 2 | 最大並行 processor 數 |
| `MOMENTRY_POLL_INTERVAL` | 5 | 輪詢間隔（秒） |
| `MOMENTRY_WORKER_ENABLED` | true | 是否啟用 worker |

---

## 2. 系統架構

### 2.1 完整流程圖

```
┌─────────────────────────────────────────────────────────────────────────┐
│                          檔案註冊觸發處理流程                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. SFTPGo 上傳                                                      │
│         │                                                              │
│         ▼                                                              │
│  2. Hook 呼叫 Register API                                            │
│         │                                                              │
│         ▼                                                              │
│  3. Register API                                                     │
│     ├─► ffprobe 提取 metadata                                        │
│     ├─► 寫入 videos 表                                               │
│     └─► 建立 monitor_jobs 記錄 (status=pending)                         │
│         │                                                              │
│         ▼                                                              │
│  4. Job Worker (獨立進程，輪詢機制)                                    │
│     ├─► 輪詢 pending jobs                                            │
│     ├─► 檢查 videos 表 fs_json 決定需要處理什麼                        │
│     ├─► 並行執行 processors (最多 2 個)                               │
│     └─► 更新 videos, monitor_jobs, processor_results 表                  │
│         │                                                              │
│         ▼                                                              │
│  5. 處理結果                                                          │
│     ├─► 更新 videos 表 (fs_json, psql_chunk, qvector_chunk)            │
│     ├─► 更新 monitor_jobs 表 (status, progress)                       │
│     ├─► 更新 processor_results 表 (每個模組狀態)                       │
│     └─► Redis Pub/Sub 即時進度                                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

### 2.2 Job Worker 架構

```
┌─────────────────────────────────────────────────────────────────────┐
│                      Job Worker 架構                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────┐      ┌─────────────┐      ┌─────────────┐    │
│  │ PostgreSQL  │ ───▶ │  Worker     │ ───▶ │ Processor   │    │
│  │ Job Queue  │      │  Loop       │      │ Pool        │    │
│  └─────────────┘      └──────┬──────┘      └──────┬──────┘    │
│                              │                      │               │
│                              ▼                      ▼               │
│                      ┌─────────────┐      ┌─────────────┐       │
│                      │ Video State │      │ Processor 1 │       │
│                      │ Check      │      │ (ASR/YOLO) │       │
│                      └─────────────┘      ├─────────────┤       │
│                                          │ Processor 2 │       │
│                                          │ (CUT/OCR)   │       │
│                                          └─────────────┘       │
│                                                                     │
│  Redis ──── Pub/Sub ──── 即時進度                                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
```

---

## 3. 資料庫結構

### 3.1 Migration 檔案

**檔案**: `migrations/003_job_worker.sql`

```sql
-- ================================================================
-- Migration 003: Job Worker System
-- ================================================================

-- 3.1.1 更新 videos 表
ALTER TABLE videos ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'pending';
ALTER TABLE videos ADD COLUMN IF NOT EXISTS user_id BIGINT;
ALTER TABLE videos ADD COLUMN IF NOT EXISTS job_id INTEGER REFERENCES monitor_jobs(id);

COMMENT ON COLUMN videos.status IS 'pending, processing, completed, failed';
COMMENT ON COLUMN videos.user_id IS 'WordPress user ID';
COMMENT ON COLUMN videos.job_id IS 'Associated monitor_jobs ID';

-- 3.1.2 更新 monitor_jobs 表
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS video_id BIGINT REFERENCES videos(id);
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS user_id BIGINT;
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS processors VARCHAR(20)[];
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS completed_processors VARCHAR(20)[];
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS failed_processors VARCHAR(20)[];

COMMENT ON COLUMN monitor_jobs.processors IS 'Processors to run: asr, cut, yolo, ocr, face, pose, asrx';
COMMENT ON COLUMN monitor_jobs.completed_processors IS 'Successfully completed processors';
COMMENT ON COLUMN monitor_jobs.failed_processors IS 'Failed processors';

-- 3.1.3 新增 processor_results 表
CREATE TABLE IF NOT EXISTS processor_results (
    id SERIAL PRIMARY KEY,
    job_id INTEGER REFERENCES monitor_jobs(id) ON DELETE CASCADE,
    video_id BIGINT REFERENCES videos(id) ON DELETE CASCADE,
    processor VARCHAR(20) NOT NULL,
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
    output_path TEXT,
    started_at TIMESTAMP,
    completed_at TIMESTAMP,
    error_message TEXT,
    progress_total INT DEFAULT 0,
    progress_current INT DEFAULT 0,
    last_checkpoint JSONB,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    UNIQUE(job_id, processor)
);

CREATE INDEX IF NOT EXISTS idx_processor_results_job ON processor_results(job_id);
CREATE INDEX IF NOT EXISTS idx_processor_results_video ON processor_results(video_id);
CREATE INDEX IF NOT EXISTS idx_processor_results_status ON processor_results(status);

COMMENT ON TABLE processor_results IS 'Tracks individual processor execution status';
COMMENT ON COLUMN processor_results.status IS 'pending, running, completed, failed, skipped';

-- 3.1.4 更新 videos 表標記欄位用途
COMMENT ON COLUMN videos.fs_video IS 'Video file exists on filesystem';
COMMENT ON COLUMN videos.fs_json IS 'All processor JSON files generated';
COMMENT ON COLUMN videos.fs_chunks IS 'Chunk files generated';
COMMENT ON COLUMN videos.fs_vectors IS 'Vector files generated';
COMMENT ON COLUMN videos.psql_chunk IS 'Chunks stored in PostgreSQL';
COMMENT ON COLUMN videos.pvector_chunk IS 'Vectors stored in PostgreSQL';
COMMENT ON COLUMN videos.qvector_chunk IS 'Vectors stored in Qdrant';
```

### 3.2 表關係圖

```
videos                              monitor_jobs
┌──────────────────────┐         ┌──────────────────────┐
│ id (PK)              │◄────────│ video_id (FK)       │
│ uuid                 │         │ user_id              │
│ status               │         │ processors[]          │
│ fs_video             │         │ completed_processors[]│
│ fs_json              │         │ failed_processors[]   │
│ job_id (FK)─────────┼────────►│ status               │
│ user_id              │         │ id (PK)              │
└──────────────────────┘         └──────────────────────┘
                                       │
                                       │
                              processor_results
                              ┌──────────────────────┐
                              │ job_id (FK)          │
                              │ video_id (FK)        │
                              │ processor            │
                              │ status               │
                              │ progress_current     │
                              │ last_checkpoint      │
                              │ id (PK)              │
                              └──────────────────────┘
```

---

## 4. 模組並行策略

### 4.1 模組分類

| 模組 | 資源需求 | 獨立性 | 建議並行 |
|------|----------|--------|----------|
| ASR | GPU/CPU | 高 | ✅ 可並行 |
| CUT | CPU | 高 | ✅ 可並行 |
| YOLO | GPU | 中 | ✅ 可並行 |
| OCR | GPU/CPU | 高 | ✅ 可並行 |
| Face | GPU | 中 | ✅ 可並行 |
| Pose | GPU | 中 | ✅ 可並行 |
| ASRX | GPU/CPU | 高 | ✅ 可並行 |

### 4.2 建議並行組合

| 組合 | 模組 1 | 模組 2 | 說明 |
|------|---------|---------|------|
| GPU+CPU | YOLO/Pose/Face | ASR/CUT/OCR | 平衡負載 |
| 雙GPU | YOLO | Pose | 雙 GPU 卡片 |
| 雙CPU | ASR | CUT/OCR | 無 GPU 時 |

### 4.3 Worker 配置

```rust
// src/worker/config.rs

#[derive(Debug, Clone)]
pub struct WorkerConfig {
    pub max_concurrent: usize,      // 預設 2
    pub poll_interval_secs: u64,    // 預設 5
    pub enabled: bool,              // 預設 true
}

impl Default for WorkerConfig {
    fn default() -> Self {
        Self {
            max_concurrent: 2,
            poll_interval_secs: 5,
            enabled: true,
        }
    }
}

impl WorkerConfig {
    pub fn from_env() -> Self {
        Self {
            max_concurrent: std::env::var("MOMENTRY_MAX_CONCURRENT")
                .ok()
                .and_then(|v| v.parse().ok())
                .unwrap_or(2),
            poll_interval_secs: std::env::var("MOMENTRY_POLL_INTERVAL")
                .ok()
                .and_then(|v| v.parse().ok())
                .unwrap_or(5),
            enabled: std::env::var("MOMENTRY_WORKER_ENABLED")
                .ok()
                .map(|v| v != "false")
                .unwrap_or(true),
        }
    }
}
```

---

## 5. 失敗處理機制

### 5.1 設計原則

```
每個模組獨立處理：
- 成功 → 產出完整 .json，status=completed
- 失敗 → 產出 .json 包含 error 狀態，status=failed
- 部分完成 → 可從 checkpoint 繼續，status=running
```

### 5.2 Processor 輸出格式

```json
{
  "processor": "asr",
  "status": "completed|failed|partial",
  "completed_at": "2026-03-24T12:00:00Z",
  "result": { ... },
  "error": null,
  "last_checkpoint": {
    "frame": 5000,
    "timestamp": 180.5
  }
}
```

### 5.3 失敗處理流程

```rust
async fn run_processor(&self, module: &str, video: &Video) -> Result<()> {
    let output_path = self.get_output_path(video, module);
    
    match self.execute_processor(module, video, &output_path).await {
        Ok(result) => {
            // 成功：更新狀態
            self.db.update_processor_status(job_id, module, "completed").await?;
            self.publish_progress(job_id, module, 100).await?;
        }
        Err(e) => {
            // 失敗：仍然保存部分結果
            let partial_result = self.get_partial_result(&output_path);
            self.db.update_processor_status(job_id, module, "failed").await?;
            self.db.save_error_message(job_id, module, &e.to_string()).await?;
            
            // 記錄錯誤但不中斷其他模組
            tracing::warn!("Processor {} failed: {}", module, e);
        }
    }
    
    Ok(())
}
```

---

## 6. 實作結構

### 6.1 目錄結構

```
src/
├── worker/
│   ├── mod.rs           # Worker 模組導出
│   ├── config.rs        # Worker 配置
│   ├── worker.rs        # Worker 主邏輯
│   ├── processor.rs     # Processor 執行器
│   ├── queue.rs        # Job 佇列管理
│   └── progress.rs      # 進度追蹤
├── api/
│   └── server.rs        # 更新 Register API
└── main.rs              # 新增 worker 命令
```

### 6.2 核心模組

#### 6.2.1 Worker Config (`src/worker/config.rs`)

```rust
pub struct WorkerConfig {
    pub max_concurrent: usize,
    pub poll_interval_secs: u64,
    pub enabled: bool,
}

impl WorkerConfig {
    pub fn from_env() -> Self { ... }
}
```

#### 6.2.2 Worker Loop (`src/worker/worker.rs`)

```rust
pub struct JobWorker {
    db: PostgresDb,
    redis: RedisCache,
    config: WorkerConfig,
    semaphore: Arc<Semaphore>,
}

impl JobWorker {
    pub async fn run(&self) -> Result<()> {
        loop {
            if self.config.enabled {
                self.process_pending_jobs().await?;
            }
            tokio::time::sleep(Duration::from_secs(self.config.poll_interval_secs)).await;
        }
    }
    
    async fn process_pending_jobs(&self) -> Result<()> {
        // 1. 檢查並發數
        // 2. 取得 pending jobs
        // 3. 分配給 worker pool
        // 4. 並行執行 processors
    }
}
```

#### 6.2.3 Processor Pool (`src/worker/processor.rs`)

```rust
pub struct ProcessorPool {
    max_concurrent: usize,
}

impl ProcessorPool {
    pub async fn execute(&self, job: &Job, video: &Video) -> Result<ProcessorResult> {
        // 根據 videos 表決定需要執行哪些 processor
        // 並行執行最多 2 個
        // 處理失敗但不中斷其他 processor
    }
}
```

---

## 7. API 端點設計

### 7.1 新增端點

| 端點 | 方法 | 說明 |
|------|------|------|
| `/api/v1/jobs` | GET | 列出所有 jobs |
| `/api/v1/jobs/:uuid` | GET | 取得特定 job 詳細 |
| `/api/v1/jobs/:uuid/retry` | POST | 重試失敗的 processor |
| `/api/v1/jobs/:uuid/cancel` | POST | 取消 job |

### 7.2 端點詳情

#### GET /api/v1/jobs

```json
Response:
{
    "jobs": [
        {
            "id": 1,
            "uuid": "abc123def456",
            "status": "running",
            "progress": 60,
            "processors": ["asr", "cut", "yolo", "ocr", "face", "pose"],
            "completed": ["asr", "cut", "yolo"],
            "failed": []
        }
    ]
}
```

#### GET /api/v1/jobs/:uuid

```json
Response:
{
    "id": 1,
    "uuid": "abc123def456",
    "video_id": 10,
    "status": "running",
    "processors": {
        "asr": {"status": "completed", "progress": 100},
        "cut": {"status": "completed", "progress": 100},
        "yolo": {"status": "running", "progress": 45, "current": 5000, "total": 11000},
        "ocr": {"status": "pending"},
        "face": {"status": "pending"},
        "pose": {"status": "pending"}
    },
    "created_at": "2026-03-24T12:00:00Z",
    "started_at": "2026-03-24T12:01:00Z"
}
```

---

## 8. Redis Key 設計

### 8.1 現有 Key 保持

```bash
momentry:job:{uuid}                    # Job Hash
momentry:job:{uuid}:processor:{name}   # Processor Hash
momentry:progress:{uuid}               # Pub/Sub Channel
momentry:jobs:active                   # Set: 運行中 UUIDs
momentry:jobs:completed                 # Set: 完成 UUIDs
momentry:jobs:failed                   # Set: 失敗 UUIDs
```

### 8.2 進度更新時序

```
Processor 執行
    │
    ├─► 每秒更新 Redis Hash (即時)
    │
    ├─► 每 10% 或完成時更新 PostgreSQL (持久)
    │
    └─► 失敗時立即更新 PostgreSQL (錯誤記錄)
```

---

## 9. 實作順序

### Phase 1: 資料庫遷移

| 任務 | 說明 |
|------|------|
| 1.1 | 建立 `migrations/003_job_worker.sql` |
| 1.2 | 更新 `postgres_db.rs` 對應的 struct |
| 1.3 | 執行 migration 驗證 |

### Phase 2: Worker 框架

| 任務 | 說明 |
|------|------|
| 2.1 | 建立 `src/worker/mod.rs` |
| 2.2 | 建立 `src/worker/config.rs` |
| 2.3 | 建立 `src/worker/worker.rs` |
| 2.4 | 建立 `src/worker/processor.rs` |

### Phase 3: Register API 整合

| 任務 | 說明 |
|------|------|
| 3.1 | 修改 `src/api/server.rs` 的 register 函數 |
| 3.2 | 加入建立 monitor_jobs 的邏輯 |
| 3.3 | 更新 videos 表 status 欄位 |

### Phase 4: Processor 執行

| 任務 | 說明 |
|------|------|
| 4.1 | 實作 processor 並行執行（最多 2 個） |
| 4.2 | 實作失敗處理（保存部分結果） |
| 4.3 | 實作 checkpoint 恢復 |

### Phase 5: 進度追蹤

| 任務 | 說明 |
|------|------|
| 5.1 | Redis Pub/Sub 整合 |
| 5.2 | PostgreSQL 定期同步 |
| 5.3 | API 進度端點更新 |

### Phase 6: API 端點

| 任務 | 說明 |
|------|------|
| 6.1 | GET /api/v1/jobs |
| 6.2 | GET /api/v1/jobs/:uuid |
| 6.3 | POST /api/v1/jobs/:uuid/retry |
| 6.4 | POST /api/v1/jobs/:uuid/cancel |

### Phase 7: CLI 命令

| 任務 | 說明 |
|------|------|
| 7.1 | `cargo run -- worker` 命令 |
| 7.2 | Worker 啟動/停止/狀態顯示 |
| 7.3 | launchd plist 設定 |

### Phase 8: 測試

| 任務 | 說明 |
|------|------|
| 8.1 | 單元測試 |
| 8.2 | 端到端測試 |
| 8.3 | 失敗處理測試 |
| 8.4 | 並行執行測試 |

---

## 10. CLI 命令

### 10.1 Worker 命令

```bash
# 啟動 worker
cargo run -- worker

# 顯示 worker 幫助
cargo run -- worker --help
```

### 10.2 環境變數

```bash
# Worker 配置
export MOMENTRY_MAX_CONCURRENT=2
export MOMENTRY_POLL_INTERVAL=5
export MOMENTRY_WORKER_ENABLED=true

# 現有環境變數
export DATABASE_URL=postgres://accusys@localhost:5432/momentry
export REDIS_URL=redis://:accusys@localhost:6379
```

---

## 11. 預估工時

| Phase | 任務 | 預估工時 |
|-------|------|----------|
| 1 | 資料庫遷移 | 2h |
| 2 | Worker 框架 | 4h |
| 3 | Register API 整合 | 2h |
| 4 | Processor 執行 | 4h |
| 5 | 進度追蹤 | 2h |
| 6 | API 端點 | 3h |
| 7 | CLI 命令 | 2h |
| 8 | 測試 | 4h |
| **總計** | | **23h** |

---

## 12. 參考文件

| 文件 | 用途 |
|------|------|
| `docs_v1.0/OPERATIONS/MOMENTRY_CORE_MONITORING.md` | 監控系統規範 |
| `docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md` | Redis Key 設計 |
| `docs_v1.0/ARCHITECTURE/PROCESSING_PIPELINE.md` | 處理流程 |
| `docs_v1.0/ARCHITECTURE/CHUNK_DESIGN.md` | 資料庫設計 |
| `docs_v1.0/REFERENCE/API_REFERENCE.md` | API 參考 |

---

## 13. 附錄

### A. 狀態機

```
                    ┌──────────────┐
                    │   PENDING   │
                    └──────┬───────┘
                           │ register 後
                           ▼
                    ┌──────────────┐
              ┌─────▶│ PROCESSING  │◀──────┐
              │      └──────┬───────┘       │
              │             │                │
         部分失敗      all completed      全部失敗
              │             │                │
              ▼             ▼                ▼
       ┌──────────┐ ┌──────────┐ ┌──────────┐
       │ PARTIAL   │ │COMPLETED │ │  FAILED  │
       └──────────┘ └──────────┘ └──────────┘
```

### B. videos 表 status 欄位

| 值 | 說明 |
|------|------|
| `pending` | 已註冊，等待處理 |
| `processing` | 處理中 |
| `completed` | 所有處理完成 |
| `failed` | 處理失敗 |

### B.1 videos 表 processing_status 欄位

| 值 | 說明 | 適用場景 |
|------|------|----------|
| `REGISTERED` | 已註冊 | 新註冊的視頻，尚未觸發處理 |
| `PENDING` | 等待處理 | 已觸發處理，等待作業分配 |
| `PROBING` | 探測中 | ffprobe 分析執行中 |
| `ASR` | ASR 處理中 | ASR 作業執行中 |
| `OCR` | OCR 處理中 | OCR 作業執行中 |
| `YOLO` | YOLO 處理中 | YOLO 作業執行中 |
| `FACE` | 人臉偵測中 | Face 作業執行中 |
| `POSE` | 姿態估計中 | Pose 作業執行中 |
| `CUT` | 分塊處理中 | Cut 作業執行中 |
| `ASRX` | 說話者分離中 | ASRX 作業執行中 |
| `COMPLETED` | 完成 | 所有處理完成 |
| `FAILED` | 失敗 | 處理失敗 |
| `PAUSED` | 暫停 | 斷點續傳暫停狀態 |
| `RESUMING` | 恢復中 | 斷點續傳恢復中 |

#### B.1.1 status 與 processing_status 的關係

| status | processing_status | 說明 |
|--------|-------------------|------|
| `pending` | `REGISTERED` | 新註冊，Portal顯示「已註冊」（藍色） |
| `processing` | `PENDING` | 已觸發，Portal顯示「等待處理」（黃色） |
| `processing` | `PROBING`/`ASR`/... | 各處理器執行中，Portal顯示處理器名稱（靛藍） |
| `completed` | `COMPLETED` | 完成，Portal顯示「已完成」（綠色） |
| `failed` | `FAILED` | 失敗，Portal顯示「處理失敗」（紅色） |

#### B.1.2 Portal顯示優先級

Portal 優先使用 `processing_status`（詳細狀態），Fallback 使用 `status`（基本狀態）。

#### B.1.3 processing_status JSONB 結構（V1.2 起）

從 V1.2 起，`processing_status` 改為 **JSONB** 格式，支持多層級進度追蹤。

詳細規範請參考: `REFERENCE/PROCESSING_STATUS_JSONB_SPEC.md`

##### JSONB 主要字段

| 字段 | 類型 | 說明 |
|------|------|------|
| `phase` | String | 當前階段（PROCESSING, COMPLETED, FAILED） |
| `active_processors` | Array[String] | 正在執行的處理器列表（大寫） |
| `total_frames` | Integer | 影片總帧數 |
| `processing_summary` | Object | 處理器完成狀態總覽 |
| `pre_chunks_summary` | Object | pre_chunks 表絕計（按處理器） |
| `chunks_summary` | Object | chunks 表絕計（按 Rule） |
| `agents` | Object | Agent 任務狀態（5W1H, Translation） |
| `vectorization_summary` | Object | 向量化絕計 |
| `progress` | Object | 各處理器詳細進度 |

##### JSONB 範例（處理中）

```json
{
  "phase": "PROCESSING",
  "active_processors": ["YOLO", "OCR"],
  "total_frames": 412343,
  "progress": {
    "YOLO": {
      "current_frame": 25000,
      "percentage": 6.0,
      "status": "running"
    }
  }
}
```

##### JSONB 範例（完成）

```json
{
  "phase": "COMPLETED",
  "active_processors": [],
  "pre_chunks_summary": {
    "total_records": 25000,
    "by_processor": {
      "asr": {"records": 1466},
      "yolo": {"records": 11000}
    }
  },
  "chunks_summary": {
    "total_chunks": 2798,
    "by_rule": {
      "rule_1": {"chunks_count": 1466},
      "rule_3": {"chunks_count": 1332}
    }
  },
  "agents": {
    "5w1h": {"status": "completed"}
  }
}
```

##### SQL 查詢範例

```sql
-- 取得 phase
SELECT processing_status->>'phase' FROM videos WHERE uuid = 'xxx';

-- 取得 active_processors
SELECT processing_status->'active_processors' FROM videos WHERE uuid = 'xxx';

-- 取得 pre_chunks 絕計
SELECT processing_status->'pre_chunks_summary'->>'total_records' FROM videos;
```

---

### C. processor_results 表 status 欄位

| 值 | 說明 |
|------|------|
| `pending` | 等待執行 |
| `running` | 執行中 |
| `completed` | 執行成功 |
| `failed` | 執行失敗 |
| `skipped` | 跳過（如檔案已存在） |
