docs: RCA — identity_uuid missing + file identities NULL appearance
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
# RCA: Identity UUID Missing + File Identities First/Last Appearance NULL
|
||||
|
||||
**Date**: 2026-05-15
|
||||
**Author**: M5
|
||||
**Status**: Resolved
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Two related defects in the identity listing endpoints prevented external systems from using `identity_uuid` for further API calls, and returned null time ranges for face identities.
|
||||
|
||||
| Issue | Endpoint | Symptom | Severity |
|
||||
|-------|----------|---------|:--------:|
|
||||
| #2 | `GET /api/v1/identities`<br>`GET /api/v1/file/:file_uuid/identities` | Response missing `identity_uuid` | High |
|
||||
| #3 | `GET /api/v1/file/:file_uuid/identities` | `first_appearance` / `last_appearance` always `null` | Medium |
|
||||
|
||||
---
|
||||
|
||||
## Issue #2 — identity_uuid Missing
|
||||
|
||||
### Root Cause
|
||||
|
||||
Two list endpoints only returned `identity_id` (integer primary key) without `identity_uuid` (public UUID):
|
||||
|
||||
```json
|
||||
// GET /api/v1/identities — before fix
|
||||
[{"id": 18276, "name": "PERSON_aeed7134_367", "metadata": {}}]
|
||||
```
|
||||
|
||||
```json
|
||||
// GET /api/v1/file/:uuid/identities — before fix
|
||||
{"identity_id": 18276, "name": "PERSON_aeed7134_367", ...}
|
||||
```
|
||||
|
||||
The `identities` table has both columns:
|
||||
- `id` (SERIAL INTEGER) — internal FK, used in `face_detections.identity_id`
|
||||
- `uuid` (UUID, `gen_random_uuid()`) — stable external identifier
|
||||
|
||||
All identity detail/chunks/files/bind routes require `identity_uuid` in the URL path (`/api/v1/identity/:identity_uuid/...`). Without it in the list response, clients had no way to navigate from a list entry to its detail.
|
||||
|
||||
### Code Location
|
||||
|
||||
| File | Line | Issue |
|
||||
|------|------|-------|
|
||||
| `src/api/identities.rs:177` | SQL query | `SELECT id, name, metadata FROM identities` — missing `uuid` |
|
||||
| `src/api/identities.rs:197-200` | Response mapping | `IdentityResponse { id, name, metadata }` — no `identity_uuid` |
|
||||
| `src/api/identity_api.rs:206-217` | Response mapping | `FileIdentityItem { identity_id, name, ... }` — no `identity_uuid` |
|
||||
| `src/core/db/postgres_db.rs:2405-2418` | DB query | `get_file_identities` SELECT missing `i.uuid` |
|
||||
|
||||
### Timeline
|
||||
|
||||
| When | What |
|
||||
|------|------|
|
||||
| V4.0 | `identity_uuid` existed in DB schema and detail endpoints, but list endpoints never included it |
|
||||
| 2026-05-14 | M4 reported inability to navigate from identity list to detail/files |
|
||||
| 2026-05-15 | Fixed in `37799ff` |
|
||||
|
||||
### Fix
|
||||
|
||||
1. **`GET /api/v1/identities`** (`identities.rs`): Query changed to `SELECT id, uuid, name, metadata`; response struct `IdentityResponse` added `identity_uuid: String`
|
||||
2. **`GET /api/v1/file/:uuid/identities`** (`identity_api.rs` + `postgres_db.rs`): `FileIdentityRecord` added `identity_uuid: Option<uuid::Uuid>`; SQL added `i.uuid as identity_uuid`; GROUP BY updated to include `i.uuid`
|
||||
|
||||
### Lesson
|
||||
|
||||
**All list endpoints should expose the stable UUID of the resource, not just the internal integer ID.** The external API surface is UUID-based (`:identity_uuid` in routes), so any endpoint that returns identity references must include the UUID.
|
||||
|
||||
---
|
||||
|
||||
## Issue #3 — first_appearance / last_appearance Always NULL
|
||||
|
||||
### Root Cause
|
||||
|
||||
The SQL query in `get_file_identities()` hardcoded `NULL::float8` for both fields instead of computing actual frame ranges:
|
||||
|
||||
```sql
|
||||
-- Before fix
|
||||
SELECT 0 as id, fd.file_uuid, fd.identity_id::int4, i.name, i.metadata,
|
||||
COUNT(*)::int4 as face_count,
|
||||
0::int4 as speaker_count,
|
||||
NULL::float8 as first_appearance, -- ← hardcoded NULL
|
||||
NULL::float8 as last_appearance, -- ← hardcoded NULL
|
||||
AVG(fd.confidence)::float8 as confidence
|
||||
FROM face_detections fd
|
||||
JOIN identities i ON fd.identity_id = i.id
|
||||
```
|
||||
|
||||
### Code Location
|
||||
|
||||
| File | Line | Issue |
|
||||
|------|------|-------|
|
||||
| `src/core/db/postgres_db.rs:2409-2410` | SQL query | `NULL::float8 as first_appearance, NULL::float8 as last_appearance` |
|
||||
|
||||
The data was available (`face_detections.frame_number`) but never queried. This appears to have been a placeholder left during initial development that was never implemented.
|
||||
|
||||
### Timeline
|
||||
|
||||
| When | What |
|
||||
|------|------|
|
||||
| V1.0 (initial) | Query written with `NULL::float8` — never implemented |
|
||||
| 2026-05-14 | M4 report mentioned identity data quality issues |
|
||||
| 2026-05-15 | Fixed in `fdcec82` |
|
||||
|
||||
### Fix
|
||||
|
||||
Replaced with actual aggregate queries:
|
||||
|
||||
```sql
|
||||
-- After fix
|
||||
MIN(fd.frame_number) as start_frame,
|
||||
MAX(fd.frame_number) as end_frame,
|
||||
```
|
||||
|
||||
Time values are computed in Rust from frame + fps:
|
||||
```rust
|
||||
start_time: r.start_frame.map(|sf| sf as f64 / r.fps),
|
||||
end_time: r.end_frame.map(|ef| ef as f64 / r.fps),
|
||||
```
|
||||
|
||||
Also renamed fields to match naming convention:
|
||||
- `first_appearance` → `start_frame`
|
||||
- `last_appearance` → `end_frame`
|
||||
- Added `start_time`, `end_time`, `fps`
|
||||
|
||||
### Lesson
|
||||
|
||||
**Do not commit placeholder SQL with hardcoded NULL values.** If a field is not yet implemented, either omit it from the response or mark it explicitly as `"not_implemented"` rather than returning `null` which suggests the data exists but is missing.
|
||||
|
||||
---
|
||||
|
||||
## Related Commits
|
||||
|
||||
| Commit | Fix |
|
||||
|--------|-----|
|
||||
| `fdcec82` | Issue #3: Start/end frame + time + fps |
|
||||
| `37799ff` | Issue #2: identity_uuid in list responses |
|
||||
|
||||
## Affected Files
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `src/api/identities.rs` | SQL query + struct + mapping to include `uuid` |
|
||||
| `src/api/identity_api.rs` | `FileIdentityItem` + response struct + mapping |
|
||||
| `src/core/db/postgres_db.rs` | `get_file_identities` query + `FileIdentityRecord` |
|
||||
Reference in New Issue
Block a user