# Phase 2: 엔티티 추출

> 시나리오에서 인물(Character), 배경(Location), 소품(Prop)을 3단계 분리 추출하고, 병합/필터링/상세화/T2I 프롬프트 생성까지 수행한다.
> **현재 기준**: v0.6.0 (2026-04-21). 비전문가용 요약은 `docs/architecture-easy/02-finding-characters.md` 참조.

## Active vs Legacy 구분표

| 구분 | Step ID | 실행 여부 |
|------|---------|-----------|
| **Active** | `entity_all_character`, `entity_extract_character`, `entity_all_location`, `entity_extract_location`, `entity_all_prop`, `entity_extract_prop`, `entity_merge`, `entity_relation`, `entity_filter`, `entity_detail`, `entity_t2i` | 항상 실행 |
| **Dead code** | `analysis_steps_legacy.py` 내 구 엔티티 로직 | STEP_CLASSES 미등록 |

## 단계 목록

| 순서 | Step ID | 이름 | 모델 | 병렬 | 의존 |
|------|---------|------|------|------|------|
| 8 | `entity_all_character` | 인물 리스팅 | Gemini Pro | - | scene_save, visual_world_rules, **shot_validator** |
| 9 | `entity_extract_character` | 인물 추출 (상세) | GPT | - | entity_all_character |
| 10 | `entity_all_location` | 배경 리스팅 | GPT | - | scene_save, visual_world_rules, **shot_validator** |
| 11 | `entity_extract_location` | 배경 추출 (상세) | Gemini Pro | - | entity_all_location |
| 12 | `entity_all_prop` | 소품 리스팅 | GPT | - | scene_save, visual_world_rules, **shot_validator** |
| 13 | `entity_extract_prop` | 소품 추출 (상세) | Gemini Pro | - | entity_all_prop |
| 13.5 | `entity_merge` | 요소 중복 병합 | GPT | - | entity_extract_* + scene_summary + visual_world_rules |
| 13.6 | `entity_relation` | 요소 변형 관계 추출 | GPT | - | entity_merge |
| 13.7 | `entity_filter` | 저빈도 요소 필터링 | GPT Mini | - | entity_relation |
| 14 | `entity_detail` | 요소 상세 + enum | GPT | - | entity_filter |
| 15 | `entity_t2i` | 요소 T2I 프롬프트 | GPT Mini | 병렬 | entity_detail |

> v0.5.4 이후: `entity_all_*`는 `shot_extract`가 아니라 **`shot_validator`** 체크포인트를 읽는다 (한 찰나 원칙 재작성본). 업스트림 교체만으로 다운스트림은 변경 없음.

## 흐름도

```mermaid
flowchart TD
    subgraph Listing["리스팅 (이름 + 출현 씬 + short_id)"]
        AC[entity_all_character] --> EC[entity_extract_character]
        AL[entity_all_location] --> EL[entity_extract_location]
        AP[entity_all_prop] --> EP[entity_extract_prop]
    end

    EC --> EM[entity_merge]
    EL --> EM
    EP --> EM

    EM --> ER[entity_relation]
    ER --> EF[entity_filter]
    EF --> ED[entity_detail]
    ED --> ET[entity_t2i]

    style AC fill:#fff3e0
    style AL fill:#fff3e0
    style AP fill:#fff3e0
    style EC fill:#ffe0b2
    style EL fill:#ffe0b2
    style EP fill:#ffe0b2
    style EM fill:#ffcc80
    style ER fill:#ffcc80
    style EF fill:#ffcc80
    style ED fill:#ffb74d
    style ET fill:#ffa726
```

## 3단계 분리 추출의 이유

인물/배경/소품을 한 번에 추출하면:
1. LLM이 특정 타입에 편향 (인물 과다, 소품 누락)
2. 토큰 제한으로 디테일 저하
3. Short ID 충돌 가능성

따라서 **타입별 독립 추출** 후 병합하는 2-pass 구조:
- **1st pass (all)**: 이름 + 출현 씬 수 + short_id 부여 (`C01`, `L01`, `P01`)
- **2nd pass (extract)**: 상세 설명 + visual traits 추출

---

## entity_all_character / location / prop

**목적**: 시나리오 전문에서 해당 타입의 엔티티를 리스팅. Short ID 부여.

### entity_all_character
- **입력**: shot_validator 결과 (shot 기반) 또는 scene_save segments (fallback)
- **출력**: `characters` (이름, 출현 횟수, short_id)
- **모델**: Gemini Pro
- **핵심 로직**:
  - shot_validator 체크포인트가 있으면 `list_characters_from_shots()` (shot 기반 1회 호출)
  - 없으면 `list_entities_by_type()` (씬 체이닝 fallback)
  - `_assign_short_ids(characters, "C")` 로 C01, C02, ... 부여
- **코드**: `backend/app/core/steps/entity_steps.py:71` (`EntityAllCharacterStep`)

### entity_all_location / entity_all_prop
- 구조 동일, prefix만 `L` / `P`로 다름
- **코드**: `backend/app/core/steps/entity_steps.py:100`, `entity_steps.py:125`

---

## entity_extract_character / location / prop

**목적**: 리스팅된 엔티티에 상세 설명, visual traits 추가.

- **입력**: entity_all 결과의 이름 목록 + cleaned_text + visual_world_rules
- **출력**: 상세 설명이 추가된 엔티티 목록
- **모델**: GPT (인물), Gemini Pro (배경/소품)
- **핵심 로직**: `extract_entities_by_type_with_list()` - 리스트 기반으로 상세 추출. entity_all의 short_id를 주입 (`_inject_short_ids`)
- **코드**: `backend/app/core/steps/entity_steps.py:203`, `entity_steps.py:224`, `entity_steps.py:245`

---

## entity_merge

**목적**: 3개 타입의 추출 결과를 통합하고, 타입 간 중복/유사 요소를 판별하여 병합.

- **입력**: entity_extract_character + entity_extract_location + entity_extract_prop + scene_summary + visual_world_rules
- **출력**: 병합된 전체 엔티티 목록 (`characters`, `locations`, `props`)
- **모델**: GPT
- **핵심 로직**: 이름 유사성, 설명 겹침 등으로 중복 판별
- **코드**: `backend/app/core/steps/entity_steps.py:229` (order 13.5)

---

## entity_relation

**목적**: 같은 대상의 변형(변신, 빙의 등) 쌍을 식별하고, 시각적 유사성 여부를 판단.

- **입력**: entity_merge 결과 + beat/shot 컨텍스트
- **출력**:
  - `relations`: 변형 관계 목록 (base_short_id, variant_short_id, visual_similarity)
  - `candidates_checked`: 검사한 후보 수
- **모델**: GPT
- **핵심 로직**:
  1. `_build_beat_shot_context()`: beat + shot 결과를 컨텍스트 문자열로 구성 (시나리오 전문 대신)
  2. `extract_entity_relations()`: LLM이 변형 관계 판별
  3. `visual_similarity=false`인 변형 캐릭터는 outlook_phase1에서 O00(Null Outlook) 자동 할당
- **코드**: `backend/app/core/steps/entity_relation_step.py:11` (`EntityRelationStep`)

```mermaid
flowchart LR
    Merge[entity_merge 결과] --> Candidates[후보 추출]
    Candidates --> |beat/shot 컨텍스트| LLM[GPT 변형 판별]
    LLM --> Relations[관계 목록]
    Relations --> |visual_similarity=false| O00[O00 Null Outlook 할당]
    Relations --> |visual_similarity=true| Variants[시각적 변형 참조 이미지]
```

---

## entity_filter

**목적**: 저빈도(2씬 이하) 요소를 LLM에게 판단 의뢰하여 제거 여부 결정.

- **입력**: entity_merge 결과 + entity_relation (보호 대상) + segments + fulltext
- **출력**: `filtered_entities` (필터링된 엔티티 목록)
- **모델**: GPT Mini
- **핵심 로직**:
  - entity_all에서 `shot_count` 복원 (extract/merge에서 유실 가능)
  - entity_relation에서 `visual_similarity=true`인 요소는 보호 (삭제 금지)
  - `filter_low_frequency_entities()`: 2씬 이하 등장 요소를 LLM에게 "제거 vs 유지" 질의
- **코드**: `backend/app/core/steps/entity_steps.py:269` (`EntityFilterStep`)

---

## entity_detail

**목적**: 필터링된 엔티티에 최종 상세 설명 + enum 확정.

- **입력**: entity_filter 결과
- **출력**: 최종 상세 엔티티 목록 (visual_traits, enum 확정)
- **모델**: GPT
- **코드**: `backend/app/core/steps/entity_steps.py` (order 14)

---

## entity_t2i

**목적**: 각 엔티티의 시각적 T2I 프롬프트 생성. 이미지 생성의 기초.

- **입력**: entity_detail 결과
- **출력**: 각 엔티티에 `t2i_prompt` 추가
- **모델**: GPT Mini
- **병렬**: ThreadPool 병렬 처리
- **핵심 로직**: 엔티티의 visual_traits와 description을 기반으로 영문 T2I 프롬프트 생성. 고유명사 배제, 보통명사 기반.
- **코드**: `backend/app/core/steps/entity_steps.py` (order 15)

> T2I 프롬프트 규칙: 콘티형 단순함 (한 컷 = 한 문장), 인물 최대 2-3명, 복합 ID (C08O09) 금지.

---

## 참고: 레거시

이 Phase에는 on_demand/disabled 레거시 step이 없다. 단, `backend/app/core/steps/analysis_steps_legacy.py`에는 과거 v3 파이프라인의 엔티티 추출 로직이 남아 있으나 `STEP_CLASSES`에 등록되어 있지 않아 실행되지 않는다 (dead code).
