diff --git a/docs_v1.0/API_WORKSPACE/modules/07_identity.md b/docs_v1.0/API_WORKSPACE/modules/07_identity.md index 066428c..f662b2f 100644 --- a/docs_v1.0/API_WORKSPACE/modules/07_identity.md +++ b/docs_v1.0/API_WORKSPACE/modules/07_identity.md @@ -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 diff --git a/docs_v1.0/doc_wasm/modules/07_identity.md b/docs_v1.0/doc_wasm/modules/07_identity.md index 066428c..f662b2f 100644 --- a/docs_v1.0/doc_wasm/modules/07_identity.md +++ b/docs_v1.0/doc_wasm/modules/07_identity.md @@ -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 diff --git a/src/api/identity_binding.rs b/src/api/identity_binding.rs index a2c0861..e08ba9d 100644 --- a/src/api/identity_binding.rs +++ b/src/api/identity_binding.rs @@ -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)