# Portal Face 操作演示计划

> Date: 2026-04-28 21:15
> Target: 演示完整 Face → Identity 流程
> Environment: Playground (dev schema)

---

## 当前状态

### 数据状态

| 数据 | 数量 | 状态 |
|------|------|------|
| **identities** | 15 | ✅ 已创建 |
| **face_detections** | 78 | ⚠️ 全部未绑定 (candidates) |
| **face_detections (bound)** | 0 | ❌ 无绑定数据 |
| **file_identities** | ? | 待检查 |

### 测试视频

```
UUID: 384b0ff44aaaa1f14cb2cd63b3fea966 (已清理重复，唯一记录)
File: Old_Time_Movie_Show_-_Charade_1963.HD.mov
Faces: 78 (全部 candidates)
birth_registration: ✅ 已添加
```

### API 状态

| API | 状态 | 说明 |
|-----|------|------|
| GET `/api/v1/identities` | ✅ 可用 | List identities |
| GET `/api/v1/faces/candidates` | ❌ 缺失 | List unbound faces |
| GET `/api/v1/identities/:uuid/faces` | ❌ 缺失 | List identity faces |
| POST `/api/v1/identities/register` | ✅ 可用 | Register identity |
| POST `/api/v1/identities/:uuid/bind` | ✅ 可用 | Bind faces |

### 前端组件

| 文件 | 状态 | 说明 |
|------|------|------|
| `IdentityDetailView.vue` | ✅ 存在 | Identity 详情页 |
| Face candidates 页 | ❌ 缺失 | 需创建 |
| Identity faces list | ❌ 缺失 | 需创建 |

---

## 演示目标

### 目标 1: 展示 Face Candidates (未注册列表)

**用户场景**:
- 浏览视频中的所有 face detections
- 看到每个 face 的 thumbnail、confidence、pose_angle
- 筛选高质量 candidates (min_confidence > 0.8)

**需要**:
- ✅ 数据: 78 个 face_detections
- ❌ API: `/api/v1/faces/candidates`
- ❌ 前端: Candidates.vue

---

### 目标 2: 注册 Identity (从 Face 创建)

**用户场景**:
- 选择 face candidates (例如 face_100, face_150)
- 输入 name: "Audrey Hepburn"
- 点击注册按钮
- 系统创建 identity 并绑定 faces

**需要**:
- ✅ API: `/api/v1/identities/register`
- ✅ 数据: face_detections 可用
- ❌ 前端: RegisterIdentity.vue

---

### 目标 3: 展示 Identity Faces (已绑定列表)

**用户场景**:
- 点击 identity 详情
- 看到所有绑定的 faces (thumbnails)
- 看到 pose distribution (frontal: 20, profile: 10)

**需要**:
- ✅ 数据: identities 表有 15 个
- ❌ API: `/api/v1/identities/:uuid/faces`
- ❌ 前端: IdentityFaces.vue

---

## 演示计划

### Phase 1: 后端 API 实现 (优先)

**任务清单**:

1. **实现 `/api/v1/faces/candidates`**
   - 查询 face_detections WHERE identity_id IS NULL
   - 返回 thumbnail、confidence、pose_angle
   - 支持筛选 (min_confidence, pose_angle, file_uuid)

2. **实现 `/api/v1/identities/:uuid/faces`**
   - 查询 face_detections WHERE identity_id = $uuid
   - 返回 face list with thumbnails
   - 统计 pose distribution

3. **实现 `/api/v1/files/:uuid/faces/candidates`**
   - 单文件的 face candidates
   - 用于视频详情页

**预计时间**: 2-3 小时

---

### Phase 2: 前端 UI 实现

**任务清单**:

1. **创建 FaceCandidates.vue**
   - 显示 face thumbnails grid
   - 筛选器: confidence slider, pose dropdown
   - 点击选择 → 注册流程

2. **更新 IdentityDetailView.vue**
   - 添加 Faces tab
   - 显示已绑定 faces grid
   - 添加 Bind/Unbind 操作

3. **创建 RegisterIdentity.vue**
   - Modal/Dialog 组件
   - Face selection (multi-select)
   - Name input
   - 提交注册

**预计时间**: 3-4 小时

---

### Phase 3: 演示数据准备

**任务清单**:

1. **手动注册测试 Identity**
   - 使用 API 创建 identity
   - 绑定 10-20 个 faces
   - 生成演示数据

2. **准备 Thumbnail**
   - Face thumbnail API 实现
   - 缓存 thumbnail images
   - 优化加载速度

**预计时间**: 1 小时

---

## 演示流程设计

### 步骤 1: 进入 Candidates 页面

```
Portal → Files → 384b0ff44aaaa1f14cb2cd63b3fea966 → Face Candidates
  ↓
显示 78 个 face thumbnails
  ↓
筛选: confidence > 0.85 → 显示 20 个高质量 faces
```

---

### 步骤 2: 选择 Faces 并注册

```
点击 face thumbnail → Checkbox 选中
  ↓
选择 5 个高质量 frontal faces
  ↓
点击 "Register Identity" 按钮
  ↓
输入 name: "Audrey Hepburn"
  ↓
提交 → POST /api/v1/identities/register
```

---

### 步骤 3: 查看 Identity 详情

```
Portal → Identities → Audrey Hepburn
  ↓
显示 identity 信息:
  - name: Audrey Hepburn
  - total_faces: 5
  - pose_distribution: frontal: 5
  ↓
切换到 Faces tab
  ↓
显示 5 个 bound faces thumbnails
```

---

### 步骤 4: 绑定更多 Faces

```
Identity 详情页 → Bind Faces 按钮
  ↓
打开 Candidates 列表
  ↓
选择额外 10 个 faces
  ↓
POST /api/v1/identities/:uuid/bind
  ↓
Faces tab 更新: 显示 15 个 faces
```

---

## 技术实现细节

### API 设计

#### 1. GET /api/v1/faces/candidates

```rust
// identities.rs 或新建 identity_faces.rs
async fn list_face_candidates(
    Query(query): Query<CandidatesQuery>,
) -> Result<Json<CandidatesResponse>, (StatusCode, String)> {
    let sql = r#"
        SELECT 
            id, face_id, file_uuid, frame_number, confidence,
            bbox, attributes
        FROM face_detections
        WHERE identity_id IS NULL
          AND confidence >= $1
        ORDER BY confidence DESC
        LIMIT $2
    "#;
    
    // 返回 face list with thumbnails
}
```

**Response**:
```json
{
  "candidates": [
    {
      "face_id": "face_100",
      "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
      "frame": 100,
      "confidence": 0.92,
      "thumbnail_url": "/api/v1/faces/face_100/thumbnail",
      "pose_angle": "frontal"
    }
  ],
  "total": 78,
  "statistics": {
    "avg_confidence": 0.85,
    "pose_distribution": {"frontal": 20, "profile": 30}
  }
}
```

---

#### 2. GET /api/v1/identities/:uuid/faces

```rust
async fn get_identity_faces(
    Path(identity_uuid): Path<String>,
    Query(query): Query<FaceListQuery>,
) -> Result<Json<IdentityFacesResponse>, (StatusCode, String)> {
    let sql = r#"
        SELECT 
            fd.id, fd.face_id, fd.file_uuid, fd.frame_number,
            fd.confidence, fd.bbox, fd.attributes,
            v.file_name
        FROM face_detections fd
        LEFT JOIN videos v ON fd.file_uuid = v.uuid
        WHERE fd.identity_id = $1
        ORDER BY fd.confidence DESC
        LIMIT $2
    "#;
    
    // 绑定 identity_id (INT) → identities.id
}
```

**Response**:
```json
{
  "identity_uuid": "a9a90105...",
  "name": "Audrey Hepburn",
  "faces": [
    {
      "face_id": "face_100",
      "file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
      "file_name": "Charade_1963.mp4",
      "frame": 100,
      "confidence": 0.92,
      "thumbnail_url": "/api/v1/faces/face_100/thumbnail"
    }
  ],
  "total_faces": 5,
  "pose_distribution": {"frontal": 5}
}
```

---

### 前端组件结构

```
portal/src/
├── views/
│   ├── FaceCandidates.vue       (新增)
│   ├── IdentityDetailView.vue   (更新)
│   └── FileDetailView.vue       (更新)
├── components/
│   ├── FaceThumbnail.vue        (新增)
│   ├── FaceGrid.vue             (新增)
│   ├── RegisterIdentityModal.vue (新增)
│   └── BindFacesModal.vue       (新增)
```

---

## 实施顺序建议

### 方案 A: 后端优先（推荐）

**优点**: 数据驱动，前端开发有实际数据

**顺序**:
1. 实现 `/api/v1/faces/candidates` API
2. 实现 `/api/v1/identities/:uuid/faces` API
3. 测试 API (curl)
4. 创建前端 Candidates.vue
5. 更新 IdentityDetailView.vue
6. 整合演示

**时间**: 6-8 小时

---

### 方案 B: 前端优先

**优点**: UI 先行，后端跟进

**顺序**:
1. 创建 FaceCandidates.vue (mock data)
2. 创建 RegisterIdentityModal.vue
3. 实现后端 API
4. 整合测试

**时间**: 7-9 小时

---

## 演示环境配置

### Playground 环境

```bash
# API Server
Port: 3003
Schema: dev
Redis Prefix: momentry_dev:

# Portal Frontend
Port: 1420
API Endpoint: http://localhost:3003
```

### 测试 API Key

```bash
curl -H "X-API-Key: muser_test_001" ...
```

---

## 验收标准

### 后端验收

- [ ] `/api/v1/faces/candidates` 返回 78 个 candidates
- [ ] `/api/v1/identities/:uuid/faces` 返回 bound faces
- [ ] `/api/v1/identities/register` 创建 identity 并绑定
- [ ] `/api/v1/identities/:uuid/bind` 绑定额外 faces

### 前端验收

- [ ] Candidates 页面显示 face thumbnails
- [ ] 筛选器正常工作 (confidence, pose)
- [ ] Register 流程完整 (select → name → submit)
- [ ] Identity Faces tab 显示已绑定 faces

### 演示验收

- [ ] 完整流程: candidates → select → register → view identity
- [ ] 数据持久化: 注册后 identity 和 faces 正确绑定
- [ ] UI 流畅: thumbnail 加载快速，操作响应及时

---

## 风险评估

| 风险 | 影响 | 解决方案 |
|------|------|----------|
| **Thumbnail API 缺失** | 高 | 使用 placeholder 或快速实现 |
| **pose_angle 字段缺失** | 中 | 从 attributes JSON 解析 |
| **前端时间不足** | 中 | 先实现核心功能，UI 简化 |
| **数据量少** | 低 | 78 个足够演示 |

---

## 建议执行方案

**立即执行**: 后端 API 实现（方案 A）

**理由**:
- 数据已存在（78 candidates）
- identities API 已有基础
- 前端可并行开发

**下一步**:
1. 选择 API 实现方式
2. 开始编码 `/api/v1/faces/candidates`
3. 测试并验证数据
