# Experiment v4 — Floor-plan 기반 배경 생성 (개발 상세 계획서, 보완판)

> **상태**: 설계안 — 사용자 리뷰 대기. 코드 작성 전.
> **참조 (Source of Truth)**: `backend/scripts/experiment_floor_plan_v4_background_generation_plan.md`
> 본 문서는 그 검토 결과를 받아 **§3.1 anchor cluster, §3.3 base photo 생성 흐름, §5 Phase 0~4, §6 검증, §10 사전 확인** 등을 그대로 옮기고, 데이터 모델·CLI·검증 체크리스트로 구체화했다.
> **검토 코드**:
> `experiment_floor_plan_v3.py`, `experiment_plan_to_photo.py`,
> `app/modules/pipeline/set_design.py`, `app/core/steps/set_design_step.py`,
> `app/services/scene_checkpoint_loaders.py`, `app/services/scene_generation_coordinator.py`,
> `app/services/prompt_service.py`, `app/core/steps/detail_steps.py`
>
> **절대 규칙 (CLAUDE.md, 모든 단계 적용)**:
> 1. 시나리오 의존 어휘(인명·지명·작품명) 코드/시스템 프롬프트/출력 모든 필드에 절대 금지.
> 2. LLM에 전달하는 데이터 절대 자르지 마라 (`[:N]` 금지).
> 3. 한국어 일반명사(예: "거실", "옥탑방")는 도면 라벨에서만 OK. T2I 본문엔 보통명사만.

---

## 1. 결론 요약 (참조 §1)

기존 v4 초안의 핵심 진단(v3는 base 도면을 각각 독립 `generate`로 만들고, shot만 base 위에 `edit`한다 → base 도면끼리·base 사진끼리 같은 건물/같은 공간이라는 보장이 없다)은 맞다. 다만 **새 개발 방향은 기존 계획보다 한 단계 더 분리해야 한다**.

1. **단일 anchor 하나로 모든 base를 묶는 방식은 부족하다.** `site_map`, `exterior`, `interior`는 이미지 도메인과 시점이 달라서 한 anchor에서 모두 edit하면 변환 실패 가능성이 높다.
2. **`environment_canon`은 전역으로 유지하되, 이미지는 `visual_domain`별 anchor cluster로 묶어야 한다.**
3. **사진 생성은 단순히 도면 JSON을 텍스트로 넘기는 것이 아니라**, 가능한 경우 "**대상 도면 PNG + 같은 domain의 photo anchor**"를 reference로 넘겨야 한다.
4. **운영 파이프라인 통합은 곧바로 하지 말고**, 먼저 v4 실험 출력이 `set_design` checkpoint 계약과 호환되도록 **adapter 형태**로 설계한다.
5. **최종 씬 생성에서 `set_design` 배경 참조가 현재는 "lighting, architecture, mood" 수준으로만 해석된다.** 도면 기반 배경을 쓰려면 이후 통합 단계에서 "**exact background layout**" 지시로 바꾸는 작업이 필요하다.
6. **safety 검증은 warn-only가 아니라 sanitize 재호출**까지 가야 v3 단계에서 보였던 `bloodstain`, `bloody footprints` 같은 잔존 어휘를 제거할 수 있다 (참조 §5 Phase 0).
7. **prompt/schema는 DB 의존을 줄이려면 파일 fallback이 필요**하다 (참조 §2.3, §10).

---

## 2. 현재 코드 검토 결과 (참조 §2)

### 2.1 v3 floor plan 실험 — `experiment_floor_plan_v3.py`

흐름:
- Step 1: 공간 분석 JSON 생성
- Step 2: base plan prompt 생성 → 각 plan을 **독립 이미지 generate**
- Step 3: shot overlay JSON 생성 → matching base image 위에 edit

**문제 지점**:
- base 이미지 루프가 `plans["base_plans"]`를 돌며 각 prompt를 독립 `gen_image()`로 호출 (`client.images.generate`)
- shot은 `edit_image(base_path, shot_prompt)`라서 base에 묶임
- 따라서 **base 간 visual identity는 prompt에만 의존**하며 이미지 reference anchor가 없음

**개발 판단**:
- v4에서 base 간 일관성을 해결하려면 **`Step 2b base image generation` 자체를 anchor/reference 기반으로 바꿔야 한다**.
- shot overlay만 고쳐서는 base 사진/도면 불일치가 해결되지 않는다.

### 2.2 plan_to_photo 실험 — `experiment_plan_to_photo.py`

흐름:
- v3 run directory의 `step1_spatial`, `step2_base_plans`, `step3_shot_*` JSON을 읽음
- LLM으로 `base_photos`, `shot_photos` prompt 생성
- 모든 사진은 `client.images.generate()`로 생성

**문제 지점**:
- **실제 도면 PNG를 이미지 reference로 넣지 않음**
- 사진 prompt는 도면 JSON을 텍스트로 해석한 결과 → "**도면 기반**"이라기보다 "**도면 설명 기반**"
- base photo들도 독립 generate라 도면 base 간 불일치 문제가 사진에서도 반복

**개발 판단**:
- v4 photo phase는 `generate`만 쓰면 안 된다.
- 최소한 shot photo는 `base_photo` 위에 edit해야 하고, base photo도 가능한 경우 `target floor plan PNG`를 reference로 사용해야 한다.

### 2.3 운영 set_design 파이프라인 — `app/modules/pipeline/set_design.py`

흐름:
- **Phase 0**: selected shot을 primary location 기준으로 grouping
- **Phase 1**: location별 base image 1~3장
- **Phase 2**: shot별 background image
- 위치별 병렬, 위치 내부 shot은 순차 처리

**장점**:
- 운영 checkpoint와 DB 등록 경로가 이미 존재
- `locations[loc_id].base_images[]`, `locations[loc_id].shot_backgrounds[]` 구조는 v4 도면 기반 결과를 싣기에 적합
- shot별 background image가 최종 scene image generation에 reference로 주입되는 경로가 이미 있음

**한계**:
- **Phase 1 base 이미지는 이전 base 한 장만 reference로 넣는 순차 chaining**. domain별 anchor cluster 개념 없음.
- **전역 canon, domain별 anchor, 도면 geometry, floor plan image reference 개념은 없다.**
- **현재 repo에는 `set_design` prompt/schema 파일 fallback이 보이지 않는다.** DB prompt가 있으면 동작하겠지만, 재현 가능한 실험/배포를 위해 prompt/schema 파일화가 필요 (참조 §2.3 후반 + §10 #3).

### 2.4 최종 씬 생성과 연결

**관련 호출 체인**:
- `scene_checkpoint_loaders.load_set_design_bg_map()` → `set_design` checkpoint의 `shot_backgrounds`를 읽어 `(scene_index, shot_index) → (image_bytes, label)` map 생성
- `scene_generation_coordinator` → 위 background ref를 `labeled_refs` 첫 번째 자리에 삽입
- `prompt_service.resolve_ref_roles()` → label에 `background` 또는 `location`이 있으면 "**lighting, architecture, environment mood**"만 쓰도록 지시
- `detail_steps` → `set_design`의 `t2i`와 `state_changes`를 `scene_detail` prompt에 힌트로 주입

**문제 지점 (참조 §2.4)**:
> 도면 기반 배경은 최종 씬에서 "**정확한 방 구조/가구 위치/벽/문/창문**"을 유지해야 하는데, 현재 generic `background` label은 mood/architecture 참고에 가깝다.
> **`previous shot same room` label일 때만 exact background에 가까운 지시**가 생긴다.

**개발 판단**:
- v4 결과를 운영에 붙일 때는 **loader/label/prompt_service 중 하나**에서 `exact_floor_plan_background` 의미를 명확히 전달해야 한다.
- 그렇지 않으면 v4가 정확한 배경 이미지를 만들어도 **최종 scene generation에서 구조가 다시 흔들릴 수 있다**.
- **권장 layer: `prompt_service.resolve_ref_roles()` 분기 추가** (참조 §10 option B). label string hack(option A)보다 명시적이고, 기존 `previous shot same room` 처리와 같은 책임 위치.

---

## 3. 목표 / 비목표

### 3.1 목표
1. base 도면 간 **건물 visual identity 일관성** (재질·색·창문 패턴·층수)
2. base 도면 ↔ base 사진 간 **공간 매칭** (interior 도면 가구 배치가 사진에서도 보임)
3. shot은 base anchor에 묶여 동일하게 일관성 유지
4. 모든 출력 JSON에 후속 파이프라인 통합 가능한 **구조화 메타** (`source_plan_id`, `source_floor_plan_image_path`, `visual_domain`, `anchor_cluster_id`)
5. 시나리오 의존 어휘 0건 + 안전 어휘 자동 sanitize
6. `set_design` checkpoint 계약과 호환 가능한 **adapter preview** (운영 통합 X, 변환만)

### 3.2 비목표
- 메인 파이프라인 즉시 통합 (Phase 4 별도 단계)
- 운영 DB / checkpoint 수정
- 기존 `set_design` 동작 변경
- 100% photo-realistic accuracy (시각 일관성이 우선)

---

## 4. 핵심 설계 변경 (참조 §3)

### 4.1 단일 anchor 대신 `visual_domain`별 `anchor_clusters` (참조 §3.1)

**기존 v4 계획 (폐기)**:
```json
{ "anchor_id": "one base plan id" }
```

**수정 방향**:
```json
{
  "environment_canon": {
    "building": {},
    "interior": {},
    "exterior": {},
    "site_context": {}
  },
  "anchor_clusters": [
    {
      "id": "cluster_site",
      "visual_domain": "site_map",
      "anchor_plan_id": "<plan id>",
      "purpose": "global spatial relationship"
    },
    {
      "id": "cluster_exterior",
      "visual_domain": "exterior",
      "anchor_plan_id": "<plan id>",
      "purpose": "building facade, rooftop, stairs, exterior material"
    },
    {
      "id": "cluster_interior",
      "visual_domain": "interior",
      "anchor_plan_id": "<plan id>",
      "purpose": "room layout, furniture, doors, windows"
    }
  ]
}
```

**이유**:
- **`site_map`은 부감 배치도라서 실내/외관 사진 anchor로 쓰기 어렵다.**
- `exterior`와 `interior`는 공유 canon은 같지만 reference image로는 별도 anchor가 필요하다.
- 향후 위치가 여러 개일 때도 location별·domain별로 자연스럽게 확장된다.

### 4.2 도면 기반 사진 생성의 정의 (참조 §3.2)

v4에서 "**도면 기반 배경**"은 다음 3개가 모두 충족되어야 한다.

1. `spatial JSON`의 room/door/window/furniture/meta가 prompt에 들어간다.
2. **`floor plan PNG`가 photo generation/edit의 reference로 들어간다.**
3. **같은 domain의 `photo anchor`가 visual identity reference로 들어간다.**

**우선순위**:
1. **구조 정확도**: 대상 도면 PNG와 `elements_meta`를 우선한다.
2. **visual identity**: `environment_canon`과 같은 domain `photo anchor`로 유지한다.
3. **shot 대응**: shot overlay의 camera/additions를 base photo 위에 edit한다.

### 4.3 base photo 생성 방식 (참조 §3.3)

**권장 흐름**:
```text
For each visual_domain:
  1. domain anchor base plan PNG를 만든다.
  2. domain anchor photo를 만든다.
       input: anchor plan PNG + canon + photo prompt
  3. 같은 domain의 non-anchor photo를 만든다.
       input: target plan PNG + domain anchor photo
  4. shot photo를 만든다.
       input: matching base photo + optional shot overlay plan PNG
```

**Fallback** (참조 §3.3):
- 이미지 API가 multi-reference를 안정적으로 지원하지 않으면 `target plan PNG` 또는 `domain anchor photo` 중 하나만 사용.
- 이 경우 **우선순위**:
  - **base photo 생성**: `target plan PNG`
  - **shot photo 생성**: `matching base photo`

### 4.4 도면 phase ↔ 사진 phase 분리, metadata 공유 (참조 §3.4)

**도면 phase 산출**:
- `step1_spatial.json`
- `step2_plan_specs.json`
- `base_plan_<plan_id>.png`
- `shot_plan_<S##_Shot#>.png`

**사진 phase 산출**:
- `step7_photo_specs.json`
- `photo_base_<plan_id>.png`
- `photo_shot_<S##_Shot#>.png`

**공유 metadata** (사진 phase가 도면 phase 결과를 직접 참조):
- `environment_canon`
- `anchor_clusters`
- `elements_meta`
- `source_plan_id`
- `visual_domain`
- `source_floor_plan_image_path`
- `source_base_photo_path`

### 4.5 모든 t2i_prompt에 Canon 강제 삽입
Step 2 / Step 7 시스템 프롬프트는 모든 base T2I 프롬프트가 `environment_canon` 한 단락 응축본을 시작부에 포함하도록 강제. 검증 grep으로 canon key 단어 존재 확인.

### 4.6 Shot은 항상 matching base에 묶임
Step 3 `shot_specs[].base_plan_id`가 `base_plans[].id` 중 하나여야 함. visual_domain은 base에서 추론.

---

## 5. 새 흐름 다이어그램

```
[Inputs] project, episode, scene-indices, location-ids, target-shots, scenario-banned-words
     │
     ▼
┌────────────── Phase A — 분석 (LLM only) ──────────────┐
│ Step 1  spatial + environment_canon (LLM × 1)         │
│ Step 2  plan_specs (LLM × 1) — anchor_clusters 결정    │
│ Step 3  shot_specs (LLM × N) — base_plan_id 매핑       │
└───────────────────────────────────────────────────────┘
     │
     ▼
┌────────────── Phase B — 도면 이미지 ──────────────────┐
│ Step 4  domain anchor base plan generate × D          │
│ Step 5  non-anchor base plan edit(domain anchor)       │
│ Step 6  shot plan edit(matching base plan)             │
└───────────────────────────────────────────────────────┘
     │
     ▼
┌────────────── Phase C — 사진 ─────────────────────────┐
│ Step 7  photo_specs (LLM × 1) — canon 재참조           │
│ Step 8  domain anchor photo (input: anchor plan PNG)  │
│ Step 9  non-anchor photo (input: target plan PNG +    │
│            domain anchor photo)                        │
│ Step 10 shot photo (input: matching base photo +      │
│            optional shot plan overlay PNG)             │
└───────────────────────────────────────────────────────┘
     │
     ▼
┌────────────── Phase D — set_design adapter ──────────┐
│ Step 11 set_design_adapter_preview.json 생성         │
│         (운영 DB/checkpoint 수정 X, JSON 변환만)      │
└──────────────────────────────────────────────────────┘
     │
     ▼
[Output] manifest + step1~step11 JSON + 14 PNG + gallery.html + adapter preview
```

D = visual_domain 수 (보통 2~3)
N = target shot 수
B = base plan 수

---

## 6. 데이터 모델 (참조 §4)

### 6.1 step1_spatial.json
```jsonc
{
  "space_groups": [
    { "id": "snake_case", "label": "한영", "covers_location_ids": ["L##"],
      "scale_estimate": "approx Nm x Mm",
      "type": "interior|exterior|site_map",
      "visual_domain": "interior|exterior|site_map",
      "rooms": [
        { "name":"한영", "walls":["..."], "doors":[{"connects_to":"","note":""}],
          "windows":[{"position":"","note":""}],
          "furniture":[{"name":"","position":"","note":""}],
          "fixtures":[{"name":"","position":""}] }
      ]
    }
  ],
  "shared_elements": [
    { "id":"...","appears_in_scenes":[12],"in_space_group_id":"...",
      "in_room":"...","description":"영문","note":"한국어 보조" }
  ],
  "environment_canon": {
    "building": { "stories":"...","primary_material":"...","color_palette":[],
                  "exterior_stairs":"...","rooftop_features":[],
                  "window_pattern":"...","weathering":"..." },
    "interior": { "wall_finish":"...","floor_finish":"...","ceiling":"...",
                  "lighting_fixtures":[],"general_clutter_level":"..." },
    "exterior": { /* optional */ },
    "site_context": { /* optional */ },
    "shared_furniture_styles": [ {"category":"...","style_note":"..."} ]
  }
}
```

### 6.2 step2_plan_specs.json
```jsonc
{
  "anchor_clusters": [
    { "id":"cluster_site","visual_domain":"site_map",
      "anchor_plan_id":"<plan id>","purpose":"..." },
    { "id":"cluster_exterior","visual_domain":"exterior",
      "anchor_plan_id":"<plan id>","purpose":"..." },
    { "id":"cluster_interior","visual_domain":"interior",
      "anchor_plan_id":"<plan id>","purpose":"..." }
  ],
  "base_plans": [
    { "id":"snake_case","label":"한영","visual_domain":"interior",
      "anchor_cluster_id":"cluster_interior",
      "is_anchor": true,
      "canon_refs": ["building.primary_material","interior.floor_finish"],
      "t2i_prompt":"영문 (canon spec 포함)",
      "legend":[{"symbol":"","meaning":""}],
      "elements_meta":[{"id":"","kind":"wall|door|window|furniture|fixture",
                        "label":"","position_hint":"","note":""}],
      "annotations":[{"text":"","placement":""}] }
  ],
  "shot_assignment": [
    { "scene_index":12,"shot_index":4,"base_plan_id":"<id>",
      "visual_domain":"interior" }
  ]
}
```

### 6.3 step3_shot_*.json (per shot)
v3 schema에 `visual_domain` 추가. `base_plan_id` 필수 검증. `characters[].name`은 메타 보존(인명 한국어), `t2i_prompt` 본문엔 일반 명사만.

### 6.4 step7_photo_specs.json
```jsonc
{
  "anchor_clusters": [
    { "id":"cluster_interior_photo","visual_domain":"interior",
      "anchor_plan_id":"<plan id>",
      "source_floor_plan_image_path":"base_plan_<plan_id>.png",
      "reference_strategy":"plan_png_only" }
  ],
  "base_photos": [
    { "id":"photo_base_<plan_id>","source_plan_id":"<plan id>",
      "visual_domain":"interior",
      "source_floor_plan_image_path":"base_plan_<plan_id>.png",
      "source_anchor_photo_id":"photo_base_<anchor plan id>",
      "reference_strategy":"plan_png+domain_anchor_photo|plan_png_only|domain_anchor_photo_only",
      "label":"한영 (사진엔 그래픽 라벨 그리지 않음)",
      "lighting":"...","camera_note":"...","t2i_prompt":"영문 (canon 동일 포함)" }
  ],
  "shot_photos": [
    { "id":"photo_shot_S##_Shot#",
      "source_shot":{"scene_index":int,"shot_index":int},
      "source_plan_id":"<base plan id>",
      "matching_base_photo_id":"photo_base_<plan id>",
      "shot_overlay_plan_image_path":"shot_plan_<S##_Shot#>.png",
      "reference_strategy":"base_photo+shot_overlay|base_photo_only",
      "lighting":"...","camera_note":"...","t2i_prompt":"영문" }
  ]
}
```

### 6.5 set_design_adapter_preview.json (참조 §4.2)
```jsonc
{
  "locations": {
    "L##": {
      "location_name": "일반명사 위치명",
      "floor_plan": {
        "environment_canon": { /* §6.1 */ },
        "anchor_clusters": [ /* §6.2 */ ],
        "space_groups": [ /* §6.1 */ ]
      },
      "base_images": [
        { "base_id":"base_<plan_id>",
          "angle_description":"floor-plan guided <visual_domain> base",
          "image_path":".../photo_base_<plan_id>.png",
          "floor_plan_image_path":".../base_plan_<plan_id>.png",
          "source_plan_id":"<plan_id>",
          "visual_domain":"interior|exterior|site_map",
          "anchor_cluster_id":"cluster_<domain>",
          "is_anchor": true,
          "t2i":"...",
          "covers_shots":["S12_Shot4","S12_Shot6"] }
      ],
      "shot_backgrounds": [
        { "scene_index":12,"shot_index":4,"shot_label":"S12_Shot4",
          "ref_type":"exact_floor_plan_background",
          "ref_id":"base_<plan_id>",
          "source_plan_id":"<plan_id>",
          "visual_domain":"interior",
          "image_path":".../photo_shot_S##_Shot#.png",
          "floor_plan_image_path":".../shot_plan_S##_Shot#.png",
          "t2i":"...","state_changes":"..." }
      ]
    }
  }
}
```

**호환성 원칙 (참조 §4.2)**:
- 기존 loader는 모르는 필드를 무시 → `floor_plan`, `visual_domain`, `source_plan_id`를 추가해도 구조적으로는 안전.
- 단, **최종 scene generation이 exact layout으로 쓰게 하려면** 추후 `prompt_service` 또는 label 생성 로직 변경이 필요 (Phase 4, D5).

### 6.6 manifest.json (참조 §4.1)
```jsonc
{
  "run_id":"run_YYYYMMDD_HHMMSS",
  "mode":"standalone_experiment",
  "args":{ /* CLI 전체 */ },
  "models":{"text":"gpt-5.5","image":"gpt-image-2"},
  "stats":{"space_groups":0,"base_plans":0,"shot_plans":0,
           "base_photos":0,"shot_photos":0},
  "phase_durations":{"phaseA":0,"phaseB":0,"phaseC":0,"phaseD":0},
  "validation":{
    "scenario_word_hits":[],
    "unsafe_word_hits":[],
    "missing_files":[],
    "schema_errors":[],
    "reference_integrity_errors":[],
    "canon_coverage":{"missing_in_prompts":[]}
  },
  "set_design_adapter":{
    "enabled":true,
    "checkpoint_preview_path":"set_design_adapter_preview.json"
  }
}
```

---

## 7. 시스템 프롬프트 (시나리오 무관, 모든 단계)

### 7.1 SYSTEM_STEP1 — spatial + canon
- 도면 분석 + canon 추출 양쪽 책임
- canon 작성 규칙: 시나리오 어휘 금지, 일반 명사만
  - ❌ 어떤 도시·지역·국가명 + 건물 유형 결합도 금지
  - ✅ "low-rise multi-family residential building"
- `space_groups[].visual_domain` 필수: `interior|exterior|site_map` 중 하나
- 데이터 의존 강조 (추측 금지)
- 모든 텍스트 필드(rooms, walls, furniture, note 등)에 인명·지명 등 고유명사 금지

### 7.2 SYSTEM_STEP2 — plan_specs + anchor_clusters
- `anchor_clusters` 결정: 각 visual_domain마다 anchor base plan 1개씩 (정보량 최대 base, 보통 site_map 부감 / facade elevation / largest interior plan)
- 모든 `base_plans[].t2i_prompt`에 canon spec 응축본 첫 단락 강제
- 라벨 한영 OK, t2i 본문은 보통명사만
- 안전 어휘: 도면 단계라도 사망/혈흔/외상/도구 직접 단어 금지 (참조 §5 Phase 0 — v3가 잔존시킨 `bloodstain`, `bloody footprints` 같은 어휘 차단)

### 7.3 SYSTEM_STEP3 — shot_specs (v3 유지 + 강화)
- 카메라 표기 규칙 (triangular wedge + FOV cone)
- 인물 표기 규칙 (filled circle / outline marker)
- 안전 어휘: 사망/혈흔/외상/도구/행위 직접 단어 금지
- t2i_prompt 본문 인명·지명 절대 금지 (`characters[].name`은 메타 보존)

### 7.4 SYSTEM_STEP7 — photo_specs
- 사진 어휘로 canon spec 재진술
- **모든 `base_photos`/`shot_photos`에 `source_floor_plan_image_path` + `source_anchor_photo_id` 또는 `matching_base_photo_id` 명시 필수**
- `reference_strategy` 필드로 어떤 reference 조합을 쓸지 명시
- 인물 절대 묘사 금지 (no people, empty room)
- 위험 조합 회피 (low angle + bedroom + stain + doll/backpack)
- 사진엔 그래픽 라벨/도면 선이 남지 않도록 명시 ("clean photo, no overlaid text or diagram lines")

---

## 8. 안전 어휘 / Moderation 회피

### 8.1 도면 단계
- 사망: "motionless seated figure marker", "still figure outline" (avoid: dead, corpse, deceased, victim, body)
- 혈흔: "dark fluid stippled region", "reddish marker stain" (avoid: blood, bloodstain, bloody, gore)
- 상처: "condition note", "state indicator" (avoid: wound, trauma, torn flesh, injury)
- 행위: "altercation marker", "approach trail" (avoid: kill, stab, strangle, choke, attack, assault, beat, slash, bite)
- 도구: 형태(triangular/elongated)만 (avoid: knife, blade, weapon, gun)

### 8.2 사진 단계
도면보다 더 엄격:
- 사망 묘사 직접 금지 (사진은 인물 0)
- 혈흔: "dark dried floor stain"만 (광택·번짐 표현 금지)
- 위험 조합 명시 회피:
  - "low angle through doorway" + "bedroom" + "floor stain"
  - "doll" + "backpack" + "dim bedroom" + "stain/footprint"
  - "open door" + "dark bedroom interior" + "reddish stain"
- 도면 라벨 텍스트가 사진으로 번지지 않게 "no overlaid text, no architectural label letters"

### 8.3 사전 검증 — sanitize 재호출 권장 (D6, 참조 §5 Phase 0)
**참조 §5 Phase 0 인용**:
> 실제 v3 출력의 Step 2 예시는 `bloodstain`, `bloody footprints` 같은 단어가 도면 prompt와 label에 남아 있었다. v4는 도면 단계라도 안전 어휘를 후처리 검증해야 한다.

warn-only로는 부족. **sanitize 재호출**:
1. step2/3/7 LLM 응답을 받자마자 `unsafe_word_grep()` 실행
2. 매치 시 같은 system prompt로 재호출 + 사용자 메시지에 "다음 단어 사용 금지: [...]" 명시
3. 재호출 후에도 매치 시 manifest.errors에 기록 + 해당 단계 fail 또는 강제 generic 대체

추가 비용: LLM 호출 1~2회 (~30초~1분).

---

## 9. 코드 구조

### 9.1 파일
- `backend/scripts/experiment_floor_plan_v4.py` (신규, standalone)
- v3, plan_to_photo는 비교 baseline으로 보존

### 9.2 모듈 분리 (단일 파일 함수 분리)
```python
# 데이터 수집
def collect_context(...) -> Dict
def msg_step1(ctx) -> str
def msg_step2(ctx, spatial) -> str
def msg_step3(ctx, spatial, plans, shot) -> str
def msg_step7(ctx, spatial, plans, shots, plan_image_paths, base_photo_paths) -> str

# LLM
def call_llm_json(client, model, system, user) -> Dict
def call_llm_json_with_sanitize(client, model, system, user, banned_words) -> Dict

# 이미지
def gen_image(client, model, prompt, ...) -> Path
def edit_image(client, model, ref_paths, prompt, ...) -> Path  # multi-ref
def _decode_or_raise(resp, op) -> bytes

# Phase 단계
def phase_a(...) -> {spatial, plans, shots}
def phase_b(...) -> {plan_image_paths, anchor_plan_paths_by_domain}
def phase_c(...) -> {photo_specs, photo_paths, anchor_photo_paths_by_domain}
def phase_d(...) -> Path  # adapter preview JSON

# 검증
def verify_no_scenario_words(text, banned_words, label) -> List[str]
def verify_unsafe_words(text, label) -> List[str]
def verify_schema(obj, required_keys, label) -> List[str]
def verify_reference_integrity(spatial, plans, shots, photos) -> List[str]
def verify_canon_coverage(canon, prompts) -> List[str]
def auto_extract_banned_words(ctx) -> List[str]  # entity_merge에서 인명 + locations에서 지명 자동 추출

# Adapter
def build_set_design_adapter_preview(spatial, plans, shots, photos, paths) -> Dict

# Gallery
def write_gallery_html(out_dir, ...) -> Path  # 4축 비교 (plan↔photo, domain grid, v3 baseline link)

# 메인
def main() -> int
```

### 9.3 CLI 인자
```
--project-id <uuid>            (필수)
--episode-id <uuid>            (필수)
--scene-indices N [N ...]      (필수)
--location-ids L## [L## ...]   (필수)
--target-shots S:S [S:S ...]   (필수, 형식 "scene:shot")
--text-model gpt-5.5
--image-model gpt-image-2
--image-size-plan 1024x1024
--image-size-photo 1536x1024
--image-quality high
--out-name <run_id>            (선택)
--scenario-banned-words "w1,w2,..." (선택; 미지정 시 entity_merge + locations에서 자동 추출)
--reference-strategy auto|plan_png_only|plan_png+domain_anchor_photo  (기본 auto)
--anchor-strategy llm|first|biggest  (기본 llm — Step 2가 LLM 추론으로 anchor 선택)
--moderation-precheck warn|sanitize|skip  (기본 sanitize, D6 권장)
--skip-photo                   (Phase C 건너뛰기)
--skip-shots                   (Step 6, 10 건너뛰기)
--skip-adapter                 (Phase D 건너뛰기)
--max-image-retries 3          (이미지 API 500/timeout 재시도 한계)
```

### 9.4 출력 디렉토리
```
backend/scripts/output/floor_plan_v4/<run_id>/
  context.json
  step1_spatial.json
  step2_plan_specs.json
  step3_shot_<S##_Shot#>.json    (× N)
  step7_photo_specs.json
  base_plan_<plan_id>.png         (× B)
  shot_plan_<S##_Shot#>.png       (× N)
  photos/
    photo_base_<plan_id>.png      (× B)
    photo_shot_<S##_Shot#>.png    (× N)
  set_design_adapter_preview.json
  gallery.html                     (자동 생성, §11 비교 4축)
  manifest.json
```

---

## 10. 검증 체크리스트 (참조 §6)

### 10.1 자동 검증 (manifest.validation 기록, 참조 §6.1)

**필수**:
- [ ] 모든 JSON schema 검증
- [ ] 모든 image path 존재 검증
- [ ] **`base_plan_id`, `source_plan_id`, `visual_domain`, `anchor_cluster_id` 참조 무결성 검증**
- [ ] **scenario banned words grep** (자동 + 수동 banned list)
- [ ] **unsafe image words grep** (도면 + 사진 prompt 모두)
- [ ] **`photo_*` prompt에 `no people`, `no overlaid text` 존재 검증**
- [ ] **`shot_backgrounds[]`가 target shot 수와 일치 검증**

**권장**:
- [ ] **canon keyword coverage 검증** (`environment_canon` 핵심 단어가 모든 base prompt에 등장하는지)
- [ ] **domain별 anchor가 최소 1개인지 검증**
- [ ] **`interior` shot이 `exterior` base에 잘못 매핑되지 않았는지 검증**
- [ ] adapter preview의 `base_images[]` × `shot_backgrounds[]` 무결성

### 10.2 수동 시각 비교 (gallery.html, 참조 §6.2)

`gallery.html`은 다음 비교 축을 보여야 한다:
1. **base plan ↔ base photo 쌍**
2. **shot plan ↔ shot photo 쌍**
3. **같은 visual_domain 내 base photo grid**
4. **v3 baseline과 v4 결과 비교**
5. **adapter preview의 `shot_backgrounds` 목록**

**체크리스트**:
- [ ] 같은 건물 재질·색·창문 패턴이 유지되는가
- [ ] 실내의 문/창문/가구 배치가 plan과 photo에서 대응되는가
- [ ] shot photo가 base photo 구조를 깨지 않는가
- [ ] 사진에 도면 라벨 텍스트가 남지 않는가
- [ ] 위험 단어/위험 조합으로 moderation block이 발생하지 않는가

---

## 11. Gallery 자동 생성 (4축 비교)

`gallery.html` 섹션:
1. **Header** — run_id, models, validation badges (scenario 0, unsafe 0, missing 0)
2. **Anchor Clusters** — domain별 anchor base plan + anchor base photo
3. **Base Plan ↔ Photo 비교** — 모든 base, 좌(plan) ↔ 우(photo)
4. **Shot Plan ↔ Photo 비교** — 모든 shot, 좌(plan) ↔ 우(photo)
5. **Visual Domain Grid** — interior 모음 / exterior 모음 / site_map 모음 (사진만)
6. **Adapter Preview Sample** — `set_design_adapter_preview.json`의 `locations[].shot_backgrounds[]` 일부 표시
7. **JSON Metadata 링크** — 모든 step JSON + manifest

---

## 12. 운영 통합 path (Phase 4, 별도 단계, 참조 §5 Phase 4)

### 12.1 목표 / 제약 (참조 §5 Phase 4)
- **목표**: 실험 결과가 충분히 안정적일 때만 통합
- **제약**: 기존 `set_design`이 꺼져 있을 때 동작 변화가 없어야 함

### 12.2 필요 변경 (참조 §5 Phase 4)

**1) Feature flag 분리**:
- 기존 `SET_DESIGN_ENABLED`
- 신규 후보: `FLOOR_PLAN_SET_DESIGN_ENABLED`

**2) `prompt_service.resolve_ref_roles()`에 `exact_floor_plan_background` label 처리 추가** (D5 핵심)
- **현재 동작**: label에 `background`/`location`이 있으면 "lighting, architecture, environment mood"만 쓰도록 지시
- **추가 분기**: label이 `exact_floor_plan_background`인 경우 → "**mirror the reference image structure: exact wall layout, door positions, window locations, furniture placement**" 지시
- **동일 파일 내 기존 처리와의 연결점**: `previous shot same room` label일 때 이미 exact background에 가까운 지시가 있음. 새 분기를 그 옆에 둔다.

**3) `scene_generation_coordinator`의 background ref label 변경**
- v4 adapter가 만든 background ref의 label을 `exact_floor_plan_background`로 박아야 prompt_service 분기를 탐
- 또는 ref_type 필드를 prompt_service까지 전달하도록 typed ref 확장

**4) `set_design_step` DB 등록 시 metadata 확장**
- `generation_model`, `variant_type` 기록 방식 확장
- `source_plan_id`, `visual_domain`, `floor_plan_image_path` 메타 기록 (가급적 lineage_fields/extra_metadata JSON 필드 활용, 새 컬럼 회피)

**5) prompt/schema 파일 fallback 추가** (D7, 참조 §2.3 후반)
- `backend/app/prompts/_base/set_design/<version>/system.md`
- `backend/app/prompts/_base/set_design/<version>/schema.json`
- DB 부재 시 file fallback으로 동작

### 12.3 성공 기준 (참조 §5 Phase 4)
- 최종 scene image generation에서 배경 구조가 "**분위기 참고**"가 아니라 "**정확한 구조 참고**"로 사용된다.
- 기존 set_design이 꺼져 있을 때 동작 변화가 없다.

---

## 13. 비용 / 시간 예상 (옥탑방 케이스: B=4, N=3, D=3)

| Phase | 항목 | 횟수 | 단위 시간 | 누적 |
|-------|------|------|-----------|------|
| A | LLM Step 1 (spatial + canon) | 1 | ~60s | ~1분 |
| A | LLM Step 2 (plan_specs + clusters) | 1 | ~60s | ~1분 |
| A | LLM Step 3 (shot_specs) | N=3 | ~30s | ~1.5분 |
| A | sanitize 재호출 (예상) | ~1 | ~30s | ~0.5분 |
| B | image generate (anchor plan × D) | D=3 | ~3분 | ~9분 |
| B | image edit (non-anchor base plan) | B-D=1 | ~3분 | ~3분 |
| B | image edit (shot plan) | N=3 | ~3분 | ~9분 |
| C | LLM Step 7 (photo_specs) | 1 | ~60s | ~1분 |
| C | image generate (anchor photo × D) | D=3 | ~3분 | ~9분 |
| C | image edit (non-anchor photo) | B-D=1 | ~3분 | ~3분 |
| C | image edit (shot photo) | N=3 | ~3분 | ~9분 |
| D | adapter preview JSON 생성 | 1 | ~1s | <1분 |
| **Total** | | ~20+ | | **~47분** |

---

## 14. 리스크 + 대응 (참조 §7)

| 리스크 | 영향 | 대응 |
| --- | --- | --- |
| **단일 anchor로 site/interior/exterior 모두 변환 시 시점 변환 실패** | 다른 공간처럼 보임 | **`visual_domain`별 anchor cluster** (참조 §3.1) |
| **도면 PNG가 사진에 라벨/선으로 남음** | 실사 배경 품질 저하 | photo prompt에 "no overlaid text, no diagram lines"; 필요 시 도면 crop/label-free variant 별도 생성 (참조 §10 #2) |
| **도면 JSON만으로 사진 생성 → geometry 손실** | 구조 어긋남 | **`target plan PNG`를 reference로 사용** (D2) |
| **set_design ref가 mood reference로만 해석** | 최종 scene에서 구조 흔들림 | **`exact_floor_plan_background` label + `prompt_service` 분기** (Phase 4, D5) |
| **안전 어휘가 도면 단계에 잔존** | image generation block | **prompt 후처리 grep + sanitize 재호출** (D6) |
| **prompt/schema가 DB에만 있음** | 재현성 저하 | **file fallback 추가** (D7, Phase 4) |
| **위치별 병렬 처리와 anchor 공유 충돌** | 다른 location 간 reference 오염 | **location/domain 단위로 anchor scope 제한** |
| 이미지 API multi-reference 불안정 | 사진 일관성 저하 | fallback: target plan PNG 또는 domain anchor photo 중 하나만; reference_strategy CLI/JSON 명시 |
| OpenAI 500 retry 무한 hang | 실행 abort | 이미지 API 재시도 횟수 제한 (`--max-image-retries 3`) + manifest.errors 기록 후 다음 항목 진행 |
| 한 번에 14장 생성 중 일부 실패 | 결과 불완전 | partial-skip (continue + missing_files 기록), 재실행 시 해당 항목만 fill (partial-resume은 v4.1) |

---

## 15. Pre-implementation 사전 확인 (참조 §10)

코드 작성 전에 확인:
1. **이미지 API multi-reference edit 안정성** — `gpt-image-2`의 `images.edit`이 `image=[plan_png, anchor_photo]` 두 reference 입력 시 잘 동작하는지 작은 샘플 테스트
2. **도면 PNG → 사진 generation 시 라벨 번짐** — 도면 한영 라벨이 사진에 텍스트로 새겨지는지 작은 샘플 테스트. 번지면 도면 label-free variant 또는 crop 필요
3. **set_design prompt/schema가 DB only인지 확인** — file fallback 추가 범위 결정
4. **`exact_floor_plan_background` 처리 layer** — 어디서 처리?
   - **option A**: loader label에 exact instruction 삽입 (string hack — 불투명, 비추)
   - **option B (참조 §10 권장)**: `prompt_service.resolve_ref_roles()`에 ref_type/label 분기 추가 (명시적, 기존 `previous shot same room` 처리와 같은 책임 위치)
   - **option C**: `labeled_refs`를 string label이 아니라 typed reference 객체로 확장 (장기적, 큰 변경)

> 참조 §10 권장:
> > 권장 순서는 B다. label string hack보다 명시적이고, 기존 `previous shot same room` 처리와 같은 책임 위치에 놓인다.

---

## 16. DECISION 요약 (참조 §8)

| ID | 항목 | 옵션 | 권장 (참조 §8) |
|----|------|------|----------------|
| **D1** | anchor 방식 | 단일 anchor / **visual_domain별 cluster** / Responses API conversational | **cluster** |
| **D2** | base photo reference | 텍스트만 / **target plan PNG + domain anchor photo** / target plan PNG only | **multi-ref, fallback plan PNG** |
| **D3** | shot photo reference | base photo only / **base photo + shot overlay plan PNG** | **multi-ref, fallback base photo** |
| **D4** | 운영 통합 | 즉시 통합 / **adapter preview만 먼저** | **adapter 먼저** |
| **D5** | scene ref 의미 | label string hack / **`prompt_service` 분기** / typed ref 객체 | **prompt_service 분기 (Phase 4)** |
| **D6** | safety 검증 | skip / warn / **sanitize 재호출** | **sanitize** |
| **D7** | prompt/schema | DB only / **file fallback 추가** | **file fallback (Phase 4)** |
| **D8** | 시나리오 banned list | 자동 추출 / 수동 / **자동 + 수동 override** | **혼합** |
| **D9** | gallery v3 비교 | **별도 폴더 + 수동** / gallery 통합 (cross-run iframe) | **별도 + 수동** |
| **D10** | partial-resume | 추가 / **v4 단순, v4.1로 미룸** | **단순** |

---

## 17. 작업 순서 + Phase별 성공 기준 (참조 §5, §9)

### Phase 0 — 계약과 검증 먼저 고정 (참조 §5 Phase 0)
**목표**: 코드 구현 전에 JSON schema와 출력 디렉토리 계약을 확정한다.

**작업**:
1. `step1_spatial` schema에 `environment_canon`과 `visual_domain`을 추가한다.
2. `step2_plan_specs` schema에 `anchor_clusters`, `base_plans[].visual_domain`, `base_plans[].canon_refs`를 추가한다.
3. `step7_photo_specs` schema에 `source_floor_plan_image_path`, `source_anchor_photo_id`, `reference_strategy`를 추가한다.
4. **금지어 검증을 두 층으로 나눈다** (참조 §5 Phase 0):
   - **scenario words**: 인명, 지명, 작품명
   - **unsafe image words**: blood, corpse, victim, wound, weapon, knife 등

> **주의 (참조 §5 Phase 0)**:
> > 실제 v3 출력의 Step 2 예시는 `bloodstain`, `bloody footprints` 같은 단어가 도면 prompt와 label에 남아 있었다. v4는 도면 단계라도 안전 어휘를 후처리 검증해야 한다.

**완료 조건**: 본 계획서 lock + DECISION 1~10 확정 + Pre-implementation 4건 확인 결과 공유.

### Phase 1 — standalone v4 실험 스크립트 (참조 §5 Phase 1)
**목표**: 메인 파이프라인에 붙이지 않고 `backend/scripts/output/floor_plan_v4/<run_id>/` 아래에서 끝나는 실험.

**작업 방향**:
1. v3의 `collect_context`, `msg_step1`, `msg_step2`, `msg_step3` 흐름을 기반으로 한다.
2. `[:N]` truncation은 넣지 않는다.
3. **base 도면 이미지는 domain별 anchor를 먼저 만들고, 같은 domain의 non-anchor를 reference edit로 만든다.**
4. shot 도면은 matching base plan image 위에 edit한다.
5. 모든 중간 JSON과 prompt를 저장한다.

**성공 기준 (참조 §5 Phase 1)**:
- [ ] base 도면끼리 같은 건물/공간이라는 identity가 유지된다.
- [ ] **`site_map`, `exterior`, `interior`가 서로 다른 건물처럼 보이지 않는다.**
- [ ] shot 도면은 matching base의 가구/벽/문 위치를 유지한다.

### Phase 2 — 도면 기반 photo generation (참조 §5 Phase 2)
**목표**: 사진을 단순 generate하지 않고 도면 이미지와 photo anchor에 묶는다.

**작업 방향**:
1. domain anchor photo 생성:
   - reference: domain anchor floor plan PNG
   - prompt: canon + no people + clean photo + no overlaid text
2. non-anchor base photo 생성:
   - **preferred reference**: target floor plan PNG + domain anchor photo
   - **fallback reference**: target floor plan PNG
3. shot photo 생성:
   - **preferred reference**: matching base photo + shot plan overlay PNG
   - **fallback reference**: matching base photo
4. 사진 prompt에서는 도면 라벨 텍스트가 사진으로 복사되지 않도록 명시한다.

**성공 기준 (참조 §5 Phase 2)**:
- [ ] base photo가 **base plan의 문/창문/주요 가구 배치를 반영**한다.
- [ ] 같은 domain의 base photo들이 **동일 건물/동일 실내 마감**으로 보인다.
- [ ] shot photo가 shot camera intent를 반영하면서도 **base photo 구조를 유지**한다.

### Phase 3 — set_design adapter preview (참조 §5 Phase 3)
**목표**: v4 결과를 운영 `set_design` checkpoint 구조로 변환하는 preview JSON. **이 단계까지도 운영 DB와 기존 checkpoint는 수정하지 않는다.**

**작업 방향**:
1. `photo_base_*`를 `base_images[]`로 매핑한다.
2. `photo_shot_*`를 `shot_backgrounds[]`로 매핑한다.
3. **`ref_type`은 `exact_floor_plan_background`로 기록**한다.
4. 기존 `load_set_design_bg_map()`이 읽을 수 있는 필수 필드는 유지한다.
5. 추가 metadata는 무시 가능하도록 optional로 둔다.

**성공 기준 (참조 §5 Phase 3)**:
- [ ] adapter JSON만으로 `scene_checkpoint_loaders.load_set_design_bg_map()` 형식의 map을 만들 수 있다.
- [ ] 기존 테스트 fixture와 충돌하지 않는다.

### Phase 4 — 운영 통합 (참조 §5 Phase 4)
**목표**: 실험 결과가 충분히 안정적일 때만 `set_design` 또는 별도 step으로 통합.

**필요 변경** (§12.2 참조):
1. feature flag 분리 — `FLOOR_PLAN_SET_DESIGN_ENABLED`
2. `prompt_service.resolve_ref_roles()`에 `exact_floor_plan_background` label 처리 추가
3. `scene_generation_coordinator`의 background ref label에 exact layout 지시가 전달되도록 변경
4. `set_design_step` DB 등록 시 `generation_model`, `variant_type`, metadata 기록 방식 확장
5. prompt/schema 파일 fallback을 repo에 추가

**성공 기준 (참조 §5 Phase 4)**:
- [ ] 최종 scene image generation에서 배경 구조가 "분위기 참고"가 아니라 "**정확한 구조 참고**"로 사용된다.
- [ ] 기존 set_design이 꺼져 있을 때 **동작 변화가 없다**.

### Phase 5 — 듀얼 코드 리뷰 + 회귀 검증
- Codex + Claude 동시 리뷰 (CLAUDE.md `feedback_dual_code_review.md`)
- 옥탑방 케이스 final run + gallery 비교
- 다른 시나리오/location 그룹으로 일반화 테스트

### 구현 순서 요약 (참조 §9)
1. 본 문서 lock (Phase 0 완료)
2. standalone `experiment_floor_plan_v4.py` 작성
3. 기존 v3 run과 동일 입력으로 v4 실행하여 base plan/photo 일관성 확인
4. `set_design_adapter_preview.json`만 생성, 운영 checkpoint는 건드리지 않음
5. gallery에서 v3/v4 비교 후 통합 여부 결정
6. 통합 승인 후 `prompt_service`, loader label, step manifest/feature flag, tests 수정

---

## 18. 본 문서 / 코드 / 출력 위치

- **본 문서**: `backend/scripts/experiment_floor_plan_v4_plan.md`
- **참조 문서 (Source of Truth)**: `backend/scripts/experiment_floor_plan_v4_background_generation_plan.md`
- **실험 코드 (예정)**: `backend/scripts/experiment_floor_plan_v4.py`
- **실험 출력**: `backend/scripts/output/floor_plan_v4/<run_id>/`

---

## 19. 사용자 리뷰 후 다음 단계

1. **DECISION 1~10 답변** + 추가 요구사항 알려주기
2. **Phase 0 사전 확인 (§15) 4건 결과 공유**
3. Phase 1 코드 작성 → 듀얼 리뷰 → 옥탑방 케이스 실행
4. 결과 검증 후 Phase 2/3 진행 결정

---

## 20. 부록 A — 참조 문서와의 매핑

| 참조 문서 § | 본 문서 § | 비고 |
|-------------|-----------|------|
| §1 결론 요약 (5가지) | §1 (7가지로 확장) | D6 sanitize, D7 file fallback 추가 |
| §2.1 v3 검토 | §2.1 | 동일 |
| §2.2 plan_to_photo 검토 | §2.2 | 동일 |
| §2.3 set_design 운영 | §2.3 | "위치별 병렬, 위치 내부 shot 순차" + "prompt/schema fallback 부재" 인용 |
| §2.4 최종 씬 연결 | §2.4 + §12.2 | 호출 체인 명시 + `previous shot same room` 인용 |
| §3.1 anchor cluster | §4.1 | 그대로 |
| §3.2 도면 기반 사진 정의 (3가지) | §4.2 | 그대로 + 우선순위 |
| §3.3 base photo 생성 흐름 (4단계 + fallback) | §4.3 | 그대로 |
| §3.4 metadata 공유 | §4.4 | 7개 키 그대로 |
| §4.1 manifest | §6.6 | 그대로 |
| §4.2 set_design adapter | §6.5 | locations 단위, ref_type=`exact_floor_plan_background` |
| §5 Phase 0~4 | §17 | Phase별 success criteria 그대로 |
| §6.1 자동 검증 (필수 + 권장) | §10.1 | 그대로 |
| §6.2 수동 검증 (4축) | §10.2 + §11 | gallery 4축 명시 |
| §7 리스크 7개 | §14 | 그대로 + 추가 3개 (multi-ref 불안정, 500 retry, partial-skip) |
| §8 결정 권장안 D1~D7 | §16 | D1~D10으로 확장 (D8 banned list, D9 gallery 비교, D10 partial-resume) |
| §9 구현 순서 6단계 | §17 마지막 | 그대로 |
| §10 코드 작성 전 4개 확인 | §15 | option B 권장 인용 |

### 부록 B — 본 문서가 참조와 다르게 보강한 부분
1. **§6.1~§6.6 데이터 모델 jsonc 예시** — 참조는 §3.1, §4에 일부만, 본 문서는 6개 schema 모두 jsonc로 통일 명시
2. **§9.2 코드 모듈 분리** — 참조는 추상적, 본 문서는 함수 단위 분리
3. **§9.3 CLI 인자** — 참조는 일부, 본 문서는 모든 인자 명시
4. **§13 비용/시간 표** — 참조는 부재, 본 문서는 step별 시간 추정
5. **§16 DECISION** — 참조 7개를 10개로 확장 (gallery 비교, partial-resume 등)
6. **§19 다음 단계** — 사용자 리뷰 흐름 명시

---

## 21. 사용자 리뷰 시 확인 포인트

다음 질문에 대한 답을 주시면 Phase 1 코드 작성 들어갑니다:

1. **DECISION 1~10**: 각 항목 권장안 그대로 진행할지, 다른 옵션 선호하는지
2. **Pre-implementation 4건 (§15)**: 직접 확인할지, 코드 안에서 동시 검증할지
3. **추가 요구사항**: 본 문서에 없는 것 (location 여러 개 동시 처리 등)
4. **출력 위치**: `backend/scripts/output/floor_plan_v4/`로 진행 OK인지
