docs: RCA — identity_uuid missing + file identities NULL appearance

This commit is contained in:
Accusys
2026-05-15 10:59:23 +08:00
parent 37799fff4e
commit 802beb2db6

View File

@@ -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` |