# Momentry Core 全新系統架構設計

> 更新日期: 2026-04-25
> 版本: V1.0 (全新設計)
> 狀態: 設計中

---

## 1. 核心設計理念

### 1.1 兩大核心實體

系統僅有兩種核心概念：

| 實體 | 說明 | 範例 |
|------|------|------|
| **File** | 任何檔案 | video, pdf, ppt, png, doc, audio... |
| **Identity** | 任何可識別列管的 object | 人、物件、品牌、概念、場景... |

### 1.2 關係模型

```
File ──[包含/出現]──→ Identity
Identity ──[出現在]──→ File
```

- 一個 File 可包含多個 Identity
- 一個 Identity 可出現在多個 File
- Identity 可歸屬於分類系統

---

## 2. Identity 設計

### 2.1 Identity 類型

任何可命名的事物都是 Identity：

| 類型 | 說明 | 範例 | 參考向量 |
|------|------|------|----------|
| people | 人 | 演員、公眾人物、虛構角色 | face_embedding (512), voice_embedding (192) |
| logo | 商標 | LV logo、Nike 勾勾、Accusys Logo | identity_embedding (768) |
| symbol | 符號 | 交通標誌、品牌符號 | identity_embedding (768) |
| object | 物件 | 車輛、建築、道具 | identity_embedding (768) |
| brand | 品牌 | LV、Hello Kitty、Nike | identity_embedding (768) |
| concept | 概念 | 愛、自由、科技 | identity_embedding (768) |
| scene | 場景 | 室內、室外、街道 | identity_embedding (768) |
| sound | 聲音 | 動物叫聲、雷雨、槍炮、樂器 | sound_embedding (TBD) |
| animal | 動物 | 狗、貓、鳥 | identity_embedding (768) + sound_embedding (TBD) |
| environmental | 環境音 | 雨聲、風聲、海浪 | sound_embedding (TBD) |

### 2.2 People Identity 特殊設計

**核心需求**: 同一個人（演員）在不同電影中有不同的角色名和定妝造型。

#### 階層結構
```
Identity (真實人物): 張曼玉
├── File A (花樣年華): 角色 "蘇麗珍" → 定妝: 旗袍造型、老妝+白髮頭套
├── File B (東邪西毒): 角色 "歐陽鋒妻子" → 定妝: 武俠造型
├── File C (甜蜜蜜): 角色 "李翹" → 定妝: 現代造型
└── File D: 角色 "XXX" → 定妝: 醜妝+傷妝
```

#### 在 File 中的呈現方式
| 呈現方式 | 說明 | 數據來源 |
|----------|------|----------|
| face | 臉孔出現 | Face Detection |
| speaker | 聲音出現 | ASR/Speaker Diarization |
| pose | 姿態/身體出現 | Pose Estimation |
| name_mention | 名字被提到 | ASR 文本/OCR |

### 2.3 Identity 屬性

```sql
CREATE TABLE identities (
    id              BIGSERIAL PRIMARY KEY,
    uuid            VARCHAR(36) UNIQUE NOT NULL,
    name            TEXT NOT NULL,           -- 可識別名稱
    identity_type   VARCHAR(30),             -- people, object, brand, concept...
    description     TEXT,
    
    -- People 特有
    real_name       TEXT,                    -- 真實姓名
    
    -- TMDB 整合
    tmdb_id         INTEGER,                 -- TMDB 人物 ID
    tmdb_profile    TEXT,                    -- TMDB 人臉照 URL
    source          VARCHAR(20),             -- 'tmdb', 'manual', 'ai_detection'
    
    -- 參考向量 (用於自動比對)
    face_embedding  VECTOR(512),             -- 參考臉向量 (ArcFace)
    voice_embedding VECTOR(192),             -- 參考聲紋向量 (ECAPA-TDNN)
    identity_embedding VECTOR(768),          -- 身份向量 (CLIP ViT-L/14) 用於 logo/symbol/object
    
    -- 1對多參考向量存儲 (多角度/多場景/多版本)
    reference_data  JSONB,                   -- 存儲多個 embedding，結構見下方說明
    
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    updated_at      TIMESTAMPTZ DEFAULT NOW()
);
```

#### reference_data JSONB 結構

```json
{
  "face_embeddings": [
    {
      "embedding": [0.1, 0.2, ...],          // 512-dim ArcFace
      "source": "tmdb_profile",              // tmdb_profile, tmdb_images, manual_upload, auto_detection
      "image_url": "https://...",             // 來源圖片 URL
      "angle": "frontal",                    // frontal, profile_left, profile_right, three_quarter
      "quality_score": 0.95,                 // 人臉質量評分
      "created_at": "2026-04-28T10:00:00Z"
    }
  ],
  "voice_embeddings": [
    {
      "embedding": [0.1, 0.2, ...],          // 192-dim ECAPA-TDNN
      "source": "video_segment",
      "file_uuid": "xxx",
      "timestamp_start": 120.5,
      "timestamp_end": 135.2,
      "quality_score": 0.88,
      "created_at": "2026-04-28T10:00:00Z"
    }
  ],
  "identity_embeddings": [
    {
      "embedding": [0.1, 0.2, ...],          // 768-dim CLIP ViT-L/14
      "source": "logo_image",                // logo_image, symbol_image, object_image
      "image_url": "https://...",
      "context": "brand_logo",               // brand_logo, symbol, object, concept
      "created_at": "2026-04-28T10:00:00Z"
    }
  ],
  "sound_embeddings": [
    {
      "embedding": [0.1, 0.2, ...],          // TBD (動物、雷雨、槍炮、樂器)
      "source": "audio_segment",
      "file_uuid": "xxx",
      "timestamp_start": 10.0,
      "timestamp_end": 15.0,
      "sound_type": "animal_dog_bark",       // animal_dog_bark, environmental_thunder, weapon_gunshot, musical_guitar
      "created_at": "2026-04-28T10:00:00Z"
    }
  ],
  "image_urls": [
    "https://www.accusys.com.tw/wp-content/uploads/2023/03/Accusys-Orange-2017.png",
    "https://image.tmdb.org/t/p/original/xxx.jpg"
  ]
}
```

---

## 3. File 設計

### 3.1 File 屬性

```sql
CREATE TABLE files (
    id              BIGSERIAL PRIMARY KEY,
    uuid            VARCHAR(36) UNIQUE NOT NULL,
    file_path       TEXT NOT NULL,
    file_name       TEXT NOT NULL,
    file_type       VARCHAR(20),             -- video, pdf, ppt, png, audio...
    file_size       BIGINT,
    mime_type       VARCHAR(100),
    metadata        JSONB,                   -- 類型特定元數據
    tmdb_movie_id   INTEGER,                 -- TMDB 電影 ID (可選)
    imdb_id         VARCHAR(20),             -- IMDb ID (可選)
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    updated_at      TIMESTAMPTZ DEFAULT NOW()
);
```

### 3.2 File 類型特定元數據

| 檔案類型 | 元數據內容 |
|----------|-----------|
| video | duration, width, height, fps, codec |
| audio | duration, sample_rate, channels |
| image | width, height, format |
| document | page_count, language |

---

## 4. File-Identity 關聯設計

### 4.1 關聯表

```sql
CREATE TABLE file_identities (
    id              BIGSERIAL PRIMARY KEY,
    file_uuid       VARCHAR(36) REFERENCES files(uuid),
    identity_uuid   VARCHAR(36) REFERENCES identities(uuid),
    
    -- People 特有
    role_name       TEXT,                    -- 劇中角色名
    costume_design  TEXT,                    -- 定妝造型描述
    presentation    TEXT[],                  -- ['face', 'speaker', 'pose', 'name_mention']
    
    -- 時間位置
    timestamp_start FLOAT,                   -- 開始時間 (秒)
    timestamp_end   FLOAT,                   -- 結束時間
    frame_start     BIGINT,                  -- 開始幀
    frame_end       BIGINT,                  -- 結束幀
    
    -- 檢測數據
    face_data       JSONB,                   -- {face_id, confidence, bbox}
    speaker_data    JSONB,                   -- {speaker_id, audio_segment}
    pose_data       JSONB,                   -- {keypoints, action}
    
    -- 匹配資訊
    match_confidence FLOAT,                  -- AI 匹配置信度
    is_confirmed    BOOLEAN DEFAULT FALSE,   -- 人工確認
    
    created_at      TIMESTAMPTZ DEFAULT NOW()
);
```

### 4.2 呈現方式說明

| 呈現方式 | 說明 | 適用 Identity 類型 |
|----------|------|-------------------|
| face | 臉孔出現在畫面中 | people |
| speaker | 聲音出現在音軌中 | people |
| pose | 身體姿態出現在畫面中 | people |
| name_mention | 名字在文本中被提到 | people, brand, concept |
| object_detection | 物件被檢測到 | object, brand, logo |
| text_mention | 文字提到 | 所有類型 |
| logo_detection | 商標被檢測到 | brand, logo |

---

## 5. 分類系統設計

### 5.1 階層式編號格式

參考 IPC 但更靈活：

```
X-NNN-NNN/NNN
│  │   │   └─ 細分類 (Subgroup)
│  │   └───── 主分類 (Main Group)
│  └───────── 子分類 (Subclass)
└──────────── 大分類 (Section)
```

### 5.2 範例

```
P-001-000/000  人物 (People)
├── P-001-010/000  演員
│   ├── P-001-010/010  電影演員
│   └── P-001-010/020  電視演員
├── P-001-020/000  公眾人物
└── P-001-030/000  虛構角色

B-002-000/000  品牌 (Brand)
├── B-002-010/000  時尚品牌
│   ├── B-002-010/010  LV
│   └── B-002-010/020  Gucci
└── B-002-020/000  科技品牌

O-003-000/000  物件 (Object)
├── O-003-010/000  車輛
├── O-003-020/000  建築
└── O-003-030/000  道具

C-004-000/000  概念 (Concept)
├── C-004-010/000  情感
│   ├── C-004-010/010  愛
│   └── C-004-010/020  自由
└── C-004-020/000  思想
```

### 5.3 分類表結構

```sql
CREATE TABLE categories (
    id              BIGSERIAL PRIMARY KEY,
    code            VARCHAR(20) UNIQUE NOT NULL,   -- P-001-010/010
    name            TEXT NOT NULL,
    parent_code     VARCHAR(20) REFERENCES categories(code),
    description     TEXT,
    category_type   VARCHAR(20),                   -- 'file', 'identity', 'both'
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    updated_at      TIMESTAMPTZ DEFAULT NOW()
);

-- File-Category 關聯
CREATE TABLE file_categories (
    file_uuid       VARCHAR(36) REFERENCES files(uuid),
    category_code   VARCHAR(20) REFERENCES categories(code),
    PRIMARY KEY (file_uuid, category_code)
);

-- Identity-Category 關聯
CREATE TABLE identity_categories (
    identity_uuid   VARCHAR(36) REFERENCES identities(uuid),
    category_code   VARCHAR(20) REFERENCES categories(code),
    PRIMARY KEY (identity_uuid, category_code)
);
```

### 5.4 特點

1. **可擴充**：任何層級都可新增，不需重新編號
2. **有意義**：首字母代表大分類 (P=People, B=Brand, O=Object, C=Concept...)
3. **層級清晰**：通過編號即可知道所属分类深度
4. **靈活套用**：可套用在 File、Identity 或兩者

---

## 6. TMDB 整合設計

### 6.1 資料流

```
TMDB API → 電影資訊 + 演員名單 → 自動建立 Identity → 關聯到 File
```

### 6.2 整合流程

1. **匯入電影檔案時**：
   - 用戶提供 TMDB 電影 ID 或 IMDb ID
   - 系統自動從 TMDB API 獲取：
     - 演員名單 + 角色名
     - 演員人臉照 (profile_path)
     - 演員多張照片 (TMDB /person/:id/images 端點)
     - 電影元數據

2. **建立 Identity**：
   - 自動建立或更新 Identity（演員）
   - 儲存 TMDB ID + 多張人臉照 URL
   - 關聯到 File（這部電影）

3. **提取參考向量 (1對多)**：
   - 下載 TMDB 多張人臉照 (不同角度、定妝造型)
   - 對每張照片提取 face_embedding (512-dim ArcFace)
   - 將多個 embedding 存儲到 reference_data JSONB：
     ```json
     {
       "face_embeddings": [
         {
           "embedding": [...],
           "source": "tmdb_images",
           "image_url": "https://image.tmdb.org/t/p/original/xxx.jpg",
           "angle": "frontal",
           "quality_score": 0.95
         },
         {
           "embedding": [...],
           "source": "tmdb_images",
           "image_url": "https://image.tmdb.org/t/p/original/yyy.jpg",
           "angle": "profile_left",
           "quality_score": 0.88
         }
       ]
     }
     ```
   - 計算 centroid（中心向量）存儲到 face_embedding 字段

4. **後續 AI 識別**：
   - 系統檢測 File 中的 Face
   - 自動匹配到已有的 Identity（使用 1對多匹配算法）
   - 更新 file_identities 表

#### 6.2.1 1對多匹配算法

```python
def match_face_to_identity(detected_embedding, identity_reference_data):
    """
    1對多匹配：檢測到的臉與 Identity 的多個參考向量比對
    
    策略：
    1. 最佳匹配：取所有參考向量中的最高相似度
    2. 投票機制：統計超過閾值的參考向量數量
    3. 加權平均：根據質量評分加權計算相似度
    """
    face_embeddings = identity_reference_data.get("face_embeddings", [])
    
    if not face_embeddings:
        return None
    
    # 策略 1: 最佳匹配
    similarities = [
        cosine_similarity(detected_embedding, ref["embedding"])
        for ref in face_embeddings
    ]
    best_match = max(similarities)
    
    # 策略 2: 投票機制
    threshold = 0.85
    votes = sum(1 for sim in similarities if sim >= threshold)
    vote_ratio = votes / len(similarities)
    
    # 策略 3: 加權平均
    weighted_sim = sum(
        sim * ref.get("quality_score", 1.0)
        for sim, ref in zip(similarities, face_embeddings)
    ) / sum(ref.get("quality_score", 1.0) for ref in face_embeddings)
    
    # 綜合評分
    final_score = (best_match * 0.5 + vote_ratio * 0.3 + weighted_sim * 0.2)
    
    return {
        "best_match": best_match,
        "vote_ratio": vote_ratio,
        "weighted_sim": weighted_sim,
        "final_score": final_score,
        "is_match": final_score >= threshold
    }
```

### 6.3 TMDB API 端點

| 端點 | 說明 |
|------|------|
| `/api/v1/tmdb/search/movie?q=關鍵字` | 搜尋電影 |
| `/api/v1/tmdb/movie/:id` | 獲取電影詳情 |
| `/api/v1/tmdb/movie/:id/cast` | 獲取演員列表 |
| `/api/v1/tmdb/person/:id` | 獲取人物詳情 |
| `/api/v1/tmdb/person/:id/images` | 獲取人物照片 |

---

## 7. 自動辨識比對設計

### 7.1 核心目標

**從 Identity (People) 的一張參考臉，自動辨識比對，找到所有出現的 File 和片段。**

### 7.2 比對流程

```
1. 建立 Identity
   └── 取得參考臉 (TMDB 照片或手動上傳)
   └── 提取 face_embedding (512-dim)

2. 處理 File (Video)
   └── AI 檢測所有 Face → 提取每張臉的向量
   └── AI 檢測所有 Speaker → 提取聲紋向量
   └── AI 檢測所有 Object → 提取特徵向量

3. 自動比對匹配
   └── Face 比對: 檢測臉 vs Identity face_embedding
   └── Voice 比對: 檢測聲紋 vs Identity voice_embedding
   └── 超過閾值 → 自動建立 file_identities 關聯

4. 人工確認 (可選)
   └── 低置信度匹配標記為待確認
   └── 用戶確認/修正
```

### 7.3 比對閾值

| 比對類型 | 預設閾值 | 說明 |
|----------|----------|------|
| Face | 0.85 | ArcFace 512-dim |
| Voice | 0.75 | ECAPA-TDNN 192-dim |
| Object | 0.80 | YOLO + 特徵 |

---

## 8. API 架構

### 8.1 File API

```
GET     /api/v1/files                              # 列表
  參數: ?page=1&page_size=20&type=video&category=P-001&sort=created_at

GET     /api/v1/files/search?q=關鍵字               # 搜尋

GET     /api/v1/files/:uuid                         # 詳情

GET     /api/v1/files/:uuid/identities              # File 有哪些 Identity
  參數: ?presentation=face&role_name=蘇麗珍

POST    /api/v1/files/:uuid/import-tmdb             # 從 TMDB 匯入演員
  Body: {"tmdb_movie_id": 12345}
```

### 8.2 Identity API

```
GET     /api/v1/identities                          # 列表
  參數: ?page=1&page_size=20&type=people&category=B-002&sort=name

GET     /api/v1/identities/search?q=名字             # 搜尋

GET     /api/v1/identities/:id                      # 詳情

GET     /api/v1/identities/:id/files                # Identity 在哪些 File
  參數: ?presentation=face&role_name=蘇麗珍

POST    /api/v1/identities                          # 手動建立 Identity

PUT     /api/v1/identities/:id                      # 更新 Identity

POST    /api/v1/identities/:id/reference-face       # 上傳參考臉

POST    /api/v1/identities/:id/scan                 # 掃描所有 File 比對
```

### 8.3 Category API

```
GET     /api/v1/categories                          # 分類樹

GET     /api/v1/categories/:code/children           # 子分類

POST    /api/v1/categories                          # 建立分類

PUT     /api/v1/categories/:code                    # 更新分類

DELETE  /api/v1/categories/:code                    # 刪除分類
```

### 8.4 TMDB API

```
GET     /api/v1/tmdb/search/movie?q=關鍵字           # 搜尋電影

GET     /api/v1/tmdb/movie/:id                      # 獲取電影詳情

GET     /api/v1/tmdb/movie/:id/cast                 # 獲取演員列表
```

---

## 9. 搜尋範例

### 9.1 場景：找到張曼玉出現在哪些 File

```bash
# Step 1: 搜尋 Identity
GET /api/v1/identities/search?q=張曼玉

# Step 2: 獲取相關 File
GET /api/v1/identities/{identity_uuid}/files

# 返回:
{
  "files": [
    {
      "file_uuid": "xxx",
      "file_name": "花樣年華.mp4",
      "role_name": "蘇麗珍",
      "costume_design": "老妝+白髮頭套",
      "presentation": ["face", "speaker"],
      "timestamp_start": 120.5,
      "timestamp_end": 135.2
    }
  ]
}
```

### 9.2 場景：找到某 File 中所有演員

```bash
GET /api/v1/files/{file_uuid}/identities?presentation=face

# 返回:
{
  "identities": [
    {
      "identity_uuid": "abc",
      "name": "張曼玉",
      "role_name": "蘇麗珍",
      "presentation": ["face", "speaker"]
    }
  ]
}
```

### 9.3 場景：通過分類瀏覽

```bash
# 所有演員相關 Identity
GET /api/v1/identities?category=P-001-010

# 所有電影相關 File
GET /api/v1/files?category=M-001-010
```

### 9.4 場景：組合搜尋

```bash
GET /api/v1/identities/search?q=張&type=people&category=P-001
```

---

## 10. 執行計畫

### Phase 1: 資料庫重構
- [ ] 建立新表 (files, identities, file_identities, categories, file_categories, identity_categories)
- [ ] 資料遷移腳本 (從現有 videos/person_identities 遷移)
- [ ] 向量索引配置 (face_embedding, voice_embedding)
- [ ] 測試資料建立

### Phase 2: 核心 API
- [ ] File CRUD + 列表/搜尋/過濾
- [ ] Identity CRUD + 列表/搜尋/過濾
- [ ] Category 樹狀結構 CRUD
- [ ] File-Identity 關聯 API
- [ ] Category 關聯 API

### Phase 3: TMDB 整合
- [ ] TMDB API 串接 (搜尋電影、獲取演員、獲取照片)
- [ ] 自動建立 Identity 流程
- [ ] 人臉照下載與向量提取
- [ ] 角色名自動關聯

### Phase 4: AI 自動辨識
- [ ] Face 檢測整合 (現有)
- [ ] 向量比對匹配邏輯
- [ ] file_identities 自動建立
- [ ] 低置信度標記與人工確認流程

### Phase 5: Portal 前端
- [ ] File 列表 + 搜尋 + 過濾
- [ ] Identity 列表 + 搜尋
- [ ] 分類瀏覽
- [ ] Identity 詳情 (顯示所有相關 File)
- [ ] File 詳情 (顯示所有 Identity)
- [ ] TMDB 匯入介面
- [ ] 參考臉上傳介面

---

## 11. 待確認問題

| 編號 | 問題 | 選項 | 決策 |
|------|------|------|------|
| Q1 | 參考臉來源 | TMDB / 手動上傳 / 兩者都有 | |
| Q2 | 比對閾值 | Face: 0.85, Voice: 0.75 | |
| Q3 | 非電影檔案 | 手動建立 Identity | |
| Q4 | 分類編號格式 | P-001-010/010 | |
| Q5 | 現有系統遷移 | 需要相容層 | |
| Q6 | People 階層 | Identity → File (含角色名+造型) | |
| Q7 | 非人物件階層 | 是否需要類似造型層級？ | |
| Q8 | AI 識別觸發 | 自動 / 手動 / 兩者都有 | |

---

## 12. 技術棧

| 層級 | 技術 |
|------|------|
| 後端 | Rust (momentry_core) |
| 前端 | Vue 3 + TypeScript (Portal) |
| 資料庫 | PostgreSQL + pgvector |
| 向量庫 | Qdrant |
| 快取 | Redis |
| AI 處理 | Python (Whisper, ArcFace, YOLO...) |
| TMDB | TMDB API v3 |

---

## 13. 版本歷史

| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.0 | 2026-04-25 | 全新設計 (File + Identity + Category) | OpenCode |
| V1.1 | 2026-04-28 | 添加 identity_embedding (768維 CLIP)、reference_data JSONB (1對多參考向量)、擴展 identity_type (logo/symbol/sound/animal/environmental)、TMDB 多角度人臉整合 | OpenCode |
