# Task: V2 콘텐츠 구조 변경 Phase B (task-288.1)

## 프로젝트 경로
`/home/jay/projects/ThreadAuto/`

## 배경
에이전트 미팅에서 V2 렌더러 vs vibe.tip 비교 평가 결과, **근본 원인은 콘텐츠 생성기의 "1슬라이드=1항목(body)" 구조**임이 밝혀졌다.
렌더러에는 이미 `render_card_list()`, `render_detail()`, `render_from_slides()`가 구현되어 있으나,
콘텐츠 생성기가 body 타입만 출력하고 파이프라인이 `render_all()`만 호출하여 미활용 상태.

Phase A(렌더러 수준 개선)는 1팀이 완료함 (task-287.1). 이 작업은 **콘텐츠 구조 + 파이프라인 연결** 변경.

## 수정 대상 파일
1. `content/prompts_v2.py` — B-1, B-2, B-3: 출력 형식 변경
2. `content/content_generator_v2.py` — B-4: validate 로직 업데이트
3. `publisher/threads_publisher.py` — B-5: render_from_slides() 전환

## 참조 파일 (읽기 전용, 수정 금지)
- `renderer/cardnews.py` — render_from_slides() 시그니처 확인용
  - `render_from_slides(slides: list[dict], theme=None) -> list[Path]`
  - cover: `{"type": "cover", "title": str, "subtitle": str, "category": str, "keywords": list[str]}`
  - card_list: `{"type": "card_list", "title": str, "subtitle": str, "items": [{"title": str, "description": str}], "cta_text": str, "page_num": int, "total_pages": int}`
  - detail: `{"type": "detail", "title": str, "subtitle": str, "items": [{"label": str, "value": str}], "tip_text": str, "page_num": int, "total_pages": int}`
  - cta: `{"type": "cta", "title": str, "subtitle": str, "items": [{"title": str, "description": str}], "cta_text": str}`
- `renderer/themes.py` — 테마 시스템 (수정 금지)

## 개선 항목 (5건)

### B-1: prompts_v2.py 출력 형식을 card_list/detail 기반으로 변경 [P0]
**파일**: `content/prompts_v2.py`

**현재 `_OUTPUT_FORMAT_BLOCK`** (줄51~76):
```
cover 1장 + body 5장 + cta 1장 = 7장 고정
body: {"type": "body", "number": N, "title": str, "description": str}
```

**변경할 출력 형식**:
```json
{
  "slides": [
    {
      "type": "cover",
      "hook": "훅 문구",
      "title": "메인 타이틀",
      "keywords": ["키워드1", "키워드2", "키워드3"]
    },
    {
      "type": "card_list",
      "title": "슬라이드 제목",
      "subtitle": "보조 설명 (선택)",
      "items": [
        {"title": "포인트 1 제목", "description": "설명 1~2문장"},
        {"title": "포인트 2 제목", "description": "설명 1~2문장"}
      ],
      "cta_text": ""
    },
    {
      "type": "detail",
      "title": "심층 분석 제목",
      "subtitle": "",
      "items": [
        {"label": "항목명", "value": "상세 설명"},
        {"label": "항목명", "value": "상세 설명"}
      ],
      "tip_text": "팁 또는 핵심 한 줄 (선택)"
    },
    {
      "type": "cta",
      "title": "핵심 정리",
      "subtitle": "메인 타이틀 반복",
      "items": [{"title": "요약1", "description": ""}, {"title": "요약2", "description": ""}],
      "cta_text": "CTA 문구"
    }
  ],
  "caption": "Threads 캡션 (500자 이내)",
  "hashtags": ["태그1", "태그2"]
}
```

**핵심 변경점**:
- body 타입 → card_list / detail 타입으로 대체
- 각 슬라이드 `items`에 **2개 항목** 기본 (보험 타겟 맞춤, DA 의견 반영)
- cover에 `keywords` 필드 추가 (Phase A에서 추가된 pill badge 지원)
- 슬라이드 구성 예시: cover + card_list 2~3장 + detail 1~2장 + cta = 총 5~7장

### B-2: 슬라이드당 items 2개 기본 [P0]
**파일**: `content/prompts_v2.py`

프롬프트에 명시:
- card_list의 items: **2개 필수** (3개까지 허용)
- detail의 items: **2~3개**
- "1슬라이드 1항목" 구조 명확히 금지
- 보험 설계사 대상 → 정보 밀도는 적당히 (개발자 대상 vibe.tip보다 약간 낮게)

### B-3: 슬라이드 5~7장 유연 범위 [P1]
**파일**: `content/prompts_v2.py`

**현재**: "반드시 7장(cover 1 + body 5 + cta 1)" 고정
**변경**: "총 5~7장 (cover 1장 + 콘텐츠 3~5장 + cta 1장). 주제에 따라 자연스럽게 조절."
- 짧은 주제: 5장 (cover + card_list 2장 + detail 1장 + cta)
- 긴 주제: 7장 (cover + card_list 3장 + detail 2장 + cta)

**카테고리별 `_SLIDE_GUIDES` 업데이트**:
- 기존: body 1~5 고정 가이드 → card_list/detail 혼합 가이드로 변경
- 예시 (고민공감):
  - cover: 공감 훅
  - card_list 1: 고민 상황 공감 + 본질 짚기 (items 2개)
  - card_list 2: 해결 방향 + TOP사업단 장점 (items 2개)
  - detail 1: 변화 후 모습 심층 (items 2~3개)
  - cta: 부담 없는 상담 초대
- 다른 카테고리도 동일 패턴으로 업데이트

### B-4: content_generator_v2 validate 로직 업데이트 [P1]
**파일**: `content/content_generator_v2.py`

**현재 `_validate_structure()`** (줄191~256):
- slides 정확히 7장 강제
- cover 1, body 5, cta 1 수량 검증
- body의 number/title/description 필수

**변경**:
- slides 5~7장 허용 (len 체크: `5 <= len(slides) <= 7`)
- 허용 타입: cover, card_list, detail, cta (body는 하위 호환으로 허용하되 비권장)
- cover 1장, cta 1장 필수 (위치: 첫 번째=cover, 마지막=cta)
- 중간 슬라이드: card_list 또는 detail (최소 2장)
- card_list 검증: items가 list이고 len >= 1, 각 item에 title/description
- detail 검증: items가 list이고 len >= 1, 각 item에 label/value
- cover에 keywords 필드 검증 (list[str], 비어도 OK)
- 기존 body 타입도 통과시킴 (하위 호환) — title/description 필수

### B-5: 파이프라인에서 render_from_slides() 전환 [P0]
**파일**: `publisher/threads_publisher.py`

**현재** (줄135~158):
```python
renderer.render_all(title=title, hook_text=..., items=items, cta_text=..., theme=theme)
```
render_all()은 body 타입만 렌더링.

**변경**:
publish_cardnews() 내부에서:
1. content dict에 `slides` 키가 있으면 → `render_from_slides(slides, theme)` 호출
2. slides 키가 없으면 → 기존 `render_all()` 호출 (하위 호환)

```python
# publish_cardnews() 안에서:
if "slides" in content and isinstance(content["slides"], list):
    # V2 파이프라인: render_from_slides
    image_paths = [str(p) for p in renderer.render_from_slides(content["slides"], theme=theme)]
else:
    # 레거시 파이프라인: render_all
    image_paths = [str(p) for p in renderer.render_all(...)]
```

**주의**: publish_cardnews() 함수 시그니처에 `content: dict | None = None` 파라미터를 추가하거나,
또는 기존 `items` 파라미터 대신 `slides` 파라미터를 추가.
어느 방식이든 **하위 호환 필수** — 기존 호출 코드가 깨지면 안 됨.

## 테스트 요구사항
1. **프롬프트 변경 테스트**: `get_system_prompt()`, `get_user_prompt()` 호출하여 출력 형식이 card_list/detail 기반인지 확인
2. **validate 테스트**:
   - 정상 케이스: cover + card_list 3장 + cta = 5장 → PASS
   - 정상 케이스: cover + card_list 2장 + detail 2장 + cta = 6장 → PASS
   - 에러 케이스: slides 4장 → FAIL
   - 에러 케이스: slides 8장 → FAIL
   - 에러 케이스: cover 없음 → FAIL
   - 하위 호환: cover + body 5장 + cta = 7장 → PASS
3. **실제 생성 테스트**: ContentGeneratorV2.generate() 호출하여 card_list/detail이 나오는지 확인
4. **파이프라인 연결 테스트**: publish_cardnews()에 slides가 있는 content를 전달하여 render_from_slides()가 호출되는지 확인 (실제 Threads API 호출은 불필요, 렌더링만 확인)
5. **하위 호환 테스트**: 기존 body 타입 content로 publish_cardnews() 호출 → render_all() 경유 확인

## 주의사항
- **renderer/cardnews.py, renderer/themes.py 수정 금지** — Phase A에서 이미 완료됨
- cover의 "hook" 필드는 render_from_slides()에서 "subtitle"로 매핑됨을 인지할 것
  - cover slide 형식: `{"type": "cover", "title": title, "subtitle": hook, "category": category, "keywords": keywords}`
  - 따라서 프롬프트 출력에서 hook → subtitle로 변환하거나, content_generator가 변환하거나, render_from_slides 호출 전에 변환
- 기존 테스트가 있다면 업데이트. 깨뜨리지 말 것.
- 카테고리별 _SLIDE_GUIDES 5개 모두 업데이트 필요
