# Pipeline v4 전체 플로우 — 입력/출력 상세

## 분석 Phase (33단계)

### 1. 텍스트 Phase

| # | step_id | 모델 | 라벨 | 입력 | 출력 | 의존 |
|---|---------|------|------|------|------|------|
| 1 | text_cleanup | gemini-lite | 텍스트 정리 | PDF에서 추출한 원문 (fulltext) | cleaned_text (정리된 텍스트), 정규식 규칙 | - |
| 2 | scene_segmentation | gemini-flash | 씬 세그먼테이션 | cleaned_text | segments [{scene_index, heading, start_char, end_char}] | text_cleanup |
| 3 | episode_summary | gpt-mini | 에피소드 요약 | fulltext (원문 전체) | summary (500자 요약) | - |
| 4 | visual_world_rules | gpt | 시각적 세계관 규칙 | cleaned_text + episode_summary | rules[], era, region, director_notes[] | episode_summary |
| 6 | scene_save | - | 씬 저장 | segments | segments (DB 저장용, 원본 씬 보존) | scene_segmentation |

### 2. 요약 + Beat/Shot Phase

| # | step_id | 모델 | 라벨 | 입력 | 출력 | 의존 |
|---|---------|------|------|------|------|------|
| 7 | scene_summary | gpt-mini | 씬별 요약 (병렬) | segments + fulltext + episode_summary + visual_rules | summaries [{scene_index, scene_summary}] | scene_save, episode_summary, visual_world_rules |
| 7.1 | beat_extract | gemini-pro | Beat 추출 (병렬) | 씬별 텍스트(segments로 슬라이스) + visual_rules(director_notes) | scenes [{scene_index, beats[{beat_index, description, change_type, before_state, after_state}]}] | scene_save, visual_world_rules |
| 7.2 | shot_extract | gemini-pro | Shot 추출 (순차) | beats + 씬별 텍스트(segments로 슬라이스) + visual_rules + prev_shots_ctx(이전 번들 shot 전체) | scenes [{scene_index, shots[{shot_index, description, based_on_beat}]}] | beat_extract, visual_world_rules |

**shot_extract 특이사항:**
- 3000자 번들 단위로 순차 실행 (씬 단위 묶음, 씬을 자르지 않음)
- 이전 번들의 shot 결과를 prev_shots_ctx로 전달 (제한 없음, 전체)
- description에 **복장 금지**, 변형만 괄호 귀속: `(백발에 아홉 꼬리가 솟은 A)가 돌진한다`

### 3. 요소 추출 Phase

| # | step_id | 모델 | 라벨 | 입력 | 출력 | 의존 |
|---|---------|------|------|------|------|------|
| 8 | entity_all_character | gemini-pro | 인물 리스팅 | shot_extract 결과(shots_scenes) + visual_rules | characters [{name, short_id(C##)}] | scene_save, visual_world_rules, shot_extract |
| 9 | entity_extract_character | gpt | 인물 추출 | entity_all_character 목록 + segments + fulltext | characters [{short_id, name, description, scenes[]}] | entity_all_character |
| 10 | entity_all_location | gpt | 배경 리스팅 | shot_extract 결과 + visual_rules | locations [{name, short_id(L##)}] | scene_save, visual_world_rules, shot_extract |
| 11 | entity_extract_location | gemini-pro | 배경 추출 | entity_all_location 목록 + segments + fulltext | locations [{short_id, name, description, scenes[]}] | entity_all_location |
| 12 | entity_all_prop | gpt | 소품 리스팅 | shot_extract 결과 + visual_rules | props [{name, short_id(P##)}] | scene_save, visual_world_rules, shot_extract |
| 13 | entity_extract_prop | gemini-pro | 소품 추출 | entity_all_prop 목록 + segments + fulltext | props [{short_id, name, description, scenes[]}] | entity_all_prop |

### 4. 요소 정제 Phase

| # | step_id | 모델 | 라벨 | 입력 | 출력 | 의존 |
|---|---------|------|------|------|------|------|
| 13.5 | entity_merge | gpt | 요소 중복 병합 | characters + locations + props + scene_summary + visual_rules | 병합된 {characters, locations, props} | entity_extract_* 3개, scene_summary, visual_world_rules |
| 13.6 | entity_relation | gpt | 요소 변형 관계 | 병합된 entities + beat_shot_context | relations [{base_short_id, variant_short_id, visual_similarity(bool)}] | entity_merge |
| 13.7 | entity_filter | gpt-mini | 저빈도 요소 필터링 | entities + relations + scene 등장 횟수 | 필터된 {characters, locations, props} (3씬 이하 LLM 판단) | entity_relation |
| 14 | entity_detail | gpt | 요소 상세 + enum | 필터된 entities | entities에 description, stable_traits 추가 | entity_filter |
| 15 | entity_t2i | gemini-pro | 요소 T2I 프롬프트 (병렬) | entity_detail 결과 | entities에 t2i_prompt 추가 (영어). description / visual_traits 는 source detail forward (LLM 의 unsourced trait 차단) | entity_detail |

### 5. 씬 감독 Phase

| # | step_id | 모델 | 라벨 | 입력 | 출력 | 의존 |
|---|---------|------|------|------|------|------|
| 15.5 | shot_selection | gpt-mini | 중요 샷 선택 | shot_extract 결과 (씬별) | scenes [{scene_index, selected_shot_indices[]}] (씬당 최대 3개) | shot_extract |
| 16 | scene_director | gemini-pro | 씬 감독 (물리적 존재) | segments + fulltext + entity_t2i(short_id enum) | scenes [{scene_index, present_entity_ids[]}] (씬별 VE) | scene_save, entity_t2i |
| 16.5 | shot_director | gpt | Shot별 VE 판정 | scene_director VE + shot_extract + shot_selection + entity_relation | scenes [{scene_index, shots[{shot_index, visible_entity_ids[]}]}] 변형 전환 시점 확정 | scene_director, shot_selection, entity_relation, shot_extract |
| 18.1 | shot_dependency | gpt | Shot 연관 분석 (병렬) | shot_selection + scene_director | dependencies [{scene_index, shot_index, location_refs[], character_refs[]}] | shot_selection, scene_director |

### 6. 아웃룩 Phase

| # | step_id | 모델 | 라벨 | 입력 | 출력 | 의존 |
|---|---------|------|------|------|------|------|
| 19 | outlook_phase1 | gemini-pro | 아웃룩 목록 추출 | segments + fulltext + characters + scene_char_map + visual_rules + entity_relation(vs=false) | outlooks [{short_id(O##), name, description, character_id}] + null_outlook_chars[] + non_humanoid_characters[] | scene_director |
| 19.1 | outlook_phase2 | gemini-pro | 아웃룩 씬별 매핑 | segments + fulltext + outlooks 카탈로그 + characters + scene_char_map | scene_assignments [{scene_index, assignments[{character_id, outlook_id}]}] (O00 자동 주입) | outlook_phase1 |
| 19.2 | outlook_phase3 | gemini-pro | 아웃룩 병합 정리 | outlooks + scene_assignments + scene_summaries + character_routes | cleaned_outlooks + cleaned_assignments + removed[] (O00 보호) | outlook_phase2 |
| 100 | outlook_dedup | gpt | 아웃룩 중복 판별 | outlooks (O00 제외) | merge_groups [{keep, remove[], reason}] | outlook_phase3 |

**O00 (Null Outlook) 처리:**
- entity_relation `visual_similarity=false` 변형 + LLM `non_humanoid_characters` → O00 자동 할당
- ⚠ **현재 LLM 판별 불안정** — 다음 세션에서 별도 스텝으로 분리 예정
- O00 캐릭터: outfit 이미지 스킵, composite 스킵, 전신 이미지 생성, C##O00은 캐릭터 base ref 직접 사용

### 7. 씬 상세 Phase

| # | step_id | 모델 | 라벨 | 입력 | 출력 | 의존 |
|---|---------|------|------|------|------|------|
| 20 | scene_detail | gpt | 씬 상세 분석 (병렬) | fulltext + segments + director VE + shot_director VE + shot_dependency + outlook_phase3 + entity_t2i + beats + shots + shot_types DB | scenes [{scene_index, _shot_index, representative_moment, t2i_variations[{t2i_prompt, outfit_assignments[{character_id, outlook_id}]}], visible_entities[]}] | shot_dependency, shot_director, outlook_phase3, entity_t2i |
| 21 | scene_verify | gpt | 교차 검증 (앞2씬) | scene_detail 결과 + 앞 2씬 컨텍스트 | 검증 결과 (pass/fail per shot) | scene_detail |

**scene_detail 특이사항:**
- shot별 1회 LLM 호출 (병렬 ThreadPool)
- t2i_prompt에 bare C## → 코드에서 C##O## 치환 (outfit_assignments 기반)
- VE 위반 검사 + retry + 강제 제거
- O00 캐릭터: 자동 배정, "아웃룩 없음" LLM 표시
- t2i_variations 정확히 N개 (SHOT_VARIATION_COUNT)

## 이미지 Phase (3단계)

| # | step_id | 모델 | 라벨 | 입력 | 출력 | 의존 |
|---|---------|------|------|------|------|------|
| 22 | world_guide | gpt | 월드 가이드 | fulltext + entities + stills | world_guide JSON (스타일 가이드) | entity_t2i, scene_detail |
| 23 | ref_image_gen | gemini-image | 요소 참조 이미지 | entity_t2i + dependency_graph + world_guide | 엔티티별 참조 이미지 (character=증명사진, character_nonhuman=전신, prop=1:1) + outfit 단독 이미지 + composite(얼굴+아웃룩) 이미지 | entity_t2i |
| 24 | composite_image_gen | gemini-image | 인물+아웃룩 합성 | ref_image_gen 결과 + CharacterOutlook DB | composite 이미지 (O00 제외) | ref_image_gen, outlook_phase3 |
| 25 | scene_image_pipeline | mixed | 씬 이미지 생성 | scene_detail + composite refs + world_guide | 씬별 스틸컷 이미지 (T2I + GPT LVM 검증 + 비교 선택) | composite_image_gen, scene_verify, world_guide |

**ref_image_gen 3단계:**
1. Phase 1: 엔티티별 참조 이미지 (topological sort, variant는 base 먼저)
   - character: 증명사진 (passport-style)
   - character_nonhuman (O00): 전신 (full body)
   - prop: 1:1 비율
   - variant: base 참조이미지 + "KEEP THIS FACE" 지시
2. Phase 2: 아웃룩 단독 이미지 (outfit-only, 얼굴 없이) — O00 스킵
3. Phase 3: 합성 이미지 (얼굴 + 아웃룩 → 전신) — O00 스킵

**scene_image_pipeline 서브스텝:**
1. prompt_translation (gpt-mini): T2I 한국어→영어 변환 + 참조이미지 역할 지시
2. scene_t2i_gen (gemini-image): 이미지 생성 (참조이미지 첨부)
3. scene_t2i_validation (gpt): GPT LVM 검증
4. prompt_sanitize (gpt): 프롬프트 안전화
5. angle_recommend (gpt): 앵글 추천
6. fal_angle_apply (fal-ai): fal.ai 앵글 적용 (FAL_AI_ENABLED=true 시만)
7. final_select (gpt): 최종 선택

## 씬 이미지 참조 해석 (_resolve_refs_for_prompt)

| T2I 프롬프트 패턴 | 참조 이미지 | 라벨 |
|------------------|-----------|------|
| C01O03 | composite:char_id:outlook_id → 합성 이미지 | "character C01 in outfit" |
| C01O03 (composite 없으면) | char_id → 캐릭터 얼굴 + outlook_id → 아웃룩 단독 | "character identity" + "outfit appearance" |
| **C19O00** | **char_id → 캐릭터 base ref 직접** | **"character C19 identity"** |
| P05 | prop_id → 소품 이미지 | "object P05" |

## 체크포인트 구조

```
projects/{PID}/checkpoints/episodes/{EID}/
  ├── text_cleanup/manifest.json
  ├── scene_segmentation/manifest.json
  ├── scene_save/manifest.json
  ├── episode_summary/manifest.json
  ├── visual_world_rules/manifest.json
  ├── scene_summary/manifest.json
  ├── beat_extract/manifest.json
  ├── shot_extract/manifest.json
  ├── entity_all_character/manifest.json
  ├── entity_extract_character/manifest.json
  ├── ... (각 step별)
  ├── scene_detail/manifest.json
  └── scene_verify/manifest.json
```

- force 실행 시: 기존 manifest.json → manifest_YYYYMMDD_HHMMSS.json 으로 archive
- 스냅샷: manifest_YYYYMMDD_HHMMSS_label.json

## DB 동기화 (_sync_checkpoints_to_db)

scene_detail 완료 후 체크포인트 → DB 동기화:
- scene_still: shot 기반 UPSERT (scene_index + shot_index)
- entity_canon: 요소 UPSERT (short_id 기준 ID 보존)
- character_outlook: DELETE → re-INSERT (O00 포함)
- shot_dependency → dependent_scene_id
- 아웃룩 orphan 삭제 (O00 보호)
