# Short ID 체계 도입 설계서 (v2 — 리뷰 반영)

> 모든 엔티티에 `short_id` (C01, L01, P01, O01)를 부여하여 LLM 통신에서 UUID/이름 매칭을 완전히 제거한다.

## 1. Short ID 규칙

| 타입 | 접두어 | 예시 | 용도 |
|------|--------|------|------|
| Character | C | C01, C02 | 인물 |
| Location | L | L01, L02 | 배경 |
| Prop | P | P01, P02 | 소품 |
| Outlook | O | O01, O02 | 아웃룩 |

**씬은 제외** — `still_index`(정수)가 이미 존재하고 115+곳에서 사용 중. `S01` 추가는 투자 대비 효과 없음.

**복합 표현**: `C01O02` = 인물 C01이 아웃룩 O02 착용 (기존 `[[김의원]+[VIP정장1]]` 대체)

**발급 시점**: 엔티티 DB 저장 시 자동 발급

**범위**: 프로젝트 단위 유니크 (에피소드 간 공유)

**자릿수**: 3자리 (`C001`~`C999`) — 대규모 프로젝트 대비

## 2. DB 변경

```sql
ALTER TABLE entity_canon ADD COLUMN IF NOT EXISTS short_id TEXT;
CREATE UNIQUE INDEX IF NOT EXISTS uq_entity_canon_short_id ON entity_canon(project_id, short_id);
```

- `entity_canon.short_id`: `C01`, `L03`, `O05` 등
- UUID(PK)는 그대로 유지 — 내부 조인, 파일 참조, API 엔드포인트용

## 3. 사용처별 ID 형태

| 레이어 | 사용하는 ID | 예시 |
|--------|------------|------|
| LLM 프롬프트 (입력) | short_id | `C01O02 walks toward L03` |
| LLM 응답 (출력) | short_id + enum 강제 | `present_entity_ids: ["C01", "L03", "P05"]` |
| 체크포인트 파일 | short_id | `{"present_entity_ids": ["C01", "L03"]}` |
| DB 내부 조인 | UUID | `entity_canon.id = entity_episode_link.canon_id` |
| 파일 시스템 | UUID | `projects/{uuid}/assets/...` |
| API 응답 | UUID + short_id + name 모두 | `{"id": "uuid", "short_id": "C01", "name": "김의원"}` |
| UI 표시 | `[[이름]+[아웃룩]]` | `[[김의원]+[VIP정장1]]` |
| LVM 이미지 검증 | **이름 유지** | LVM은 이미지 내 인물 판별이라 이름 필요 |

## 4. 변경 대상 파일 (전수 조사 반영)

### 4.1 DB/모델
- `backend/app/models/project.py` — EntityCanon에 short_id 컬럼
- `backend/app/core/database.py` — 마이그레이션 SQL (IF NOT EXISTS)

### 4.2 엔티티 생성 (short_id 발급)
- `backend/app/api/v1/steps.py` `_sync_checkpoints_to_db()` — short_id 발급
- `backend/app/services/analysis_service.py` `_save_entities_v2()` — 레거시 경로

### 4.3 분석 단계 (LLM 프롬프트)
- `backend/app/core/steps/analysis_steps.py`
  - `SceneDirectorStep` — `E01` → `C01/L01/P01` 타입별 접두어 + DB 조회
  - `EntityStyleStep`, `EntityReviewStep`, `EntityDetailBatchStep`, `EntityT2iStep`
  - `OutlookExtractionStep` — character_name + outlook_name → short_id
  - `SceneDetailStep` — entities에 short_id 전달
  - `SceneVerifyStep` — entity_name → short_id
  - `SceneCinematographyStep`

### 4.4 파이프라인 모듈
- `backend/app/modules/pipeline/scene_extractor_v2.py`
  - `_build_scene_entity_block()` — `[[name]]` → `C01O02` 형태
  - `extract_scenes_multiturn()` — visible_entities 스키마 + 후처리
  - 8곳의 `[[name]+[outlook]]` 정규식 전부 교체
- `backend/app/modules/pipeline/outlook_extractor.py` — character_name → C01, outlook_name → O01
- `backend/app/modules/pipeline/scene_validator.py` — entity_name → short_id
- `backend/app/modules/pipeline/scene_dependency_extractor.py`
- `backend/app/modules/pipeline/outlook_dedup.py` — 마커 dedup 정규식
- `backend/app/modules/pipeline/outlook_merger.py` — 아웃룩 병합 이름 → short_id

### 4.5 이미지 관련 (누락 보완)
- `backend/app/core/steps/image_steps.py` — `_sync_analysis_to_db` entity_name 매칭
- `backend/app/services/image_service.py` — `_resolve_refs_for_prompt()` 정규식
- `backend/app/modules/t2i_visual_converter.py` — entity_name → short_id
- `backend/app/modules/pipeline/ref_image_pipeline.py` — entity_name 함수 인자
- `backend/app/modules/pipeline/scene_image_pipeline.py` — 마커
- `backend/app/modules/scene_image_generator.py` — prop entity_name
- `backend/app/modules/reference_image_generator.py` — entity_name 프롬프트
- `backend/app/modules/entity_dependency.py` — visible_entities 파싱

### 4.6 LVM 검증 프롬프트 (이름 유지)
- `prompts/_base/lvm_prompts/*/ref_validation.md` — `{entity_name}` 이름 유지
- `prompts/_base/lvm_prompts/*/ref_comparison.md` — 이름 유지
- `prompts/_base/image_validation/*/scene_validation.md` — 이름 유지
- `prompts/_base/image_validation/*/reference_validation.md` — 이름 유지
- 이들은 short_id로 바꾸지 않음 — LVM이 이미지에서 사람 판별 시 이름 필요

### 4.7 분석 출력 JSON 스키마 (프롬프트)
- `prompts/_base/scene_extractor_v2/*/scene_detail_schema.json` — entity_name → short_id + enum
- `prompts/_base/outlook_extractor/*/extract_schema.json` — character_name → short_id + enum
- `prompts/_base/scene_director/*/analyze_schema.json` — 이미 short_id (접두어 변경)
- `prompts/_base/entity_extractor_v2/*/turn_entity_detail.md` — `{entity_name}` (발급 전이므로 이름 유지)

### 4.8 API/UI
- `backend/app/api/v1/entities.py` — 응답에 short_id 포함
- `backend/app/api/v1/episodes.py` — 씬 응답
- `backend/app/schemas/entity.py` — Pydantic 모델에 short_id
- `backend/app/services/project_export_service.py` — export 포맷 호환
- `frontend/src/components/shared/SceneVariationCard.tsx` — `C01O02` → `[[이름]+[아웃룩]]`
- `frontend/src/pages/EpisodeDetail.tsx` — ResolvedEntity
- `frontend/src/pages/Entities.tsx` — 관계 표시
- `frontend/src/components/shared/ImageGalleryModal.tsx` — 마커 파싱

### 4.9 정규식 교체 대상 (8곳)
| 파일 | 현재 패턴 | 변경 |
|------|----------|------|
| scene_extractor_v2.py:755 | `\[\[([^\]]+)\]\+\[([^\]]+)\]\]` | `(C\d{2,3})(O\d{2,3})` |
| image_service.py:1445 | 동일 | 동일 |
| scene_validator.py:223 | `\[\[{escaped}\]\+\[...\]\]` | `C\d{2,3}O\d{2,3}` |
| outlook_dedup.py:59 | 동일 | 동일 |
| analysis_service.py:405 | 동일 | 동일 |
| entities.py:347 | 동일 | 동일 |
| SceneVariationCard.tsx:212 | `\[\[(.*?)\]\]` | `[CLPO]\d{2,3}` |
| ImageGalleryModal.tsx:120 | 동일 | 동일 |

## 5. visible_entities_json 구조 변경

**Before:**
```json
[{"entity_name": "김의원", "entity_type": "character"}]
```

**After:**
```json
[{"short_id": "C01", "entity_type": "character"}]
```

**API 응답 시 (확장):**
```json
[{"short_id": "C01", "entity_type": "character", "name": "김의원", "id": "uuid"}]
```

## 6. T2I 프롬프트 구조 변경

**LLM 출력 (Before → After):**
```
Before: [[김의원]+[VIP정장1]] walks toward [[강의원]+[차콜클럽정장1]]
After:  C01O02 walks toward C03O05
```

**번역 프롬프트에 매핑 테이블:**
```
Entity references:
C01O02 = young Korean man (30s) in burgundy formal suit
C03O05 = middle-aged Korean man in charcoal formal suit
L03 = luxury VIP lounge with recliner sofas
```

**UI 표시 (프론트엔드 변환):**
```
[[김의원]+[VIP정장1]] walks toward [[강의원]+[차콜클럽정장1]]
```

## 7. 마이그레이션 전략

### 기존 프로젝트 호환
1. 마이그레이션 시 기존 entity_canon에 short_id 자동 발급
2. 기존 visible_entities_json (entity_name 기반) → 읽기 시 name fallback 유지
3. 새 분석 실행 시 short_id 기반으로 갱신
4. 기존 이미지/체크포인트는 보존 (손대지 않음)

### 마이그레이션 SQL
```sql
-- 기존 엔티티에 short_id 발급
WITH ranked AS (
  SELECT id, entity_type, project_id,
    ROW_NUMBER() OVER (PARTITION BY project_id, entity_type ORDER BY created_at) as rn
  FROM entity_canon
  WHERE short_id IS NULL
)
UPDATE entity_canon SET short_id =
  CASE ranked.entity_type
    WHEN 'character' THEN 'C' || LPAD(ranked.rn::text, 2, '0')
    WHEN 'location' THEN 'L' || LPAD(ranked.rn::text, 2, '0')
    WHEN 'prop' THEN 'P' || LPAD(ranked.rn::text, 2, '0')
    WHEN 'outlook' THEN 'O' || LPAD(ranked.rn::text, 2, '0')
  END
FROM ranked WHERE entity_canon.id = ranked.id;
```

### 체크포인트 호환
- 기존 체크포인트: `entity_name` 키 → sync 시 name fallback으로 매칭
- 새 체크포인트: `short_id` 키 → 직접 매칭
- `_sync_checkpoints_to_db`에서 dual-path: `short_id` 우선, 없으면 `entity_name` fallback

## 8. enum 제약 적용 범위

| 단계 | 필드 | enum 소스 |
|------|------|-----------|
| scene_director | present_entity_ids, not_present.id | DB entity_canon.short_id WHERE type IN (character, location, prop) |
| scene_detail | visible_entities[].short_id | 동일 |
| outlook_extraction | scene_assignments[].character | DB short_id WHERE type=character |
| outlook_extraction | scene_assignments[].outlook | DB short_id WHERE type=outlook |
| scene_verify | entity verification | DB short_id 전체 |

## 9. 구현 순서

1. **DB 마이그레이션** — short_id 컬럼 + 기존 데이터 발급
2. **short_id 발급 로직** — `_sync_checkpoints_to_db`, `_save_entities_v2`
3. **scene_director** — `E01` → `C01/L01/P01` 타입별 접두어
4. **scene_detail + scene_extractor_v2** — 가장 큰 변경 (마커 + visible_entities + enum)
5. **outlook_extraction** — character/outlook short_id
6. **scene_verify** — short_id 기반 검증
7. **outlook_dedup + outlook_merger** — short_id 기반
8. **image_service** — `_resolve_refs_for_prompt` 정규식
9. **기타 이미지 모듈** — ref_image_pipeline, t2i_visual_converter 등
10. **API 응답** — short_id 포함 + UI 변환
11. **프론트엔드** — `C01O02` → `[[이름]+[아웃룩]]` 변환
12. **테스트** — 전체 파이프라인 재실행

## 10. LVM 프롬프트 — 이름 유지 원칙

LVM(GPT Vision)이 이미지 안에서 인물/물체를 판별할 때는 **이름이 필요**합니다.
`C01`이면 이미지에서 누구인지 판별 불가.

→ LVM 프롬프트에 보내는 `entity_name`은 이름 유지. short_id에서 DB 조회로 이름 변환 후 전달.
