# Face / Speaker / Person API 簡易指南

> **版本**: 1.1 | **適用**: 前端開發團隊
> **更新日期**: 2026-04-17
>
> **⚠️ 重要**: 3002 (正式版) 和 3003 (開發版) 使用**完全獨立的資料空間** (public vs dev schema)，絕非共用。開發版測試不會影響正式版資料。

---

## 快速開始

```bash
export BASE="http://localhost:3002"
export KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
export UUID="384b0ff44aaaa1f14cb2cd63b3fea966"
```

---

## 1. 用 uuid + chunk_id 查看 face / speaker / person

### 取得 chunk 內的人物

```bash
curl "$BASE/api/v1/chunks/sentence_0093/persons" \
  -H "X-API-Key: $KEY"
```

```json
{
  "success": true,
  "chunk_id": "sentence_0093",
  "persons": [
    {
      "person_id": "Person_0",
      "name": "Person_0",
      "confidence": 0.85,
      "overlap_duration": 3.2
    }
  ]
}
```

### 取得 chunk 的 speaker（從 content 欄位）

```bash
curl -X POST "$BASE/api/v1/search/universal" \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "", "uuid": "'$UUID'", "types": ["chunk"], "filters": {"speaker_id": "SPEAKER_0"}, "limit": 10}'
```

```json
{
  "results": [
    {
      "type": "chunk",
      "chunk_id": "sentence_0093",
      "chunk_type": "sentence",
      "start_frame": 29795,
      "end_frame": 29963,
      "fps": 59.94,
      "start_time": 497.08,
      "end_time": 499.88,
      "text": "You could have the stamps.",
      "speaker_id": "SPEAKER_0"
    }
  ]
}
```

### 統一搜尋 chunk + face + person

```bash
curl -X POST "$BASE/api/v1/search/universal" \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "stamp", "uuid": "'$UUID'", "types": ["chunk", "person"], "limit": 10}'
```

```json
{
  "query": "stamp",
  "results": [
    {
      "type": "chunk",
      "chunk_id": "sentence_1566",
      "chunk_type": "sentence",
      "start_frame": 329980,
      "end_frame": 330040,
      "fps": 59.94,
      "start_time": 5506.84,
      "end_time": 5507.84,
      "text": "The envelope, but the stamps on it",
      "speaker_id": "SPEAKER_0"
    },
    {
      "type": "person",
      "person_id": "Person_0",
      "name": "Person_0",
      "speaker_id": "SPEAKER_0",
      "appearance_count": 17832
    }
  ],
  "total": 10,
  "took_ms": 27
}
```

---

## 2. 選擇 face 並綁定 person

### 步驟 1: 列出所有人物

```bash
curl "$BASE/api/v1/person/list?min_appearances=100&has_speaker=true&limit=20" \
  -H "X-API-Key: $KEY"
```

```json
{
  "persons": [
    {
      "person_id": "Person_0",
      "name": "Person_0",
      "speaker_id": "SPEAKER_0",
      "appearance_count": 17832
    },
    {
      "person_id": "Person_17",
      "name": "Person_17",
      "speaker_id": "SPEAKER_1",
      "appearance_count": 1636
    }
  ],
  "total": 9
}
```

### 步驟 2: 查看人物詳情 + 取得截圖

```bash
# 查看詳情
curl "$BASE/api/v1/person/Person_0" -H "X-API-Key: $KEY"

# 取得臉部截圖
curl "$BASE/api/v1/person/Person_0/thumbnail?file_uuid=$UUID" \
  -H "X-API-Key: $KEY" -o person0_face.jpg

# 取得第 5 次出現的臉部截圖
curl "$BASE/api/v1/person/Person_0/thumbnail?file_uuid=$UUID&index=4" \
  -H "X-API-Key: $KEY" -o person0_face_5.jpg
```

### 步驟 3: 綁定名稱（將 face 關聯到 person）

```bash
curl -X PATCH "$BASE/api/v1/person/Person_0" \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Cary Grant", "is_confirmed": true}'
```

```json
{
  "success": true,
  "message": "Person 'Cary Grant' updated successfully",
  "person_id": "Person_0"
}
```

### 步驟 4: 註冊新臉孔（建立參考樣本）

```bash
curl -X POST "$BASE/api/v1/face/register" \
  -H "X-API-Key: $KEY" \
  -F "image=@known_face.jpg" \
  -F "name=Cary Grant" \
  -F 'metadata={"imdb_id": "nm0000001"}'
```

---

## 3. 合併前檢視：取得臉部截圖

### 取得單張截圖

```bash
# 預設：第一次出現的臉部
curl "$BASE/api/v1/person/Person_0/thumbnail?file_uuid=$UUID" \
  -H "X-API-Key: $KEY" -o face.jpg

# 指定第 N 次出現
curl "$BASE/api/v1/person/Person_0/thumbnail?file_uuid=$UUID&index=10" \
  -H "X-API-Key: $KEY" -o face_10.jpg
```

### 找出相似人物（可能為同一人）

```bash
curl "$BASE/api/v1/person/Person_0/similar?threshold=0.5&limit=10" \
  -H "X-API-Key: $KEY"
```

```json
{
  "person_id": "Person_0",
  "similar_persons": [
    {
      "person_id": "Person_4",
      "name": "Person_4",
      "speaker_id": "SPEAKER_0",
      "similarity": 0.7
    },
    {
      "person_id": "Person_25",
      "name": "Person_25",
      "speaker_id": "SPEAKER_0",
      "similarity": 0.7
    }
  ]
}
```

### 取得 AI 合併建議

```bash
curl -X POST "$BASE/api/v1/person/suggest" \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"file_uuid": "'$UUID'"}'
```

```json
{
  "merge_suggestions": [
    {
      "person_id": "Person_0",
      "merge_with": ["Person_4", "Person_25"],
      "confidence": 0.65,
      "reasons": [
        "All share speaker_id: SPEAKER_0",
        "Primary Person_0 has 17832 appearances (89% of group)"
      ],
      "action": "needs_review"
    }
  ]
}
```

---

## 統一搜尋

### ⚠️ 重要：搜尋 chunks 時 uuid 為必填

**只有 `uuid + chunk_id` 組合才是唯一識別碼。** 單獨 `chunk_id` 在不同影片中可能重複。

```bash
# ✅ 正確：包含 uuid
curl -X POST "$BASE/api/v1/search/universal" \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "stamp", "uuid": "'$UUID'", "types": ["chunk"]}'

# ❌ 錯誤：缺少 uuid
curl -X POST "$BASE/api/v1/search/universal" \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "stamp", "types": ["chunk"]}'
# 回傳: {"error": "uuid is required for chunk search"}
```

---

## 4. 使用 API 合併 face / speaker / person

### ⚠️ 重要：合併撤銷限制

**合併撤銷完全依賴 `merge_history` 記錄。**

| 情況 | 可否撤銷 |
|------|:---:|
| 使用 `POST /api/v1/person/merge` API 合併 | ✅ 可以（自動記錄歷史） |
| 手動修改資料庫合併 | ❌ 不可以（無歷史記錄） |
| 舊版程式碼合併（無 merge_history 表） | ❌ 不可以 |
| 已撤銷過的合併 | ❌ 不可以（防止重複撤銷） |

**每次合併 API 都會回傳 `merge_id`，請務必儲存以便日後撤銷。**

### 執行合併

```bash
curl -X POST "$BASE/api/v1/person/merge" \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "target_person_id": "Person_0",
    "source_person_ids": ["Person_4", "Person_25"]
  }'
```

```json
{
  "success": true,
  "message": "Merged 2 persons into Person_0",
  "target_person_id": "Person_0",
  "merge_id": "5b12e3ac-12fa-45c0-88e1-5cff67604a7d"
}
```

### 合併做了什麼？

```
合併前:
  Person_0   (17832 幀, SPEAKER_0)
  Person_4   (936 幀, SPEAKER_0)
  Person_25  (217 幀, SPEAKER_0)

合併後:
  Person_0   (17832+936+217=18985 幀, SPEAKER_0)  ← 保留
  Person_4   ← 刪除
  Person_25  ← 刪除
```

### 撤銷合併

```bash
# 使用合併時回傳的 merge_id
curl -X POST "$BASE/api/v1/person/merge/undo" \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"merge_id": "5b12e3ac-12fa-45c0-88e1-5cff67604a7d"}'
```

```json
{
  "success": true,
  "message": "Undo merge completed. Restored 2 source persons",
  "merge_id": "5b12e3ac-12fa-45c0-88e1-5cff67604a7d",
  "target_person_id": "Person_0",
  "restored_persons": ["Person_4", "Person_25"]
}
```

**⚠️ 如果沒有 merge_id（手動合併/舊版合併），無法撤銷。**

### 查看合併歷史

```bash
curl "$BASE/api/v1/person/merge/history" -H "X-API-Key: $KEY"
```

### 完整合併流程

```
1. 取得建議 → POST /api/v1/person/suggest
2. 檢視截圖 → GET  /api/v1/person/:id/thumbnail
3. 檢視相似 → GET  /api/v1/person/:id/similar
4. 執行合併 → POST /api/v1/person/merge        ← 儲存 merge_id!
5. 確認結果 → GET  /api/v1/person/list
6. 如需撤銷 → POST /api/v1/person/merge/undo   ← 需要 merge_id
```

---

## API 速查表

| 用途 | 方法 | 端點 |
|------|:---:|------|
| **查看 chunk 內人物** | GET | `/api/v1/chunks/:chunk_id/persons` |
| **搜尋人物** | GET | `/api/v1/search/persons?query=Person` |
| **列出人物** | GET | `/api/v1/person/list?limit=20` |
| **人物詳情** | GET | `/api/v1/person/:id` |
| **人物截圖** | GET | `/api/v1/person/:id/thumbnail?file_uuid=...` |
| **相似人物** | GET | `/api/v1/person/:id/similar` |
| **AI 建議** | POST | `/api/v1/person/suggest` |
| **綁定名稱** | PATCH | `/api/v1/person/:id` |
| **合併人物** | POST | `/api/v1/person/merge` |
| **撤銷合併** | POST | `/api/v1/person/merge/undo` |
| **合併歷史** | GET | `/api/v1/person/merge/history` |
| **統一搜尋** | POST | `/api/v1/search/universal` |
| **註冊臉孔** | POST | `/api/v1/face/register` |

---

## 錯誤處理

```bash
# 錯誤回應
curl -X POST "$BASE/api/v1/person/merge" \
  -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"target_person_id": "Person_0", "source_person_ids": []}'
# → "source_person_ids cannot be empty"
```

| 狀態碼 | 說明 |
|:---:|------|
| 200 | 成功 |
| 400 | 參數錯誤 |
| 401 | API Key 無效 |
| 404 | 找不到 |
| 500 | 伺服器錯誤 |

---

## 資料修正

發現綁定錯誤時，參考 [人物資料修正機制指南](./PERSON_CORRECTION_GUIDE.md)

| 錯誤類型 | 修正方式 |
|---------|---------|
| Speaker 綁錯 | `POST /person/:id/reassign-speaker` |
| 不該綁 Speaker | `POST /person/:id/unbind-speaker` |
| Appearance 分錯人 | `POST /person/:id/reassign-appearance` |
| 錯誤 Appearance | `POST /person/:id/remove-appearance` |
| 兩人被合併為一 | `POST /person/:id/split` |
| 錯誤合併 | `POST /person/merge/undo` |
| 錯誤命名 | `PATCH /person/:id` |
