# task-1018.1 완료 보고서: One Source Multi Use — 카드뉴스 렌더링 중복 제거

## SCQA

**S**: ThreadAuto 프로젝트의 `CrossPublisher`가 Threads + Instagram 동시 발행 시, 각 publisher가 독립적으로 `CardNewsRenderer`를 생성하여 동일 콘텐츠를 2번 렌더링하고 있었다.

**C**: 같은 slides 데이터로 이미지를 2세트 생성하여 토큰/시간/디스크가 2배 낭비됨. 슬라이드 5장 기준 렌더링 2회 = 이미지 10개 생성 (필요량 5개의 200%).

**Q**: CrossPublisher 레벨에서 1회만 렌더링하고 양 플랫폼에서 동일 이미지를 공유할 수 있는가?

**A**: CrossPublisher에서 사전 렌더링 후 `image_paths` 파라미터로 양 publisher에 전달하는 "One Source Multi Use" 패턴을 구현. 렌더링 1회로 감소하여 이미지 생성량 50% 절감. 기존 단독 사용 호환 100% 유지. pytest 70건 전체 통과, pyright 에러 0건.

## 수정 파일 목록

- `/home/jay/projects/ThreadAuto/publisher/cross_publisher.py` — 렌더링 로직 상위 이동 (1회 렌더링 + 양 publisher에 image_paths 전달)
- `/home/jay/projects/ThreadAuto/publisher/threads_publisher.py` — `image_paths: list[str] | None = None` 파라미터 추가, 조건부 렌더링 스킵
- `/home/jay/projects/ThreadAuto/publisher/instagram_publisher.py` — 동일하게 파라미터 추가, 조건부 렌더링 스킵
- `/home/jay/projects/ThreadAuto/tests/test_cross_publisher.py` — 신규 테스트 4건 추가 + 기존 테스트 2건 보정
- `/home/jay/projects/ThreadAuto/tests/test_publisher.py` — 신규 테스트 2건 추가 (TestPublishCardnewsV2 클래스)
- `/home/jay/projects/ThreadAuto/tests/test_instagram_publisher.py` — 신규 테스트 2건 추가

## 테스트 결과

- pytest: **70 passed** (3.83s)
  - test_cross_publisher.py: 17건 (기존 13 + 신규 4)
  - test_publisher.py: 41건 (기존 39 + 신규 2)
  - test_instagram_publisher.py: 12건 (기존 10 + 신규 2)
- pyright: **0 errors, 0 warnings**
- black + isort: 포매팅 적용 완료

## 발견 이슈 및 해결

### 자체 해결 (3건)

1. **기존 test_cross_publisher.py pyright 에러 80건** — `dict` 타입 추론 문제. `_CARDNEWS_KWARGS`와 kwargs 변수에 `dict[str, Any]` 타입 명시로 해결.
   - 수정: test_cross_publisher.py에 `from typing import Any` 추가 + 타입 어노테이션

2. **기존 파라미터 전달 테스트(#6, #7) 실패** — CrossPublisher가 이제 `image_paths`도 전달하므로 `assert_called_once_with`가 정확히 매칭되지 않음. `image_paths=ANY` 추가로 해결.
   - 수정: `from unittest.mock import ANY` 추가 + 기존 assert에 `image_paths=ANY` 파라미터 추가

3. **content=None 경로에서 렌더링 fallback** — CrossPublisher의 auto_generate=False + content=None 케이스에서 `render_all`이 호출되는데, mock의 return_value가 설정되지 않아 테스트 실패. `_RENDERED_PATHS` 상수로 일관된 mock 반환값 설정.
   - 수정: test_cross_publisher.py에 `_RENDERED_PATHS` 상수 + mock_renderer.render_all.return_value 설정

### 범위 외 미해결 (0건)

없음.

## 설계 결정

- **fallback 전략**: CrossPublisher 렌더링 실패 시 `pre_rendered_paths=None` 설정 → 각 publisher가 자체 렌더링. 이로써 단일 장애점(SPOF)을 방지하고 기존 호환성을 보장.
- **V2 + legacy 모두 지원**: CrossPublisher가 slides 유무에 따라 `render_from_slides` / `render_all` 분기 처리.
- **이미지 파일 삭제 방지**: 한쪽 publisher가 파일을 삭제하지 않으므로 파일 공유 안전 (기존 publisher에 삭제 로직 없음).

## QC 자동 검증

- **Overall**: PASS (8 PASS, 4 SKIP)
- file_check: PASS (3파일 크기 확인)
- data_integrity: PASS
- test_runner: PASS (29 passed in 3.39s)
- pyright_check: PASS (0 errors, 0 warnings)
- style_check: PASS (black OK, isort OK)
- critical_gap: PASS
- spec_compliance: PASS
- duplicate_check: PASS (최대 유사도 13.1%)
- api_health: SKIP (서버 작업 아님)
- tdd_check: SKIP (audit-trail 감지 제한)
- schema_contract: SKIP (workers 없음)
- scope_check: SKIP
