fix: face group name read consistency, sync_file_status fix, cleanup ghost records, identity_agent replaced with face_dedup
- get_face_groups_handler: COALESCE(tp.name, tn.label) for name consistency - sync_file_status: compare JSON vs pre_chunks (not chunk table) - face consistency: compare frames.len() not total_faces - cleanup 2 ghost records with NULL file_name/file_path - replace identity_agent with face_dedup in pipeline stages - remove identity_agent_api.rs and all references - update required_processors to match actual processors - update AGENTS.md with team responsibilities - add Studio pipeline changes documentation
This commit is contained in:
@@ -145,11 +145,30 @@ Get real-time processing progress for a file via Redis pub/sub. Includes per-pro
|
||||
| 3 | `asrx` | asr | Speaker diarization |
|
||||
| 4 | `yolo` | — | Object detection |
|
||||
| 5 | `ocr` | — | Text recognition |
|
||||
| 6 | `face` | — | Face detection & embedding |
|
||||
| 7 | `pose` | — | Pose estimation |
|
||||
| 8 | `visual_chunk` | yolo | Visual scene chunks |
|
||||
| 9 | `story` | asr, asrx, cut, yolo, face | Scene summaries (template) |
|
||||
| 10 | `5w1h` | story | 5W1H analysis (Gemma4 LLM) |
|
||||
| 6 | `face` | — | Face detection & embedding (8Hz sampling) |
|
||||
| 7 | `face_trace` | face | Face tracking (IoU + embedding, assigns trace_id) |
|
||||
| 8 | `pose` | face_trace | Pose expansion from face traces, inherits trace_id |
|
||||
| 9 | `appearance` | pose | Appearance expansion from pose traces, inherits trace_id |
|
||||
|
||||
**Key Concepts:**
|
||||
- **Face** = Identity anchor (who is this person?) — requires high-quality embedding
|
||||
- **Pose** = Tracking (where is this person?) — extends tracking when face is occluded
|
||||
- **Appearance** = Tracking (what do they look like?) — extends tracking when pose is occluded
|
||||
|
||||
**Trace ID Inheritance:**
|
||||
```
|
||||
Face trace (identity anchor)
|
||||
↓ inherits trace_id
|
||||
Pose expansion (tracking continuity)
|
||||
↓ inherits trace_id
|
||||
Appearance expansion (tracking continuity)
|
||||
```
|
||||
|
||||
**Frame Count Relationship:**
|
||||
```
|
||||
face frames ≤ pose frames ≤ appearance frames
|
||||
```
|
||||
(Each level expands outward from the previous level's traces)
|
||||
|
||||
All processors except `story` and `5w1h` run concurrently when their dependencies are met. Story and 5W1H run sequentially after their prerequisites.
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ The following routes are defined in source code but are **NOT** currently mounte
|
||||
|
||||
| Endpoint | Source file |
|
||||
|----------|-------------|
|
||||
| `/api/v1/search/persons` | `universal_search.rs` (not mounted) |
|
||||
| `/api/v1/search/people` | `universal_search.rs` (mounted) |
|
||||
| `/api/v1/who` | `who.rs` |
|
||||
| `/api/v1/who/candidates` | `who.rs` |
|
||||
|
||||
|
||||
@@ -6,6 +6,68 @@
|
||||
|
||||
Endpoints for managing trace profiles (face track metadata stored in TKG) and file profiles (video metadata stored in PostgreSQL).
|
||||
|
||||
---
|
||||
|
||||
## Terminology
|
||||
|
||||
### Core Concepts
|
||||
|
||||
| Term | Definition | Identifier | Display Format |
|
||||
|------|------------|------------|----------------|
|
||||
| **Face** | A single human face detection on one frame | `face_id` | Usually not displayed |
|
||||
| **Face Sequence (Trace)** | A collection of faces across multiple frames representing the same person | `trace_id` | `FS#233` |
|
||||
| **Face Group** | A collection of traces sharing the same name/label | `label` (string) | `"Person A"` |
|
||||
|
||||
### Data Model Hierarchy
|
||||
|
||||
```
|
||||
Video
|
||||
└── Frame (F#233, F#234, ...) ← Single frame number
|
||||
└── Face ← Single detection (bbox + confidence)
|
||||
└── Face Sequence / Trace ← Same person across frames (FS#233)
|
||||
└── Face Group ← Multiple traces with same name
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
```
|
||||
Video: "interview.mp4"
|
||||
├── Frame F#100
|
||||
│ └── Face (bbox: {100, 200, 50, 50}, confidence: 0.95)
|
||||
├── Frame F#105
|
||||
│ └── Face (bbox: {110, 205, 48, 48}, confidence: 0.92)
|
||||
├── Frame F#110
|
||||
│ └── Face (bbox: {115, 210, 52, 52}, confidence: 0.88)
|
||||
...
|
||||
|
||||
Face Sequence FS#233 = [Face@F#100, Face@F#105, Face@F#110, ...]
|
||||
↓
|
||||
Face Group "Person A" = [FS#233, FS#234, FS#235]
|
||||
```
|
||||
|
||||
### Storage
|
||||
|
||||
| Entity | Storage | Table/Collection |
|
||||
|--------|---------|------------------|
|
||||
| Face | Qdrant | `_faces` collection |
|
||||
| Face Sequence / Trace | PostgreSQL (TKG) | `tkg_nodes` where `node_type='face_track'` |
|
||||
| Face Group | PostgreSQL (TKG) | `tkg_nodes.label` field |
|
||||
|
||||
### Operations
|
||||
|
||||
| Operation | Level | API |
|
||||
|-----------|-------|-----|
|
||||
| View faces | Face | Internal (embedded in trace data) |
|
||||
| Merge traces | Trace | `POST /api/v1/trace/:file_uuid/merge` |
|
||||
| Rename group | Group | `PUT /api/v1/trace-profile/group` |
|
||||
| Merge groups | Group | `POST /api/v1/file/:file_uuid/groups/merge` |
|
||||
|
||||
### Naming Convention
|
||||
|
||||
- **Frame**: `F#{number}` — e.g., `F#233`
|
||||
- **Face Sequence / Trace**: `FS#{number}` — e.g., `FS#233`
|
||||
- **Face Group**: String name — e.g., `"Person A"`, `"Speaker 1"`
|
||||
|
||||
### `GET /api/v1/trace-profile`
|
||||
|
||||
**Auth**: Required
|
||||
@@ -158,6 +220,151 @@ curl -s -X PUT "$API/api/v1/trace-profile/group" \
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/file/:file_uuid/groups/merge`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Merge multiple face groups into one target group. All traces from source groups are reassigned to the target group name.
|
||||
|
||||
#### Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `file_uuid` | string | Yes | File UUID |
|
||||
| `source_groups` | string[] | Yes | List of group names to merge from |
|
||||
| `target_group_name` | string | Yes | Target group name to merge into |
|
||||
|
||||
#### Examples
|
||||
|
||||
**Merge 2 groups**:
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/file/$FILE_UUID/groups/merge" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"file_uuid": "c36f35685177c981aa139b66bbbccc5b",
|
||||
"source_groups": ["Person A"],
|
||||
"target_group_name": "Person B"
|
||||
}'
|
||||
```
|
||||
|
||||
**Merge 4 groups**:
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/file/$FILE_UUID/groups/merge" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"file_uuid": "c36f35685177c981aa139b66bbbccc5b",
|
||||
"source_groups": ["Person A", "Person C", "Person D"],
|
||||
"target_group_name": "Person B"
|
||||
}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"file_uuid": "c36f35685177c981aa139b66bbbccc5b",
|
||||
"source_groups": ["Person A", "Person C", "Person D"],
|
||||
"target_group_name": "Person B",
|
||||
"traces_merged": 12,
|
||||
"message": "Merged 3 group(s) into 'Person B'"
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | Condition |
|
||||
|------|-----------|
|
||||
| `400` | Target group in source_groups list |
|
||||
| `400` | Empty source_groups array |
|
||||
| `500` | Database error |
|
||||
|
||||
#### Behavior
|
||||
|
||||
1. Find all traces with `label IN (source_groups)`
|
||||
2. Update their labels to `target_group_name`
|
||||
3. All source groups disappear (no traces left)
|
||||
4. Target group contains all traces from merged groups
|
||||
|
||||
---
|
||||
|
||||
### Merging Groups: Two Methods
|
||||
|
||||
#### Method 1: Use Merge Groups API (Recommended)
|
||||
|
||||
```bash
|
||||
POST /api/v1/file/:file_uuid/groups/merge
|
||||
{
|
||||
"file_uuid": "...",
|
||||
"source_groups": ["Person A", "Person C"],
|
||||
"target_group_name": "Person B"
|
||||
}
|
||||
```
|
||||
|
||||
**Pros**: Simple, only requires group names, supports multi-group merge
|
||||
**Cons**: New API (requires backend update)
|
||||
|
||||
#### Method 2: Use Batch Update API (Existing)
|
||||
|
||||
```bash
|
||||
PUT /api/v1/trace-profile/group
|
||||
{
|
||||
"file_uuid": "...",
|
||||
"trace_ids": [13, 14, 15, 43, 44],
|
||||
"name": "Person B"
|
||||
}
|
||||
```
|
||||
|
||||
**Pros**: Works with existing API
|
||||
**Cons**: Frontend must collect all trace_ids from both groups
|
||||
|
||||
#### Example: Merge Group A and C into Group B
|
||||
|
||||
**Before**:
|
||||
```
|
||||
Group A: [13, 14, 15]
|
||||
Group B: [43, 44]
|
||||
Group C: [67, 68]
|
||||
```
|
||||
|
||||
**Method 1 (Recommended)**:
|
||||
```bash
|
||||
curl -X POST "$API/api/v1/file/$FILE_UUID/groups/merge" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"file_uuid": "c36f35685177c981aa139b66bbbccc5b",
|
||||
"source_groups": ["Person A", "Person C"],
|
||||
"target_group_name": "Person B"
|
||||
}'
|
||||
```
|
||||
|
||||
**Method 2 (Existing API)**:
|
||||
```bash
|
||||
curl -X PUT "$API/api/v1/trace-profile/group" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"file_uuid": "c36f35685177c981aa139b66bbbccc5b",
|
||||
"trace_ids": [13, 14, 15, 43, 44, 67, 68],
|
||||
"name": "Person B"
|
||||
}'
|
||||
```
|
||||
|
||||
**After**:
|
||||
```
|
||||
Group A: [] (disappeared)
|
||||
Group B: [43, 44, 13, 14, 15, 67, 68]
|
||||
Group C: [] (disappeared)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/file-profile`
|
||||
|
||||
**Auth**: Required
|
||||
@@ -264,5 +471,6 @@ curl -s -X PUT "$API/api/v1/file-profile" \
|
||||
| `aliases` | `properties->'aliases'` | Multi-language name aliases |
|
||||
|
||||
---
|
||||
*Updated: 2026-07-25 — Added Merge Groups API (POST /groups/merge), added Terminology section (Face, Face Sequence, Face Group)*
|
||||
*Updated: 2026-07-21 — Fixed external_id matching (trace_N + face_track_N formats), fixed parameter ordering in UPDATE query*
|
||||
*Updated: 2026-07-18 — New profile module: trace-profile (GET, PUT, PUT group) and file-profile (GET, PUT)*
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
# People API
|
||||
|
||||
**Version**: 2.0
|
||||
**Date**: 2026-07-26
|
||||
**Base URL**: `http://localhost:3002`
|
||||
**Auth**: Requires API key header (`Authorization: Bearer <api_key>`)
|
||||
**Doc Path**: `/Users/accusys/momentry_core/docs_v1.0/API_WORKSPACE/modules/19_people_api.md`
|
||||
|
||||
---
|
||||
|
||||
## 資料架構
|
||||
|
||||
### trace_profiles(PostgreSQL — People Search 專用)
|
||||
|
||||
每筆 `file_uuid + trace_id` 對應一個 face trace profile:
|
||||
|
||||
| 欄位 | 類型 | 說明 |
|
||||
|------|------|------|
|
||||
| `file_uuid` | string | 影片 UUID |
|
||||
| `trace_id` | integer | Face trace ID(8Hz 取樣追蹤) |
|
||||
| `name` | string | FS name(Face 頁面顯示 + People Search 搜尋) |
|
||||
| `start_frame` | integer | 起始 frame |
|
||||
| `end_frame` | integer | 結束 frame |
|
||||
| `frame_count` | integer | 追蹤 frame 數(8Hz 取樣,多數 > 1) |
|
||||
| `key_frame` | string | 關鍵幀圖片路徑 |
|
||||
| `key_face` | string | 關鍵人臉裁切路徑 |
|
||||
| `avg_confidence` | float | 平均偵測信心值 |
|
||||
| `status` | string | 狀態(pending/confirmed) |
|
||||
|
||||
### 可搜尋註記(metadata / VLM 欄位)
|
||||
|
||||
`trace_profiles` 包含 VLM 產生的註記,可用於進階搜尋和統計:
|
||||
|
||||
| 欄位 | 類型 | 說明 |
|
||||
|------|------|------|
|
||||
| `vlm_description` | text | VLM 人物外貌描述 |
|
||||
| `vlm_clothing` | text | VLM 衣著描述 |
|
||||
| `vlm_tags` | text[] | VLM 標籤陣列 |
|
||||
| `vlm_location` | string | VLM 地點 |
|
||||
| `vlm_setting` | string | VLM 場景設定 |
|
||||
| `vlm_lighting` | string | VLM 光線 |
|
||||
| `vlm_weather` | string | VLM 天氣 |
|
||||
| `vlm_background` | text | VLM 背景描述 |
|
||||
| `vlm_bg_tags` | text[] | VLM 背景標籤 |
|
||||
|
||||
### tkg_nodes(PostgreSQL — TKG 圖譜專用,與 profile 獨立)
|
||||
|
||||
| 欄位 | 說明 |
|
||||
|------|------|
|
||||
| `external_id` | 如 "trace_9",對應 `trace_profiles.trace_id` |
|
||||
| `label` | TKG 圖譜節點標籤(與 `trace_profiles.name` 獨立) |
|
||||
| `properties` | TKG 節點屬性 |
|
||||
|
||||
### _faces(Qdrant — Face 向量比對)
|
||||
|
||||
Face embedding 向量存在 Qdrant `_faces` collection,可用作 seed 比對:
|
||||
|
||||
| Payload 欄位 | 類型 | 說明 |
|
||||
|-------------|------|------|
|
||||
| `file_uuid` | string | 影片 UUID |
|
||||
| `trace_id` | integer | Face trace ID |
|
||||
| `identity_id` | integer \| null | 已綁定的 identity ID |
|
||||
| `frame` | integer | Frame 編號 |
|
||||
| `embedding` | vector[512] | FaceNet 512-d embedding |
|
||||
| `bbox` | object | 人臉 bbox(x, y, width, height) |
|
||||
| `confidence` | float | 偵測信心值 |
|
||||
|
||||
---
|
||||
|
||||
## 1. GET /api/v1/search/people
|
||||
|
||||
搜尋已命名的 face trace profiles。回傳符合 name 的卡片列表。
|
||||
|
||||
### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `query` | string | ✅ Yes | 搜尋關鍵字(ILIKE 模糊比對 `trace_profiles.name`) |
|
||||
| `file_uuid` | string | ❌ Optional | 限制搜尋範圍 |
|
||||
| `limit` | integer | ❌ Optional | 回傳筆數上限(預設 10) |
|
||||
|
||||
### Example Request
|
||||
|
||||
```bash
|
||||
# 搜尋名字
|
||||
curl -X GET "http://localhost:3002/api/v1/search/people?query=Susan"
|
||||
|
||||
# 搜尋 VLM 註記(衣著、地點、標籤)
|
||||
curl -X GET "http://localhost:3002/api/v1/search/people?query=blue+shirt"
|
||||
|
||||
# 限定影片
|
||||
curl -X GET "http://localhost:3002/api/v1/search/people?file_uuid=abc123&query=Tom"
|
||||
```
|
||||
|
||||
### 搜尋範圍
|
||||
|
||||
People Search 會搜尋以下欄位(ILIKE 模糊比對):
|
||||
|
||||
| 欄位 | 說明 |
|
||||
|------|------|
|
||||
| `name` | FS name |
|
||||
| `vlm_description` | VLM 人物外貌描述 |
|
||||
| `vlm_clothing` | VLM 衣著描述 |
|
||||
| `vlm_tags` | VLM 標籤陣列 |
|
||||
| `vlm_location` | VLM 地點 |
|
||||
| `vlm_setting` | VLM 場景設定 |
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"people": [
|
||||
{
|
||||
"file_uuid": "4655a0ab3c077e30c12b2298c2750650",
|
||||
"file_name": "",
|
||||
"trace_id": 9,
|
||||
"external_id": "trace_9",
|
||||
"name": "Susan",
|
||||
"start_frame": 118,
|
||||
"end_frame": 384,
|
||||
"frame_count": 39,
|
||||
"start_time": null,
|
||||
"end_time": null,
|
||||
"key_frame": "key_frame.jpg",
|
||||
"key_face": null,
|
||||
"avg_confidence": 0.694
|
||||
}
|
||||
],
|
||||
"total": 18
|
||||
}
|
||||
```
|
||||
|
||||
### Response Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `file_uuid` | string | 影片 UUID |
|
||||
| `trace_id` | integer | Face trace ID |
|
||||
| `external_id` | string \| null | TKG node external_id(如 "trace_9") |
|
||||
| `name` | string \| null | 使用者命名的 FS name |
|
||||
| `start_frame` | integer \| null | 起始 frame |
|
||||
| `end_frame` | integer \| null | 結束 frame |
|
||||
| `frame_count` | integer \| null | 追蹤 frame 數(8Hz 取樣) |
|
||||
| `start_time` | float \| null | 起始時間(秒) |
|
||||
| `end_time` | float \| null | 結束時間(秒) |
|
||||
| `key_frame` | string \| null | 關鍵幀圖片路徑 |
|
||||
| `key_face` | string \| null | 關鍵人臉裁切路徑 |
|
||||
| `avg_confidence` | float \| null | 平均偵測信心值 |
|
||||
|
||||
---
|
||||
|
||||
## 2. POST /api/v1/search/universal
|
||||
|
||||
統一搜尋。設定 `types: ["people"]` 搜尋人物,或組合 `"chunk"`、`"frame"`。
|
||||
|
||||
People Search 會搜尋 `trace_profiles` 的 `name` + `vlm_*` 欄位:
|
||||
- `name` — FS name
|
||||
- `vlm_description` — VLM 人物外貌描述
|
||||
- `vlm_clothing` — VLM 衣著描述
|
||||
- `vlm_tags` — VLM 標籤陣列
|
||||
- `vlm_location` — VLM 地點
|
||||
- `vlm_setting` — VLM 場景設定
|
||||
|
||||
### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "Susan",
|
||||
"file_uuid": "abc123",
|
||||
"types": ["people"],
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
```
|
||||
|
||||
### Request Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `query` | string | ✅ Yes | 搜尋關鍵字 |
|
||||
| `file_uuid` | string | ❌ Optional | 限制搜尋範圍 |
|
||||
| `types` | string[] | ❌ Optional | `["chunk", "frame", "people"]` — 預設全部 |
|
||||
| `page` | integer | ❌ Optional | 頁碼(預設 1) |
|
||||
| `page_size` | integer | ❌ Optional | 每頁筆數(預設 20,最大 200) |
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "Susan",
|
||||
"results": [
|
||||
{
|
||||
"type": "person",
|
||||
"file_uuid": "4655a0ab3c077e30c12b2298c2750650",
|
||||
"trace_id": 9,
|
||||
"external_id": "trace_9",
|
||||
"name": "Susan",
|
||||
"frame_count": 39,
|
||||
"score": 0.95,
|
||||
"start_time": null,
|
||||
"end_time": null,
|
||||
"key_frame": "key_frame.jpg",
|
||||
"key_face": null
|
||||
}
|
||||
],
|
||||
"total": 18,
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"has_more": false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. PUT /api/v1/trace-profile(單筆改名)
|
||||
|
||||
更新單一 face trace profile 的 name 和 metadata。
|
||||
|
||||
### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"file_uuid": "4655a0ab3c077e30c12b2298c2750650",
|
||||
"trace_id": 9,
|
||||
"name": "Susan",
|
||||
"properties": { "custom_key": "custom_value" },
|
||||
"key_frame": "path/to/key_frame.jpg",
|
||||
"key_face": "path/to/key_face.jpg",
|
||||
"aliases": ["Susie", "Sue"]
|
||||
}
|
||||
```
|
||||
|
||||
### Request Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `file_uuid` | string | ✅ Yes | 影片 UUID |
|
||||
| `trace_id` | integer | ✅ Yes | Face trace ID |
|
||||
| `name` | string | ❌ Optional | 新的 FS name |
|
||||
| `properties` | object | ❌ Optional | 自訂屬性(合併更新) |
|
||||
| `key_frame` | string | ❌ Optional | 關鍵幀路徑 |
|
||||
| `key_face` | string | ❌ Optional | 關鍵人臉路徑 |
|
||||
| `aliases` | string[] | ❌ Optional | 別名列表 |
|
||||
|
||||
---
|
||||
|
||||
## 4. PUT /api/v1/trace-profile/group(批次改名)
|
||||
|
||||
批次更新多個 face trace profiles 的 name。
|
||||
|
||||
### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"file_uuid": "4655a0ab3c077e30c12b2298c2750650",
|
||||
"trace_ids": [9, 22, 5],
|
||||
"name": "Susan"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. POST /api/v1/groups/merge(合併群組)
|
||||
|
||||
將多個 group 的 traces 合併為一個 group name。
|
||||
|
||||
### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"file_uuid": "4655a0ab3c077e30c12b2298c2750650",
|
||||
"source_groups": ["Person_0", "Person_1"],
|
||||
"target_group_name": "Susan"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
### 400 Bad Request
|
||||
```json
|
||||
{ "error": "name is required" }
|
||||
```
|
||||
|
||||
### 401 Unauthorized
|
||||
```
|
||||
HTTP 401 (no body) — Missing or invalid API key
|
||||
```
|
||||
|
||||
### 404 Not Found
|
||||
```json
|
||||
{ "error": "Profile not found" }
|
||||
```
|
||||
|
||||
### 500 Internal Server Error
|
||||
```json
|
||||
{ "error": "DB error: connection refused" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 如何命名 Face Trace
|
||||
|
||||
使用 profile API 為 face trace 命名:
|
||||
|
||||
```bash
|
||||
curl -X PUT "http://localhost:3002/api/v1/trace-profile" \
|
||||
-H "Authorization: Bearer <api_key>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"file_uuid": "4655a0ab3c077e30c12b2298c2750650",
|
||||
"trace_id": 9,
|
||||
"name": "Susan"
|
||||
}'
|
||||
```
|
||||
|
||||
命名後即可透過 People Search API 搜尋:
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3002/api/v1/search/people?query=Susan" \
|
||||
-H "Authorization: Bearer <api_key>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pipeline 狀態定義
|
||||
|
||||
所有檔案的 status 由 `/api/v1/file/{file_uuid}/sync-status` 自動更新,依據 processor outputs 和功能就緒狀態判定。
|
||||
|
||||
| Status | 前端顯示 | 條件 | 啟用功能 |
|
||||
|--------|---------|------|---------|
|
||||
| `registered` | 📋 已註冊 | 0 processor outputs | 檔案註冊 |
|
||||
| `scanning` | 🔍 掃描中 | 1-4/5 processor outputs | — |
|
||||
| `keyword_ready` | 🔎 關鍵字可用 | sentence chunks > 0 | Smart Search |
|
||||
| `semantic_ready` | 🧠 語意可用 | embedded chunks > 0 | Semantic Search |
|
||||
| `face_mgmt_ready` | 🎭 臉部管理可用 | face_trace + trace_profiles + tkg_nodes(face_track) | People Search, Face Profile 讀寫, Face Group |
|
||||
| `agent_ready` | 🤖 Agent 可用 | face_mgmt_ready + tkg_edges | Agent TKG Tools |
|
||||
| `completed` | ✅ 完成 | 5/5 processors + 全部功能就緒 | — |
|
||||
|
||||
### Face Management Ready 明確定義
|
||||
|
||||
`face_mgmt_ready` 表示檔案已具備完整臉部管理功能,需同時滿足以下四個條件:
|
||||
|
||||
| 依賴項 | 檢查條件 | 說明 | 提供功能 |
|
||||
|--------|---------|------|---------|
|
||||
| **Face Qdrant Ready** | `_faces` collection 有 face points | Face embeddings 已存入 Qdrant | 臉部比對、相似度搜尋 |
|
||||
| **Face Trace Ready** | `face_traced.json` 存在且有 traces | Face tracking 已完成 | 人臉軌跡資料 |
|
||||
| **Face Profile Ready** | `trace_profiles` 有紀錄 | Profile 資料已建立 | 名字、VLM 註記、key_frame/key_face |
|
||||
| **Face TKG Node Ready** | `tkg_nodes` 有 `face_track` nodes | TKG 圖譜節點已建立 | 臉部群組合併、關係查詢 |
|
||||
|
||||
#### 檢查 SQL
|
||||
|
||||
```sql
|
||||
-- Face Qdrant Ready
|
||||
SELECT COUNT(*) FROM _faces WHERE file_uuid = $1; -- > 0
|
||||
|
||||
-- Face Trace Ready
|
||||
-- 檢查 face_traced.json 檔案存在且有 traces
|
||||
|
||||
-- Face Profile Ready
|
||||
SELECT COUNT(*) FROM trace_profiles WHERE file_uuid = $1; -- > 0
|
||||
|
||||
-- Face TKG Node Ready
|
||||
SELECT COUNT(*) FROM tkg_nodes WHERE file_uuid = $1 AND node_type = 'face_track'; -- > 0
|
||||
```
|
||||
|
||||
#### 啟用功能清單
|
||||
|
||||
| 功能 | API Endpoint | 說明 |
|
||||
|------|-------------|------|
|
||||
| People Search | `GET /api/v1/search/people` | 搜尋已命名人臉 |
|
||||
| Face Profile 讀寫 | `PUT /api/v1/trace-profile` | 更新名字、VLM 註記 |
|
||||
| Face Group 合併 | `POST /api/v1/groups/merge` | 合併多個群組 |
|
||||
| Face Trace 改名 | `PUT /api/v1/trace-profile` | 為單個 trace 命名 |
|
||||
| Agent Face Search | Agent Tool: `face_profile_search` | Agent 搜尋人臉 |
|
||||
|
||||
---
|
||||
|
||||
### 狀態依賴關係
|
||||
|
||||
```
|
||||
registered → scanning → keyword_ready → semantic_ready
|
||||
↓
|
||||
face_mgmt_ready → agent_ready → completed
|
||||
```
|
||||
|
||||
### 每個階段對應的 Processor
|
||||
|
||||
| 階段 | 必要 Processor | 產出 |
|
||||
|------|---------------|------|
|
||||
| `keyword_ready` | ASR, ASRX | sentence chunks |
|
||||
| `semantic_ready` | keyword_ready + Vectorize | embedded chunks |
|
||||
| `face_mgmt_ready` | Face, FaceCluster | trace_profiles (People Search + Face Management) |
|
||||
| `agent_ready` | face_mgmt_ready + TKG Edges | tkg_edges |
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
# Studio Team API 變更指南
|
||||
|
||||
**Version**: 1.0
|
||||
**Date**: 2026-07-26
|
||||
**Doc Path**: `/Users/accusys/momentry_core/docs_v1.0/API_WORKSPACE/modules/21_studio_team_guide.md`
|
||||
**目標**: 通知 Studio team 後端 API 狀態欄位變更,Momentry Studio 前端需配合更新
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
後端已將檔案狀態從技術術語改為**功能導向**名稱。Studio team 的 `~/momentry_studio/` 前端顯示邏輯需要更新。
|
||||
|
||||
**影響範圍:**
|
||||
- 後端 API:`/Users/accusys/momentry_core/src/api/files.rs`(Core team 已修改)
|
||||
- Studio 前端:`~/momentry_studio/`(需 Studio team 更新)
|
||||
|
||||
---
|
||||
|
||||
## 2. 團隊職責
|
||||
|
||||
| 團隊 | 負責專案 | 目錄 |
|
||||
|------|---------|------|
|
||||
| **Core team** | Momentry Core 後端 | `~/momentry_core/` |
|
||||
| **Studio team** | Momentry Studio 前端 | `~/momentry_studio/` |
|
||||
| **Marcom team** | WordPress 網站 | `/Users/accusys/wordpress/` |
|
||||
|
||||
---
|
||||
|
||||
## 3. API 變更
|
||||
|
||||
### `/api/v1/file/{file_uuid}/sync-status` 回傳格式
|
||||
|
||||
**POST** `/api/v1/file/{file_uuid}/sync-status`
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"file_uuid": "4655a0ab3c077e30c12b2298c2750650",
|
||||
"status": "face_mgmt_ready",
|
||||
"processors_complete": 4,
|
||||
"processors_total": 5,
|
||||
"worker": {
|
||||
"has_job": true,
|
||||
"job_status": "pending",
|
||||
"current_processor": null,
|
||||
"progress_total": 7,
|
||||
"progress_current": 7,
|
||||
"completed_processors": ["cut", "asr", "face", "ocr", "pose", "asrx", "face_cluster"],
|
||||
"failed_processors": [],
|
||||
"updated_at": "2026-07-24T21:26:06"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**無 job 的檔案:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"file_uuid": "...",
|
||||
"status": "registered",
|
||||
"processors_complete": 0,
|
||||
"processors_total": 5,
|
||||
"worker": {
|
||||
"has_job": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Worker 狀態說明
|
||||
|
||||
| 欄位 | 說明 |
|
||||
|------|------|
|
||||
| `has_job` | 是否有 monitor_job 紀錄 |
|
||||
| `job_status` | pending / running / completed / failed |
|
||||
| `current_processor` | 目前正在執行的 processor |
|
||||
| `progress_total` | 總 processor 數量 |
|
||||
| `progress_current` | 已完成的 processor 數量 |
|
||||
| `completed_processors` | 已完成的 processor 列表 |
|
||||
| `failed_processors` | 失敗的 processor 列表 |
|
||||
| `updated_at` | 最後更新時間 |
|
||||
|
||||
### 使用者可區分的情境
|
||||
|
||||
| 情境 | `job_status` | `current_processor` | 顯示建議 |
|
||||
|------|-------------|-------------------|---------|
|
||||
| **正在處理** | `running` | `face` | 🔄 正在執行 Face |
|
||||
| **排隊等待** | `pending` | `null` | ⏳ 等待處理 |
|
||||
| **已完成** | `completed` | `null` | ✅ Processor 完成 |
|
||||
| **處理失敗** | `failed` | `face` | ❌ Face 失敗 |
|
||||
| **從未提交** | `has_job=false` | - | 📋 僅註冊 |
|
||||
|
||||
### 新狀態清單
|
||||
|
||||
| Status | 中文顯示 | 圖示 | 說明 |
|
||||
|--------|---------|------|------|
|
||||
| `registered` | 已註冊 | 📋 | 0 processor outputs |
|
||||
| `scanning` | 掃描中 | 🔍 | 1-4/5 processor outputs |
|
||||
| `keyword_ready` | 關鍵字可用 | 🔎 | sentence chunks > 0 |
|
||||
| `semantic_ready` | 語意可用 | 🧠 | embedded chunks > 0 |
|
||||
| `face_mgmt_ready` | 臉部管理可用 | 🎭 | trace_profiles + tkg_nodes > 0 |
|
||||
| `agent_ready` | Agent 可用 | 🤖 | tkg_edges > 0 |
|
||||
| `completed` | 已完成 | ✅ | 全部就緒 |
|
||||
|
||||
### 已移除的舊狀態
|
||||
|
||||
| 舊狀態 | 新狀態 |
|
||||
|--------|--------|
|
||||
| `pending` | `registered` |
|
||||
| `processing` | `scanning` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Studio 前端需修改項目
|
||||
|
||||
### 4.1 狀態顯示邏輯
|
||||
|
||||
**位置:** `~/momentry_studio/` 中處理檔案狀態顯示的元件
|
||||
|
||||
**修改前:**
|
||||
```javascript
|
||||
switch (file.status) {
|
||||
case 'completed': return '✅ 已完成';
|
||||
case 'processing': return '🔄 處理中';
|
||||
case 'pending': return '⏳ 待處理';
|
||||
default: return '📦 未註冊';
|
||||
}
|
||||
```
|
||||
|
||||
**修改後:**
|
||||
```javascript
|
||||
const statusMap = {
|
||||
completed: { label: '已完成', icon: '✅', color: 'green' },
|
||||
agent_ready: { label: 'Agent 可用', icon: '🤖', color: 'orange' },
|
||||
face_mgmt_ready: { label: '臉部管理可用', icon: '🎭', color: 'cyan' },
|
||||
semantic_ready: { label: '語意可用', icon: '🧠', color: 'purple' },
|
||||
keyword_ready: { label: '關鍵字可用', icon: '🔎', color: 'green' },
|
||||
scanning: { label: '掃描中', icon: '🔍', color: 'blue' },
|
||||
registered: { label: '已註冊', icon: '📋', color: 'gray' },
|
||||
};
|
||||
|
||||
const display = statusMap[file.status] || { label: '未註冊', icon: '📦', color: 'gray' };
|
||||
return `${display.icon} ${display.label}`;
|
||||
```
|
||||
|
||||
### 4.2 狀態過濾選項
|
||||
|
||||
**位置:** `~/momentry_studio/` 中檔案列表過濾元件
|
||||
|
||||
**移除:** `pending`, `processing`
|
||||
**新增:** `registered`, `scanning`, `keyword_ready`, `semantic_ready`, `face_mgmt_ready`, `agent_ready`
|
||||
|
||||
---
|
||||
|
||||
## 5. Face Management Ready 明確定義
|
||||
|
||||
`face_mgmt_ready` 表示檔案已具備完整臉部管理功能,需同時滿足以下四個條件:
|
||||
|
||||
| 依賴項 | 檢查條件 | 說明 |
|
||||
|--------|---------|------|
|
||||
| **Face Qdrant Ready** | `_faces` collection 有 face points | Face embeddings 已存入 Qdrant |
|
||||
| **Face Trace Ready** | `face_traced.json` 存在且有 traces | Face tracking 已完成 |
|
||||
| **Face Profile Ready** | `trace_profiles` 有紀錄 | Profile 資料已建立 |
|
||||
| **Face TKG Node Ready** | `tkg_nodes` 有 `face_track` nodes | TKG 圖譜節點已建立 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 狀態依賴關係
|
||||
|
||||
```
|
||||
registered → scanning → keyword_ready → semantic_ready
|
||||
↓
|
||||
face_mgmt_ready → agent_ready → completed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 測試 API
|
||||
|
||||
```bash
|
||||
# 取得所有檔案
|
||||
curl http://localhost:3002/api/v1/files \
|
||||
-H "Authorization: Bearer <api_key>"
|
||||
|
||||
# 觸發狀態同步
|
||||
curl -X POST http://localhost:3002/api/v1/file/{file_uuid}/sync-status \
|
||||
-H "Authorization: Bearer <api_key>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 參考文件
|
||||
|
||||
| 文件 | 完整路徑 |
|
||||
|------|---------|
|
||||
| People API | `/Users/accusys/momentry_core/docs_v1.0/API_WORKSPACE/modules/19_people_api.md` |
|
||||
| Status Unification | `/Users/accusys/momentry_core/docs_v1.0/API_WORKSPACE/modules/20_status_unification.md` |
|
||||
| Studio Team Guide | `/Users/accusys/momentry_core/docs_v1.0/API_WORKSPACE/modules/21_studio_team_guide.md` |
|
||||
| AGENTS.md | `/Users/accusys/momentry_core/AGENTS.md` |
|
||||
@@ -0,0 +1,207 @@
|
||||
# Studio Team 配套修改指南
|
||||
|
||||
**Version**: 1.0
|
||||
**Date**: 2026-07-26
|
||||
**Doc Path**: `/Users/accusys/momentry_core/docs_v1.0/API_WORKSPACE/modules/22_studio_pipeline_changes.md`
|
||||
**目標**: 通知 Studio team 後端 Pipeline 狀態變更,前端需配合更新
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
後端已完成以下修改:
|
||||
1. 移除 `identity_agent`,替換為 `face_dedup`(Face Deduplication)
|
||||
2. 重構 `sync_file_status` 邏輯:檢查 JSON 存在 + DB 一致性
|
||||
3. Pipeline 進度階段重新分配
|
||||
|
||||
**影響範圍:**
|
||||
- 檔案狀態顯示(status)
|
||||
- Pipeline 進度顯示(progress stages)
|
||||
- 統計 API 回傳格式
|
||||
|
||||
---
|
||||
|
||||
## 2. 檔案狀態變更
|
||||
|
||||
### 新狀態清單
|
||||
|
||||
| Status | 中文顯示 | 圖示 | 說明 |
|
||||
|--------|---------|------|------|
|
||||
| `registered` | 已註冊 | 📋 | 尚未開始處理 |
|
||||
| `scanning` | 掃描中 | 🔍 | 處理中(JSON 存在但 DB 不一致) |
|
||||
| `completed` | 已完成 | ✅ | 所有 processor 完成且 DB 一致 |
|
||||
| `agent_ready` | Agent 可用 | 🤖 | TKG edges 存在 |
|
||||
|
||||
### 已移除的狀態
|
||||
|
||||
| 舊狀態 | 新狀態 | 說明 |
|
||||
|--------|--------|------|
|
||||
| `processing` | `scanning` | 更明確表示正在處理 |
|
||||
| `pending` | `registered` | 尚未開始 |
|
||||
| `face_mgmt_ready` | `completed` | 最終狀態統一為 completed |
|
||||
|
||||
### 狀態判斷邏輯
|
||||
|
||||
| 條件 | 狀態 |
|
||||
|------|------|
|
||||
| 所有 JSON 存在且 DB 一致 | `completed` |
|
||||
| 部分 JSON 存在但不一致 | `scanning` |
|
||||
| 無 JSON 存在 | `registered` |
|
||||
| TKG edges 存在 | `agent_ready` |
|
||||
|
||||
---
|
||||
|
||||
## 3. Pipeline 進度階段變更
|
||||
|
||||
### 修改前(7 個 stage)
|
||||
|
||||
```
|
||||
Processors (30%) → Rule1 (5%) → Face Tracing (5%) → identity_agent (10%) → TKG Nodes (20%) → TKG Edges (15%) → Rule2 (15%)
|
||||
```
|
||||
|
||||
### 修改後(7 個 stage)
|
||||
|
||||
```
|
||||
Processors (30%) → Rule1 (5%) → Face Tracing (5%) → face_dedup (10%) → TKG Nodes (20%) → TKG Edges (15%) → Rule2 (15%)
|
||||
```
|
||||
|
||||
### 需要修改的前端元件
|
||||
|
||||
| 元件 | 修改內容 |
|
||||
|------|---------|
|
||||
| Pipeline Progress Bar | 移除 `identity_agent`,新增 `face_dedup` |
|
||||
| Stage Labels | 更新 stage 名稱 |
|
||||
| Overall Progress | 重新計算權重 |
|
||||
|
||||
---
|
||||
|
||||
## 4. API 變更
|
||||
|
||||
### `/api/v1/file/{file_uuid}/sync-status`
|
||||
|
||||
**回傳格式變更:**
|
||||
|
||||
**修改前:**
|
||||
```json
|
||||
{
|
||||
"status": "face_mgmt_ready",
|
||||
"processors_complete": 5,
|
||||
"processors_total": 5,
|
||||
"worker": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**修改後:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"file_uuid": "...",
|
||||
"status": "completed",
|
||||
"processors": {
|
||||
"asr": { "json_exists": true, "consistent": true },
|
||||
"asrx": { "json_exists": true, "consistent": true },
|
||||
"ocr": { "json_exists": true, "consistent": true },
|
||||
"pose": { "json_exists": true, "consistent": true },
|
||||
"cut": { "json_exists": true, "consistent": true },
|
||||
"face": { "json_exists": true, "consistent": true },
|
||||
"face_cluster": { "json_exists": true, "consistent": true }
|
||||
},
|
||||
"worker": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### `/api/v1/file/{file_uuid}/stats`
|
||||
|
||||
**回傳格式變更:**
|
||||
|
||||
**修改前:**
|
||||
```json
|
||||
{
|
||||
"identity_agent": {
|
||||
"clusters": 0,
|
||||
"identities_created": 0,
|
||||
"tmdb_matches": 0,
|
||||
"speaker_bindings": 0,
|
||||
"confirmations": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**修改後:**
|
||||
```json
|
||||
{
|
||||
"face_dedup": {
|
||||
"clusters": 5,
|
||||
"face_tracks": 24,
|
||||
"consistent": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Studio 前端需修改項目
|
||||
|
||||
### 5.1 狀態顯示邏輯
|
||||
|
||||
**位置:** Studio 前端檔案列表元件
|
||||
|
||||
**修改前:**
|
||||
```javascript
|
||||
const statusLabels = {
|
||||
'completed': '✅ 已完成',
|
||||
'processing': '🔄 處理中',
|
||||
'pending': '⏳ 待處理',
|
||||
'face_mgmt_ready': '🎭 臉部管理可用',
|
||||
'agent_ready': '🤖 Agent 可用'
|
||||
};
|
||||
```
|
||||
|
||||
**修改後:**
|
||||
```javascript
|
||||
const statusLabels = {
|
||||
'completed': '✅ 已完成',
|
||||
'scanning': '🔍 掃描中',
|
||||
'registered': '📋 已註冊',
|
||||
'agent_ready': '🤖 Agent 可用'
|
||||
};
|
||||
```
|
||||
|
||||
### 5.2 Pipeline 進度顯示
|
||||
|
||||
**位置:** Studio 前端 Pipeline Progress 元件
|
||||
|
||||
**修改項目:**
|
||||
1. 移除 `identity_agent` stage
|
||||
2. 新增 `face_dedup` stage
|
||||
3. 更新 stage 名稱映射
|
||||
|
||||
### 5.3 統計面板
|
||||
|
||||
**位置:** Studio 前端檔案統計面板
|
||||
|
||||
**修改項目:**
|
||||
1. 移除 `Identity Agent` 區塊
|
||||
2. 新增 `Face Deduplication` 區塊
|
||||
3. 顯示欄位:`clusters`, `face_tracks`, `consistent`
|
||||
|
||||
---
|
||||
|
||||
## 6. 測試清單
|
||||
|
||||
- [ ] 檔案列表狀態顯示正確
|
||||
- [ ] Pipeline 進度階段正確顯示
|
||||
- [ ] Overall Progress 計算正確
|
||||
- [ ] 統計面板 Face Dedup 區塊顯示正確
|
||||
- [ ] sync-status API 回傳格式解析正確
|
||||
- [ ] stats API 回傳格式解析正確
|
||||
|
||||
---
|
||||
|
||||
## 7. 參考文件
|
||||
|
||||
| 文件 | 完整路徑 |
|
||||
|------|---------|
|
||||
| Studio Team Guide | `/Users/accusys/momentry_core/docs_v1.0/API_WORKSPACE/modules/21_studio_team_guide.md` |
|
||||
| People API | `/Users/accusys/momentry_core/docs_v1.0/API_WORKSPACE/modules/19_people_api.md` |
|
||||
| Pipeline Changes | `/Users/accusys/momentry_core/docs_v1.0/API_WORKSPACE/modules/22_studio_pipeline_changes.md` |
|
||||
@@ -0,0 +1,114 @@
|
||||
# Studio Fix: Face Group Name 讀寫不一致
|
||||
|
||||
**Date**: 2026-07-27
|
||||
**Author**: Studio Team
|
||||
**Status**: ✅ 已修復並部署
|
||||
|
||||
---
|
||||
|
||||
## 問題描述
|
||||
|
||||
Studio 前端在 Face 頁面 rename group name 後,離開再回來時名稱未更新。但 People Search 可以查到新名稱。
|
||||
|
||||
### 根因
|
||||
|
||||
**寫入**和**讀取**走了不同的資料來源:
|
||||
|
||||
| 操作 | API Endpoint | 實際讀寫欄位 |
|
||||
|------|-------------|------------|
|
||||
| **寫入 (rename)** | `PUT /api/v1/trace-profile/group` | `trace_profiles.name` |
|
||||
| **讀取 (卡片顯示)** | `GET /api/v1/file/:uuid/face-groups` | `tkg_nodes.label` |
|
||||
|
||||
`trace_profiles.name` 和 `tkg_nodes.label` 是兩張獨立的表/欄位(見 `19_people_api.md`),rename 只更新了 `trace_profiles.name`,但卡片顯示讀的是 `tkg_nodes.label`。
|
||||
|
||||
---
|
||||
|
||||
## 修復內容
|
||||
|
||||
### 修改檔案
|
||||
`/Users/accusys/momentry_core/src/api/profile.rs` — `get_face_groups_handler` (line 552-591)
|
||||
|
||||
### 改前 SQL
|
||||
```sql
|
||||
SELECT label, properties FROM tkg_nodes
|
||||
WHERE file_uuid = $1 AND node_type = 'face_track'
|
||||
ORDER BY (properties->>'trace_id')::int
|
||||
```
|
||||
|
||||
### 改後 SQL
|
||||
```sql
|
||||
SELECT COALESCE(tp.name, tn.label) as name, tn.properties
|
||||
FROM tkg_nodes tn
|
||||
LEFT JOIN trace_profiles tp ON tp.file_uuid = tn.file_uuid
|
||||
AND tp.trace_id = (tn.properties->>'trace_id')::int
|
||||
WHERE tn.file_uuid = $1 AND tn.node_type = 'face_track'
|
||||
ORDER BY (tn.properties->>'trace_id')::int
|
||||
```
|
||||
|
||||
### Rust 程式碼變更
|
||||
```diff
|
||||
pub async fn get_face_groups_handler(...) {
|
||||
let tkg_table = schema::table_name("tkg_nodes");
|
||||
+ let tp_table = schema::table_name("trace_profiles");
|
||||
|
||||
let rows: Vec<(String, serde_json::Value)> = sqlx::query_as(&format!(
|
||||
- "SELECT label, properties FROM {} \
|
||||
- WHERE file_uuid = $1 AND node_type = 'face_track' \
|
||||
- ORDER BY (properties->>'trace_id')::int",
|
||||
- tkg_table
|
||||
+ "SELECT COALESCE(tp.name, tn.label) as name, tn.properties \
|
||||
+ FROM {} tn \
|
||||
+ LEFT JOIN {} tp ON tp.file_uuid = tn.file_uuid \
|
||||
+ AND tp.trace_id = (tn.properties->>'trace_id')::int \
|
||||
+ WHERE tn.file_uuid = $1 AND tn.node_type = 'face_track' \
|
||||
+ ORDER BY (tn.properties->>'trace_id')::int",
|
||||
+ tkg_table, tp_table
|
||||
))
|
||||
...
|
||||
- for (label, properties) in rows {
|
||||
+ for (name, properties) in rows {
|
||||
...
|
||||
- if label.starts_with("Face Trace ") || label.starts_with("Trace ") {
|
||||
+ if name.starts_with("Face Trace ") || name.starts_with("Trace ") {
|
||||
unassigned.push(trace_id);
|
||||
} else {
|
||||
- groups.entry(label).or_default().push(trace_id);
|
||||
+ groups.entry(name).or_default().push(trace_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 影響範圍
|
||||
|
||||
| 項目 | 影響 |
|
||||
|------|------|
|
||||
| **Face 頁面卡片顯示** | ✅ 現在從 `trace_profiles.name` 讀取,rename 後立即生效 |
|
||||
| **People Search** | 不受影響(本來就查 `trace_profiles.name`) |
|
||||
| **未命名 traces** | 不受影響(`COALESCE` fallback 到 `tkg_nodes.label`,如 `Person_0`) |
|
||||
| **Cluster Agent 初始化** | 不受影響(初始化時兩邊都寫入相同值) |
|
||||
|
||||
---
|
||||
|
||||
## 注意事項(給 Core Team)
|
||||
|
||||
1. **未來如果有需要同時更新 `tkg_nodes.label` 的情境**(例如 graph 顯示需要),請注意 `PUT /api/v1/trace-profile/group` 目前只更新 `trace_profiles.name`
|
||||
2. **建議**:如果 `tkg_nodes.label` 和 `trace_profiles.name` 應該保持一致,可以考慮:
|
||||
- 方案 A(目前做法):讀取端用 `COALESCE` 優先取 `trace_profiles.name`
|
||||
- 方案 B:寫入端同時更新兩個欄位(需要改 `update_trace_profile_group_handler`)
|
||||
3. **`merge_groups_handler`** 目前同時更新 `trace_profiles.name` 和 `tkg_nodes.label`(line 278-310),行為正確,不需要改
|
||||
|
||||
---
|
||||
|
||||
## 驗證
|
||||
|
||||
```bash
|
||||
# 測試 face-groups endpoint 正確回傳 rename 後的名稱
|
||||
curl -s "http://localhost:3002/api/v1/file/4655a0ab3c077e30c12b2298c2750650/face-groups" \
|
||||
-H "X-API-Key: <key>" | jq '.face_groups[] | {name, trace_count}'
|
||||
|
||||
# 預期輸出包含 "Susan"(已 rename 的 group)
|
||||
# { "name": "Susan", "trace_count": 7 }
|
||||
```
|
||||
@@ -166,7 +166,7 @@ These endpoints are defined in source code but not mounted in the router:
|
||||
|
||||
| Endpoint | Notes |
|
||||
|----------|-------|
|
||||
| `/api/v1/search/persons` | Defined but not mounted |
|
||||
| `/api/v1/search/people` | ✅ Mounted |
|
||||
| `/api/v1/who` | Defined but not mounted |
|
||||
| `/api/v1/who/candidates` | Defined but not mounted |
|
||||
|
||||
@@ -179,7 +179,7 @@ These endpoints are defined in source code but not mounted in the router:
|
||||
| Undocumented | 3 (resource management) |
|
||||
| Partially documented | 5 (5W1H ×3, identity agent ×2) |
|
||||
| Stub/not functional | 5 (visual search) |
|
||||
| Defined but unmounted | 3 (persons, who, who/candidates) |
|
||||
| Defined but unmounted | 2 (who, who/candidates) |
|
||||
| **Total** | **16** |
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user