feat: identity PATCH update, alias system, name UNIQUE removal
- Add PATCH /api/v1/identity/:identity_uuid endpoint - Migration 030: remove name UNIQUE, add tmdb_id index - TMDb upsert: ON CONFLICT (name) -> ON CONFLICT (tmdb_id) - get_or_create_identity: pre-check by name - upload_identity: ON CONFLICT (name) -> ON CONFLICT (uuid) - Search: include aliases in identity text search - Add scripts/llm_metadata_enhancer.py - Add DESIGN/IdentityUpdateAndAliasSystem.md
This commit is contained in:
229
docs_v1.0/DESIGN/IdentityUpdateAndAliasSystem.md
Normal file
229
docs_v1.0/DESIGN/IdentityUpdateAndAliasSystem.md
Normal file
@@ -0,0 +1,229 @@
|
||||
---
|
||||
document_type: "design_doc"
|
||||
service: "MOMENTRY_CORE"
|
||||
title: "Identity Update & Alias System"
|
||||
version: "V1.0"
|
||||
date: "2026-05-22"
|
||||
author: "M5"
|
||||
status: "draft"
|
||||
---
|
||||
|
||||
# Identity Update & Alias System
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Scope | Identity CRUD expansion, alias system, LLM-enhanced metadata |
|
||||
| Status | Draft |
|
||||
| Key principle | `uuid` is the true identity key; `name` is display label (no longer UNIQUE) |
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Currently, identity records have no update endpoint and `name` is constrained to UNIQUE. This design adds:
|
||||
|
||||
1. `PATCH /api/v1/identity/:identity_uuid` — partial update for name, metadata, aliases, status, type
|
||||
2. Remove `name UNIQUE` constraint — allow multiple identities with the same display name
|
||||
3. TMDb upsert key changes from `name` to `tmdb_id`
|
||||
4. Alias system with BCP 47 locale tagging stored in `metadata.aliases`
|
||||
5. LLM background task for metadata structuring and alias generation
|
||||
|
||||
---
|
||||
|
||||
## Schema Changes
|
||||
|
||||
### Migration 030
|
||||
|
||||
File: `migrations/030_remove_identity_name_unique.sql`
|
||||
|
||||
```sql
|
||||
-- Phase 1: Remove name UNIQUE (keep NOT NULL)
|
||||
ALTER TABLE identities DROP CONSTRAINT IF EXISTS identities_name_key;
|
||||
|
||||
-- Phase 2: Partial unique index for TMDb-sourced identities
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_identities_tmdb_id
|
||||
ON identities(tmdb_id) WHERE tmdb_id IS NOT NULL;
|
||||
```
|
||||
|
||||
### Aliases Storage
|
||||
|
||||
Aliases are stored in `identities.metadata::jsonb` under the key `aliases`:
|
||||
|
||||
```json
|
||||
{
|
||||
"aliases": [
|
||||
{"locale": "en", "name": "John Smith"},
|
||||
{"locale": "zh-TW", "name": "約翰·史密斯"},
|
||||
{"locale": "ja", "name": "ジョン・スミス"}
|
||||
],
|
||||
"summary": "American actor and producer...",
|
||||
"nationality": "American",
|
||||
"birth_date": "1970-01-15",
|
||||
"profession": ["actor", "producer"]
|
||||
}
|
||||
```
|
||||
|
||||
No schema change needed — `metadata JSONB DEFAULT '{}'` already supports this.
|
||||
|
||||
---
|
||||
|
||||
## API Changes
|
||||
|
||||
### PATCH /api/v1/identity/:identity_uuid
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"name": "John Smith",
|
||||
"aliases": [
|
||||
{"locale": "en", "name": "John Smith"},
|
||||
{"locale": "zh-TW", "name": "約翰·史密斯"}
|
||||
],
|
||||
"metadata": {"summary": "American actor..."},
|
||||
"status": "confirmed",
|
||||
"identity_type": "people"
|
||||
}
|
||||
```
|
||||
|
||||
All fields are optional. Only provided fields are updated.
|
||||
|
||||
**Response (200):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"identity_uuid": "abc-...",
|
||||
"updated_fields": ["name", "aliases", "metadata"]
|
||||
}
|
||||
```
|
||||
|
||||
**Processing flow:**
|
||||
1. Lookup identity by UUID → 404 if not found
|
||||
2. Dynamic UPDATE SQL (COALESCE for optional fields)
|
||||
3. If name changed → update `_index.json`
|
||||
4. If name changed → update Qdrant face point payloads
|
||||
5. Call `save_identity_file_by_pool()` → sync identity.json to disk
|
||||
6. Return updated identity detail
|
||||
|
||||
---
|
||||
|
||||
## Alias System
|
||||
|
||||
### Locale Tagging (BCP 47)
|
||||
|
||||
Standard tags for alias entries:
|
||||
|
||||
| Locale | Tag | Example |
|
||||
|--------|-----|---------|
|
||||
| English | `en` | John Smith |
|
||||
| Traditional Chinese | `zh-TW` | 約翰·史密斯 |
|
||||
| Simplified Chinese | `zh-CN` | 约翰·史密斯 |
|
||||
| Japanese | `ja` | ジョン・スミス |
|
||||
| Korean | `ko` | 존 스미스 |
|
||||
| Cantonese | `yue` | 約翰·史密夫 |
|
||||
| French | `fr` | Jean Smith |
|
||||
| Spanish | `es` | Juan Smith |
|
||||
|
||||
### Frontend Display Logic
|
||||
|
||||
```javascript
|
||||
function getDisplayName(identity, preferredLocale) {
|
||||
// 1. Try exact locale match
|
||||
const match = identity.aliases?.find(a => a.locale === preferredLocale);
|
||||
if (match) return match.name;
|
||||
|
||||
// 2. Try language-only match (zh-TW → zh)
|
||||
const lang = preferredLocale.split('-')[0];
|
||||
const langMatch = identity.aliases?.find(a => a.locale.startsWith(lang));
|
||||
if (langMatch) return langMatch.name;
|
||||
|
||||
// 3. Fallback to identity.name
|
||||
return identity.name;
|
||||
}
|
||||
```
|
||||
|
||||
### Search Integration
|
||||
|
||||
Identity search should include aliases:
|
||||
|
||||
```sql
|
||||
SELECT * FROM identities
|
||||
WHERE name ILIKE $1
|
||||
OR metadata->'aliases' @> $2::jsonb
|
||||
ORDER BY name
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Upsert Key Changes
|
||||
|
||||
| Location | Current Key | New Key | Rationale |
|
||||
|----------|------------|---------|-----------|
|
||||
| `tmdb/probe.rs` | `ON CONFLICT (name)` | `ON CONFLICT (tmdb_id) WHERE tmdb_id IS NOT NULL` | TMDb ID is the true identity for TMDb sources |
|
||||
| `tmdb/ingest.rs` | `ON CONFLICT (name)` | `ON CONFLICT (tmdb_id)` | Same as above |
|
||||
| `postgres_db.rs:get_or_create_identity` | `ON CONFLICT (name) DO UPDATE` | Query by name first, then INSERT with uuid | Maintain backward compatibility for name lookup |
|
||||
| `identity_api.rs:upload_identity` | `ON CONFLICT (name)` | `ON CONFLICT (uuid)` | Upload provides uuid; uuid is the true key |
|
||||
|
||||
---
|
||||
|
||||
## LLM Metadata Enhancer
|
||||
|
||||
### Script: `scripts/llm_metadata_enhancer.py`
|
||||
|
||||
**Trigger:** Called as background task after TMDb registration or manual PATCH.
|
||||
|
||||
**Input:**
|
||||
```json
|
||||
{
|
||||
"name": "John Smith",
|
||||
"biography": "John Smith (born January 15, 1970) is an American actor...",
|
||||
"existing_metadata": {"tmdb_id": 123, "tmdb_profile": "..."}
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```json
|
||||
{
|
||||
"metadata": {
|
||||
"summary": "American actor and producer known for...",
|
||||
"nationality": "American",
|
||||
"birth_date": "1970-01-15",
|
||||
"profession": ["actor", "producer"],
|
||||
"aliases": [
|
||||
{"locale": "en", "name": "John Smith"},
|
||||
{"locale": "zh-TW", "name": "約翰·史密斯"},
|
||||
{"locale": "ja", "name": "ジョン・スミス"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### LLM Prompt Design
|
||||
|
||||
The prompt defines a fixed output schema for the LLM to follow:
|
||||
|
||||
1. Read biography text + identity name
|
||||
2. Extract structured fields: summary, nationality, birth_date, profession
|
||||
3. Generate locale-tagged aliases based on known translations
|
||||
4. Output as JSON only (no extra text)
|
||||
|
||||
---
|
||||
|
||||
## Affected Files
|
||||
|
||||
| File | Change | Complexity |
|
||||
|------|--------|------------|
|
||||
| `migrations/030_remove_identity_name_unique.sql` | New | Low |
|
||||
| `src/api/identity_api.rs` | Add PATCH route + handler | Medium |
|
||||
| `src/core/tmdb/probe.rs` | Change upsert key | Low |
|
||||
| `src/core/db/postgres_db.rs` | Change get_or_create_identity | Low |
|
||||
| `src/api/identity_api.rs` (upload) | Change upsert key | Low |
|
||||
| `scripts/llm_metadata_enhancer.py` | New script | Medium |
|
||||
| `src/api/tmdb_api.rs` or background | LLM task integration | Low |
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Author | Description |
|
||||
|---------|------|--------|-------------|
|
||||
| V1.0 | 2026-05-22 | M5 | Initial design |
|
||||
Reference in New Issue
Block a user