# Phase 7 — Background Pipeline 재설계 (Master Plan 일원화)

> **상태**: design (사용자 검토 대기)
> **작성일**: 2026-04-29
> **선행 세션 메모리**: `next_session_phase7_plan.md`
> **대체 대상**: Phase 5 (`background_planner`, `background_chain_planning`, `location_floor_plan`, `background_chain_render`)

---

## 1. 배경 — Phase 5+5.3+6 production 검증에서 드러난 문제

### 1.1 검증 결과 (2026-04-29 c00bbe19 EP1)
- **Phase 5.3 시간 단축**: chain_bg_render 14분 43초 (-79% vs sequential 70분) — 성공
- **Phase 6 t2i_guide loader**: 양쪽 shape 지원 동작 — 성공
- **Phase 5.2 needs_floor_plan boolean**: skip 결정 정확 — 성공

### 1.2 발견된 architectural 부족함 — Phase 7로 해결

#### A. 같은 building 내 sub-room 무작위 분기
같은 옥탑방(L05) 4 variant가 서로 다른 sub-room을 그림:
- `v01 day_normal` → 거실 (TV + 식탁)
- `v02 night_blood` → 안방
- `v03 night_clean` → 안방
- `v04 dusk_ransacked` → 거실 (TV 위치 다름)

원인: chain_bg_planning 단계에서 sub_location 명시 의무화 부재 → LLM 자유 선택. 같은 도면(fp_rooftop_unit) 위에 그려야 하는데 도면-배경 매핑 안 묶여 있음.

#### B. parent chain 끊김 (ID mismatch)
`v04 parent=cb_l05_interior_day_hallucination` 같은 ID가 chain_bg_planning groups에 부재 → 실제 렌더 단계에서 silently drop. 18 group 중 11개 dropped. 이건 Phase 5의 두 step(planner / chain_bg_planning) 사이 **ID consensus 보장 부재**가 근본 원인.

#### C. Plot-critical 시각 요소 underrepresented
시나리오의 "커튼이 시체를 가린다" 같은 핵심 시각 plot device가 fp / chain_bg 프롬프트에서 한두 번만 언급 → gpt-image-2가 우선순위 낮게 처리. Step 단계마다 새로 짧게 요약하느라 plot device가 희석됨.

### 1.3 사용자 요구사항 (architectural intent 재확인)
> "도면을 기반으로 모든 관련 옥탑방 내부는 도면 이미지 기반으로 LLM 어떤 순서로(앞쪽 생성된 배경이미지도 넣어야 일관성 유지) 어떤 샷들을 어떤 배경이미지를 사용할지 모두 결정하게 해야해."

핵심: **계획(plan)** 과 **실행(image gen)** 이 명확히 분리되어, 모든 ID/순서/ref 결정이 **단일 master plan** 단계에서 일관되게 결정되어야 함.

---

## 2. Architecture — 6 step (1+2 묶음)

```
┌─────────────────────────────────────────────────────────────────┐
│ Step 1 (background_classify) — 분류 + 그룹화 (LLM 1회)          │
│   in:  모든 location 리스트 + 출현 횟수 + 설명                  │
│   out: building_groups[] 각 그룹 분류                           │
│         {kind: "chain_bg" | "prev_shot_ref"}                    │
│   note: t2i 미생성, 분류만 → 단순한 prompt + schema             │
└─────────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 2 (background_master_plan) — 그룹별 master plan            │
│                            (그룹당 LLM 1회, **그룹간 병렬**)    │
│   in:  group spec + 관련 씬 세그먼트(전문) + 샷 정보            │
│   out (per group): MasterPlan {                                 │
│     floor_plans:   [{id, sub_location?, depends_on_fp[]}]       │
│     backgrounds:   [{id, sub_location, state_label,             │
│                      depends_on_fp[], depends_on_bg[],          │
│                      applies_to_shots[]}]                       │
│     gen_order:     [...]   # 도면+배경 통합 위상정렬            │
│   }                                                             │
│   note: t2i 프롬프트 미생성. 종류 / 순서 / ref / 매핑만.        │
└─────────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 3 (floor_plan_prompt) — 도면 t2i 프롬프트                  │
│                            (도면당 LLM 1회, **level 병렬**)     │
│   in:  master plan의 도면 spec + 관련 씬 세그먼트 + 적용 샷     │
│   out: t2i 프롬프트 (상세, plot-critical 시각 요소 포함)        │
└─────────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 4 (floor_plan_render) — 도면 image gen (gpt-image-2)       │
│   in:  Step 3 프롬프트 + master plan의 ref 결정                 │
│   note: dependency level 병렬 (Phase 5.3 패턴 재활용)           │
└─────────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 5 (background_prompt) — 배경 t2i 프롬프트                  │
│                            (배경당 LLM 1회, **level 병렬**)     │
│   in:  master plan의 배경 spec + 관련 씬 세그먼트 + 적용 샷     │
│   out: t2i 프롬프트 + 적용 샷 list + ref 가이드                 │
│         (예: "TV는 ref에 있으니 다시 그리지 마라")              │
└─────────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────────┐
│ Step 6 (background_render) — 배경 image gen (gpt-image-2)       │
│   in:  Step 5 프롬프트 + master plan의 ref(도면 PNG + 이전 배경)│
│   note: dependency level 병렬                                   │
└─────────────────────────────────────────────────────────────────┘
```

### 2.1 Step ID 명명 (D3 implementer 자율)

| 신규 step | 기존 step (deprecate) |
|---|---|
| `background_classify` | (없음, building 분류는 chain_bg_planning에서 partial) |
| `background_master_plan` | `background_planner` + `chain_bg_planning` 합침 |
| `floor_plan_prompt` | `location_floor_plan` (prompt phase) |
| `floor_plan_render` | `location_floor_plan` (image phase) |
| `background_prompt` | `chain_bg_render` (prompt phase) |
| `background_render` | `chain_bg_render` (image phase) |

### 2.2 Manifest order 배치

| step | order | category | applicability | parallel |
|---|---|---|---|---|
| background_classify | 19.51 | analysis | if_background_mode | LLM 1 호출 |
| background_master_plan | 19.52 | analysis | if_background_mode | 그룹간 LLM 병렬 |
| floor_plan_prompt | 19.61 | analysis | if_background_mode | level 병렬 |
| floor_plan_render | 21.55 | image | if_background_mode | level 병렬 |
| background_prompt | 21.60 | analysis | if_background_mode | level 병렬 |
| background_render | 24.72 | image | if_background_mode | level 병렬 |

> **Note (T20 갱신, 2026-04-30)**: 위 값은 충돌 회피용. active `shot_staging`@19.5 / Phase 5 `background_planner`@19.55 / deprecated `background_chain_planning`@19.6 / `location_floor_plan`@21.5 / `background_chain_render`@24.7와 충돌 방지.

**불변식**: image step(21.5+) order > analysis step(19.x) order. Phase 5에서 적용했던 "image > analysis" 룰 유지.

**applicability**: `if_background_mode` (settings.background_mode != 'off' 시 활성). Phase 5에서 사용한 `if_floor_plan_mode`를 rename.

### 2.3 Phase 5.3 병렬화 패턴 그대로 적용

`_compute_chain_bg_levels(planner_chain_order, planning_groups, renderable_set)` helper(parent_id DAG → level batch)를 generic `_compute_dag_levels(order, items, renderable_set)` 로 리팩토링하여 Step 4 (도면 render), Step 6 (배경 render) 모두 재사용.

---

## 3. Step별 schema 상세

### 3.1 Step 1 — `background_classify`

**Input**:
```python
{
  "locations": [
    {
      "loc_id": "L05",
      "label": "옥탑방 내부",
      "summary": "...",          # entity_detail에서
      "shot_count": 12,           # 얼마나 많은 샷에 등장?
      "is_indoor": true,          # entity 메타에서
      "scene_indices": [3, 7, 12, 15, ...],
    },
    ...
  ],
  "world_rules_text": "...",      # visual_world_rules
}
```

**LLM**: gpt-5.5 (분류 정확도 우선)

**Output schema**:
```json
{
  "building_groups": [
    {
      "group_id": "bg_rooftop_unit",
      "members": ["L05", "L04", "L10", "L11"],
      "anchor_loc": "L05",          // 실내 anchor (도면 그릴 대상)
      "kind": "chain_bg",            // 또는 "prev_shot_ref"
      "rationale": "옥탑방 + 외부 + 옥상 + 사다리 — 4 멤버, 12 shot anchor 실내 → chain_bg"
    },
    {
      "group_id": "bg_police_office",
      "members": ["L08"],
      "anchor_loc": "L08",
      "kind": "prev_shot_ref",        // 단일실 단순, 3 shot 미만 → prev-shot 충분
      "rationale": "단일실 + 2 shot → prev-shot 충분"
    },
    ...
  ]
}
```

**분류 기준** (system prompt에 명시):
- `chain_bg`: 3+ shot AND indoor anchor 존재
- `prev_shot_ref`: 그 외 (3 shot 미만 OR 야외만)

**시나리오 의존성 0**: 보통명사 룰만 사용 ("indoor", "outdoor", "shot count"). 작품 고유명사 절대 금지.

### 3.2 Step 2 — `background_master_plan`

**Input** (그룹당 한 호출):
```python
{
  "group": {
    "group_id": "bg_rooftop_unit",
    "members": ["L05", "L04", "L10", "L11"],
    "anchor_loc": "L05",
    # 멤버 location 별 entity_detail full text (절대 절단 금지)
    "members_detail": {...},
  },
  "scenes": [
    # 그룹 멤버에 등장하는 씬 전체 (truncation 금지)
    {"scene_index": 3, "text": "...전문...", "shots": [{...}, {...}]},
    {"scene_index": 7, "text": "...", "shots": [...]},
    ...
  ],
  "world_rules_text": "...",
}
```

**LLM**: gpt-5.5 (long context). 한 그룹은 보통 4-12 씬, 12-30 샷 → 입력 ~10-30k token.

**Output schema** (한 그룹의 master plan):
```json
{
  "group_id": "bg_rooftop_unit",
  "floor_plans": [
    {
      "fp_id": "fp_rooftop_living",
      "sub_location": "rooftop_unit_living_room",   // 멤버 내 sub-room
      "scope": "L05의 거실 부분",
      "depends_on_fp": []                            // 다른 도면 참조 ref (선택)
    },
    {
      "fp_id": "fp_rooftop_bedroom",
      "sub_location": "rooftop_unit_bedroom",
      "scope": "L05의 안방 부분",
      "depends_on_fp": ["fp_rooftop_living"]         // 거실 도면을 먼저 그리고 ref로 사용
    },
    {
      "fp_id": "fp_rooftop_outside",
      "sub_location": "rooftop_outside",
      "scope": "옥탑방 옥상/외부",
      "depends_on_fp": ["fp_rooftop_living"]
    }
  ],
  "backgrounds": [
    {
      "bg_id": "cb_l05_living_day",
      "loc_id": "L05",
      "sub_location": "rooftop_unit_living_room",   // 반드시 도면 sub_location과 일치
      "state_label": "day_normal",
      "depends_on_fp": ["fp_rooftop_living"],        // 도면 ref
      "depends_on_bg": [],                           // 이전 배경 ref
      "applies_to_shots": ["S03_Shot1", "S07_Shot2"] // 어떤 shot이 이 배경 사용
    },
    {
      "bg_id": "cb_l05_living_dusk",
      "loc_id": "L05",
      "sub_location": "rooftop_unit_living_room",
      "state_label": "dusk_ransacked",
      "depends_on_fp": ["fp_rooftop_living"],
      "depends_on_bg": ["cb_l05_living_day"],        // 일관성 위해 day → dusk
      "applies_to_shots": ["S15_Shot4"]
    },
    ...
  ],
  "gen_order": [
    "fp_rooftop_living",     // root 도면 먼저
    "fp_rooftop_bedroom",
    "fp_rooftop_outside",
    "cb_l05_living_day",     // 도면 완료 후 배경
    "cb_l05_living_dusk",
    "cb_l05_bedroom_night_blood",
    ...
  ]
}
```

**핵심 invariant** (system prompt에 강조):
1. 같은 sub_location은 도면 1장 + 배경 N장 (state별)으로 분리
2. 같은 sub_location 배경끼리 chain (`depends_on_bg`)으로 연결 — 일관성 유지
3. 도면-배경 매핑: 모든 배경은 정확히 1개 도면을 ref로 가짐 (`depends_on_fp` 1+개 — ref slot 다중 허용)
4. `applies_to_shots`은 group 멤버의 shot만 가능 (cross-group 금지)
5. plot-critical 시각 요소 (예: 커튼/시체 가림)는 해당 시점/state 배경 spec의 `state_label`/scope에 명시
6. 시나리오 고유명사 0건 (sub_location 라벨도 보통명사 — "living_room", "bedroom_corner")

### 3.3 Step 3 — `floor_plan_prompt`

**Input** (도면당 한 호출, level 병렬):
```python
{
  "fp_id": "fp_rooftop_living",
  "sub_location": "rooftop_unit_living_room",
  "scope": "L05의 거실 부분",
  "applied_to_backgrounds": ["cb_l05_living_day", "cb_l05_living_dusk"],
  # 이 도면이 ref로 쓰일 모든 배경의 state_label/applies_to_shots 통합
  "all_shots_using_this_fp": ["S03_Shot1", "S07_Shot2", "S15_Shot4"],
  "scene_segments": [...전문...],   # truncation 금지
  "world_rules_text": "...",
}
```

**LLM**: gpt-5.5

**Output schema**:
```json
{
  "fp_id": "fp_rooftop_living",
  "t2i_prompt": "Top-down floor plan...",   // 영문, plot-critical 요소 포함
  "key_elements": ["TV cabinet by east wall", "dining table center", "curtain rod by south window"]
}
```

### 3.4 Step 4 — `floor_plan_render`

**Input**: Step 3 프롬프트 + master plan의 `depends_on_fp` ref 결정.
**Image Model**: gpt-image-2.
**Parallel**: dependency level 병렬 (Phase 5.3 `_compute_dag_levels`).
**Output**: `ImageAsset` row (asset_type='floor_plan', variant_index=0, t2i_guide=프롬프트).

### 3.5 Step 5 — `background_prompt`

**Input** (배경당 한 호출, level 병렬):
```python
{
  "bg_id": "cb_l05_living_dusk",
  "loc_id": "L05",
  "sub_location": "rooftop_unit_living_room",
  "state_label": "dusk_ransacked",
  "applies_to_shots": ["S15_Shot4"],
  "depends_on_fp_path": "/path/to/fp_rooftop_living.png",
  "depends_on_bg_paths": ["/path/to/cb_l05_living_day.png"],
  "scene_segments": [...S15 전문...],
  "world_rules_text": "...",
}
```

**LLM**: gpt-5.5

**Output schema**:
```json
{
  "bg_id": "cb_l05_living_dusk",
  "t2i_prompt": "...",
  "ref_guide": "TV cabinet and dining table in the same position as the reference floor plan; do not redraw layout. Add scattered debris on the floor and tipped chair to convey ransacking. Curtains drawn closed on the south window.",
  "shot_guides": [
    {"shot_id": "S15_Shot4", "guide_text": "Curtain mostly closed; faint silhouette behind."}
  ]
}
```

### 3.6 Step 6 — `background_render`

**Input**: Step 5 프롬프트 + master plan ref(도면 PNG + 이전 배경 PNG).
**Image Model**: gpt-image-2.
**Parallel**: level 병렬.
**Output**: `ImageAsset` row (asset_type='chain_bg', variant_index=N, variant_label=state_label, t2i_guide=프롬프트, t2i_guide의 shot_guides는 scene_detail consumer가 사용).

---

## 4. 마이그레이션 — Phase 5 → Phase 7

### 4.1 deprecate
- step_manifest 4개 entry: `background_planner`, `background_chain_planning`, `location_floor_plan`, `background_chain_render`
  - lifecycle="deprecated", applicability="disabled" 로 변경 (즉시 삭제 X — old projects DB 호환).
- 코드 4 step + 4 module + 4 prompt 디렉토리:
  - 신규 step 안정화 후 별도 PR로 cleanup 단계 (Phase 7.1).

### 4.2 데이터 마이그레이션 — **Aggressive (D2)**
사용자 결정: **완전 대체**. 기존 데이터 보존 시 새 plan과 align 안 되어 오히려 confusion.

마이그레이션 alembic:
1. 새 Phase 7 step ID들을 `step_run` 테이블에 신규 row 추가용 (자동, 스키마 변경 X).
2. Phase 5 step_run row는 **그대로 유지** (lifecycle=deprecated 이므로 stale로 남음).
3. `ImageAsset` 테이블의 chain_bg / floor_plan asset row:
   - **새 PID 새 EP 권장** — 기존 row와 충돌 위험 회피.
   - 같은 PID에서 Phase 7 재실행 시: 기존 PNG 파일 그대로 두고 새 variant_index로 신규 row 생성 (디스크 row 따로 — `cleanup_orphaned_assets` job이 이후 정리).
   - Aggressive 옵션: 사용자가 명시 force 시 Phase 5 chain_bg / floor_plan row drop API 제공 (`/api/v1/projects/{pid}/episodes/{eid}/background-cleanup-phase5`).

### 4.3 env / feature flag
- 기존 `BACKGROUND_MODE` env 의미 변경:
  - `off` (default) — 모든 background step disabled
  - `on` — Phase 7 전체 활성
  - `floor_plan_anchored` (legacy 별칭) → `on`으로 매핑 + deprecation warning
- 기존 `CHAIN_BG_GUIDE_ENABLED` 유지 (scene_detail consumer는 그대로 작동).
- 기존 `BACKGROUND_CHAIN_RENDER_WORKERS` → `BACKGROUND_RENDER_WORKERS`로 rename + alias.

### 4.4 Rollback
Phase 7 도입 후 문제 발견 시:
1. `BACKGROUND_MODE=off` 로 즉시 unhook (unhook 시 모든 Phase 7 step `not_applicable`).
2. Phase 5 코드는 deprecated이지만 삭제 X → revert 필요 시 manifest 4 entry의 `applicability` 만 `if_background_mode_legacy` 로 변경 + env 추가하면 재활성 가능. (수동 rollback path)

---

## 5. 테스트 전략

### 5.1 단위 테스트
| Step | 테스트 대상 | 케이스 |
|---|---|---|
| `background_classify` | 분류 룰 | 3+ indoor → chain_bg / 단일 indoor → prev_shot_ref / outdoor only → prev_shot_ref / 시나리오 고유명사 grep 0건 |
| `background_master_plan` | invariant 검증 | sub_location 도면-배경 매칭 / cross-group `applies_to_shots` 거부 / gen_order 위상정렬 |
| `floor_plan_prompt` | LLM 호출 / schema | empty fp_id 거부 / scene_segments 절단 검사 |
| `floor_plan_render` | level 병렬 / ref 주입 | DAG level 정렬 / depends_on_fp PNG 첨부 |
| `background_prompt` | LLM 호출 / schema | shot_guides 모든 applies_to_shots 커버 |
| `background_render` | level 병렬 / multi-ref | depends_on_fp + depends_on_bg ref 모두 첨부 / `_compute_dag_levels` 재사용 |

### 5.2 통합 테스트 (mock LLM/image)
- end-to-end pipeline: Step 1 → 6 통과 / DB row 정확성 / ImageAsset variant 일관성
- migration: Phase 5 데이터 있는 상태 + Phase 7 활성 → conflict 없음 검증
- rollback: BACKGROUND_MODE=off → 모든 step not_applicable

### 5.3 production E2E
- PID c00bbe19 EP1 신규 episode 또는 신규 PID 재실행
- 검증 항목:
  - 같은 sub_location 4 variant가 모두 같은 도면 ref → 같은 sub-room 일관성 OK
  - parent chain ID consensus 100% (master plan 단일 출처이므로 자동)
  - plot-critical 요소(커튼) gpt-image-2 출력에 visible
  - 시간: chain_bg level 병렬 + 도면 level 병렬 → 30분 이내 (~Phase 5.3 + 도면 step 추가분)

### 5.4 회귀 검증
- Phase 5 deprecate 후에도 기존 1153 tests pass (Phase 5.3 시점 기준)
- scene_detail consumer (Phase 6) 양쪽 shape 지원 → 새 shape 추가하여 3-way 지원 (Phase 5 legacy / Phase 4 legacy / Phase 7)

---

## 6. 위험 및 완화

| 위험 | 영향 | 완화 |
|---|---|---|
| Step 2 master plan LLM이 invariant 위반 (cross-group shot 매핑) | 심각 — 후단 모두 영향 | 스키마 validator + 코드 검증 + retry 2회. 실패 시 그룹 단위로 prev_shot_ref fallback |
| Step 2 입력 token 폭발 (큰 그룹) | 중간 | 그룹 사이즈 가드 (멤버 8개 초과 시 split logic). 하지만 truncation 절대 금지 |
| Step 3-6 LLM 호출 N배 증가 → 비용/시간 | 중간 | level 병렬 + 그룹간 병렬로 wall-time 최소화. 비용은 호출당 input 작아져 net 증가 미미 |
| 마이그레이션에서 stale Phase 5 row가 UI 혼동 유발 | 낮음 | UI 측에서 lifecycle=deprecated step 숨김 (이미 지원) |
| ImageAsset variant_index 충돌 (Phase 5 row + Phase 7 row 같은 loc_id) | 중간 | PID 단위로 Phase 7 활성 시 cleanup endpoint 제공 (4.2 옵션) |
| `_compute_dag_levels` 일반화 시 기존 `_compute_chain_bg_levels` 회귀 | 낮음 | Phase 5.3 16 tests 그대로 재실행 + signature 호환 wrapper |

---

## 7. Implementation Phasing

### Phase 7.0 — design (이 문서)
- ✅ 작성 완료, 사용자 검토 대기

### Phase 7.1 — plan (다음 단계)
- subagent-driven implementation plan 작성

### Phase 7.2 — implementation
- 2개 분리 worktree에서 진행 권장 (frontend 영향 거의 없음)
- 6 step 신설 + 4 prompt 디렉토리 + 1 alembic migration
- 분량: 1-1.5주

### Phase 7.3 — production 검증
- PID c00bbe19 EP1 재실행
- HTML viewer 갱신
- 메모리 갱신

### Phase 7.4 — Phase 5 cleanup (별도 PR)
- 4 step + 4 module + 4 prompt 디렉토리 삭제
- step_manifest 4 entry 삭제
- alembic data migration (옵션 — orphaned ImageAsset)

---

## 8. 핵심 design 원칙 (재확인)

1. **계획(planning)과 실행(execution) 명확한 분리** — Step 1-2-3-5는 plan/prompt, Step 4-6은 image
2. **모든 ref/순서 결정은 Step 2 master plan에서** — 후단은 plan 따라가기만
3. **plot-critical 시각 요소는 Step 3/5의 detailed LLM call이 시나리오 세그먼트 받아 자연히 잡음** — 별도 step 불필요
4. **"계획 단계에서 정확한 방향성"** = system prompt에서 sub-room 일관성, 도면 ref 활용, 배경 chain 일관성 명시 강조
5. **시나리오 의존적 코딩/프롬프트 절대 금지** — 보통명사만, 작품 고유명사 0건

---

## 9. 사용자 결정 요약 (D1~D4)

- **D1**: Step 2 (master plan) 한 LLM 호출 — t2i 프롬프트 미생성이라 schema 단순
- **D2**: Phase 5 완전 대체 (deprecate). 마이그레이션 = aggressive
- **D3**: step naming은 implementer 자율 결정
- **D4**: 이미지 gen 외 모든 step LLM 호출 → 대부분 병렬 가능. Phase 5.3 level-based 패턴 그대로 적용

---

## 10. 시작점 코드 위치 (참고)

| 신규 step 참고용 | 기존 코드 (deprecate 대상) |
|---|---|
| `background_classify` | `background_planner_step.py` (분류 부분만) |
| `background_master_plan` | `background_planner_step.py` + `background_chain_planning_step.py` (합침) |
| `floor_plan_prompt` | `location_floor_plan_step.py` (prompt 부분) |
| `floor_plan_render` | `location_floor_plan_step.py` (image 부분) |
| `background_prompt` | `background_chain_render_step.py` (prompt 부분) |
| `background_render` | `background_chain_render_step.py` (image 부분, **Phase 5.3 병렬화 패턴 재활용**) |

---

## 변경 이력
- 2026-04-29: 초안 작성. D1~D4 사용자 결정 반영. 사용자 검토 대기.
