fix: stranger_id=NULL on bind/merge; doc: add traces+mergeinto endpoints
This commit is contained in:
@@ -234,6 +234,11 @@ Bind a face detection to an identity. Associates the face trace with the identit
|
||||
| `file_uuid` | string | Yes | File where face is detected |
|
||||
| `face_id` | string | Yes | Face ID (format: `{frame}_{idx}`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 清除該 face detection row 的 `stranger_id`(設為 NULL)
|
||||
- 不影響 `identities` 表中原有的 stranger auto-identity 記錄
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
@@ -259,6 +264,11 @@ Bind all face detections of a trace to an identity. Updates all rows in `face_de
|
||||
| `file_uuid` | string | Yes | File where trace exists |
|
||||
| `trace_id` | integer | Yes | Trace ID (from `face_detections.trace_id`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 清除該 trace 所有 face detection rows 的 `stranger_id`(設為 NULL)
|
||||
- 不影響 `identities` 表中原有的 stranger auto-identity 記錄
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
@@ -287,6 +297,78 @@ curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/bind/trace" \
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/identity/:identity_uuid/traces`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: identity-level
|
||||
|
||||
Get paginated face traces (continuous tracking segments) associated with this identity across all files.
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `page` | integer | No | `1` | Page number |
|
||||
| `page_size` | integer | No | `20` | Items per page |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s "$API/api/v1/identity/$IDENTITY_UUID/traces?page=1&page_size=3" \
|
||||
-H "X-API-Key: $KEY" | jq '{total, total_faces, traces}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
|
||||
"name": "Cary Grant",
|
||||
"total": 18,
|
||||
"page": 1,
|
||||
"page_size": 3,
|
||||
"total_faces": 542,
|
||||
"traces": [
|
||||
{
|
||||
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692",
|
||||
"trace_id": 906,
|
||||
"frame_count": 52,
|
||||
"first_frame": 37974,
|
||||
"last_frame": 38127,
|
||||
"first_sec": 1519.0,
|
||||
"last_sec": 1525.1,
|
||||
"avg_confidence": 0.8254
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `success` | bool | Always `true` |
|
||||
| `identity_uuid` | string | Identity UUID |
|
||||
| `name` | string | Identity display name |
|
||||
| `total` | integer | Total number of traces (across all pages) |
|
||||
| `total_faces` | integer | Sum of all face detections in returned traces |
|
||||
| `traces[].file_uuid` | string | File where trace exists |
|
||||
| `traces[].trace_id` | integer | Trace tracking ID |
|
||||
| `traces[].frame_count` | integer | Number of frames in this trace |
|
||||
| `traces[].first_frame` | integer | Start frame number |
|
||||
| `traces[].last_frame` | integer | End frame number |
|
||||
| `traces[].first_sec` | float | Start time in seconds |
|
||||
| `traces[].last_sec` | float | End time in seconds |
|
||||
| `traces[].avg_confidence` | float | Average detection confidence (0.0–1.0) |
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `404` | Identity not found |
|
||||
| `500` | Database error |
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/identity/:identity_uuid/unbind`
|
||||
|
||||
**Auth**: Required
|
||||
@@ -294,6 +376,61 @@ curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/bind/trace" \
|
||||
|
||||
Unbind a face detection from an identity. Removes the identity association from the face record.
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 只清除 `identity_id`(設為 NULL),**不會恢復 `stranger_id`**
|
||||
- 被 unbind 的 face 不會自動成為 stranger
|
||||
- 要重新標記為 stranger 需重新跑 Agent API(`identity/analyze`)
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/identity/:identity_uuid/mergeinto`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: identity-level
|
||||
|
||||
Transfer all face bindings from this identity to another identity, then optionally delete or mark the source as merged.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `into_uuid` | string | Yes | — | Target identity UUID to merge into |
|
||||
| `keep_history` | bool | No | `true` | Keep source identity record with `status='merged'` (`true`) or delete it (`false`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 轉移所有 `face_detections.identity_id` 到目標 identity
|
||||
- 同時清除所有被轉移 rows 的 `stranger_id`
|
||||
- `keep_history: true`(預設):source identity 設為 `status='merged'`,保留記錄
|
||||
- `keep_history: false`:**刪除** source identity 及其 identity JSON 檔案
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/identity/$SOURCE_UUID/mergeinto" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"into_uuid": "'"$TARGET_UUID"'", "keep_history": false}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Merged 'stranger_13894' into 'Louis Viret' (52 faces transferred, source deleted)",
|
||||
"data": { "faces_transferred": 52 }
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `404` | Source or target identity not found |
|
||||
| `500` | Database error |
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/identities/search`
|
||||
@@ -491,4 +628,4 @@ PATCH /api/v1/identity/:identity_uuid
|
||||
This **replaces** the entire `aliases` array. To add to existing aliases, include all existing entries in the request.
|
||||
|
||||
---
|
||||
*Updated: 2026-05-22
|
||||
*Updated: 2026-05-25
|
||||
|
||||
@@ -234,6 +234,11 @@ Bind a face detection to an identity. Associates the face trace with the identit
|
||||
| `file_uuid` | string | Yes | File where face is detected |
|
||||
| `face_id` | string | Yes | Face ID (format: `{frame}_{idx}`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 清除該 face detection row 的 `stranger_id`(設為 NULL)
|
||||
- 不影響 `identities` 表中原有的 stranger auto-identity 記錄
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
@@ -259,6 +264,11 @@ Bind all face detections of a trace to an identity. Updates all rows in `face_de
|
||||
| `file_uuid` | string | Yes | File where trace exists |
|
||||
| `trace_id` | integer | Yes | Trace ID (from `face_detections.trace_id`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 清除該 trace 所有 face detection rows 的 `stranger_id`(設為 NULL)
|
||||
- 不影響 `identities` 表中原有的 stranger auto-identity 記錄
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
@@ -287,6 +297,78 @@ curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/bind/trace" \
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/identity/:identity_uuid/traces`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: identity-level
|
||||
|
||||
Get paginated face traces (continuous tracking segments) associated with this identity across all files.
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `page` | integer | No | `1` | Page number |
|
||||
| `page_size` | integer | No | `20` | Items per page |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s "$API/api/v1/identity/$IDENTITY_UUID/traces?page=1&page_size=3" \
|
||||
-H "X-API-Key: $KEY" | jq '{total, total_faces, traces}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
|
||||
"name": "Cary Grant",
|
||||
"total": 18,
|
||||
"page": 1,
|
||||
"page_size": 3,
|
||||
"total_faces": 542,
|
||||
"traces": [
|
||||
{
|
||||
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692",
|
||||
"trace_id": 906,
|
||||
"frame_count": 52,
|
||||
"first_frame": 37974,
|
||||
"last_frame": 38127,
|
||||
"first_sec": 1519.0,
|
||||
"last_sec": 1525.1,
|
||||
"avg_confidence": 0.8254
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `success` | bool | Always `true` |
|
||||
| `identity_uuid` | string | Identity UUID |
|
||||
| `name` | string | Identity display name |
|
||||
| `total` | integer | Total number of traces (across all pages) |
|
||||
| `total_faces` | integer | Sum of all face detections in returned traces |
|
||||
| `traces[].file_uuid` | string | File where trace exists |
|
||||
| `traces[].trace_id` | integer | Trace tracking ID |
|
||||
| `traces[].frame_count` | integer | Number of frames in this trace |
|
||||
| `traces[].first_frame` | integer | Start frame number |
|
||||
| `traces[].last_frame` | integer | End frame number |
|
||||
| `traces[].first_sec` | float | Start time in seconds |
|
||||
| `traces[].last_sec` | float | End time in seconds |
|
||||
| `traces[].avg_confidence` | float | Average detection confidence (0.0–1.0) |
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `404` | Identity not found |
|
||||
| `500` | Database error |
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/identity/:identity_uuid/unbind`
|
||||
|
||||
**Auth**: Required
|
||||
@@ -294,6 +376,61 @@ curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/bind/trace" \
|
||||
|
||||
Unbind a face detection from an identity. Removes the identity association from the face record.
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 只清除 `identity_id`(設為 NULL),**不會恢復 `stranger_id`**
|
||||
- 被 unbind 的 face 不會自動成為 stranger
|
||||
- 要重新標記為 stranger 需重新跑 Agent API(`identity/analyze`)
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/identity/:identity_uuid/mergeinto`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: identity-level
|
||||
|
||||
Transfer all face bindings from this identity to another identity, then optionally delete or mark the source as merged.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `into_uuid` | string | Yes | — | Target identity UUID to merge into |
|
||||
| `keep_history` | bool | No | `true` | Keep source identity record with `status='merged'` (`true`) or delete it (`false`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 轉移所有 `face_detections.identity_id` 到目標 identity
|
||||
- 同時清除所有被轉移 rows 的 `stranger_id`
|
||||
- `keep_history: true`(預設):source identity 設為 `status='merged'`,保留記錄
|
||||
- `keep_history: false`:**刪除** source identity 及其 identity JSON 檔案
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/identity/$SOURCE_UUID/mergeinto" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"into_uuid": "'"$TARGET_UUID"'", "keep_history": false}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Merged 'stranger_13894' into 'Louis Viret' (52 faces transferred, source deleted)",
|
||||
"data": { "faces_transferred": 52 }
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `404` | Source or target identity not found |
|
||||
| `500` | Database error |
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/identities/search`
|
||||
@@ -491,4 +628,4 @@ PATCH /api/v1/identity/:identity_uuid
|
||||
This **replaces** the entire `aliases` array. To add to existing aliases, include all existing entries in the request.
|
||||
|
||||
---
|
||||
*Updated: 2026-05-22
|
||||
*Updated: 2026-05-25
|
||||
|
||||
@@ -100,7 +100,7 @@ pub async fn bind_identity(
|
||||
|
||||
// Direct UPDATE face_detections.identity_id
|
||||
let result = sqlx::query(&format!(
|
||||
"UPDATE {} SET identity_id = $1 WHERE file_uuid = $2 AND face_id = $3",
|
||||
"UPDATE {} SET identity_id = $1, stranger_id = NULL WHERE file_uuid = $2 AND face_id = $3",
|
||||
table
|
||||
))
|
||||
.bind(identity_id)
|
||||
@@ -272,7 +272,7 @@ pub async fn merge_identities(
|
||||
|
||||
// Transfer all face bindings from source → target
|
||||
let updated = sqlx::query(&format!(
|
||||
"UPDATE {} SET identity_id = $1 WHERE identity_id = $2",
|
||||
"UPDATE {} SET identity_id = $1, stranger_id = NULL WHERE identity_id = $2",
|
||||
face_table
|
||||
))
|
||||
.bind(into_id)
|
||||
@@ -427,7 +427,7 @@ pub async fn bind_identity_trace(
|
||||
})?;
|
||||
|
||||
let result = sqlx::query(&format!(
|
||||
"UPDATE {} SET identity_id = $1 WHERE file_uuid = $2 AND trace_id = $3",
|
||||
"UPDATE {} SET identity_id = $1, stranger_id = NULL WHERE file_uuid = $2 AND trace_id = $3",
|
||||
fd_table
|
||||
))
|
||||
.bind(identity_id)
|
||||
|
||||
Reference in New Issue
Block a user