# task-922.1: ThreadAuto 카드뉴스 렌더링 이상 현상 심층 분석

**작성자**: 오딘 (dev2-team 팀장)
**작성일**: 2026-03-24
**한정위임**: 완료까지

---

## SCQA

**S**: ThreadAuto는 3/22까지 6장 멀티슬라이드 캐러셀 카드뉴스를 정상 생성하여 Threads/Instagram에 `post_carousel()`로 업로드하고 있었다. 마지막 성공 건: Post ID 18096845894001055 (6장, card_type "I").

**C**: task-918.1에서 `python3 cli.py pipeline -c 1 -t TypeB --source news --format image --upload` 명령을 실행한 결과, 6장 캐러셀 대신 **단일 이미지 1장**(69KB)이 생성되었고, 브랜딩이 "인카다이렉트 TOP사업단"으로 바뀌어 기존 "서울대보험크루/서울대보험쌤" 브랜딩이 누락되었다. Post ID 17968207400881917로 업로드되어 제이회장님이 직접 삭제했다.

**Q**: `-t TypeB`가 왜 기존과 다른 결과물을 생성했으며, 기존 6장 캐러셀을 재현하는 정확한 CLI 명령은 무엇인가?

**A**: ThreadAuto에는 **2개의 완전히 독립된 렌더링 시스템**이 공존한다. `-t cardnews`는 V2 CardNewsRenderer(5-7장 캐러셀)를, `-t TypeB`는 레거시 단일 이미지 렌더러를 호출한다. 올바른 명령은 `python3 cli.py pipeline -t cardnews --source news --upload`이다. 상세 CLI 가이드를 별도 산출물로 작성했다.

---

## 1. 근본 원인 분석 (코드 레벨 추적)

### 1-1. 두 개의 독립 렌더링 시스템

ThreadAuto 코드베이스에는 **완전히 분리된 2개의 렌더링 파이프라인**이 존재한다:

**V2 카드뉴스 파이프라인** (`-t cardnews`):
- 경로: `cli.py` → `orchestrator.run_cardnews()` → `FiveStagePipeline.generate()` → `CardNewsRenderer.render_from_slides()` → `ThreadsPublisher.publish_cardnews()` → `post_carousel()`
- 렌더러: `renderer/cardnews.py:CardNewsRenderer` (line 208)
- 출력: 5-7장 PNG (`cardnews_{ts}_{idx:02d}.png`)
- 브랜딩: themes.py 3개 상수 사용 ("서울대보험크루" badge + "서울대보험쌤" watermark + "인카다이렉트 TOP사업단" bar)

**레거시 단일 카드 파이프라인** (`-t TypeA/TypeB/TypeC/TypeD/TypeE`):
- 경로: `cli.py` → `orchestrator.run_single()` → `TypeB().render()` → `render_image()` → PNG 1장 저장
- 렌더러: `renderer/templates.py:TypeB` (line 165), `BaseRenderer` 상속
- 출력: 단일 PNG (`{ts}_{TypeX}.png`)
- 브랜딩: `engine.py:BaseRenderer.draw_footer()` 하드코딩 기본값 "인카다이렉트 TOP사업단"만 표시

### 1-2. `-t TypeB` 실행 시 코드 경로 (이상 흐름)

`cli.py` line 646~768의 분기 로직:
```
if template_type == "text":        → 텍스트 파이프라인
elif template_type == "cardnews":  → V2 카드뉴스 파이프라인
else:                              → 레거시 단일 카드 (TypeA~TypeE 여기로)
    orchestrator.run_single("TypeB", "news")
```

`run_single()` 내부:
1. `_get_renderer_class("TypeB")` → `templates.py:TypeB` 클래스 반환
2. `TypeB().render(data)` → `Image.Image` 1개 반환 (NEWS 배지 + 헤드라인 + 본문)
3. `render_image()` → `output/{ts}_TypeB.png` 1장 저장

**치명적 문제**: `--upload` 플래그는 레거시 TypeA~E 분기에서 **silently 무시**된다. 업로드 로직이 해당 분기에 구현되지 않았다.

### 1-3. 정상 흐름 (3/22 성공 건)

`-t cardnews` 또는 `run_card_post.py` / `run_full_pipeline.py` 실행 시:
1. `orchestrator.run_cardnews(upload=True)` 호출
2. `FiveStagePipeline.generate()` → Claude 프롬프트로 5-7장 슬라이드 JSON 생성
3. `CardNewsRenderer.render_from_slides(slides, theme)` → 각 슬라이드별 PNG 생성
4. `ThreadsPublisher.publish_cardnews()` → `post_carousel(image_urls, caption)`
5. 결과: `cardnews_post_result_{ts}.json` 저장

### 1-4. card_type "I"의 의미

`card_type`은 `content/evergreen_topics.json`에서 토픽별로 정의된 **콘텐츠 분류 메타데이터**이다:
- `I` = Information (정보제공) — 60건
- `E` = Empathy (고민공감/리쿠르팅) — 46건
- `S` = Social Proof (사회적증거) — 40건
- `C` = CTA — 30건
- `T` = Trend (업계동향) — 30건

**card_type은 렌더러 선택과 무관**하다. Claude 프롬프트의 참고 정보 + 결과 JSON의 메타데이터 필드로만 사용된다. 렌더러는 슬라이드 JSON의 `"type"` 필드(`"cover"`, `"card_list"`, `"detail"`, `"cta"`)로 분기한다.

### 1-5. 브랜드 차이 원인

| 브랜드 요소 | V2 CardNewsRenderer | 레거시 TypeA~E |
|---|---|---|
| "서울대보험크루" badge | themes.py `BRAND_COVER_LABEL` → `render_cover()` | 미사용 |
| "서울대보험쌤" watermark | themes.py `BRAND_NAME_SECONDARY` → `_draw_watermark()` | 미사용 |
| "인카다이렉트 TOP사업단" | themes.py `BRAND_NAME_PRIMARY` → branding bar | engine.py `draw_footer()` 하드코딩 기본값 |

레거시 렌더러(`BaseRenderer`)의 `draw_footer(text="인카다이렉트 TOP사업단")`가 하드코딩되어 있어, TypeA~E는 "인카다이렉트 TOP사업단"만 표시한다.

---

## 2. 정상 카드뉴스 재현 CLI 명령

```bash
# 방법 1: CLI pipeline 명령 (생성 + 업로드)
python3 /home/jay/projects/ThreadAuto/cli.py pipeline -t cardnews --source news --upload

# 방법 2: 전용 스크립트 (fact_guard 포함)
python3 /home/jay/projects/ThreadAuto/run_card_post.py

# 방법 3: 풀 파이프라인 스크립트
python3 /home/jay/projects/ThreadAuto/run_full_pipeline.py
```

**핵심**: `-t` 파라미터에 반드시 `cardnews`를 지정해야 V2 파이프라인이 동작한다. `TypeA`~`TypeE`는 레거시 단일 카드용이다.

---

## 3. 발견 이슈 및 해결

### 자체 해결 (0건)
- 분석 전용 작업으로 코드 수정 불가 (수정 금지 조건)

### 범위 외 미해결 (3건)
1. **`--upload` 플래그 silent 무시** — 레거시 TypeA~E 분기에서 `--upload`가 효과 없이 무시됨. 경고 메시지도 없음. → 범위 외: 코드 수정 금지 조건
2. **`-t` 파라미터 유효성 검증 부재** — `cardnews`/`text` 외의 값 입력 시 경고 없이 레거시 경로로 fallthrough. → 범위 외: 코드 수정 금지 조건
3. **브랜드 설정 이원화** — `engine.py` 하드코딩 vs `themes.py` 상수 분리 관리로 일관성 부재. → 범위 외: 코드 수정 금지 조건

---

## 4. 개선 제안 (코드 수정 별도 작업으로)

1. **`-t` 파라미터 입력 검증**: `cardnews`, `text` 이외의 값 입력 시 경고 + 확인 프롬프트 추가
2. **`--upload` 플래그 미지원 경고**: 레거시 TypeA~E에서 `--upload` 사용 시 "이 타입은 업로드를 지원하지 않습니다" 경고 출력
3. **CLI 도움말 강화**: `--help`에 각 `-t` 옵션별 동작 차이 명시
4. **위임 시 명령 템플릿**: CLI 가이드 문서를 별도 산출물로 작성 완료 (`memory/projects/threadauto/cardnews-cli-guide.md`)

---

## 산출물

1. `memory/reports/task-922.1.md` — 본 분석 보고서
2. `memory/projects/threadauto/cardnews-cli-guide.md` — CLI 사용 가이드

## 셀프 QC

- [x] 1. 다른 파일 영향: 분석 전용, 코드 수정 없음
- [x] 2. 엣지 케이스: `-t` 파라미터 오입력이 핵심 원인으로 규명
- [x] 3. 작업 지시 일치: 4개 분석 과제(렌더링 추적, 코드 구조, CLI 명령, 개선방안) 모두 완료
- [x] 4. 에러 처리/보안: 해당 없음 (분석 전용)
- [x] 5. 테스트 커버리지: 해당 없음 (분석 전용)
- [x] 6. 발견 이슈: 3건 범위 외 미해결 (코드 수정 금지 조건), 사유 명시

## 검증 기준 충족 확인

1. **근본 원인 코드 레벨 규명**: `-t TypeB`가 레거시 `PipelineOrchestrator.run_single()` → `templates.py:TypeB.render()` 경로로 라우팅되어 단일 이미지 생성. 코드 파일 7개(cli.py, orchestrator.py, cardnews.py, templates.py, engine.py, themes.py, content_generator_v2.py) 분석 완료.
2. **정상 재현 CLI 명령**: `python3 cli.py pipeline -t cardnews --source news --upload` 특정 완료.
3. **관계 문서화**: card_type(콘텐츠 분류) / template_type(렌더러 선택) / 업로드 방식(publish_cardnews vs publish) 관계를 CLI 가이드에 정리.
