# 이미지 파이프라인 아키텍처 v2

> Updated: 2026-03-22

---

## 1. 전체 흐름

```
분석 완료 (entity_t2i + scene_detail + outlook_extraction)
    ↓
Phase 1: 기본 참조 이미지 생성
    - character: 얼굴 (1:1, 증명사진)
    - location: 배경 (16:9, 빈 장소)
    - prop: 소품 (1:1, 제품 사진)
    ↓
Phase 2: 아웃룩 단독 이미지 생성
    - 의상/장비만 (1:1, 투명 마네킹)
    - 얼굴/피부 없음
    - 같은 아웃룩은 1장만 (중복 방지: seen_outlook_ids)
    ↓
Phase 3: 합성 이미지 생성
    - 얼굴 ref (image 1) + 아웃룩 ref (image 2+) → 전신 (16:9)
    - CharacterOutlook 테이블 기반 조합
    - N개 아웃룩 참조 지원 (옷+모자+무기 등 차후 확장)
    ↓
Phase 4: 씬 이미지 생성 (scene_image_pipeline)
    - T2I 변형 N-1개 + fal.ai 앵글 1개 = N개
    - 참조 매칭 → 프롬프트 번역 → Gemini T2I → GPT LVM 검증 → fal.ai 앵글 → GPT 최종 선택
```

---

## 2. 참조 이미지 매칭 (`_resolve_refs_for_prompt`)

### 입력
- `t2i_prompt`: T2I 프롬프트 (영어, [[마커]] 포함)
- `visible_entities`: 씬의 등록 요소 (entity_lookup에서 resolve된 dict 리스트)

### 매칭 로직
```python
for [[char_name]+[outlook_name]] in t2i_prompt:
    1) composite:{char_id}:{outlook_id} 키로 합성 이미지 검색
       → 있으면: ("character in outfit", composite_bytes) 추가
       → _used_ref_ids에 composite_key + char_id + outlook_id 모두 등록
       → continue (fallback 차단)

    2) fallback (합성 없을 때):
       → ("character identity", face_bytes) + ("outfit appearance", outfit_bytes) 분리 추가

for prop in visible_entities:
    → ("object appearance", prop_bytes) 추가

if best_prev_bytes:
    → ("previous scene for visual continuity", prev_bytes) 추가
```

### 중복 방지
`_used_ref_ids` set에 3가지 키를 모두 등록:
- `composite_key` ("composite:abc:xyz")
- `char_id` ("abc")
- `outlook_id` ("xyz")

이렇게 하면 같은 캐릭터가 다른 마커로 다시 나와도 face-only fallback이 실행 안 됨.

---

## 3. 프롬프트 번역 (`_build_final_scene_prompt`)

### 라벨 → 지시 매핑

| 라벨 | 조건 | 생성되는 지시 |
|------|------|---------------|
| `"outfit appearance"` | `label_lower == "outfit appearance"` | `dress the character in the outfit shown in image N` |
| `"character in outfit"` | `"outfit" in label_lower` | `keep face from image N` + `use outfit from image N` |
| `"character identity"` | `"character" in label_lower` | `keep face, hair, identity from image N` |
| `"object appearance"` | `"object" in label_lower` | `include the object shown in image N` |
| `"previous scene..."` | `"previous scene" in label_lower` | `maintain visual continuity with image N` |

### 순서
`"outfit appearance"` 검사가 `"outfit" in label` 검사보다 **먼저** (line 84 vs 88)

---

## 4. scene_ref_image_map 구성

```python
scene_ref_image_map = {
    # 기본 참조 (character face, prop)
    char_id: face_bytes,
    prop_id: prop_bytes,

    # 아웃룩 단독
    outlook_id: outfit_bytes,

    # 합성 이미지
    "composite:{char_id}:{outlook_id}": composite_bytes,
}
```

로딩 순서:
1. 기본 참조 (is_primary=1, location 제외)
2. 아웃룩 단독 (entity_type=outlook, is_primary=1, IN 절)
3. 합성 이미지 (prompt_used LIKE '%composite:%')

---

## 5. 씬별 요소 필터링

### `_build_scene_entity_block(entities, scene_char_names, scene_text)`

| 요소 타입 | 필터 기준 |
|-----------|----------|
| 인물 | `scene_assignments`에서 결정된 캐릭터만 (`scene_char_names`) |
| 배경 | `entity.name in scene_text` (씬 텍스트에 이름 등장) |
| 소품 | `entity.name in scene_text` (씬 텍스트에 이름 등장) |

프롬프트 말미: "위 목록에 없는 인물, 배경, 물체를 T2I 프롬프트에 절대 추가하지 마세요."

---

## 6. T2I 변형 흐름

```
scene_variation_count = 2 (config)
    ↓
scene_detail (GPT): 씬당 2개 T2I 변형 생성
    var_1: camera_effect="over-the-shoulder, cold blue", t2i_prompt="..."
    var_2: camera_effect="high angle, desaturated", t2i_prompt="..."
    ↓
DB: t2i_variations_json에 저장
    ↓
UI: 편집 가능 (PATCH /stills/{id}/t2i-variation/{index})
    ↓
씬 이미지 생성: 2개 T2I → Gemini T2I → GPT LVM 검증
    ↓
GPT 앵글 추천: 2개 중 최적 선택 + 앵글 파라미터 (H, V, Z)
    ↓
fal.ai: 선택된 이미지에 앵글 적용 → 3번째 이미지
    ↓
GPT 최종 선택: 3개 중 대표(primary) 선택
```

총 이미지 수: N-1(T2I) + 1(fal.ai) = N개 (config: 3개)

---

## 7. DB 스키마 핵심

### image_asset
| 필드 | 합성 이미지 | 아웃룩 단독 | 기본 참조 |
|------|------------|-----------|----------|
| `entity_id` | character_id | outlook_id | entity_id |
| `prompt_used` | `[composite:{char}:{outlook}] ...` | `[outfit:{outlook}] ...` | description |
| `is_primary` | 0 | 1 | 1 |
| `asset_type` | reference | reference | reference |

### visible_entities_json
```json
[
  {"entity_name": "동녘", "entity_type": "character", "id": "uuid"},
  {"entity_name": "클럽하우스 폐창고", "entity_type": "location"},
  {"entity_name": "1인용 캡슐", "entity_type": "prop", "id": "uuid"}
]
```
- `"id"` 필드: `_sync_analysis_to_db`에서 entity_name → EntityCanon.id 매칭하여 추가
- 모든 코드에서 `v.get("id") or v.get("entity_id", "")` 패턴 사용

### resolved_entities (API 응답)
```json
{
  "entity_id": "char_uuid",
  "entity_name": "동녘+서바이벌군복",
  "entity_type": "composite",
  "display_name": "동녘+서바이벌군복",
  "has_reference_image": true,
  "reference_image_id": "composite_image_uuid"
}
```

---

## 8. Outlook Extraction 프롬프트 v4 핵심 규칙

### 몽타주/교차편집
- 다른 장소 인물이 번갈아 보이는 몽타주 → 각 인물은 **자기 물리적 위치 씬에만** 배정
- 예: A가 VIP룸, B가 창고 → 몽타주에서 A-B 번갈아 보여도 → A는 VIP룸 씬에만, B는 창고 씬에만

### 소울라이드 원격 접속
- A가 B의 몸에 원격 접속 → A의 몸은 접속 장소에 있음 → 현장 씬에 A 배정 금지
- "(동녘(강의원))" → 동녘 몸만 보이고 강의원 몸은 없음

### 모델
- **GPT-5.5** (Gemini Pro에서 변경)
