# 카드뉴스 자동생성 V3 — PRD (Product Requirements Document)

> **작성일**: 2026-03-25
> **작성팀**: dev5-team (마르둑 팀장, 나부 UX/UI, 엔키 백엔드, 이쉬타르 프론트, 닌기르수 테스터)
> **Task ID**: task-1011.1
> **상태**: 초안 v1.0

---

## 문서 개요

카드뉴스 자동생성 시스템의 차세대 파이프라인(V3)에 대한 PRD이다. 기존 ThreadAuto V2 파이프라인은 그대로 유지하면서, 디자인 다양성을 확보하는 신규 병렬 트랙을 설계한다. 최종 목표는 **InsuRo 플랫폼에서 사용자가 조건을 입력하면 카드뉴스가 출력되는 서비스**이다.

---

## A. 디자인팀 신설 설계

### A.1 배경 및 목적

카드뉴스 V3는 단일 이미지 생성 방식에서 벗어나 다양한 비주얼 스타일(래스터 렌더링, 포토리얼리스틱, 하이브리드)을 요청 맥락에 따라 최적으로 제공해야 한다. 이를 위해 디자인팀 아키텍처를 신설하며, 두 가지 안을 정량적으로 비교 분석한다.

### A.2 아키텍처 비교 분석

#### 안 1 — 역할별 전문 팀원 구성

```
[디자인팀장]
    ├── 팀원A: satori-cardnews   (HTML/CSS→PNG,  0.32초/장, $0, 한글 100%)
    ├── 팀원B: gemini-image      (포토리얼,      25초/장,   $0, 한글 90%+)
    ├── 팀원C: hybrid-image      (Gemini배경+HTML오버레이,  25초/장, $0, 한글 100%)
    └── 팀원D: canvas-design     (디자인 철학 기반 시각 아트 PDF/PNG)
```

팀장은 요청 내용을 분석하여 적합한 팀원에게 작업을 배정하고, 결과를 수집·검수한다.

#### 안 2 — 팀장 직접 스킬 선택/실행

```
[디자인팀장]  ←→  image-gen-guide (라우팅 로직 내장)
    └── 보조팀원: QC, 리사이즈, 포맷 변환 전담
```

팀장이 image-gen-guide의 라우팅 로직을 직접 호출하여 스킬을 선택·실행한다.

#### 정량적 비교

| 비교 기준 | 안 1 (전문 팀원) | 안 2 (팀장 직접) | 우위 |
|---|---|---|---|
| **토큰 효율성** | 팀원 소환당 +800~1,200 토큰 오버헤드 | 팀장 단일 컨텍스트, 추가 토큰 없음 | 안 2 |
| **확장성** | 새 스킬 = 새 팀원 추가. 팀장 로직 무변경 | 새 스킬 = 팀장 프롬프트 비대화 위험 | 안 1 |
| **품질** | 전문 프롬프트 심화 학습 가능. 스킬 고도화 독립적 | 4가지 스킬 동시 보유 → 멀티태스크 오버헤드 | 안 1 |
| **병렬성** | 동시 2~3개 디자인 병렬 생성. 5장 satori 기준 ~0.64초 | 순차 처리. 5장 기준 최소 1.6초 | 안 1 |
| **장애 격리** | 개별 팀원 실패가 전체에 영향 안 줌 | 팀장 실패 = 전체 중단 | 안 1 |
| **현실적 제약** | 비너스(Venus)가 Gemini센터로 기존재. 역할 조율 필요 | 비너스 역할 충돌 가능성 존재 | 조건부 |

**종합**: 안 1 우위 4개, 안 2 우위 1개, 조건부 1개.

### A.3 최적안 제안: 안 1 채택 (비너스 연동 수정)

안 1이 품질, 병렬성, 장애 격리에서 구조적으로 우월하다. 토큰 효율성 단점은 slim-context 패턴(팀원당 300~400 토큰 수준 요약)으로 억제 가능.

**비너스(Venus) 역할 조정**: 팀원B를 별도 Agent로 신설하는 대신, 디자인팀장이 비너스에게 위임하는 구조로 설계한다.

```
[디자인팀장]
    ├── 팀원A: satori-cardnews   (신설 Agent)
    ├── 비너스(Venus) 위임       (기존 Gemini센터 활용 — gemini-image 담당)
    ├── 팀원C: hybrid-image      (신설 Agent, 비너스 배경 + 오버레이 합성)
    └── 팀원D: canvas-design     (신설 Agent)
```

**디자인팀장 역할 정의:**
- 요청 분석 및 스타일 판단 (satori / gemini / hybrid / canvas)
- 적합 팀원(또는 비너스) 배정 및 slim-context 전달
- 결과물 수집, 품질 검수, 최종 응답 조합
- 병렬 배정 시 동시 실행 오케스트레이션

---

## B. V3 카드뉴스 파이프라인 설계

### B.1 기존 V2 파이프라인 (유지)

V2는 현재 안정적으로 운영 중인 정기 자동 발행 파이프라인으로, V3 도입 이후에도 그대로 유지된다.

```
ContentGeneratorV2 → CardNewsRendererV2 (satori) → CrossPublisher
```

- **처리 속도**: 0.32초/장
- **한글**: 100%
- **발행**: Threads, SNS 멀티 플랫폼 자동
- **강점**: 안정성, 속도, 운영 자동화
- **한계**: 템플릿 고정, 디자인 다양성 부족, AI 이미지 미활용
- **용도**: 정기 자동 발행 (daily/weekly 스케줄)

V2는 건드리지 않는다. V3는 V2를 대체하는 것이 아니라 병렬 트랙으로 신설된다.

### B.2 V3 파이프라인 개요

V3는 고품질 주문형(on-demand) 카드뉴스 생성에 특화된 7단계 파이프라인이다.

```
[Input] → [Editor] → [Researcher] → [Writer] → [Designer] → [Compositor] → [QualityGate]
```

각 단계는 독립 클래스로 구현되며, Python dataclass 기반 객체를 통해 단계 간 데이터를 전달한다.

### B.3 단계별 상세 설계

#### Stage 1 — 입력 계층 (Input)

```python
from dataclasses import dataclass
from typing import Literal

@dataclass
class TopicRequest:
    topic: str                          # 예: "보험료 인하 꿀팁 5가지"
    target_audience: str                # 예: "30대 직장인"
    tone: Literal["formal", "friendly", "urgent", "educational"]
    brand: str                          # design-tokens.json 브랜드 키
    card_count: int = 5
    input_mode: Literal["manual", "auto"] = "manual"
```

- **수동**: InsuRo UI에서 사용자가 주제/조건 직접 입력
- **자동**: RSS/트렌드 모니터링으로 토픽 자동 선택 (기존 V2 연계)

#### Stage 2 — 기획 계층 (Editor)

앵글 결정, 훅(오프닝) 설계, 카드별 내러티브 구조 수립.

```python
@dataclass
class NarrativeCard:
    card_number: int
    role: Literal["hook", "problem", "solution", "evidence", "benefit", "cta"]
    theme: str
    key_message: str
    visual_direction: str               # 예: "걱정하는 직장인 이미지"

@dataclass
class EditorialPlan:
    hook: str
    narrative_cards: list[NarrativeCard]
    cta: str
    overall_angle: str                  # 예: "불안 → 해소 → 행동"
```

- `brand.md` 패턴 참조 + `prompts/editor.md` 프롬프트 템플릿 사용

#### Stage 3 — 리서치 계층 (Researcher)

```python
@dataclass
class FactItem:
    content: str                        # 예: "2024년 실손보험 청구 건수 1,200만 건"
    source_url: str
    verified: bool                      # fact_db.md 교차 확인 여부
    relevance_score: float              # 0.0 ~ 1.0

@dataclass
class ResearchData:
    facts: list[FactItem]
    statistics: list[FactItem]
    sources: list[str]
    research_summary: str
```

- Google Search API로 팩트/통계 수집
- `fact_guard` 모듈: `fact_db.md` 교차 확인. 출처 미확인 수치는 `unverified` 플래그 처리

#### Stage 4 — 작성 계층 (Writer)

```python
@dataclass
class CardText:
    card_number: int
    headline: str                       # ≤ 20자
    body: str                           # ≤ 80자
    caption: str
    cta_text: str | None = None

@dataclass
class CardTexts:
    cards: list[CardText]
    brand: str
    tone_applied: str
```

- `prompts/writer.md` 프롬프트 + 톤앤매너 파라미터 반영
- 검증된 수치만 본문에 삽입 (`verified=True` 항목만)

#### Stage 5 — 이미지 계층 (Designer)

기존 `image_router.py` 기반 방법 자동 선택. Gemini API 실행은 비너스(Venus) 담당.

| 조건 | 선택 방법 | 담당 모듈 |
|------|-----------|-----------|
| 포토리얼 인물/배경 | `gemini-image` | `gemini_pro_generate.py` (Venus) |
| 대량 생성 / 정형 레이아웃 | `satori-cardnews` | `satori_cli.js` |
| 한글 텍스트 + 포토 혼합 | `hybrid-image` | `generate_hybrid.py` |

**Fallback 체인**: Gemini→GPT, Hybrid→Gemini 단독, Satori→없음(로컬 도구)

```python
@dataclass
class ImageAsset:
    card_number: int
    image_path: str
    method_used: str                    # "gemini-image" | "satori-cardnews" | "hybrid-image" | "gpt-image"
    generation_time_sec: float
    fallback_triggered: bool = False

@dataclass
class ImageAssets:
    assets: list[ImageAsset]
    total_generation_time_sec: float
```

#### Stage 6 — 합성 계층 (Compositor)

텍스트 + 이미지 → 최종 카드뉴스 PNG 합성.

```python
@dataclass
class FinalCard:
    card_number: int
    png_path: str
    size_kb: float
    width_px: int = 1080
    height_px: int = 1080
    compositor_method: str              # "satori" | "playwright"

@dataclass
class FinalCards:
    cards: list[FinalCard]
    brand: str
    design_token_version: str
```

- **satori 기반**: HTML 템플릿에 데이터 주입 → SVG → PNG
- **hybrid 기반**: Playwright 헤드리스 캡처
- `design-tokens.json`에서 브랜드 컬러/폰트 적용

#### Stage 7 — 품질 검증 (QualityGate)

```python
@dataclass
class QualityIssue:
    card_number: int | None
    check_type: str                     # "fact" | "brand" | "image_quality" | "ocr"
    severity: str                       # "warning" | "error"
    detail: str

@dataclass
class QualityReport:
    status: str                         # "pass" | "fail"
    issues: list[QualityIssue]
    checked_at: str
    auto_retry_triggered: bool = False
```

| 검증 항목 | 기준 | 담당 |
|-----------|------|------|
| 수치 정확성 | `fact_db.md` 교차 확인 | fact_guard 모듈 |
| 브랜드 일관성 | `design-tokens.json` 컬러/폰트 준수 | 토큰 비교 로직 |
| 이미지 품질 | 해상도 ≥ 1080x1080, 파일 크기 > 50KB | PIL 검사 |
| 한글 OCR | gemini 이미지의 텍스트 정확도 ≥ 95% | OCR 검증 |

`severity="error"` 항목 존재 시 `status="fail"` → 해당 카드 재생성 큐 등록. 최대 2회 재시도.

### B.4 V2 vs V3 비교

| 항목 | V2 | V3 |
|------|----|----|
| 목적 | 정기 자동 발행 | 주문형 고품질 생성 |
| 처리 속도 | 0.32초/장 | 5초 ~ 2분/장 |
| 이미지 방식 | satori only | Gemini / GPT / satori / hybrid |
| 디자인 다양성 | 낮음 (템플릿 고정) | 높음 (AI 이미지 + 다중 방법) |
| 팩트 검증 | 없음 | fact_guard 포함 |
| 품질 게이트 | 없음 | 7단계 QualityGate |
| 운영 방식 | 스케줄러 자동 | API 요청 기반 (작업 ID 폴링) |
| 공존 | 유지 | 신규 병렬 트랙 |

---

## C. InsuRo 플랫폼 통합

### C.1 UX 설계 원칙

보험 FA를 주 사용자로 상정한다. FA는 디자인 전문가가 아니므로 진입 장벽을 최소화하고, 기다리는 동안 진행 상황을 투명하게 전달하며, 결과물을 즉시 업무에 활용할 수 있어야 한다. 전체 UX는 **"묻기 → 기다리기 → 쓰기"** 3단계 인지 모델로 구성한다.

### C.2 입력 폼 설계

**필수 입력 (Step 1 — 기본 정보)**

| 필드 | 유형 | 옵션 | 기본값 |
|---|---|---|---|
| 주제/토픽 | 텍스트 | 자유 입력 (예: "보험료 인하 꿀팁 5가지") | — |
| 타겟 독자 | 드롭다운 | 30~40대 직장인 / 시니어(60+) / 청년(20대) / 법인 담당자 / 전체 | 30~40대 직장인 |
| 톤 & 매너 | 드롭다운 | 친근하고 유익한 / 전문적이고 신뢰감 있는 / 긴급하고 행동 유발 / 감성적이고 공감적 | 친근하고 유익한 |

**선택 입력 (Step 2 — 디자인 설정)**

| 필드 | 유형 | 설명 |
|---|---|---|
| 브랜드 테마 | 라디오 | design-tokens.json 연동 (InsuRo 기본 / 파트너사) |
| 카드 수 | 드롭다운 | 3 / 5 / 7 / 10장, 기본 5장 |
| 이미지 스타일 | 카드 선택 | satori(깔끔, ~5초) / gemini(실사, ~2분) / hybrid(사진+텍스트, ~2분) |

**고급 옵션 (접힌 패널)**
- 커스텀 컬러: Primary/Secondary 컬러피커
- 로고 업로드: PNG/SVG, 최대 2MB, 카드 우상단 배치
- 폰트 선택: Noto Sans KR / Pretendard / Spoqa Han Sans

### C.3 진행 상태 표시

**단계별 진행 바**

```
기획  →  리서치  →  작성  →  이미지  →  합성
 ●         ●         ●         ○         ○     60%
```

| 단계 | 표시 메시지 |
|---|---|
| 기획 | 주제를 분석하고 카드 구성을 설계하고 있습니다 |
| 리서치 | 관련 보험 정보를 수집하고 있습니다 |
| 작성 | 각 카드의 핵심 메시지를 작성하고 있습니다 |
| 이미지 | 비주얼 요소를 생성하고 있습니다 |
| 합성 | 텍스트와 이미지를 최종 합성하고 있습니다 |

**실시간 미리보기:**
- 기획 완료: 카드 목차(제목 리스트) 텍스트 미리보기
- 작성 완료: 카드별 본문 텍스트 + 이미지 영역 스켈레톤 UI
- 이미지 완료: 완성된 카드 PNG 순차 fade-in
- 합성 완료: 최종 카드셋 갤러리 뷰 전환

### C.4 결과물 다운로드 및 공유

- **다운로드**: 개별 PNG + ZIP 일괄 다운로드
- **소셜 공유**: Threads, Instagram 직접 발행 (슬라이드 포맷 자동 패키징)
- **편집 모드**: 오버레이 패널, 인라인 텍스트 편집, 컬러 팔레트 실시간 반영, Undo/Redo (10스텝), 특정 카드만 재생성

### C.5 API 설계

```
POST /api/v3/cardnews/generate    → { "job_id": "uuid", "status": "queued" }
GET  /api/v3/cardnews/status/{id} → { "status": "running", "stage": "designer", "progress": "4/7" }
GET  /api/v3/cardnews/download/{id} → 파일 다운로드
WS   /ws/v3/cardnews/{id}          → 단계 완료 시 실시간 push (선택)
```

- 생성 요청 접수 후 작업 ID 즉시 반환 (비동기 처리)
- 클라이언트는 폴링 또는 WebSocket으로 진행 상태 확인
- 완료 시 결과물 다운로드 엔드포인트 활성화

### C.6 사용자 시나리오: 보험 FA의 카드뉴스 생성

**페르소나:** 김민준 FA, 35세, 생명보험사 소속. 디자인 툴 미경험.

| 단계 | 행동 | 소요 |
|---|---|---|
| 진입 | "콘텐츠 만들기" → "카드뉴스" 선택 | 15초 |
| 기본 입력 | 주제: "보험료 인하 꿀팁 5가지", 타겟: 30~40대, 톤: 친근한 | 1분 |
| 디자인 설정 | 브랜드: InsuRo 기본, 5장, satori 스타일 | 30초 |
| 자동 생성 | 시스템 처리 (satori ~5초) | 5~10초 |
| 미세 조정 | 3번 카드 텍스트 단축 | 2분 |
| 공유 | ZIP 다운로드 → 카톡 업로드 | 30초 |
| **합계** | | **약 4분 30초** |

### C.7 확장성

- **보험 외 업종 확장**: `design-tokens.json`에 새 브랜드를 추가하는 것만으로 다른 업종(부동산, 교육, 금융 등) 지원 가능
- **brand.md 템플릿 시스템**: 업종별 톤앤매너, 금지어, 스타일 가이드를 별도 파일로 관리
- **플러그인 아키텍처**: 새로운 이미지 생성 엔진(Flux, DALL-E 3 등) 추가 시 `image_router.py`에 엔진 등록만으로 연동

---

## D. 기술 스택 및 아키텍처

### D.1 이미지 생성 엔진

현재 인프라에서 실현 가능한 4가지 엔진:

| 엔진 | 모델/도구 | 인증 | 담당 | 용도 |
|------|-----------|------|------|------|
| Gemini Pro Image | `gemini-3-pro-image-preview` | gcloud 토큰 | 비너스(Venus) | 포토리얼 1순위 |
| GPT Image v2 | `gpt-image-1` (OpenAI) | API Key | 직접 호출 | Gemini fallback |
| Satori + resvg-js | Node.js 로컬 | 없음 | 직접 호출 | 정형 레이아웃, 대량 처리 |
| Playwright | 헤드리스 Chromium | 없음 | 직접 호출 | hybrid 캡처 |

**비너스 역할**: Gemini API 호출은 반드시 비너스를 통해 실행. 오케스트레이터가 직접 Gemini API를 호출하지 않음.

### D.2 텍스트 렌더링

| 렌더러 | 한글 | 레이아웃 | 출력 | 적합 경우 |
|--------|------|----------|------|-----------|
| satori | 100% | Flexbox (CSS subset) | SVG→PNG | 정형 템플릿, 고속 |
| Playwright | 100% | 전체 HTML/CSS | PNG | 복잡한 레이아웃, hybrid |

- satori: Noto Sans KR/Pretendard 폰트 파일 직접 로드. 경로: `/home/jay/workspace/tools/ai-image-gen/satori-test/fonts/`
- Playwright: 시스템 폰트 사용. 헤드리스 브라우저 렌더링.

### D.3 파이프라인 오케스트레이터

```python
from dataclasses import dataclass

@dataclass
class PipelineConfig:
    max_retries: int = 3
    image_timeout_sec: float = 120.0
    quality_gate_strict: bool = True
    output_dir: str = "output/"

class CardNewsPipelineV3:
    def __init__(self, config: PipelineConfig):
        self.config = config
        self.stages = [
            InputStage(),
            EditorStage(),
            ResearcherStage(),
            WriterStage(),
            DesignerStage(),
            CompositorStage(),
            QualityGateStage(),
        ]

    async def run(self, request: TopicRequest) -> FinalCards | QualityReport:
        context: dict = {"request": request}
        for stage in self.stages:
            context = await stage.execute(context)
            if context.get("pipeline_halt"):
                return context["quality_report"]
        return context["final_cards"]
```

**설계 원칙:**
- 각 단계는 독립 클래스 (`InputStage`, `EditorStage`, ..., `QualityGateStage`)
- 단계 간 의존성은 dataclass 객체로만 전달 (전역 상태 없음)
- 이미지 생성 단계는 I/O bound → `asyncio` 비동기 처리
- 단계별 retry 횟수 및 fallback 체인을 명시적 설정

### D.4 프롬프트 관리

| 파일 | 역할 | 참조 단계 |
|------|------|-----------|
| `brand.md` | 브랜드별 톤앤매너, 어조/금지어/스타일 가이드 | Editor, Writer |
| `prompts/editor.md` | 앵글/훅/내러티브 설계 프롬프트 | EditorStage |
| `prompts/researcher.md` | 검색 쿼리 생성, 팩트 추출 프롬프트 | ResearcherStage |
| `prompts/writer.md` | 카드별 카피라이팅, 글자 수 제약 | WriterStage |
| `design-tokens.json` | 브랜드 컬러/폰트/여백/레이아웃 토큰 | Compositor, QualityGate |

현재 `design-tokens.json`은 3개 브랜드(insurance/pension/default)를 정의하고 있으며, 카드 크기(1080x1080, 1080x1350), 타이포그래피, 이펙트 공유 토큰을 포함한다.

### D.5 데이터 흐름 아키텍처

```
InsuRo UI (React)
    │
    ├─ POST /api/v3/cardnews/generate → job_id 즉시 반환
    ├─ GET  /api/v3/cardnews/status/{job_id} → 폴링
    └─ WS   /ws/v3/cardnews/{job_id} → 실시간 push (선택)
           │
    FastAPI Backend → BackgroundTasks
           │
    CardNewsPipelineV3 (asyncio)
           │
    ┌──────┼──────┐
    │      │      │
  satori  Venus  Playwright
  (local) (Gemini) (hybrid)
           │
    output/{job_id}/
    ├── images/     (Stage 5)
    ├── final/      (Stage 6)
    └── reports/    (Stage 7)
```

- **저장**: 로컬 파일시스템 (output/) + 추후 클라우드 스토리지 (StorageAdapter 추상화)
- **비동기 큐**: FastAPI BackgroundTasks (MVP) → 추후 Celery/Redis 교체 가능

### D.6 모니터링

기존 `image_router.py` 로깅 시스템 확장.

| 지표 | 설명 | 경보 조건 |
|------|------|-----------|
| `generation_time_sec` | 단계별/전체 생성 소요 시간 | 전체 > 180초 |
| `success_rate` | 파이프라인 완료 성공률 | < 90% |
| `fallback_rate` | fallback 발동 비율 | > 20% |
| `quality_fail_rate` | QualityGate fail 비율 | > 10% |
| `stage_error_count` | 단계별 에러 횟수 | 특정 단계 > 5회/시간 |

구조화 JSON 로깅으로 추후 ELK/Cloud Logging 연동 대비.

---

## E. 로드맵

### E.1 전체 개요

```
Phase 1 ──────────────► Phase 2 ──────────────► Phase 3
MVP 파이프라인           품질 향상 + 다양화        InsuRo 통합
(디자인팀 구성 포함)      (리서치 + 템플릿)         (플랫폼 서비스화)
```

### E.2 Phase 1: 디자인팀 구성 + V3 파이프라인 MVP

**목표**: 최소 기능의 V3 파이프라인이 동작하여 1개 주제 → 5장 카드뉴스 생성 가능.

**산출물:**
1. 디자인팀 조직 구성 (팀장 1인 + 역할 배정)
2. V3 파이프라인 코어 모듈 (입력→기획→작성→이미지→합성 5단계)
3. MVP 실행 스크립트 (단일 명령 end-to-end)
4. 수동 입력 인터페이스 (JSON/YAML 스펙)

> 리서치 계층(Google Search + fact_guard)은 Phase 1에서 제외. 수동 입력 대체.

**의존성:**
- 기존 컴포넌트 재사용: `image_router.py`, `satori_cli.js`, `gemini_pro_generate.py`, `design-tokens.json`
- 비너스(Venus)의 Gemini API 접근 권한

**품질 기준:**

| 항목 | 기준 | 측정 |
|------|------|------|
| end-to-end 성공률 | ≥ 80% | 10회 실행 중 8회 이상 완성 |
| 이미지 해상도 | ≥ 1080x1080 | PIL/ImageMagick 자동 검사 |
| 한글 정확도 | 100% (satori/hybrid) | 깨진 글자 부재 확인 |

### E.3 Phase 2: 품질 향상 + 디자인 다양화

**목표**: V3 파이프라인 품질을 프로덕션 수준으로 향상.

**산출물:**
1. 리서치 계층 (Google Search API + fact_guard 검증)
2. 디자인 템플릿 3종 (infographic, emotional, data-driven)
3. A/B 테스트 변형 자동 생성기
4. QualityGate 자동화 (파이프라인 통합)
5. fact_db.md 초기 데이터셋 (보험 핵심 수치 50건+)

**의존성:**
- Phase 1 완료
- fact_db.md 데이터 확보
- Google Search API 키

**품질 기준:**

| 항목 | 기준 | 측정 |
|------|------|------|
| end-to-end 성공률 | ≥ 95% | 20회 연속 19회+ 완성 |
| fact_guard 통과율 | 100% | 모든 수치 fact_db.md 교차 확인 |
| 내부 만족도 | ≥ 4.0/5.0 | 5인+ 샘플 평가 |

### E.4 Phase 3: InsuRo 플랫폼 통합

**목표**: InsuRo에서 사용자가 직접 카드뉴스를 생성하는 완전한 서비스.

**산출물:**

- **백엔드**: FastAPI (`/api/v3/cardnews/generate`, `/status`, `/download`)
- **프론트엔드**: React UI (입력 폼, 진행 상태, 결과 다운로드)
- **인프라**: 비동기 작업 큐, brand.md 템플릿 시스템, Docker 배포 구성

**의존성:**
- Phase 2 완료
- InsuRo 서버 인프라 (CPU 4코어, RAM 8GB+)
- 도메인/SSL/인증

**품질 기준:**

| 항목 | 기준 | 측정 |
|------|------|------|
| API 접수 응답시간 | < 500ms | p95 기준 |
| satori 전체 생성 | < 10초 | 5장 기준 |
| gemini 전체 생성 | < 3분 | 5장 기준 |
| 동시 사용자 | 10명+ | 부하 테스트 |
| 에러율 | < 2% | 1주일 모니터링 |

### E.5 품질 검증 기준 (QualityGate 상세)

#### QG-1. 이미지 품질

| 항목 | 기준 | 방법 |
|------|------|------|
| 해상도 | ≥ 1080x1080 | `PIL Image.size` 자동 |
| 파일 크기 | > 50KB | `os.path.getsize()` |
| 텍스트 오버플로우 | 없음 | overflow 영역 감지 |
| 레이아웃 깨짐 | 없음 | bounding box 이탈 검사 |

#### QG-2. 콘텐츠 품질

| 항목 | 기준 | 방법 |
|------|------|------|
| 한글 (satori/hybrid) | 100% | 박스 문자 부재 확인 |
| 한글 (gemini 단독) | ≥ 95% | OCR/샘플링 검수 |
| 팩트 검증 | fact_db.md 교차 통과 | fact_guard 자동 대조 |
| 금소법 준수 | 수익률 보장 표현 없음 | 금지 키워드 정규식 검사 |
| 브랜드 일관성 | design-tokens.json 준수 | HEX/폰트 범위 확인 |

#### QG-3. 기술 품질

| 항목 | 기준 | 방법 |
|------|------|------|
| 생성 성공률 | ≥ 95% | 전체 요청 대비 비율 |
| satori 평균 생성 | < 2초 | p50 기준 |
| gemini 평균 생성 | < 30초 | p50 기준 |
| Fallback 작동 | Gemini→GPT 전환 확인 | 의도적 오류 주입 테스트 |

#### QualityGate 판정 흐름

```
파이프라인 출력
    ↓
[QG-1 이미지 품질] → FAIL → 재생성 (최대 2회)
    ↓ PASS
[QG-2 콘텐츠 품질] → FAIL → 콘텐츠 재작성 (최대 2회)
    ↓ PASS
[QG-3 기술 품질]   → FAIL → 오류 리포트 + 알림
    ↓ PASS
최종 승인 → 사용자 전달
```

재생성 2회 초과 실패 시: `FAILED` 상태 기록 + 담당자 알림.

---

## 부록: 기존 디자인 스킬 코드 분석 요약

### satori-cardnews
- **코드 위치**: `/home/jay/workspace/tools/ai-image-gen/satori-test/`
- **엔진**: Satori(HTML/CSS→SVG) + resvg-js(SVG→PNG), Node.js ESM
- **성능**: 0.32초/장, $0, 한글 100%, ~229KB/장
- **템플릿**: `tools/carousel-gen/templates/` (infographic.html, emotional.html, registry.json)
- **폰트**: Noto Sans KR, Pretendard (SIL OFL 라이선스)

### gemini-image
- **코드 위치**: `/home/jay/workspace/tools/ai-image-gen/gemini_pro_generate.py`
- **모델**: gemini-3-pro-image-preview
- **인증**: gcloud auth print-access-token (1시간 유효)
- **성능**: ~25초/장, 1024x1024 JPEG, 한글 90%+
- **Fallback**: GPT Image v2 (high) 자동 전환

### hybrid-image
- **코드 위치**: `/home/jay/workspace/tools/ai-image-gen/generate_hybrid.py`
- **파이프라인**: Gemini배경(JPEG) → HTML 템플릿 + 배경 + 텍스트 → Playwright 캡처(PNG)
- **성능**: ~25초/장, 한글 100%
- **의존성**: Gemini API + Playwright + Noto Sans KR

### canvas-design
- **스킬 위치**: `/home/jay/.claude/skills/canvas-design/`
- **접근**: 디자인 철학 매니페스토 작성 → 시각 아트 PDF/PNG 생성
- **특성**: 미술관 수준 품질 추구, 최소 텍스트, 반복 패턴/기하학 활용
- **용도**: 고급 비주얼 아트, 커피테이블 북 스타일

### image-gen-guide
- **스킬 위치**: `/home/jay/.claude/skills/image-gen-guide/`
- **역할**: 의사결정 트리 기반 라우팅
  - 포토리얼 + 한글 多 → hybrid-image
  - 포토리얼 + 한글 少 → gemini-image
  - 카드뉴스/배너/대량 → satori-cardnews
- **구현**: `image_router.py`가 실제 라우팅 로직 담당

### image_router.py (라우터)
- **코드 위치**: `/home/jay/workspace/tools/ai-image-gen/image_router.py`
- **라우팅**: 키워드 매칭 → ImageType(PHOTOREALISTIC/CARDNEWS/HYBRID)
- **Fallback 체인**: Gemini→GPT, Hybrid→Gemini, Satori→없음
- **결과 객체**: GenerationResult (success, image_path, method_used, fallback_used, elapsed)
- **IPTC 태깅**: 성공 시 자동 메타데이터 삽입

### design-tokens.json
- **위치**: `/home/jay/workspace/tools/ai-image-gen/design-tokens.json`
- **버전**: 1.0.0 (2026-03-24)
- **브랜드 3종**: insurance(서울대보험쌤), pension(서울대연금쌤), default(기본)
- **공유 토큰**: 5가지 사이즈(1080x1080~1200x628), 타이포그래피(48/32/24/18px), 이펙트(그림자, 오버레이)
