# Momentry Core - Metadata 及 處理器總覽

本文檔說明 Momentry Core 中 chunks 資料表的 metadata 結構，以及各類處理器的輸出欄位。

## 1. Chunks 資料表結構

### 1.1 直接欄位 (Direct Columns)

這些欄位直接儲存於 chunks 資料表中：

| 欄位 | 類型 | 來源處理器 | 說明 |
|------|------|----------|------|
| `id` | serial | 系統 | 主鍵 |
| `uuid` | varchar(32) | 系統 | 影片 UUID |
| `chunk_id` | varchar(64) | 系統 | Chunk ID (如 sentence_0001) |
| `chunk_index` | integer | 系統 | 順序編號 |
| `chunk_type` | varchar(32) | 系統 | sentence/cut/time |
| `text_content` | text | ASR processor | 語音轉文字結果 |
| `content` | jsonb | - | 原始內容 (rule, data 等) |
| `metadata` | jsonb | 多個處理器 | 參閱下方 1.2 |
| `visual_stats` | jsonb | add_yolo_to_chunks.py | YOLO 識別結果 |
| `speaker_ids` | text[] | ASRX processor | 說話者 ID 陣列 |
| `face_ids` | integer[] | Face processor | 臉部 ID 陣列 |
| `summary_text` | text | generate_chunk_summaries.py | LLM 生成摘要 |
| `parent_chunk_id` | varchar(64) | 系統 | 父 chunk ID |
| `fps` | double | ffprobe | 幀率 |
| `start_frame` | bigint | ffprobe | 開始幀 |
| `end_frame` | bigint | ffprobe | 結束幀 |
| `metadata_version` | integer | 系統 | Metadata 版本 (5W1H, identity, visual) |
| `content_version` | integer | 系統 | Content 版本 (text_content, summary_text) |
| `created_at` | timestamp | 系統 | 建立時間 |
| `updated_at` | timestamp | 系統 | 最後更新時間 |

### 版本控制說明

| 欄位 | 說明 | 遞增時機 |
|------|------|----------|
| `metadata_version` | Metadata 版本 | 更新 5W1H, identity, visual 時 |
| `content_version` | Content 版本 | 更新 text_content, summary_text 時 |
| `updated_at` | 最後更新時間 | 任何更新時自動更新 |

**判別更新語法**:

```sql
-- 檢查哪些 chunk 需要重新生成 5W1H
SELECT chunk_id, metadata_version, content_version, updated_at
FROM dev.chunks 
WHERE metadata_version < 1;

-- 檢查特定時間後的更新
SELECT chunk_id, updated_at
FROM dev.chunks 
WHERE updated_at > '2024-01-01';

-- 檢查版本差異 (需要重新處理)
SELECT c.* 
FROM dev.chunks c
WHERE c.metadata_version < 
    (SELECT MAX(metadata_version) FROM dev.chunks WHERE uuid = c.uuid);
```

## 11. 動態 Metadata 管理

### 11.1 欄位動態增減

Metadata JSONB 支援動態欄位，可根據處理器執行結果動態添加：

```python
# 動態添加欄位
metadata = existing_metadata or {}
metadata[field_name] = value
UPDATE chunks SET metadata = metadata || %s::jsonb
```

### 11.2 常見動態欄位

| 欄位 | 新增時機 | 來源處理器 |
|------|----------|------------|
| `chunk_5w1h` | 生成 summary | generate_chunk_summaries.py |
| `chunk_identity` | ASRX/Face 執行後 | 來源欄位聚合 |
| `chunk_visual` | YOLO 執行後 | add_yolo_to_chunks.py |
| `chunk_emotion` | 情緒分析 | future emotion_processor.py |
| `chunk_pose` | 姿勢辨識 | future pose_processor.py |
| `chunk_sentiment` | 情感分析 | future sentiment_processor.py |

### 11.3 版本升級策略

每次重大更新時遞增版本號：

```python
if新增重大欄位:
    metadata_version += 1
    # 記錄變更日誌
```

### 11.4 重跑機制

```bash
# 重跑特定版本後的 chunk
python scripts/generate_chunk_summaries.py --uuid <uuid> --min-version 1

# 查看版本分佈
SELECT metadata_version, COUNT(*) 
FROM dev.chunks 
GROUP BY metadata_version;
```

### 1.2 Metadata 結構 (JSONB)

`metadata` 欄位包含多個子欄位，由不同處理器產生：

```json
{
  "chunk_5w1h": {
    "who": "演員或角色",
    "what": "主要動作或事件",
    "when": "時間上下文",
    "where": "地點",
    "why": "目的或原因",
    "how": "表達方式"
  },
  "chunk_identity": {
    "speakers": ["speaker_001", "speaker_002"],
    "faces": ["face_1", "face_3"]
  },
  "chunk_visual": {
    "objects": ["person", "car", "tree"],
    "places": ["street", "office"]
  },
  "structured_summary": {
    "who": "Parent 級別角色",
    "what": "Parent 級別動作",
    ...
  }
}
```

| 子欄位 | 類型 | 來源處理器 | 說明 |
|--------|------|----------|------|
| `chunk_5w1h` | jsonb | generate_chunk_summaries.py | Chunk 級別的 5W1H + Emotion + Actions |
| `chunk_5w1h.who` | string | person | 人物名稱 (含來源標記) |
| `chunk_5w1h.what` | string | action | 具體動作 |
| `chunk_5w1h.when` | string | position | 場景中位置 (beginning/middle/end) |
| `chunk_5w1h.where` | string | location | 地點 |
| `chunk_5w1h.why` | string | purpose | 目的 |
| `chunk_5w1h.how` | string | manner | 表達方式 |
| `chunk_5w1h.emotion` | string | emotion | 情緒/語氣 |
| `chunk_5w1h.actions` | string[] | verbs | 動作動詞 |
| `chunk_identity` | jsonb | 來源欄位聚合 | speaker_ids + face_ids 資訊 |
| `chunk_visual` | jsonb | add_yolo_to_chunks.py | YOLO 物體識別結果 |
| `structured_summary` | jsonb | regenerate_parent_5w1h.py | Parent 級別 5W1H + tone + characters + key_events |

### chunk_5w1h 欄位說明 (Chunk 級)

| 欄位 | 類型 | 說明 | 範例 |
|------|------|------|------|
| `who` | string | 此 chunk 出現的角色 (含來源) | "John (SPEAKER_1), Mary (face_3)" |
| `what` | string | 此 chunk 的具體動作 | "Giving warning" |
| `when` | string | 相對時間位置 | "Mid-scene" |
| `where` | string | 地點 (如提及) | "Near taxi" |
| `why` | string | 此動作的目的 | "Warn about danger" |
| `how` | string | 表達/呈現方式 | "Urgent tone" |
| `emotion` | string | 情緒/語氣 | "Fearful, urgent" |
| `actions` | string[] | 動作動詞 | ["run", "shout", "warn"] |

**Prompt 增強內容**:
- 從 person_identities 取得驗證的人物名稱
- 包含 speaker_id 和 face_id 來源標記
- 視覺辨識: objects, places, actions
- Time range 傳入 chunk 時間範圍
- Emotion + Actions 額外欄位

### chunk_identity 欄位說明

| 欄位 | 類型 | 說明 | 範例 |
|------|------|------|------|
| `speakers` | string[] | 說話者 ID | ["speaker_001", "speaker_002"] |
| `faces` | string[] | 臉部 ID | ["face_1", "face_3"] |
| `global_identity` | string | 對應的全局人物 ID | "person_001" |
| `person_name` | string | 識別的人物名稱 | "John" |

> 說明：
> - `speakers`/`faces` 來自 ASRX/Face processor
> - `global_identity` 來自 `person_identities` 表，關聯 face_identity_id
> - `person_name` 來自 `person_identities.name`，經過確認的人物名稱

### 全域人物 Identity (person_identities 表)

每個影片會識別並記錄出現的人物，儲存於 `dev.person_identities` 表：

| 欄位 | 類型 | 說明 |
|------|------|------|
| `person_id` | varchar(255) | 人物唯一 ID (如 person_001) |
| `name` | varchar(255) | 人物名稱 (可確認) |
| `speaker_id` | varchar(255) | 對應的說話者 ID |
| `file_uuid` | varchar(255) | 影片 UUID |
| `face_identity_id` | integer | 對應的 global identity |
| `appearance_count` | integer | 出現次數 |
| `first_appearance_time` | double | 首次出現時間 |
| `last_appearance_time` | double | 最後出現時間 |
| `confidence` | double | 辨識信心度 |
| `is_confirmed` | boolean | 是否已確認 |

### 全域 Identity (face_identities 表)

跨影片的全局人物身份：

| 欄位 | 類型 | 說明 |
|------|------|------|
| `id` | serial | 主鍵 |
| `face_id` | integer | 臉部 ID |
| `name` | varchar(255) | 識別姓名 |
| `embedding` | blob | 人臉向量特徵 |

### 人物識別流程

Momentry 的人物識別分為三個層級：

```
層級 1: 原始識別 (chunks 表)
├── chunks.face_ids    → 臉部 ID (local to chunk)
└── chunks.speaker_ids → 說話者 ID (local to chunk)

層級 2: 影片級識別 (person_identities 表)
├── person_id         → 人物 ID (影片內唯一)
├── name             → 識別出的人物名稱 (如 "John")  
├── speaker_id       → 對應的說話者
└── face_identity_id → 對應的全局 Identity

層級 3: 全局身份 (face_identities 表)
├── id              → 全局唯一 ID
├── face_id         → 臉部特徵 ID
├── name            → 確認的姓名
└── embedding      → 人臉向量 (用於比對)
```

**識別流程說明**:

```
Step 1: ASRX Processor
         chunks.speaker_ids ← 說話者分離
         
Step 2: Face Processor  
         chunks.face_ids ← 臉部偵測

Step 3: Auto-identify
         person_identities ← 合併 speaker + face (影片級)

Step 4: Global Matching
         face_identities ← 人臉向量比對 (全局 Identity)
                   ↑
         合併相同人臉者為同一 Identity
```

**命名原則**:

- `person_id` = 角色名 (如 "John", "Adam")
- 而非 "Person_8"
- 透過 speaker 對應 + 手動確認

**範例**:

```sql
-- 取得影片中的人物列表
SELECT person_id, name, speaker_id, appearance_count
FROM dev.person_identities 
WHERE file_uuid = '384b0ff44aaaa1f14cb2cd63b3fea966'
ORDER BY appearance_count DESC;

-- 取得 chunk 的人物
SELECT c.chunk_id, pi.name, pi.speaker_id
FROM dev.chunks c
JOIN dev.person_identities pi ON c.uuid = pi.file_uuid
WHERE c.chunk_id = 'sentence_0001';
```

### 取得 chunk 的人物資訊

```sql
-- 取得某 chunk 的人物
SELECT pi.name, pi.speaker_id, pi.appearance_count
FROM dev.person_identities pi
JOIN dev.chunks c ON c.uuid = pi.file_uuid
WHERE c.chunk_id = 'sentence_0001';
```

### chunk_visual 欄位說明

| 欄位 | 類型 | 說明 | 範例 |
|------|------|------|------|
| `objects` | string[] | YOLO 識別物體 | ["person", "car", "tree"] |
| `places` | string[] | Places365 識別地點 | ["street", "office"] |

## 2. 處理器對照表

### 2.1 ASR 處理器 (語音辨識)

**用途**：將影片音軌轉換為文字

| 處理器 | 輸出欄位 | 說明 |
|--------|---------|------|
| asr_processor_small_multilingual.py | text_content | Small 模型，多語言 |
| asr_processor_simplified.py | text_content | 簡化版 |
| asr_processor_contract_v1.py | text_content | 契約版本 v1 |
| asr_processor_contract_v2.py | text_content | 契約版本 v2 |

**輸出**：
- `text_content`: 語音轉文字結果
- 寫入 `chunks.content` 和 `chunks.text_content`

### 2.2 ASRX 處理器 (增強說話者辨識)

**用途**：說話者分離 (Diarization)

| 處理器 | 輸出欄位 | 說明 |
|--------|---------|------|
| asrx_processor.py | speaker_ids | 標準版 |
| asrx_processor_contract_v1.py | speaker_ids | 契約版 v1 |

**輸出**：
- `speaker_ids`: 說話者 ID 陣列，如 `["speaker_001", "speaker_002"]`
- 目前為空 `{}`，需執行後才會填充

### 2.3 Face 處理器 (臉部偵測)

**用途**：偵測並追蹤人臉

| 處理器 | 輸出欄位 | 說明 |
|--------|---------|------|
| analyze_video_faces.py | face_ids | 臉部偵測 |

**輸出**：
- `face_ids`: 臉部 ID 陣列，如 `[1, 3, 5]`
- 目前為空 `{}`，需執行後才會填充

### 2.4 YOLO 處理器 (物體識別)

**用途**：識別場景中的物體和地點

| 處理器 | 輸出欄位 | 說明 |
|--------|---------|------|
| add_yolo_to_chunks.py | visual_stats, chunk_visual | YOLO + Places365 |

**輸出**：
- `visual_stats`: 原始識別結果
- `metadata.chunk_visual`: 簡化格式 `{objects: [...], places: [...]}`

### 2.5 Summary 處理器 (生成摘要)

**用途**：生成 chunk 摘要和 5W1H 分析

| 處理器 | 輸出欄位 | 說明 |
|--------|---------|------|
| generate_chunk_summaries.py | summary_text, chunk_5w1h, chunk_identity, chunk_visual | LLM 生成 |
| regenerate_parent_5w1h.py | structured_summary | Parent 場景級 5W1H |

**輸入**：
- chunk.text_content
- parent_chunks.summary_text
- parent_chunks.metadata.structured_summary
- chunk.speaker_ids (用於 chunk_identity)
- chunk.face_ids (用於 chunk_identity)
- chunk.visual_stats (用於 chunk_visual)

**輸出**：
- `summary_text`: 2-3 句摘要
- `metadata.chunk_5w1h`: Who/What/When/Where/Why/How
- `metadata.chunk_identity`: speakers, faces
- `metadata.chunk_visual`: objects, places

## 3. Parent Chunks 結構

Parent chunks 代表場景 (scene) 層級：

| 欄位 | 類型 | 說明 |
|------|------|------|
| `id` | serial | 主鍵 |
| `uuid` | varchar(32) | 影片 UUID |
| `scene_order` | integer | 場景順序 |
| `summary_text` | text | 場景摘要 (LLM 生成) |
| `metadata` | jsonb | 包含 structured_summary |

### Parent Metadata 結構

```json
{
  "structured_summary": {
    "who": "主要角色",
    "what": "主要事件",
    "when": "時間線",
    "where": "地點",
    "why": "動機",
    "how": "方式",
    "tone": ["緊張", "懸疑", "溫馨"],
    "characters": ["角色A", "角色B", "角色C"],
    "key_events": ["事件1", "事件2", "事件3"],
    "summary_5lines": "5行摘要..."
  },
  "auto_generated_by": "gemma4",
  "chunk_count": 885
}
```

### structured_summary 欄位說明

| 欄位 | 類型 | 說明 | 範例 |
|------|------|------|------|
| `who` | string | 主要角色 | "Mr. Balletman, Adam" |
| `what` | string | 主要動作或事件 | "Escape attempt" |
| `when` | string | 時間上下文 | "During critical moment" |
| `where` | string | 地點 | "Near taxi" |
| `why` | string | 動機或原因 | "Evade capture" |
| `how` | string | 執行方式 | "Quickly moving to taxi" |
| `tone` | string[] | 語氣/情緒 | ["Urgent", "Tense", "Fearful"] |
| `characters` | string[] | 場景中的角色 | ["Mr. Balletman", "Adam", "Antagonist"] |
| `key_events` | string[] | 關鍵事件 | ["Decision to flee", "Warning given"] |
| `summary_5lines` | string | 5行摘要 | "Line 1\nLine 2..." |

## 4. Chunk 類型說明

| 類型 | 需要搜尋 | 說明 |
|------|----------|------|
| `sentence` | ✓ | 有 text_content，需向量化存入 Qdrant |
| `cut` | ✗ | 場景剪輯點，無文字內容 |
| `time` | ✗ | 時間區間標記，無文字 |

**搜尋適用性**：
- sentence: 有文字內容，可進行語意搜尋
- cut/time: 無文字，僅供時間定位使用

## 5. 處理流程 (Pipeline)

```
1. ffprobe → 取得影片資訊 (fps, frame count)
2. ASR processor → text_content
3. [ASRX processor] → speaker_ids (選用)
4. [Face processor] → face_ids (選用)
5. add_yolo_to_chunks.py → visual_stats
6. generate_chunk_summaries.py → summary_text + metadata
7. [vectorize_chunk_summaries.py] → Qdrant 向量
```

## 6. Qdrant Collections

| Collection | 向量類型 | 用途 |
|------------|----------|------|
| `momentry_dev_chunk_summaries` | nomic-embed-text | Chunk summary 語意搜尋 |
| `momentry_dev_vectors` | 原始向量 | 備用 |

## 7. API 回傳格式

Chunk Detail API 合併 chunk 和 parent 的 metadata：

```
metadata
├── chunk_5w1h         (chunk 級)
├── chunk_identity     (chunk 級)  
├── chunk_visual      (chunk 級)
├── structured_summary (parent 級) ← 只在有 parent 時
├── auto_generated_by
└── chunk_count
```

## 8. 執行狀態檢查

```bash
# 檢查 summary 生成進度
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) as total, 
       COUNT(CASE WHEN summary_text IS NOT NULL THEN 1 END) as generated 
FROM dev.chunks WHERE chunk_type = 'sentence';"

# 檢查執行中的處理器
ps aux | grep -E "processor|generate" | grep -v grep

# 檢查 visual_stats
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) FROM dev.chunks WHERE visual_stats IS NOT NULL;"
```

## 9. 待執行處理器

### 人物識別處理器 (依序執行)

```bash
# Step 1: ASRX 執行說話者分離
python scripts/asrx_processor.py --uuid 384b0ff44aaaa1f14cb2cd63b3fea966

# Step 2: Face 執行臉部偵測
python scripts/analyze_video_faces.py --uuid 384b0ff44aaaa1f14cb2cd63b3fea966

# Step 3: Auto-identify 建立影片級人物
python scripts/auto_identify_persons.py --uuid 384b0ff44aaaa1f14cb2cd63b3fea966

# Step 4: 全局 Identity 比對 (需累積一定數量的 face_identities)
python scripts/match_faces_to_identities.py

# Step 5: 重新生成 chunk 5W1H (包含新的 identity 資訊)
python scripts/generate_chunk_summaries.py --uuid 384b0ff44aaaa1f14cb2cd63b3fea966
```

### 檢查待處理狀態

```bash
# 檢查 speaker_ids
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) FROM dev.chunks 
WHERE speaker_ids IS NOT NULL AND array_length(speaker_ids, 1) > 0;"

# 檢查 face_ids
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) FROM dev.chunks 
WHERE face_ids IS NOT NULL AND array_length(face_ids, 1) > 0;"

# 檢查 person_identities
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) FROM dev.person_identities 
WHERE file_uuid = '384b0ff44aaaa1f14cb2cd63b3fea966';"

# 檢查 face_identities (全局)
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) FROM dev.face_identities;"
```

## 10. 自動化重新生成機制

### 觸發條件

當以下事件發生時，應自動重新生成 chunk 的 5W1H 和相關 metadata：

| 事件 | 觸發動作 |
|------|----------|
| 第一次執行 ASRX | 重新生成含 speaker_ids 的 5W1H |
| 第一次執行 Face | 重新生成含 face_ids 的 5W1H |
| 新增 chunk | 為新 chunk 生成 5W1H |
| 修改 chunk 內容 | 更新 5W1H 和 summary |
| 新增/修改 speaker | 重新生成含新 speaker 的 5W1H |
| 新增/修改 face | 重新生成含新 face 的 5W1H |

### 重新生成流程

```
事件觸發
    ↓
更新 speaker_ids / face_ids / person_identities
    ↓
呼叫 generate_chunk_summaries.py --uuid <uuid> --regenerate
    ↓
重新產生:
├── summary_text (2-3 句)
├── metadata.chunk_5w1h (Who/What/When/Where/Why/How)
├── metadata.chunk_identity (更新後的 speakers/faces)
└── metadata.chunk_visual (若 visual_stats 有更新)
```

### 重點

每次處理器執行後，Chunk metadata 會包含最新的：
1. **speaker_ids** → 進入 `chunk_identity.speakers`
2. **face_ids** → 進入 `chunk_identity.faces`  
3. **person_identities** → 進入 `chunk_identity.person_name`

確保 LLM 產生的 5W1H 包含最新的角色資訊。
