# Phase 1: 텍스트 전처리

> 시나리오 PDF에서 텍스트를 추출하고, 씬 단위로 분할하여 하위 Phase의 입력을 준비한다.
> **현재 기준**: v0.6.0 (2026-04-21). 비전문가용 요약은 `docs/architecture-easy/01-text-reading.md` 참조.

## Active vs Legacy 구분표

| 구분 | Step ID | 실행 여부 |
|------|---------|-----------|
| **Active** | `text_cleanup`, `scene_segmentation`, `scene_save`, `episode_summary`, `visual_world_rules`, `entity_character_list`, `scene_summary` | 항상 실행 |
| **Optional** | `planning_doc_analysis` | 기획서 PDF가 있을 때만 (`if_planning_doc`) |

## 단계 목록

| 순서 | Step ID | 이름 | 모델 | 병렬 | 의존 |
|------|---------|------|------|------|------|
| 0 | `planning_doc_analysis` | 기획서 분석 | Gemini Flash Lite | - | 없음 (선택적) |
| 1 | `text_cleanup` | 텍스트 정리 | Gemini Lite | - | 없음 |
| 2 | `scene_segmentation` | 씬 세그먼테이션 | Gemini Flash | - | text_cleanup |
| 3 | `episode_summary` | 에피소드 요약 | GPT Mini | - | 없음 |
| 4 | `visual_world_rules` | 시각적 세계관 규칙 | GPT | - | episode_summary |
| 6 | `scene_save` | 씬 저장 | 없음 (코드) | - | scene_segmentation |
| 6.5 | `entity_character_list` | 인물 리스트 사전 추출 | Gemini Pro | - | scene_save, visual_world_rules |
| 7 | `scene_summary` | 씬별 요약 | GPT Mini | 병렬 | scene_save, episode_summary, visual_world_rules |

> **Phase 1 목적**: PDF 원문에서 시나리오 텍스트를 추출·정리 → 씬 단위로 세그먼트 → 각 씬에 실제 텍스트(`seg["text"]`)를 주입하여 이후 모든 단계가 원문 전체를 전달받을 수 있도록 준비. 또한 세계관(`visual_world_rules`)과 인물 목록(`entity_character_list`)을 먼저 확정하여 Phase 2/3에 enum 제약을 걸 수 있게 한다.

## 흐름도

```mermaid
flowchart TD
    PDF[시나리오 PDF] --> TC[text_cleanup]
    TC --> |정리된 텍스트| SS[scene_segmentation]
    SS --> |segments| SSV[scene_save]

    TC --> |전문| ES[episode_summary]
    ES --> |요약| VWR[visual_world_rules]

    SSV --> |segments + text| ECL[entity_character_list]
    VWR --> |director_notes| ECL

    SSV --> |segments| SCM[scene_summary]
    ES --> |요약| SCM
    VWR --> |규칙| SCM

    style TC fill:#e3f2fd
    style SS fill:#e3f2fd
    style SSV fill:#e3f2fd
    style ES fill:#e3f2fd
    style VWR fill:#e3f2fd
    style ECL fill:#e3f2fd
    style SCM fill:#e3f2fd
```

---

## 1. text_cleanup

**목적**: PDF에서 추출한 원본 텍스트의 노이즈(헤더/푸터, 깨진 문자, 불필요 공백)를 제거.

- **입력**: Episode의 `source_path` (PDF 파일 경로) 또는 `fulltext`
- **출력**: `cleaned_text` (정리된 전문)
- **모델**: Gemini Flash Lite
- **핵심 로직**: PDF 파일이 존재하면 `extract_text_from_pdf_llm()`으로 Gemini가 직접 PDF를 읽고 정리. PDF 없으면 기존 fulltext를 그대로 사용.
- **코드**: `backend/app/core/steps/text_steps.py:12` (`TextCleanupStep._execute`)

> 절대 규칙: 텍스트를 자르지 않는다 (`[:400]`, `[:500]` 등 금지). 원문 전체를 전달.

---

## 2. scene_segmentation

**목적**: 정리된 텍스트를 씬 단위로 분할. LLM이 regex 패턴을 생성하고, 코드가 실행.

- **입력**: cleaned_text (text_cleanup 결과)
- **출력**: `segments` (scene_index, heading, start_char, end_char, length)
- **모델**: Gemini Flash
- **핵심 로직**:
  1. LLM에게 씬 헤딩 regex 패턴 생성 요청 (Python `re.compile` 호환)
  2. 코드에서 regex 실행하여 매칭
  3. 평균 씬 길이 검증 (너무 짧으면 retry)
  4. 최대 3회 재시도
- **제약**: `re.MULTILINE` 금지, 가변 길이 lookbehind 금지
- **코드**: `backend/app/core/steps/scene_steps.py:60` (`SceneSegmentationStep._execute`)

```mermaid
sequenceDiagram
    participant Code
    participant LLM as Gemini Flash

    Code->>LLM: 시나리오 전문 + "regex 패턴 생성"
    LLM-->>Code: {pattern, estimated_count}
    Code->>Code: re.compile(pattern)
    Code->>Code: pattern.finditer(fulltext)

    alt 매칭 부족 또는 평균 길이 너무 짧음
        Code->>LLM: retry (에러 메시지 포함)
    end

    Code->>Code: segments 구조 변환
```

---

## 3. episode_summary

**목적**: 시나리오 전문을 500자 내외로 요약. 하위 단계의 컨텍스트 보강에 사용.

- **입력**: cleaned_text
- **출력**: `summary` (에피소드 요약)
- **모델**: GPT Mini
- **핵심 로직**: `summarize_episode()` 호출. 결과는 Episode 테이블의 `summary` 컬럼에도 저장.
- **코드**: `backend/app/core/steps/summary_steps.py:40` (`EpisodeSummaryStep._execute`)

---

## 4. visual_world_rules

**목적**: 시나리오의 시각적 세계관 규칙 추출 (시대, 지역, 의상, 물리 법칙 등).

- **입력**: cleaned_text + episode_summary + 기획서 컨텍스트 (있을 경우)
- **출력**:
  - `rules`: 시각적 규칙 목록 (rule_type별 분류)
  - `era`: 시대 배경
  - `region`: 지역/국가 배경
  - `director_notes`: 물리적 존재 판단 기준 (빙의, 영혼 등)
  - `t2i_context`: T2I 시각 컨텍스트 요약
- **모델**: GPT
- **핵심 로직**:
  - 기획서가 있으면 `planning_doc_context`에서 세계관/톤/비주얼 컨셉 주입
  - `director_notes`는 beat_extract, shot_extract, scene_director에 전달되어 물리적 존재 판단 기준으로 사용
- **코드**: `backend/app/core/steps/summary_steps.py:71` (`VisualWorldRulesStep._execute`)

> `director_notes`는 소울라이드/빙의 등 판타지 세계관에서 "카메라에 보이는 인물"을 판별하는 핵심 규칙.

---

## 5. scene_save

**목적**: scene_segmentation 결과에 실제 텍스트를 주입하여 DB + 체크포인트에 저장. LLM 호출 없음.

- **입력**: scene_segmentation의 segments (offset 기반) + cleaned_text
- **출력**: segments에 `text` 필드 추가 (fulltext 슬라이싱)
- **모델**: 없음 (순수 코드)
- **핵심 로직**: 각 segment의 `start_char:end_char`로 fulltext를 슬라이스하여 `text` 필드에 저장. 이후 downstream 단계는 `seg["text"]`만 사용.
- **코드**: `backend/app/core/steps/scene_steps.py:271` (`SceneSaveStep._execute`)

> v4에서 scene_split(큰 씬 분할)은 `on_demand` 레거시로 비활성화. beat→shot 계층이 이를 대체.

---

## 6. entity_character_list

**목적**: beat/shot 추출 전에 인물 리스트를 1회 호출로 확정. 이후 단계에서 인물명 제한에 사용.

- **입력**: scene_save segments + visual_world_rules의 director_notes
- **출력**: `characters` (이름, 출현 횟수 등)
- **모델**: Gemini Pro
- **핵심 로직**:
  - 모든 씬 텍스트를 조합하여 1회 LLM 호출
  - director_notes가 있으면 물리적 존재 판단 기준 주입
  - 결과는 출현 빈도순 정렬
  - beat_extract, shot_extract에서 이 목록의 이름을 enum으로 강제 (인물명 일관성 보장)
- **코드**: `backend/app/core/steps/character_list_step.py:14` (`EntityCharacterListStep._execute`)

---

## 7. scene_summary

**목적**: 씬별 짧은 요약 생성. scene_detail 등에서 컨텍스트로 사용.

- **입력**: scene_save segments + episode_summary + visual_world_rules
- **출력**: `summaries` (scene_index별 요약)
- **모델**: GPT Mini
- **핵심 로직**: ThreadPool 병렬로 씬별 요약 생성. `summarize_scenes()` 호출.
- **코드**: `backend/app/core/steps/summary_steps.py:130` (`SceneSummaryStep._execute`)

---

## 참고: 레거시

이 Phase에는 레거시/비활성 step이 없다. 모든 7개 단계가 Active 경로에서 항상 실행된다.
