feat: progressive multi-round face matching + pending person API
- Identity agent: per-face max matching, multi-round with derived seeds from high-confidence faces, angle diversity filter (cosine sim < 0.90) - Pending person API: POST /file/:file_uuid/pending-person + GET /file/:file_uuid/pending-persons with status=pending, source=manual - Update API docs (07_identity.md)
This commit is contained in:
@@ -923,6 +923,128 @@ curl -s "$API/api/v1/identity/$IDENTITY_UUID/json" \
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/file/:file_uuid/pending-person`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Create a manually managed "pending person" under a specific file. A pending person is an identity with `status='pending'` and `source='manual'`, used for unmatched traces that the user wants to manually label before a full identity resolution.
|
||||
|
||||
Optionally binds a list of trace IDs to this new identity.
|
||||
|
||||
#### Request
|
||||
|
||||
```json
|
||||
{
|
||||
"trace_ids": [100, 150, 200],
|
||||
"name": "Mystery Man #1"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `trace_ids` | array[int] | No | `[]` | Trace IDs to bind to this pending person |
|
||||
| `name` | string | No | `"Person N"` | Human-readable name. Auto-generated if omitted |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
# Create pending person with name and no traces
|
||||
curl -s -X POST "$API/api/v1/file/$FILE_UUID/pending-person" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "Unknown Woman #2", "trace_ids": []}'
|
||||
|
||||
# Create pending person with auto-name and bind traces
|
||||
curl -s -X POST "$API/api/v1/file/$FILE_UUID/pending-person" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"trace_ids": [100, 150, 200]}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Created pending person: Mystery Man #1 (uuid: 4d96b25b-68f0-4c52-b238-d69f7dfd588b)",
|
||||
"data": {
|
||||
"identity_uuid": "4d96b25b-68f0-4c52-b238-d69f7dfd588b",
|
||||
"identity_id": 55,
|
||||
"name": "Mystery Man #1",
|
||||
"bound_traces": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `identity_uuid` | string | UUID of the newly created pending identity |
|
||||
| `identity_id` | integer | Internal ID of the new identity |
|
||||
| `name` | string | Display name |
|
||||
| `bound_traces` | integer | Number of traces bound |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- Creates an `identities` row with `status='pending'`, `source='manual'`, `file_uuid=<file_uuid>`
|
||||
- If `trace_ids` provided: `UPDATE face_detections SET identity_id = ...` for matching traces
|
||||
- If `trace_ids` provided: TKG face_track nodes get `identity_id` / `identity_name` in properties
|
||||
- Identity JSON file synced to disk
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/file/:file_uuid/pending-persons`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
List all pending persons for a file.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s "$API/api/v1/file/$FILE_UUID/pending-persons" \
|
||||
-H "X-API-Key: $KEY"
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Found 2 pending persons for c36f35685177c981aa139b66bbbccc5b",
|
||||
"data": [
|
||||
{
|
||||
"identity_uuid": "232ecd08-a2bf-4bd0-bd25-0bd8fb7a7dae",
|
||||
"identity_id": 56,
|
||||
"name": "Person 2",
|
||||
"created_at": "2026-06-23 17:13:23",
|
||||
"trace_count": 3,
|
||||
"bound_traces": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `identity_uuid` | string | Identity UUID |
|
||||
| `identity_id` | integer | Internal identity ID |
|
||||
| `name` | string | Display name |
|
||||
| `created_at` | string | Creation timestamp |
|
||||
| `trace_count` | integer | Number of face traces bound to this pending person |
|
||||
| `bound_traces` | array[int] | List of bound trace IDs (currently null, reserved for future expansion) |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Pending persons are normal `identities` rows with `status='pending'` — they can be promoted to confirmed via `PATCH /api/v1/identity/:identity_uuid` (`{"status": "confirmed"}`)
|
||||
- They can be merged into known identities via `POST /api/v1/identity/:identity_uuid/mergeinto`
|
||||
- Use `GET /api/v1/identity/:identity_uuid/traces` to get detailed trace info for each pending person
|
||||
|
||||
---
|
||||
|
||||
## Alias System (BCP 47 Locale Tags)
|
||||
|
||||
Identity aliases support multilingual display names. Aliases are stored in `metadata.aliases` as an array of `{locale, name}` objects.
|
||||
|
||||
@@ -32,7 +32,7 @@ a { color: #0066cc; }
|
||||
<a class="logout-btn" href="#" onclick="fetch('/api/v1/auth/logout',{method:'POST'}).then(()=>window.location.reload());return false">Logout</a>
|
||||
</div>
|
||||
<!-- module: lookup -->
|
||||
<!-- description: File lookup by name and unregistration -->
|
||||
<!-- description: File listing, lookup by name, file detail, faces, identities, JSON download, unregistration -->
|
||||
<!-- depends: 01_auth, 03_register -->
|
||||
|
||||
<h2>File Lookup</h2>
|
||||
@@ -137,6 +137,537 @@ curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</s
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<hr />
|
||||
<h2>File Listing</h2>
|
||||
<h3><code>GET /api/v1/files</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>List all registered files with pagination. Optionally filter by status or fetch a specific file by UUID.</p>
|
||||
<h4>Query Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>1</td>
|
||||
<td>Page number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>20</td>
|
||||
<td>Items per page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>status</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Filter by status: <code>registered</code>, <code>processing</code>, <code>completed</code>, <code>failed</code>, <code>indexed</code>, <code>checked_out</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Fetch a specific file (returns as single-item list)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># List all files (paginated)</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files?page=1&page_size=10"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
|
||||
<span class="c1"># Filter by status</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files?status=completed"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
|
||||
<span class="c1"># Fetch specific file</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files?file_uuid=</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">42</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"video.mp4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/path/to/video.mp4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"completed"</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>success</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Always true on 200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>total</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total file count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>Current page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>Items per page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data</code></td>
|
||||
<td>array</td>
|
||||
<td>Array of file items</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].file_name</code></td>
|
||||
<td>string</td>
|
||||
<td>Registered file name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].file_path</code></td>
|
||||
<td>string</td>
|
||||
<td>Full filesystem path</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].status</code></td>
|
||||
<td>string</td>
|
||||
<td>Processing status</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Get detailed info for a specific registered file including metadata, duration, FPS, and probe data.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"video.mp4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/path/to/video.mp4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"completed"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"duration"</span><span class="p">:</span><span class="w"> </span><span class="mf">120.5</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"fps"</span><span class="p">:</span><span class="w"> </span><span class="mf">24.0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"metadata"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"duration"</span><span class="p">:</span><span class="w"> </span><span class="s2">"120.5"</span><span class="p">,</span><span class="w"> </span><span class="nt">"size"</span><span class="p">:</span><span class="w"> </span><span class="s2">"794863677"</span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"streams"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="nt">"codec_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"h264"</span><span class="p">,</span><span class="w"> </span><span class="nt">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">1920</span><span class="p">,</span><span class="w"> </span><span class="nt">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">1080</span><span class="p">}]</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-05-16T12:00:00Z"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>success</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Always true on 200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_name</code></td>
|
||||
<td>string</td>
|
||||
<td>Registered file name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_path</code></td>
|
||||
<td>string</td>
|
||||
<td>Full filesystem path</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>status</code></td>
|
||||
<td>string</td>
|
||||
<td>Processing status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>duration</code></td>
|
||||
<td>float</td>
|
||||
<td>Duration in seconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fps</code></td>
|
||||
<td>float</td>
|
||||
<td>Frames per second</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>metadata</code></td>
|
||||
<td>object</td>
|
||||
<td>Full ffprobe metadata (probe.json)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>created_at</code></td>
|
||||
<td>string</td>
|
||||
<td>Registration timestamp (ISO 8601)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Error Codes</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>File UUID not found</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/identities</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Get all identities present in a specific file with pagination.</p>
|
||||
<h4>Query Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>1</td>
|
||||
<td>Page number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>20</td>
|
||||
<td>Items per page</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/identities?page=1&page_size=50"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"fps"</span><span class="p">:</span><span class="w"> </span><span class="mf">24.0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"identity_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a90105-6d6b-46ff-92da-0c3c1a57dff4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Audrey Hepburn"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"metadata"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"source"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tmdb"</span><span class="p">,</span><span class="w"> </span><span class="nt">"tmdb_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1234</span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"face_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">142</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"speaker_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"start_frame"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"end_frame"</span><span class="p">:</span><span class="w"> </span><span class="mi">5000</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"start_time"</span><span class="p">:</span><span class="w"> </span><span class="mf">4.17</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"end_time"</span><span class="p">:</span><span class="w"> </span><span class="mf">208.33</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"confidence"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.87</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>data[].identity_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>Database identity ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].identity_uuid</code></td>
|
||||
<td>string/null</td>
|
||||
<td>Global identity UUID (null if unbound)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].name</code></td>
|
||||
<td>string</td>
|
||||
<td>Identity name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].metadata</code></td>
|
||||
<td>object</td>
|
||||
<td>Source metadata (TMDb, etc.)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].face_count</code></td>
|
||||
<td>integer/null</td>
|
||||
<td>Number of face detections</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].speaker_count</code></td>
|
||||
<td>integer/null</td>
|
||||
<td>Number of speaker segments</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].start_frame</code></td>
|
||||
<td>integer/null</td>
|
||||
<td>First appearance frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].end_frame</code></td>
|
||||
<td>integer/null</td>
|
||||
<td>Last appearance frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].start_time</code></td>
|
||||
<td>float/null</td>
|
||||
<td>First appearance time (seconds)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].end_time</code></td>
|
||||
<td>float/null</td>
|
||||
<td>Last appearance time (seconds)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].confidence</code></td>
|
||||
<td>float/null</td>
|
||||
<td>Average detection confidence</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/faces</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>List all face detections in a specific file with pagination.</p>
|
||||
<h4>Query Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>1</td>
|
||||
<td>Page number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>50</td>
|
||||
<td>Items per page</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/faces?page=1&page_size=100"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">1420</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"face_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"face_100"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"frame_number"</span><span class="p">:</span><span class="w"> </span><span class="mi">1200</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"timestamp"</span><span class="p">:</span><span class="w"> </span><span class="mf">50.0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"bbox"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="mi">300</span><span class="p">,</span><span class="w"> </span><span class="mi">400</span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"confidence"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.95</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a90105-6d6b-46ff-92da-0c3c1a57dff4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"trace_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>data[].face_id</code></td>
|
||||
<td>string</td>
|
||||
<td>Face detection ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].frame_number</code></td>
|
||||
<td>integer</td>
|
||||
<td>Frame number in video</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].timestamp</code></td>
|
||||
<td>float</td>
|
||||
<td>Timestamp in seconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].bbox</code></td>
|
||||
<td>array</td>
|
||||
<td>Bounding box <code>[x1, y1, x2, y2]</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].confidence</code></td>
|
||||
<td>float</td>
|
||||
<td>Detection confidence</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].identity_id</code></td>
|
||||
<td>integer/null</td>
|
||||
<td>Bound identity ID (null if unbound)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].identity_uuid</code></td>
|
||||
<td>string/null</td>
|
||||
<td>Bound identity UUID (null if unbound)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>data[].trace_id</code></td>
|
||||
<td>integer/null</td>
|
||||
<td>Face trace ID (null if not traced)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/file/:file_uuid/json/:processor</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Download raw JSON output for a specific processor.</p>
|
||||
<h4>Path Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>File UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processor</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>Processor name: <code>cut</code>, <code>asrx</code>, <code>yolo</code>, <code>ocr</code>, <code>face</code>, <code>pose</code>, <code>story</code>, etc.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/json/face"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'.frames | length'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<p>Returns the raw JSON output of the specified processor. Structure varies by processor type.</p>
|
||||
<h4>Error Codes</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>JSON file not found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>500</code></td>
|
||||
<td>Failed to parse JSON</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h2>Unregister</h2>
|
||||
<h3><code>POST /api/v1/unregister</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
@@ -293,7 +824,7 @@ curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<p><em>Updated: 2026-05-19 12:49:24</em></p>
|
||||
<p><em>Updated: 2026-06-20 — Added file listing, file detail, file identities, file faces, and JSON download endpoints</em></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -260,10 +260,11 @@ curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/progress/:file_uuid</code></h3>
|
||||
<h3><code>POST /api/v1/progress/:file_uuid</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Get real-time processing progress for a file via Redis pub/sub. Includes per-processor status, current/total frames, ETA, and system resource stats.</p>
|
||||
<p><strong>Note</strong>: This endpoint uses <strong>POST</strong> method, not GET. The progress data is stored in Redis as a hash, and POST is used to retrieve the latest state.</p>
|
||||
<h4>Pipeline Order</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
@@ -339,7 +340,7 @@ curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>
|
||||
</table>
|
||||
<p>All processors except <code>story</code> and <code>5w1h</code> run concurrently when their dependencies are met. Story and 5W1H run sequentially after their prerequisites.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/progress/</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{overall_progress, processors: [.processors[] | {processor_type, status}]}'</span>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/progress/</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{overall_progress, processors: [.processors[] | {name, status}]}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
@@ -506,8 +507,152 @@ curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3><code>GET /api/v1/file/:file_uuid/processor-counts</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Get counts of processor JSON output files. See <code>15_tkg.md</code> for full documentation.</p>
|
||||
<hr />
|
||||
<p><em>Updated: 2026-05-19 12:49:24</em></p>
|
||||
<h2>Pipeline Steps (Manual)</h2>
|
||||
<p>These endpoints execute individual pipeline steps. They are typically called by the worker automatically, but can be invoked manually for debugging or re-processing.</p>
|
||||
<h3><code>POST /api/v1/file/:file_uuid/store-asrx</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Store ASRX diarization results as chunk records in the database. Converts ASRX segments into searchable chunk entries.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/store-asrx"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ASRX chunks stored"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865..."</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/file/:file_uuid/rule1</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Execute Rule 1 pipeline step. Applies rule-based chunking to create structured chunk records from processor outputs.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/rule1"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Rule 1 complete: 45 chunks"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865..."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"chunks"</span><span class="p">:</span><span class="w"> </span><span class="mi">45</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>success</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Always true on 200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>message</code></td>
|
||||
<td>string</td>
|
||||
<td>Human-readable completion message</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>chunks</code></td>
|
||||
<td>integer</td>
|
||||
<td>Number of chunks produced</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/file/:file_uuid/vectorize</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Generate vector embeddings for all chunks of a file and store them in Qdrant for semantic search.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/vectorize"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Vectorization complete"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865..."</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/file/:file_uuid/phase1</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Execute Phase 1 of the post-processing pipeline. Combines store-asrx, rule1, and vectorize into a single step.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/phase1"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Phase 1 complete"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865..."</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/file/:file_uuid/complete</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Mark a video as fully processed. Updates the video status to <code>completed</code> and finalizes all pipeline state.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/complete"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Video marked as completed"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865..."</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3>Pipeline Step Order</h3>
|
||||
<div class="codehilite"><pre><span></span><code> process (trigger)
|
||||
│
|
||||
├─→ cut, yolo, ocr, face, pose, asrx (parallel processors)
|
||||
│
|
||||
├─→ store-asrx (store diarization as chunks)
|
||||
│
|
||||
├─→ rule1 (rule-based chunking)
|
||||
│
|
||||
├─→ vectorize (embed chunks to Qdrant)
|
||||
│
|
||||
└─→ complete (mark done)
|
||||
</code></pre></div>
|
||||
|
||||
<p>Phase 1 (<code>/phase1</code>) combines store-asrx + rule1 + vectorize into one call.</p>
|
||||
<hr />
|
||||
<p><em>Updated: 2026-06-20 12:00:00</em></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -32,7 +32,7 @@ a { color: #0066cc; }
|
||||
<a class="logout-btn" href="#" onclick="fetch('/api/v1/auth/logout',{method:'POST'}).then(()=>window.location.reload());return false">Logout</a>
|
||||
</div>
|
||||
<!-- module: search -->
|
||||
<!-- description: Vector search, BM25, smart search, universal search, visual search -->
|
||||
<!-- description: Vector search, BM25, smart search, universal search, LLM reranked search, frame search -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
<h2>Search APIs</h2>
|
||||
@@ -282,9 +282,251 @@ a { color: #0066cc; }
|
||||
<h3><code>POST /api/v1/search/frames</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: global / file-level</p>
|
||||
<p>Search face detection frames by identity name or trace ID.</p>
|
||||
<p>Search frames by YOLO objects, OCR text, face IDs, or pose detections. Filters frames based on visual content detected during processing.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Restrict to specific file</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>object_class</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Filter by YOLO object class (e.g., <code>person</code>, <code>car</code>, <code>dog</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ocr_text</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Filter by OCR text content (ILIKE match)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>face_id</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Filter by face detection ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>time_range</code></td>
|
||||
<td>[float, float]</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Filter by time range <code>[start_secs, end_secs]</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>limit</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>100</td>
|
||||
<td>Max results</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Search for frames containing "person" objects</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/search/frames"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_uuid": "'</span><span class="s2">"</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="s1">'", "object_class": "person", "limit": 20}'</span>
|
||||
|
||||
<span class="c1"># Search for frames with specific OCR text</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/search/frames"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_uuid": "'</span><span class="s2">"</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="s1">'", "ocr_text": "hello", "time_range": [10.0, 30.0]}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"frames"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"frame_number"</span><span class="p">:</span><span class="w"> </span><span class="mi">1200</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"timestamp"</span><span class="p">:</span><span class="w"> </span><span class="mf">50.0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"objects"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="nt">"class"</span><span class="p">:</span><span class="w"> </span><span class="s2">"person"</span><span class="p">,</span><span class="w"> </span><span class="nt">"confidence"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.95</span><span class="p">,</span><span class="w"> </span><span class="nt">"bbox"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="mi">300</span><span class="p">,</span><span class="w"> </span><span class="mi">400</span><span class="p">]}],</span>
|
||||
<span class="w"> </span><span class="nt">"ocr_texts"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"Hello World"</span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"faces"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="nt">"face_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"face_42"</span><span class="p">,</span><span class="w"> </span><span class="nt">"confidence"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.88</span><span class="p">}],</span>
|
||||
<span class="w"> </span><span class="nt">"pose_persons"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="nt">"trace_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nt">"bbox"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">120</span><span class="p">,</span><span class="w"> </span><span class="mi">60</span><span class="p">,</span><span class="w"> </span><span class="mi">280</span><span class="p">,</span><span class="w"> </span><span class="mi">380</span><span class="p">]}]</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>frames</code></td>
|
||||
<td>array</td>
|
||||
<td>Array of matching frame objects</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>frames[].frame_number</code></td>
|
||||
<td>integer</td>
|
||||
<td>Frame number in video</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>frames[].timestamp</code></td>
|
||||
<td>float</td>
|
||||
<td>Timestamp in seconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>frames[].file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>File UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>frames[].objects</code></td>
|
||||
<td>array/null</td>
|
||||
<td>YOLO detections in this frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>frames[].ocr_texts</code></td>
|
||||
<td>array/null</td>
|
||||
<td>OCR text strings in this frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>frames[].faces</code></td>
|
||||
<td>array/null</td>
|
||||
<td>Face detections in this frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>frames[].pose_persons</code></td>
|
||||
<td>array/null</td>
|
||||
<td>Pose-detected persons in this frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>total</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total matching frame count</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/search/identity_text</code></h3>
|
||||
<h3><code>POST /api/v1/search/llm-smart</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: global / file-level</p>
|
||||
<p>Smart search with LLM re-ranking. First fetches candidate results via RRF (Reciprocal Rank Fusion) using the existing smart search, then uses an LLM (Gemma4 on port 8000) to re-rank candidates by relevance to the query.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>query</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>—</td>
|
||||
<td>Search text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>File UUID to search within</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>limit</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>10</td>
|
||||
<td>Max results to return</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Pipeline</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="w"> </span><span class="mf">1.</span><span class="w"> </span><span class="n">smart_search</span><span class="w"> </span><span class="n">→</span><span class="w"> </span><span class="k">fetch</span><span class="w"> </span><span class="n">N</span><span class="w"> </span><span class="n">candidates</span><span class="w"> </span><span class="p">(</span><span class="k">limit</span><span class="w"> </span><span class="n">×</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="n">clamped</span><span class="w"> </span><span class="mi">10</span><span class="o">-</span><span class="mi">20</span><span class="p">)</span>
|
||||
<span class="w"> </span><span class="mf">2.</span><span class="w"> </span><span class="n">LLM</span><span class="w"> </span><span class="n">rerank</span><span class="w"> </span><span class="n">→</span><span class="w"> </span><span class="n">re</span><span class="o">-</span><span class="k">order</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="n">relevance</span><span class="w"> </span><span class="k">using</span><span class="w"> </span><span class="n">Gemma4</span>
|
||||
<span class="w"> </span><span class="mf">3.</span><span class="w"> </span><span class="n">trim</span><span class="w"> </span><span class="n">→</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">top</span><span class="w"> </span><span class="n n-Quoted">`limit`</span><span class="w"> </span><span class="n">results</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/search/llm-smart"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"query": "two people having a conversation about business", "limit": 5}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"query"</span><span class="p">:</span><span class="w"> </span><span class="s2">"two people having a conversation about business"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"results"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"parent_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1234</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"scene_order"</span><span class="p">:</span><span class="w"> </span><span class="mi">1234</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"start_frame"</span><span class="p">:</span><span class="w"> </span><span class="mi">5000</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"end_frame"</span><span class="p">:</span><span class="w"> </span><span class="mi">5200</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"fps"</span><span class="p">:</span><span class="w"> </span><span class="mf">24.0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"start_time"</span><span class="p">:</span><span class="w"> </span><span class="mf">208.3</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"end_time"</span><span class="p">:</span><span class="w"> </span><span class="mf">216.7</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"summary"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[208s-217s, 9s] Two people discussing project timeline..."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"similarity"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.72</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"page"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"strategy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"llm_reranked"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>strategy</code></td>
|
||||
<td>string</td>
|
||||
<td>Always <code>"llm_reranked"</code> for this endpoint</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>results</code></td>
|
||||
<td>array</td>
|
||||
<td>Re-ranked search results (same format as smart search)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Fallback</h4>
|
||||
<p>If LLM reranking fails (model unavailable, timeout), falls back to RRF order without error.</p>
|
||||
<hr />
|
||||
<h3>Visual Search</h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: global / file-level</p>
|
||||
<p>Search text chunks → find associated identities. Returns chunks where face detections overlap with text content.</p>
|
||||
@@ -392,12 +634,13 @@ a { color: #0066cc; }
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3>Visual Search</h3>
|
||||
<h3>Visual Search (Planned)</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Endpoint</th>
|
||||
<th>Status</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -405,26 +648,31 @@ a { color: #0066cc; }
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual</code></td>
|
||||
<td>Not implemented</td>
|
||||
<td>Search visual chunks</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/class</code></td>
|
||||
<td>Not implemented</td>
|
||||
<td>Search by object class</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/density</code></td>
|
||||
<td>Not implemented</td>
|
||||
<td>Search by object density</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/combination</code></td>
|
||||
<td>Not implemented</td>
|
||||
<td>Search by object combination</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/stats</code></td>
|
||||
<td>Not implemented</td>
|
||||
<td>Visual chunk statistics</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -457,7 +705,7 @@ a { color: #0066cc; }
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<p><em>Updated: 2026-05-27 — Added global search support for smart, universal, identity_text APIs</em></p>
|
||||
<p><em>Updated: 2026-06-20 — Added llm-smart search, completed frames search documentation, marked visual search as planned</em></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -790,7 +790,100 @@ curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</s
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<p><em>Updated: 2026-05-19 12:49:24</em></p>
|
||||
<h3><code>GET /api/v1/file/:file_uuid/stranger/:stranger_id/representative-face</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Get the representative face for a stranger (unidentified face trace).</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/stranger/1/representative-face"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"stranger_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">85</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"representative"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"frame_number"</span><span class="p">:</span><span class="w"> </span><span class="mi">5000</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"timestamp_secs"</span><span class="p">:</span><span class="w"> </span><span class="mf">208.33</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"bbox"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">200</span><span class="p">,</span><span class="w"> </span><span class="nt">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="nt">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">150</span><span class="p">,</span><span class="w"> </span><span class="nt">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">150</span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"confidence"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.92</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"quality_score"</span><span class="p">:</span><span class="w"> </span><span class="mi">20700</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"blur_score"</span><span class="p">:</span><span class="w"> </span><span class="mf">8.5</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/stranger/:stranger_id/thumbnail</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Extract the best face image for a stranger as JPEG (320×320).</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/stranger/1/thumbnail"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>stranger_1_face.jpg
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response</h4>
|
||||
<ul>
|
||||
<li><strong>200</strong>: <code>image/jpeg</code> binary data (320×320 cropped face)</li>
|
||||
<li><strong>404</strong>: File or stranger not found</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/chunk/:chunk_id/thumbnail</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Get thumbnail for a specific chunk. Extracts the representative frame for the chunk's time range.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/chunk/chunk_1/thumbnail"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>chunk_1.jpg
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response</h4>
|
||||
<ul>
|
||||
<li><strong>200</strong>: <code>image/jpeg</code> binary data</li>
|
||||
<li><strong>404</strong>: File or chunk not found</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/media-proxy</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>Proxy request to fetch media from external URLs. Useful for loading profile images or thumbnails from external services (TMDb, etc.) without exposing the external URL to the client.</p>
|
||||
<h4>Query Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>url</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>External URL to proxy</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/media-proxy?url=https://image.tmdb.org/t/p/w500/abc123.jpg"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>tmdb_profile.jpg
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response</h4>
|
||||
<ul>
|
||||
<li><strong>200</strong>: Proxied media data (Content-Type from external source)</li>
|
||||
<li><strong>400</strong>: Missing or invalid URL parameter</li>
|
||||
<li><strong>500</strong>: External request failed</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<hr />
|
||||
<p><em>Updated: 2026-06-20 — Added stranger endpoints, chunk thumbnail, and media proxy</em></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -125,8 +125,124 @@ If local files exist, no external API call is made. Internet is only needed for
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h3><code>POST /api/v1/tmdb/fetch</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>Fetch TMDb data by filename, create identities with profile images and embeddings. Similar to prefetch+probe combined, but also downloads profile images and generates embeddings.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>filename</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>Movie filename to search TMDb for</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/tmdb/fetch"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"filename": "charade.mp4"}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"movie_title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Charade (1963)"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"tmdb_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1234</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identities_created"</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"profile_images_downloaded"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<p><em>Updated: 2026-05-19 12:49:24</em></p>
|
||||
<h3><code>POST /api/v1/agents/tmdb/match/:file_uuid</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Match TMDb identities to face traces using Qdrant vector similarity. Compares face embeddings against TMDb identity embeddings to find the best matches.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/agents/tmdb/match/</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"matches"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"trace_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a90105-6d6b-46ff-92da-0c3c1a57dff4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Audrey Hepburn"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"confidence"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.92</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"tmdb_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1234</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"total_matches"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>matches[].trace_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>Face trace ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>matches[].identity_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>Matched TMDb identity UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>matches[].identity_name</code></td>
|
||||
<td>string</td>
|
||||
<td>Identity display name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>matches[].confidence</code></td>
|
||||
<td>float</td>
|
||||
<td>Cosine similarity score (0.0–1.0)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>matches[].tmdb_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>TMDb person ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>total_matches</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total successful matches</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3>TMDb Auto-Match</h3>
|
||||
<p>When <code>MOMENTRY_TMDB_PROBE_ENABLED=true</code>, the worker automatically runs TMDb matching during the post-process phase:</p>
|
||||
<ol>
|
||||
<li><strong>Register phase</strong>: Searches TMDb by filename, creates identities with <code>tmdb_id</code>/<code>tmdb_profile</code></li>
|
||||
<li><strong>Post-process phase</strong>: Matches detected faces against TMDb identities via cosine similarity using Qdrant</li>
|
||||
</ol>
|
||||
<p>No manual API call needed if auto-match is enabled.</p>
|
||||
<hr />
|
||||
<p><em>Updated: 2026-06-20 — Added tmdb/fetch and tmdb/match endpoints</em></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -32,12 +32,46 @@ a { color: #0066cc; }
|
||||
<a class="logout-btn" href="#" onclick="fetch('/api/v1/auth/logout',{method:'POST'}).then(()=>window.location.reload());return false">Logout</a>
|
||||
</div>
|
||||
<!-- module: identity_history -->
|
||||
<!-- description: Identity PATCH operation history, undo, and redo -->
|
||||
<!-- description: Identity operation history, undo, and redo (PATCH, bind, unbind, bind_trace, mergeinto) -->
|
||||
<!-- depends: 01_auth, 07_identity -->
|
||||
|
||||
<h2>Identity Operation History</h2>
|
||||
<p>Every <code>PATCH /api/v1/identity/:identity_uuid</code> automatically records a before/after snapshot in the <code>identity_history</code> table. Use undo/redo to revert or reapply changes, and history to inspect the operation log.</p>
|
||||
<h3>History System Overview</h3>
|
||||
<p>Every mutation on an identity automatically records a before/after snapshot. Use undo/redo to revert or reapply changes, and history to inspect the operation log.</p>
|
||||
<p>Three independent undo/redo systems exist:</p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>System</th>
|
||||
<th>Storage</th>
|
||||
<th>Operations Covered</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>PATCH</strong></td>
|
||||
<td>PostgreSQL <code>identity_history</code></td>
|
||||
<td><code>update</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Bind</strong></td>
|
||||
<td>PostgreSQL <code>identity_history</code></td>
|
||||
<td><code>bind</code>, <code>unbind</code>, <code>bind_trace</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Merge</strong></td>
|
||||
<td>MongoDB <code>identity_merge_history</code></td>
|
||||
<td>mergeinto</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Delete</strong></td>
|
||||
<td>PostgreSQL <code>identity_history</code></td>
|
||||
<td><code>delete</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3>1. PATCH History & Undo/Redo</h3>
|
||||
<h4>Overview</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -64,11 +98,11 @@ a { color: #0066cc; }
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Redo stack</td>
|
||||
<td>Cleared on new PATCH (<code>is_undone=true</code> records are deleted)</td>
|
||||
<td>Cleared on new PATCH (<code>is_undone=true</code> + <code>operation='update'</code> records are deleted)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Stack Model</h4>
|
||||
<h5>Stack Model</h5>
|
||||
<div class="codehilite"><pre><span></span><code>PATCH 1 → PATCH 2 → PATCH 3 (undo stack, is_undone=false)
|
||||
↓ undo
|
||||
PATCH 1 → PATCH 2 (undo stack)
|
||||
@@ -77,13 +111,13 @@ PATCH 1 → PATCH 2 (undo stack)
|
||||
PATCH 1 → PATCH 2 → PATCH 3 (undo stack)
|
||||
</code></pre></div>
|
||||
|
||||
<p>A new PATCH after undo clears the redo stack (PATCH 3 is lost).</p>
|
||||
<p>A new PATCH after undo clears only the operation='update' redo stack (PATCH 3 is lost). Bind/merge redo stacks are not affected.</p>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/identity/:identity_uuid/undo</code></h3>
|
||||
<h4><code>POST /api/v1/identity/:identity_uuid/undo</code></h4>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Undo the most recent PATCH operations. Restores the identity's <code>before_snapshot</code> and marks the history records as undone.</p>
|
||||
<h4>Request (JSON)</h4>
|
||||
<h5>Request (JSON)</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -104,22 +138,22 @@ PATCH 1 → PATCH 2 → PATCH 3 (undo stack)
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Behavior</h4>
|
||||
<h5>Behavior</h5>
|
||||
<ul>
|
||||
<li>Queries <code>is_undone=false</code> records, ordered by <code>created_at DESC</code></li>
|
||||
<li>Queries <code>is_undone=false</code> records with <code>operation='update'</code>, ordered by <code>created_at DESC</code></li>
|
||||
<li>Restores <code>name</code>, <code>identity_type</code>, <code>source</code>, <code>status</code>, <code>metadata</code>, <code>tmdb_id</code>, <code>tmdb_profile</code> from the last record's <code>before_snapshot</code></li>
|
||||
<li>Marks the undone records as <code>is_undone=true</code> with <code>undone_at=NOW()</code></li>
|
||||
<li>Syncs <code>identity.json</code> to disk</li>
|
||||
<li>Updates <code>_index.json</code> if name changed</li>
|
||||
</ul>
|
||||
<h4>Example</h4>
|
||||
<h5>Example</h5>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/undo"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"steps": 1}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<h5>Response (200)</h5>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a901056d6b46ff92da0c3c1a57dff4"</span><span class="p">,</span>
|
||||
@@ -159,7 +193,7 @@ PATCH 1 → PATCH 2 → PATCH 3 (undo stack)
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Error Responses</h4>
|
||||
<h5>Error Responses</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -183,11 +217,11 @@ PATCH 1 → PATCH 2 → PATCH 3 (undo stack)
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/identity/:identity_uuid/redo</code></h3>
|
||||
<h4><code>POST /api/v1/identity/:identity_uuid/redo</code></h4>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Redo previously undone PATCH operations. Restores the identity's <code>after_snapshot</code> and marks the history records as no longer undone.</p>
|
||||
<h4>Request (JSON)</h4>
|
||||
<h5>Request (JSON)</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -208,22 +242,22 @@ PATCH 1 → PATCH 2 → PATCH 3 (undo stack)
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Behavior</h4>
|
||||
<h5>Behavior</h5>
|
||||
<ul>
|
||||
<li>Queries <code>is_undone=true</code> records, ordered by <code>created_at DESC</code></li>
|
||||
<li>Queries <code>is_undone=true</code> records with <code>operation='update'</code>, ordered by <code>created_at DESC</code></li>
|
||||
<li>Restores all identity fields from the last record's <code>after_snapshot</code></li>
|
||||
<li>Marks records as <code>is_undone=false</code> with <code>undone_at=NULL</code></li>
|
||||
<li>Syncs <code>identity.json</code> to disk</li>
|
||||
<li>Updates <code>_index.json</code> if name changed</li>
|
||||
</ul>
|
||||
<h4>Example</h4>
|
||||
<h5>Example</h5>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/redo"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"steps": 1}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<h5>Response (200)</h5>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a901056d6b46ff92da0c3c1a57dff4"</span><span class="p">,</span>
|
||||
@@ -263,7 +297,7 @@ PATCH 1 → PATCH 2 → PATCH 3 (undo stack)
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Error Responses</h4>
|
||||
<h5>Error Responses</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -287,11 +321,11 @@ PATCH 1 → PATCH 2 → PATCH 3 (undo stack)
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/identity/:identity_uuid/history</code></h3>
|
||||
<h4><code>GET /api/v1/identity/:identity_uuid/history</code></h4>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Query the operation history for an identity. Returns paginated records with undo/redo stack counts.</p>
|
||||
<h4>Query Parameters</h4>
|
||||
<p>Query the PATCH operation history for an identity. Returns paginated records with undo/redo stack counts (filtered to <code>operation='update'</code>).</p>
|
||||
<h5>Query Parameters</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -319,7 +353,7 @@ PATCH 1 → PATCH 2 → PATCH 3 (undo stack)
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Response (200)</h4>
|
||||
<h5>Response (200)</h5>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a901056d6b46ff92da0c3c1a57dff4"</span><span class="p">,</span>
|
||||
@@ -357,7 +391,7 @@ PATCH 1 → PATCH 2 → PATCH 3 (undo stack)
|
||||
<tr>
|
||||
<td><code>total</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total history records for this identity</td>
|
||||
<td>Total PATCH history records for this identity</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>undo_stack_count</code></td>
|
||||
@@ -396,12 +430,12 @@ PATCH 1 → PATCH 2 → PATCH 3 (undo stack)
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<h5>Example</h5>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/history?page=1&limit=10"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Error Responses</h4>
|
||||
<h5>Error Responses</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -421,45 +455,746 @@ PATCH 1 → PATCH 2 → PATCH 3 (undo stack)
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3>Comparison: PATCH Undo vs Merge Undo</h3>
|
||||
<h3>2. Bind/Unbind/Trace History & Undo/Redo</h3>
|
||||
<p>All three operations (<code>bind</code>, <code>unbind</code>, <code>bind_trace</code>) share a single history table and undo/redo stack.</p>
|
||||
<h4>Bind Operation Overview</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Storage</td>
|
||||
<td>PostgreSQL <code>identity_history</code> table (same table as PATCH)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Snapshot</td>
|
||||
<td><code>{"file_uuid", "face_id" (or "trace_id"), "identity_id_before/after"}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Max records</td>
|
||||
<td>256 per identity (shared limit across all operation types)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Undo steps</td>
|
||||
<td>Unlimited (<code>steps</code> param)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Redo stack</td>
|
||||
<td>Cleared on new bind/unbind/bind_trace (<code>operation IN ('bind','unbind','bind_trace')</code> + <code>is_undone=true</code> records deleted)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stack isolation</td>
|
||||
<td>Bind redo stack is <strong>independent</strong> from PATCH redo stack — clearing one does not affect the other</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h5>Stack Model</h5>
|
||||
<div class="codehilite"><pre><span></span><code>bind face_1 (to id=9) → unbind face_1 → bind trace 906 (to id=9)
|
||||
(undo stack, is_undone=false) (undo stack) (undo stack)
|
||||
↓ undo (first undone: bind_trace)
|
||||
bind trace 906 (is_undone=true)
|
||||
(redo stack)
|
||||
↓ redo
|
||||
bind face_1 → unbind face_1 → bind trace 906
|
||||
(undo stack)
|
||||
</code></pre></div>
|
||||
|
||||
<p>A new bind/unbind/trace after undo clears only the bind redo stack (operations with <code>IN ('bind','unbind','bind_trace')</code>).</p>
|
||||
<h5>Snapshot Format</h5>
|
||||
<p><strong>Before (bind):</strong></p>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aeed71342a899fe4b4c57b7d41bcb692"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1_5"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_id_before"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<p><strong>After (bind):</strong></p>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aeed71342a899fe4b4c57b7d41bcb692"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1_5"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_id_after"</span><span class="p">:</span><span class="w"> </span><span class="mi">9</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<p><strong>Before (unbind) — binding existed before:</strong></p>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aeed71342a899fe4b4c57b7d41bcb692"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1_5"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_id_before"</span><span class="p">:</span><span class="w"> </span><span class="mi">9</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<p><strong>After (unbind):</strong></p>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aeed71342a899fe4b4c57b7d41bcb692"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1_5"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_id_after"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<p>For <code>bind_trace</code>, the snapshot uses <code>trace_id</code> instead of <code>face_id</code>, with <code>identity_id_before</code> capturing the first face's identity in that trace.</p>
|
||||
<hr />
|
||||
<h4><code>POST /api/v1/identity/:identity_uuid/bind/undo</code></h4>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Undo the most recent bind/unbind/bind_trace operations. Restores <code>identity_id_before</code> from the snapshot and marks records as undone.</p>
|
||||
<h5>Request (JSON)</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>steps</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td><code>1</code></td>
|
||||
<td>Number of undo steps to apply</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h5>Behavior</h5>
|
||||
<ul>
|
||||
<li>Queries <code>is_undone=false</code> records with <code>operation IN ('bind','unbind','bind_trace')</code>, ordered by <code>created_at DESC</code></li>
|
||||
<li>Restores <code>identity_id_before</code> — for bind this is <code>null</code> (face was unbound), for unbind this is the original identity (face goes back), for bind_trace this is the trace's previous identity</li>
|
||||
<li>Marks the undone records as <code>is_undone=true</code> with <code>undone_at=NOW()</code></li>
|
||||
</ul>
|
||||
<h5>Example</h5>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/bind/undo"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"steps": 1}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h5>Response (200)</h5>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a901056d6b46ff92da0c3c1a57dff4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"operation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bind"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"undone_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"affected_rows"</span><span class="p">:</span><span class="w"> </span><span class="mi">53</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>operation</code></td>
|
||||
<td>string</td>
|
||||
<td>The actual operation undone (<code>bind</code>, <code>unbind</code>, or <code>bind_trace</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>undone_count</code></td>
|
||||
<td>integer</td>
|
||||
<td>Number of history records undone</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>affected_rows</code></td>
|
||||
<td>integer</td>
|
||||
<td>Number of <code>face_detections</code> rows updated</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h5>Error Responses</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>400</code></td>
|
||||
<td>No bind undo operations available</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>Identity not found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>500</code></td>
|
||||
<td>Database error</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h4><code>POST /api/v1/identity/:identity_uuid/bind/redo</code></h4>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Redo previously undone bind/unbind/bind_trace operations. Restores <code>identity_id_after</code> from the snapshot.</p>
|
||||
<h5>Request (JSON)</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>steps</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td><code>1</code></td>
|
||||
<td>Number of redo steps to apply</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h5>Behavior</h5>
|
||||
<ul>
|
||||
<li>Queries <code>is_undone=true</code> records with <code>operation IN ('bind','unbind','bind_trace')</code>, ordered by <code>created_at DESC</code></li>
|
||||
<li>Restores <code>identity_id_after</code> — for bind this is the identity the face was bound to, for unbind this is <code>null</code></li>
|
||||
<li>Marks records as <code>is_undone=false</code> with <code>undone_at=NULL</code></li>
|
||||
</ul>
|
||||
<h5>Example</h5>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/bind/redo"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"steps": 1}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h5>Response (200)</h5>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a901056d6b46ff92da0c3c1a57dff4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"operation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unbind"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"redone_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"affected_rows"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>operation</code></td>
|
||||
<td>string</td>
|
||||
<td>The actual operation redone (<code>bind</code>, <code>unbind</code>, or <code>bind_trace</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>redone_count</code></td>
|
||||
<td>integer</td>
|
||||
<td>Number of history records redone</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>affected_rows</code></td>
|
||||
<td>integer</td>
|
||||
<td>Number of <code>face_detections</code> rows updated</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h5>Error Responses</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>400</code></td>
|
||||
<td>No bind redo operations available</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>Identity not found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>500</code></td>
|
||||
<td>Database error</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h4><code>GET /api/v1/identity/:identity_uuid/bind/history</code></h4>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Query the bind/unbind/bind_trace operation history for an identity. Returns paginated records with undo/redo stack counts.</p>
|
||||
<h5>Query Parameters</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td><code>1</code></td>
|
||||
<td>Page number (1-indexed)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>limit</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td><code>20</code></td>
|
||||
<td>Items per page (max 100)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h5>Response (200)</h5>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a901056d6b46ff92da0c3c1a57dff4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"undo_stack_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"redo_stack_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"results"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"history_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">52</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"operation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bind_trace"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"is_undone"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-05-27T14:00:00Z"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"undone_at"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"history_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"operation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unbind"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"is_undone"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-05-27T13:00:00Z"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"undone_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-05-27T14:30:00Z"</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"history_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"operation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bind"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"is_undone"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-05-27T12:00:00Z"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"undone_at"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>total</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total bind history records for this identity</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>undo_stack_count</code></td>
|
||||
<td>integer</td>
|
||||
<td>Records available for undo (<code>is_undone=false</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>redo_stack_count</code></td>
|
||||
<td>integer</td>
|
||||
<td>Records available for redo (<code>is_undone=true</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>results[].history_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>History record ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>results[].operation</code></td>
|
||||
<td>string</td>
|
||||
<td>Operation type (<code>bind</code>, <code>unbind</code>, or <code>bind_trace</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>results[].is_undone</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Whether the operation has been undone</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>results[].created_at</code></td>
|
||||
<td>string</td>
|
||||
<td>When the operation was applied</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>results[].undone_at</code></td>
|
||||
<td>string</td>
|
||||
<td>When the undo occurred (null if not undone)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h5>Example</h5>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/bind/history?page=1&limit=10"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h5>Error Responses</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>Identity not found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>500</code></td>
|
||||
<td>Database error</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3>3. Merge History & Undo/Redo</h3>
|
||||
<p>Merge operations use MongoDB for richer record-keeping, with a 24-hour undo deadline.</p>
|
||||
<h4>Merge Operation Overview</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Storage</td>
|
||||
<td>MongoDB <code>identity_merge_history</code> collection</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Snapshot</td>
|
||||
<td>Full source identity state + target identity state + aliases/metadata diffs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Trigger</td>
|
||||
<td>Every mergeinto with <code>keep_history=true</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Undo deadline</td>
|
||||
<td>24 hours (renewed on redo)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Redo support</td>
|
||||
<td>Yes — restores undone merges with new 24hr deadline</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Max records</td>
|
||||
<td>Unlimited</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h4><code>POST /api/v1/identity/merge/:merge_id/undo</code></h4>
|
||||
<p>Already documented in <a href="07_identity.md#post-apiv1identitymergemerge_idundo"><code>07_identity.md</code></a>. See that document for full details.</p>
|
||||
<hr />
|
||||
<h4><code>POST /api/v1/identity/merge/:merge_id/redo</code></h4>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Redo a previously undone merge operation within the renewed 24-hour deadline.</p>
|
||||
<h5>Request</h5>
|
||||
<p>No body required. The merge ID is taken from the URL path.</p>
|
||||
<h5>Behavior</h5>
|
||||
<ol>
|
||||
<li>Validates the merge record exists and <code>undone=true</code> (not already active)</li>
|
||||
<li>Checks the 24-hour undo deadline (if expired, the redo is rejected)</li>
|
||||
<li>Restores face bindings: moves all faces from <code>target_identity</code> back to <code>source_identity</code></li>
|
||||
<li>Re-adds aliases that were removed by the undo (aliases with <code>source: "merge"</code> tag)</li>
|
||||
<li>Re-adds metadata fields that were removed by the undo</li>
|
||||
<li>If <code>keep_history=true</code>: sets <code>source_identity.status = 'merged'</code> again</li>
|
||||
<li>If <code>keep_history=false</code>: recreates source identity from the <code>undone_snapshot</code> stored at undo time</li>
|
||||
<li>Syncs both identity JSON files to disk</li>
|
||||
<li>Sets <code>undone=false</code>, clears <code>undone_snapshot</code>, renews <code>undo_deadline = NOW() + 24h</code></li>
|
||||
<li>Records <code>redone_by</code> user for audit</li>
|
||||
</ol>
|
||||
<h5>Example</h5>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/merge/550e8400-e29b-41d4-a716-446655440000/redo"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h5>Response (200)</h5>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Redo merge completed: merged 'stranger_13894' into 'Louis Viret' (52 faces transferred)"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"merge_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"550e8400-e29b-41d4-a716-446655440000"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"faces_transferred"</span><span class="p">:</span><span class="w"> </span><span class="mi">52</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"aliases_re_added"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"metadata_fields_re_added"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>merge_id</code></td>
|
||||
<td>string</td>
|
||||
<td>The merge operation ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>faces_transferred</code></td>
|
||||
<td>integer</td>
|
||||
<td>Number of faces transferred from source to target</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>aliases_re_added</code></td>
|
||||
<td>integer</td>
|
||||
<td>Number of aliases restored to target</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>metadata_fields_re_added</code></td>
|
||||
<td>integer</td>
|
||||
<td>Number of metadata fields restored to target</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h5>Error Responses</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>400</code></td>
|
||||
<td>Merge not undone, deadline expired, or cannot redo</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>Merge record not found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>500</code></td>
|
||||
<td>Database error</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3>4. Delete History & Undo/Redo</h3>
|
||||
<h4>Delete Operation Overview</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Storage</td>
|
||||
<td>PostgreSQL <code>identity_history</code> table</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Snapshot</td>
|
||||
<td><code>{"identity": {...full row...}, "unbound_faces": [{file_uuid, face_id, trace_id}, ...]}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Max records</td>
|
||||
<td>1 active delete record per identity (redo stack cleared on new delete)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Undo support</td>
|
||||
<td>Yes — recreates identity row, re-binds faces</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Redo support</td>
|
||||
<td>Yes — re-deletes the identity</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Identity file</td>
|
||||
<td>Deleted on delete, recreated on undo</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Snapshot Format</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"identity"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">9</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a90105-6d6b-46ff-92da-0c3c1a57dff4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Cary Grant"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"people"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"source"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tmdb"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"confirmed"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"metadata"</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span>
|
||||
<span class="w"> </span><span class="nt">"tmdb_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">112</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"tmdb_profile"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"unbound_faces"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aeed71342a899fe4b4c57b7d41bcb692"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1_5"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"trace_id"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aeed71342a899fe4b4c57b7d41bcb692"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1_6"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"trace_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">906</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Stack Model</h4>
|
||||
<div class="codehilite"><pre><span></span><code>DELETE identity (undo stack, is_undone=false)
|
||||
↓ undo
|
||||
Identity recreated, faces re-bound
|
||||
→ delete history marked is_undone=true
|
||||
↓ redo (re-delete)
|
||||
Identity deleted again, faces unbound
|
||||
→ delete history marked is_undone=false
|
||||
</code></pre></div>
|
||||
|
||||
<p>A new delete after an undo clears the delete redo stack (no redo possible for the old delete).</p>
|
||||
<h4>Undo Behavior (via existing <code>POST /api/v1/identity/:identity_uuid/undo</code>)</h4>
|
||||
<ol>
|
||||
<li>Normal identity lookup fails (row was deleted)</li>
|
||||
<li>Checks <code>identity_history</code> for <code>operation='delete' AND is_undone=false</code> matching the UUID in the snapshot</li>
|
||||
<li>Recreates the identity row (new internal <code>id</code>, same UUID)</li>
|
||||
<li>Re-binds all faces listed in <code>unbound_faces</code> to the new identity</li>
|
||||
<li>Deletes the <code>identity_history</code> delete record as <code>is_undone=true</code> with <code>undone_at=NOW()</code></li>
|
||||
<li>Syncs <code>identity.json</code> to disk</li>
|
||||
<li>Updates <code>_index.json</code></li>
|
||||
</ol>
|
||||
<h4>Redo Behavior (via existing <code>POST /api/v1/identity/:identity_uuid/redo</code>)</h4>
|
||||
<ol>
|
||||
<li>Identity lookup succeeds (identity was restored by prior undo)</li>
|
||||
<li>Checks <code>identity_history</code> for <code>operation='delete' AND is_undone=true</code> matching the identity_id</li>
|
||||
<li>Deletes <code>identity.json</code> from disk</li>
|
||||
<li>Unbinds all faces (<code>identity_id = NULL</code>)</li>
|
||||
<li>Deletes the identity row</li>
|
||||
<li>Marks the delete history record as <code>is_undone=false</code></li>
|
||||
<li>Returns success</li>
|
||||
</ol>
|
||||
<h4>Error Responses (delete undo/redo)</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>Scenario</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>400</code></td>
|
||||
<td>No delete history available (either no delete or already undone/redone)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>Identity not found (for redo — identity wasn't restored)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>500</code></td>
|
||||
<td>Database error</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3>Comparison: PATCH vs Bind vs Merge vs Delete Undo/Redo</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Aspect</th>
|
||||
<th>PATCH Undo/Redo</th>
|
||||
<th>Merge Undo</th>
|
||||
<th>Bind Undo/Redo</th>
|
||||
<th>Merge Undo/Redo</th>
|
||||
<th>Delete Undo/Redo</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Storage</td>
|
||||
<td>PostgreSQL <code>identity_history</code></td>
|
||||
<td>PostgreSQL <code>identity_history</code></td>
|
||||
<td>MongoDB <code>identity_merge_history</code></td>
|
||||
<td>PostgreSQL <code>identity_history</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Operation filter</td>
|
||||
<td><code>operation='update'</code></td>
|
||||
<td><code>operation IN ('bind','unbind','bind_trace')</code></td>
|
||||
<td>—</td>
|
||||
<td><code>operation='delete'</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Trigger</td>
|
||||
<td>Every PATCH</td>
|
||||
<td>Every bind/unbind/bind_trace</td>
|
||||
<td>Every mergeinto with <code>keep_history=true</code></td>
|
||||
<td>Every DELETE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Undo deadline</td>
|
||||
<td>None (unlimited)</td>
|
||||
<td>24 hours</td>
|
||||
<td>None (unlimited)</td>
|
||||
<td>24 hours (renewed on redo)</td>
|
||||
<td>None (unlimited)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Redo support</td>
|
||||
<td>Yes</td>
|
||||
<td>No</td>
|
||||
<td>Yes</td>
|
||||
<td>Yes</td>
|
||||
<td>Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Step undo</td>
|
||||
<td>Yes (<code>steps</code> param)</td>
|
||||
<td>No (full undo only)</td>
|
||||
<td>Yes (<code>steps</code> param)</td>
|
||||
<td>No (full undo/redo only)</td>
|
||||
<td>No (single record)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Max records</td>
|
||||
<td>256 per identity</td>
|
||||
<td>256 per identity (shared)</td>
|
||||
<td>Unlimited</td>
|
||||
<td>256 per identity (shared)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>User tracking</td>
|
||||
<td><code>user_id</code> + <code>user_source</code></td>
|
||||
<td><code>user_id</code> + <code>user_source</code></td>
|
||||
<td><code>performed_by_user</code> + <code>undone_by</code> / <code>redone_by</code></td>
|
||||
<td><code>user_id</code> + <code>user_source</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
728
docs_v1.0/doc_developer/15_tkg.html
Normal file
728
docs_v1.0/doc_developer/15_tkg.html
Normal file
@@ -0,0 +1,728 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>15 Tkg - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.logout-btn { font-size: 13px; color: #999; text-decoration: none; }
|
||||
.logout-btn:hover { color: #cc0000; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="topbar">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<a class="logout-btn" href="#" onclick="fetch('/api/v1/auth/logout',{method:'POST'}).then(()=>window.location.reload());return false">Logout</a>
|
||||
</div>
|
||||
<!-- module: tkg -->
|
||||
<!-- description: Temporal Knowledge Graph — rebuild, nodes, edges, processor counts -->
|
||||
<!-- depends: 05_process, 07_identity -->
|
||||
|
||||
<h2>Temporal Knowledge Graph (TKG)</h2>
|
||||
<p>TKG is a time-aligned knowledge graph built from multi-processor outputs (face, yolo, ocr, pose, asrx, gaze, lip, appearance). It produces 9 node types and 14 edge types stored in <code>dev.tkg_nodes</code> and <code>dev.tkg_edges</code>.</p>
|
||||
<h3>Node Types</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Node Type</th>
|
||||
<th>Description</th>
|
||||
<th>Key Properties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>face_trace</code></td>
|
||||
<td>A tracked face identity over time</td>
|
||||
<td><code>trace_id</code>, <code>face_count</code>, <code>avg_confidence</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gaze_trace</code></td>
|
||||
<td>Gaze direction over time</td>
|
||||
<td><code>direction</code> (frontal/left/right/up/down + diagonals)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>lip_trace</code></td>
|
||||
<td>Lip movement synced with speech</td>
|
||||
<td><code>speaker_id</code>, <code>lip_area_range</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>text_trace</code></td>
|
||||
<td>Spoken text aligned to time</td>
|
||||
<td><code>speaker_id</code>, <code>text</code>, <code>start_time</code>, <code>end_time</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>appearance_trace</code></td>
|
||||
<td>Human appearance (clothing) over time</td>
|
||||
<td><code>clothing_color</code>, <code>upper_cloth</code>, <code>lower_cloth</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>skin_tone_trace</code></td>
|
||||
<td>Fitzpatrick skin tone classification</td>
|
||||
<td><code>fitzpatrick_type</code> (I–VI)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>accessory</code></td>
|
||||
<td>Detected accessories</td>
|
||||
<td><code>type</code> (glasses/hat/etc.), <code>confidence</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>object</code></td>
|
||||
<td>YOLO-detected object</td>
|
||||
<td><code>class</code>, <code>confidence</code>, <code>frame_count</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>speaker</code></td>
|
||||
<td>ASRX speaker segment</td>
|
||||
<td><code>speaker_id</code>, <code>segment_count</code>, <code>total_duration</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Edge Types</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Edge Type</th>
|
||||
<th>Source → Target</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>co_occurs</code></td>
|
||||
<td>object ↔ object</td>
|
||||
<td>Two objects appear together in same frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>speaker_face</code></td>
|
||||
<td>speaker ↔ face_trace</td>
|
||||
<td>Speaker matched to face trace via lip sync</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>face_face</code></td>
|
||||
<td>face_trace ↔ face_trace</td>
|
||||
<td>Two face traces interact (mutual gaze)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>mutual_gaze</code></td>
|
||||
<td>gaze_trace ↔ gaze_trace</td>
|
||||
<td>Two people looking at each other</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>lip_sync</code></td>
|
||||
<td>lip_trace ↔ text_trace</td>
|
||||
<td>Lip movement aligned with spoken text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>has_appearance</code></td>
|
||||
<td>face_trace ↔ appearance_trace</td>
|
||||
<td>Face has specific appearance</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>wears</code></td>
|
||||
<td>face_trace ↔ accessory</td>
|
||||
<td>Face wears an accessory</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/file/:file_uuid/tkg/rebuild</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Rebuild the Temporal Knowledge Graph for a file. Reads processor JSON outputs (face, yolo, ocr, pose, asrx, gaze, lip, appearance) and generates TKG nodes and edges. Clears existing nodes/edges for the file first, then rebuilds from scratch.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/tkg/rebuild"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"result"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"face_trace_nodes"</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"gaze_trace_nodes"</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"lip_trace_nodes"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"text_trace_nodes"</span><span class="p">:</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"appearance_trace_nodes"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"skin_tone_trace_nodes"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"accessory_nodes"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"object_nodes"</span><span class="p">:</span><span class="w"> </span><span class="mi">26</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"speaker_nodes"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"co_occurrence_edges"</span><span class="p">:</span><span class="w"> </span><span class="mi">94</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"speaker_face_edges"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face_face_edges"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"mutual_gaze_edges"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"lip_sync_edges"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"has_appearance_edges"</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"wears_edges"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"error"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>success</code></td>
|
||||
<td>boolean</td>
|
||||
<td>True if rebuild completed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>result</code></td>
|
||||
<td>object</td>
|
||||
<td>Node and edge counts by type</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>error</code></td>
|
||||
<td>string/null</td>
|
||||
<td>Error message if failed</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/file/:file_uuid/tkg/nodes</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Query TKG nodes with pagination and optional type filter.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>node_type</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>all</td>
|
||||
<td>Filter by node type: <code>face_trace</code>, <code>gaze_trace</code>, <code>lip_trace</code>, <code>text_trace</code>, <code>appearance_trace</code>, <code>skin_tone_trace</code>, <code>accessory</code>, <code>object</code>, <code>speaker</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>1</td>
|
||||
<td>Page number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>100</td>
|
||||
<td>Items per page (max 500)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Get all face_trace nodes</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/tkg/nodes"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"node_type": "face_trace", "page": 1, "page_size": 50}'</span>
|
||||
|
||||
<span class="c1"># Get all nodes</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/tkg/nodes"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">16</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"nodes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"node_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"face_trace"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"external_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"trace_0"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Face Trace 0"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"trace_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">142</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"avg_confidence"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.87</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>success</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Always true on 200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>total</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total matching node count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>Current page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>Items per page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>nodes</code></td>
|
||||
<td>array</td>
|
||||
<td>Array of node objects</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>nodes[].id</code></td>
|
||||
<td>integer</td>
|
||||
<td>Database primary key</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>nodes[].node_type</code></td>
|
||||
<td>string</td>
|
||||
<td>Node type (see table above)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>nodes[].external_id</code></td>
|
||||
<td>string</td>
|
||||
<td>External identifier (e.g., <code>trace_0</code>, <code>gaze_1</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>nodes[].label</code></td>
|
||||
<td>string</td>
|
||||
<td>Human-readable label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>nodes[].properties</code></td>
|
||||
<td>object</td>
|
||||
<td>Type-specific properties as JSON</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/file/:file_uuid/tkg/edges</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Query TKG edges with pagination and optional filters.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>edge_type</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>all</td>
|
||||
<td>Filter by edge type: <code>co_occurs</code>, <code>speaker_face</code>, <code>face_face</code>, <code>mutual_gaze</code>, <code>lip_sync</code>, <code>has_appearance</code>, <code>wears</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>source_type</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Filter by source node type</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>target_type</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Filter by target node type</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>1</td>
|
||||
<td>Page number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>100</td>
|
||||
<td>Items per page (max 500)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Get all co_occurrence edges</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/tkg/edges"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"edge_type": "co_occurs"}'</span>
|
||||
|
||||
<span class="c1"># Get edges between face_trace and speaker nodes</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/tkg/edges"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"source_type": "speaker", "target_type": "face_trace"}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">94</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"edges"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"edge_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"co_occurs"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"source_node_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"target_node_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"frame_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">45</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"confidence"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.92</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>success</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Always true on 200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>total</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total matching edge count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>Current page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>Items per page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>edges</code></td>
|
||||
<td>array</td>
|
||||
<td>Array of edge objects</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>edges[].id</code></td>
|
||||
<td>integer</td>
|
||||
<td>Database primary key</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>edges[].edge_type</code></td>
|
||||
<td>string</td>
|
||||
<td>Edge type</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>edges[].source_node_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>Source node ID (FK to tkg_nodes)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>edges[].target_node_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>Target node ID (FK to tkg_nodes)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>edges[].properties</code></td>
|
||||
<td>object</td>
|
||||
<td>Edge-specific properties as JSON</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/tkg/node/:node_id</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Get detail for a specific TKG node including its connected edges.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/tkg/node/1"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"node"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"node_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"face_trace"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"external_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"trace_0"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Face Trace 0"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"trace_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">142</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"avg_confidence"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.87</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"connected_edges"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"edge_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"co_occurs"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"source_node_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"target_node_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"frame_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">45</span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"edge_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>success</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Always true on 200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>node</code></td>
|
||||
<td>object</td>
|
||||
<td>Node detail (same format as nodes query)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>connected_edges</code></td>
|
||||
<td>array</td>
|
||||
<td>Edges connected to this node</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>edge_count</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total connected edge count</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Error Codes</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>Node not found</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/processor-counts</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Get counts of processor JSON output files for a file. Scans the output directory for <code>{file_uuid}.{processor}.json</code> files and extracts frame counts, segment counts, and chunk counts from each file.</p>
|
||||
<p>Supports short UUID prefix matching (e.g., <code>d3f9ae8e</code> → resolves to full <code>d3f9ae8e471a1fc4d47022c66091b920</code>).</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/processor-counts"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"output_dir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/Users/accusys/momentry/output_dev"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"processors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"processor"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cut"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"has_json"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"frame_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">5391</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"segment_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"chunk_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"last_modified"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-06-16T18:48:01.987241061+00:00"</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"processor"</span><span class="p">:</span><span class="w"> </span><span class="s2">"face"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"has_json"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"frame_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">1112</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"segment_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"chunk_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"last_modified"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-06-18T17:21:37.408383765+00:00"</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"processor"</span><span class="p">:</span><span class="w"> </span><span class="s2">"asrx"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"has_json"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"frame_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"segment_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"chunk_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"last_modified"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-06-18T17:21:40.872063642+00:00"</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"processor"</span><span class="p">:</span><span class="w"> </span><span class="s2">"story"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"has_json"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"frame_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"segment_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"chunk_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"last_modified"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-06-18T17:22:00.000000000+00:00"</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"processor"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mediapipe"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"has_json"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"frame_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"segment_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"chunk_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"last_modified"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>Full 32-char hex UUID (resolved from prefix)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>output_dir</code></td>
|
||||
<td>string</td>
|
||||
<td>Output directory scanned</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors</code></td>
|
||||
<td>array</td>
|
||||
<td>Per-processor output info</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].processor</code></td>
|
||||
<td>string</td>
|
||||
<td>Processor name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].has_json</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Whether JSON file exists</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].frame_count</code></td>
|
||||
<td>integer/null</td>
|
||||
<td>Total frames processed (frame-based processors)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].segment_count</code></td>
|
||||
<td>integer/null</td>
|
||||
<td>Segment count (ASRX segments, etc.)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].chunk_count</code></td>
|
||||
<td>integer/null</td>
|
||||
<td>Chunk count (Story chunks, etc.)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].last_modified</code></td>
|
||||
<td>string/null</td>
|
||||
<td>ISO 8601 timestamp of last modification</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Error Codes</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>File UUID not found in database</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<p><em>Updated: 2026-06-20 12:00:00</em></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
240
docs_v1.0/doc_developer/16_workspace.html
Normal file
240
docs_v1.0/doc_developer/16_workspace.html
Normal file
@@ -0,0 +1,240 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>16 Workspace - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.logout-btn { font-size: 13px; color: #999; text-decoration: none; }
|
||||
.logout-btn:hover { color: #cc0000; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="topbar">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<a class="logout-btn" href="#" onclick="fetch('/api/v1/auth/logout',{method:'POST'}).then(()=>window.location.reload());return false">Logout</a>
|
||||
</div>
|
||||
<!-- module: workspace -->
|
||||
<!-- description: Workspace checkout/checkin — lock, clear, restore file data -->
|
||||
<!-- depends: 04_lookup, 05_process -->
|
||||
|
||||
<h2>Workspace Checkin/Checkout</h2>
|
||||
<p>Workspace checkin/checkout provides a transactional editing model for file data:
|
||||
- <strong>Checkout</strong>: Clears PG tables (face_detections, speaker_detections, pre_chunks) and Qdrant vectors, creating an isolated workspace SQLite for editing.
|
||||
- <strong>Checkin</strong>: Restores data from the workspace SQLite back to PG and Qdrant, marking the file as <code>Indexed</code>.</p>
|
||||
<p>This allows safe concurrent editing — while a file is checked out, its main database records are cleared, preventing conflicts.</p>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/file/:file_uuid/checkout</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Checkout a file workspace. Clears face detections, speaker detections, pre_chunks from PostgreSQL, deletes Qdrant vectors, and creates a workspace SQLite database for isolated editing.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/checkout"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"rows_deleted"</span><span class="p">:</span><span class="w"> </span><span class="mi">1523</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"checked_out"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rows_deleted</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total rows cleared from PG tables</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>status</code></td>
|
||||
<td>string</td>
|
||||
<td><code>"checked_out"</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Error Responses</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>500</code></td>
|
||||
<td>Checkout failed (DB error, workspace creation error)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/file/:file_uuid/checkin</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Checkin a file workspace. Restores face detections, speaker detections, pre_chunks from workspace SQLite back to PostgreSQL, re-indexes vectors to Qdrant, and sets video status to <code>Indexed</code>.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/checkin"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"pre_chunks_moved"</span><span class="p">:</span><span class="w"> </span><span class="mi">45</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face_detections_moved"</span><span class="p">:</span><span class="w"> </span><span class="mi">1200</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"speaker_detections_moved"</span><span class="p">:</span><span class="w"> </span><span class="mi">320</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"vectors_moved"</span><span class="p">:</span><span class="w"> </span><span class="mi">45</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"indexed"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pre_chunks_moved</code></td>
|
||||
<td>integer</td>
|
||||
<td>Pre-chunks restored from workspace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>face_detections_moved</code></td>
|
||||
<td>integer</td>
|
||||
<td>Face detections restored from workspace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>speaker_detections_moved</code></td>
|
||||
<td>integer</td>
|
||||
<td>Speaker detections restored from workspace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>vectors_moved</code></td>
|
||||
<td>integer</td>
|
||||
<td>Vectors re-indexed to Qdrant</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>status</code></td>
|
||||
<td>string</td>
|
||||
<td><code>"indexed"</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Error Responses</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>500</code></td>
|
||||
<td>Checkin failed (DB error, workspace not found, vector index error)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/workspace</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Check if a workspace SQLite database exists for a file.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/workspace"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d3f9ae8e471a1fc4d47022c66091b920"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"exists"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>exists</code></td>
|
||||
<td>boolean</td>
|
||||
<td>True if workspace SQLite exists</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3>Workflow</h3>
|
||||
<div class="codehilite"><pre><span></span><code> REGISTERED ──→ CHECKED_OUT ──→ INDEXED
|
||||
│ │ │
|
||||
│ checkout checkin
|
||||
│ │ │
|
||||
│ clear PG + Qdrant restore from SQLite
|
||||
│ create workspace re-index vectors
|
||||
│ set status set status
|
||||
</code></pre></div>
|
||||
|
||||
<ol>
|
||||
<li><strong>Register</strong> file → status: <code>REGISTERED</code></li>
|
||||
<li><strong>Process</strong> file → processors run, data stored in PG + Qdrant</li>
|
||||
<li><strong>Checkout</strong> file → clear editable data, create workspace SQLite → status: <code>CHECKED_OUT</code></li>
|
||||
<li><strong>Edit</strong> workspace via Agent Search / identity binding</li>
|
||||
<li><strong>Checkin</strong> file → restore from workspace SQLite → status: <code>INDEXED</code></li>
|
||||
<li><strong>Rebuild TKG</strong> if needed after checkin</li>
|
||||
</ol>
|
||||
<hr />
|
||||
<p><em>Updated: 2026-06-20 12:00:00</em></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
254
docs_v1.0/doc_developer/99_incomplete.html
Normal file
254
docs_v1.0/doc_developer/99_incomplete.html
Normal file
@@ -0,0 +1,254 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>99 Incomplete - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.logout-btn { font-size: 13px; color: #999; text-decoration: none; }
|
||||
.logout-btn:hover { color: #cc0000; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="topbar">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<a class="logout-btn" href="#" onclick="fetch('/api/v1/auth/logout',{method:'POST'}).then(()=>window.location.reload());return false">Logout</a>
|
||||
</div>
|
||||
<!-- module: incomplete -->
|
||||
<!-- description: Incomplete, stub, or undocumented API endpoints — tracking list -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
<h2>Incomplete / Undocumented APIs</h2>
|
||||
<p>This module tracks API endpoints that exist in the codebase but are either undocumented, partially documented, or stubs.</p>
|
||||
<blockquote>
|
||||
<p><strong>Note</strong>: Endpoints listed here should be fully documented and moved to their appropriate module once implemented.</p>
|
||||
</blockquote>
|
||||
<hr />
|
||||
<h2>Identity Binding</h2>
|
||||
<h3><code>POST /api/v1/identity/:identity_uuid/bind</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Bind a single face detection to an identity. Unlike <code>bind/trace</code> which binds all faces in a trace, this binds one specific face.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>File containing the face</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>face_id</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>Face detection ID to bind</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Status</h4>
|
||||
<p>⚠️ <strong>Undocumented</strong> — exists in code but no full request/response documentation.</p>
|
||||
<hr />
|
||||
<h2>Resource Management</h2>
|
||||
<h3><code>POST /api/v1/resource/register</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>Register an external resource (e.g., storage backend, API service).</p>
|
||||
<h4>Status</h4>
|
||||
<p>⚠️ <strong>Undocumented</strong> — endpoint exists but no documentation.</p>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/resource/heartbeat</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>Send heartbeat for a registered resource to verify it's still alive.</p>
|
||||
<h4>Status</h4>
|
||||
<p>⚠️ <strong>Undocumented</strong> — endpoint exists but no documentation.</p>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/resources</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>List all registered resources with their status.</p>
|
||||
<h4>Status</h4>
|
||||
<p>⚠️ <strong>Undocumented</strong> — endpoint exists but no documentation.</p>
|
||||
<hr />
|
||||
<h2>5W1H Agent</h2>
|
||||
<h3><code>POST /api/v1/agents/5w1h/analyze</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Run 5W1H analysis on all cut scenes for a file. Uses LLM (Gemma4) to summarize each scene with who/what/where/when/why/how.</p>
|
||||
<h4>Status</h4>
|
||||
<p>⚠️ <strong>Partially documented</strong> — listed in <code>12_agent.md</code> but missing full request/response examples.</p>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/agents/5w1h/batch</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>Run 5W1H analysis on multiple files at once.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuids</code></td>
|
||||
<td>string[]</td>
|
||||
<td>Yes</td>
|
||||
<td>Array of file UUIDs to analyze</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Status</h4>
|
||||
<p>⚠️ <strong>Partially documented</strong> — listed in <code>12_agent.md</code> but missing full request/response examples.</p>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/agents/5w1h/status</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>Get 5W1H analysis status across all videos (which files have been analyzed, which are pending).</p>
|
||||
<h4>Status</h4>
|
||||
<p>⚠️ <strong>Partially documented</strong> — listed in <code>12_agent.md</code> but missing full response schema.</p>
|
||||
<hr />
|
||||
<h2>Identity Agent</h2>
|
||||
<h3><code>POST /api/v1/agents/identity/match-from-photo</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>Match an identity using an uploaded photo. Extracts face embedding, finds best trace match.</p>
|
||||
<h4>Status</h4>
|
||||
<p>⚠️ <strong>Partially documented</strong> — exists in <code>08_identity_agent.md</code> but missing full response schema and error cases.</p>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/agents/identity/match-from-trace</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Match an identity using a trace. Multi-angle embedding comparison with propagation.</p>
|
||||
<h4>Status</h4>
|
||||
<p>⚠️ <strong>Partially documented</strong> — exists in <code>08_identity_agent.md</code> but missing full response schema and error cases.</p>
|
||||
<hr />
|
||||
<h2>Stubs / Not Implemented</h2>
|
||||
<h3>Visual Search Endpoints</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Endpoint</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual</code></td>
|
||||
<td>Stub — defined but not functional</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/class</code></td>
|
||||
<td>Stub — defined but not functional</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/density</code></td>
|
||||
<td>Stub — defined but not functional</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/combination</code></td>
|
||||
<td>Stub — defined but not functional</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/stats</code></td>
|
||||
<td>Stub — defined but not functional</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Unmounted Routes</h3>
|
||||
<p>These endpoints are defined in source code but not mounted in the router:</p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Endpoint</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>/api/v1/search/persons</code></td>
|
||||
<td>Defined but not mounted</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>/api/v1/who</code></td>
|
||||
<td>Defined but not mounted</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>/api/v1/who/candidates</code></td>
|
||||
<td>Defined but not mounted</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h2>Tracking</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Count</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Undocumented</td>
|
||||
<td>3 (resource management)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Partially documented</td>
|
||||
<td>5 (5W1H ×3, identity agent ×2)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stub/not functional</td>
|
||||
<td>5 (visual search)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Defined but unmounted</td>
|
||||
<td>3 (persons, who, who/candidates)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Total</strong></td>
|
||||
<td><strong>16</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<p><em>Created: 2026-06-20 — Gap analysis from core API vs doc_wasm sync</em>
|
||||
<em>Updated: 2026-06-20 — Initial tracking list</em></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -29,7 +29,7 @@ a:hover td { background: #f8f8f8; border-radius: 4px; }
|
||||
<a class="logout-btn" href="#" onclick="fetch('/api/v1/auth/logout',{method:'POST'}).then(()=>window.location.reload());return false">Logout</a>
|
||||
</div>
|
||||
<p class="subtitle">API 參考手冊 — 登入後可瀏覽各模組文件</p>
|
||||
<table><tr onclick="window.location='11_error_codes.html'" style="cursor:pointer"><td class="cn">錯誤碼</td><td class="en">Error Codes</td></tr><tr onclick="window.location='14_identity_history.html'" style="cursor:pointer"><td class="cn">14 Identity History</td><td class="en"></td></tr></table>
|
||||
<table><tr onclick="window.location='11_error_codes.html'" style="cursor:pointer"><td class="cn">錯誤碼</td><td class="en">Error Codes</td></tr><tr onclick="window.location='14_identity_history.html'" style="cursor:pointer"><td class="cn">14 Identity History</td><td class="en"></td></tr><tr onclick="window.location='15_tkg.html'" style="cursor:pointer"><td class="cn">15 Tkg</td><td class="en"></td></tr><tr onclick="window.location='16_workspace.html'" style="cursor:pointer"><td class="cn">16 Workspace</td><td class="en"></td></tr><tr onclick="window.location='99_incomplete.html'" style="cursor:pointer"><td class="cn">99 Incomplete</td><td class="en"></td></tr></table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -127,13 +127,15 @@ curl -s "$API/api/v1/file/$FILE_UUID/probe" -H "X-API-Key: $KEY"
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/progress/:file_uuid`
|
||||
### `POST /api/v1/progress/:file_uuid`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Get real-time processing progress for a file via Redis pub/sub. Includes per-processor status, current/total frames, ETA, and system resource stats.
|
||||
|
||||
**Note**: This endpoint uses **POST** method, not GET. The progress data is stored in Redis as a hash, and POST is used to retrieve the latest state.
|
||||
|
||||
#### Pipeline Order
|
||||
|
||||
| Order | Processor | Dependencies | Description |
|
||||
@@ -154,7 +156,7 @@ All processors except `story` and `5w1h` run concurrently when their dependencie
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s "$API/api/v1/progress/$FILE_UUID" -H "X-API-Key: $KEY" | jq '{overall_progress, processors: [.processors[] | {processor_type, status}]}'
|
||||
curl -s -X POST "$API/api/v1/progress/$FILE_UUID" -H "X-API-Key: $KEY" | jq '{overall_progress, processors: [.processors[] | {name, status}]}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
@@ -923,6 +923,128 @@ curl -s "$API/api/v1/identity/$IDENTITY_UUID/json" \
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/file/:file_uuid/pending-person`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Create a manually managed "pending person" under a specific file. A pending person is an identity with `status='pending'` and `source='manual'`, used for unmatched traces that the user wants to manually label before a full identity resolution.
|
||||
|
||||
Optionally binds a list of trace IDs to this new identity.
|
||||
|
||||
#### Request
|
||||
|
||||
```json
|
||||
{
|
||||
"trace_ids": [100, 150, 200],
|
||||
"name": "Mystery Man #1"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `trace_ids` | array[int] | No | `[]` | Trace IDs to bind to this pending person |
|
||||
| `name` | string | No | `"Person N"` | Human-readable name. Auto-generated if omitted |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
# Create pending person with name and no traces
|
||||
curl -s -X POST "$API/api/v1/file/$FILE_UUID/pending-person" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "Unknown Woman #2", "trace_ids": []}'
|
||||
|
||||
# Create pending person with auto-name and bind traces
|
||||
curl -s -X POST "$API/api/v1/file/$FILE_UUID/pending-person" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"trace_ids": [100, 150, 200]}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Created pending person: Mystery Man #1 (uuid: 4d96b25b-68f0-4c52-b238-d69f7dfd588b)",
|
||||
"data": {
|
||||
"identity_uuid": "4d96b25b-68f0-4c52-b238-d69f7dfd588b",
|
||||
"identity_id": 55,
|
||||
"name": "Mystery Man #1",
|
||||
"bound_traces": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `identity_uuid` | string | UUID of the newly created pending identity |
|
||||
| `identity_id` | integer | Internal ID of the new identity |
|
||||
| `name` | string | Display name |
|
||||
| `bound_traces` | integer | Number of traces bound |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- Creates an `identities` row with `status='pending'`, `source='manual'`, `file_uuid=<file_uuid>`
|
||||
- If `trace_ids` provided: `UPDATE face_detections SET identity_id = ...` for matching traces
|
||||
- If `trace_ids` provided: TKG face_track nodes get `identity_id` / `identity_name` in properties
|
||||
- Identity JSON file synced to disk
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/file/:file_uuid/pending-persons`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
List all pending persons for a file.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s "$API/api/v1/file/$FILE_UUID/pending-persons" \
|
||||
-H "X-API-Key: $KEY"
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Found 2 pending persons for c36f35685177c981aa139b66bbbccc5b",
|
||||
"data": [
|
||||
{
|
||||
"identity_uuid": "232ecd08-a2bf-4bd0-bd25-0bd8fb7a7dae",
|
||||
"identity_id": 56,
|
||||
"name": "Person 2",
|
||||
"created_at": "2026-06-23 17:13:23",
|
||||
"trace_count": 3,
|
||||
"bound_traces": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `identity_uuid` | string | Identity UUID |
|
||||
| `identity_id` | integer | Internal identity ID |
|
||||
| `name` | string | Display name |
|
||||
| `created_at` | string | Creation timestamp |
|
||||
| `trace_count` | integer | Number of face traces bound to this pending person |
|
||||
| `bound_traces` | array[int] | List of bound trace IDs (currently null, reserved for future expansion) |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Pending persons are normal `identities` rows with `status='pending'` — they can be promoted to confirmed via `PATCH /api/v1/identity/:identity_uuid` (`{"status": "confirmed"}`)
|
||||
- They can be merged into known identities via `POST /api/v1/identity/:identity_uuid/mergeinto`
|
||||
- Use `GET /api/v1/identity/:identity_uuid/traces` to get detailed trace info for each pending person
|
||||
|
||||
---
|
||||
|
||||
## Alias System (BCP 47 Locale Tags)
|
||||
|
||||
Identity aliases support multilingual display names. Aliases are stored in `metadata.aliases` as an array of `{locale, name}` objects.
|
||||
|
||||
Reference in New Issue
Block a user