# Task-516 완료 보고서: ThreadAuto 배치→온디맨드 단건 생성 전환

## SCQA

**S**: ThreadAuto는 daily_runner.py가 새벽에 20개 콘텐츠를 배치 생성하고, publish_worker.py 데몬이 시간에 맞춰 발행하는 구조로 운영 중이다.

**C**: 배치 생성 시 content_type 누락(항상 cardnews fallback), 텍스트 타입에도 불필요한 이미지 렌더링, 20개를 한 세션에서 생성하여 Claude 응답 반복/유사화 등 3개 핵심 버그가 존재한다.

**Q**: 스케줄 시간 도래 시 1개씩 온디맨드 생성하여 품질을 확보하고, content_type 파이프라인을 관통시킬 수 있는가?

**A**: scheduler/on_demand_runner.py 신규 모듈(빈 큐 생성 + 단건 콘텐츠 생성 + 조건부 이미지 렌더링)을 구현하고, publish_worker.py에 15분 전 생성 트리거를 추가하여 온디맨드 플로우를 완성했다. content_type이 큐 전체 파이프라인을 관통하며, 기존 배치 플로우는 하위호환 유지. pytest 204건 전체 통과, 새 타입 에러 0건.

## 수행 내용

### 1. scheduler/on_demand_runner.py (신규)
- `generate_empty_daily_queue(target_date)`: 20슬롯 빈 큐 생성 (시간표+카테고리+content_type만, content=None, status="scheduled")
- `generate_single_content(slot_index, queue_path)`: 슬롯 1개 콘텐츠 즉시 생성 → 컴플라이언스 필터 → 조건부 이미지 렌더링 → 큐 업데이트 → 텔레그램 프리뷰 전송
- `render_content_images(content, content_type)`: text_* → 이미지 스킵, video → 스킵, cardnews → 카드뉴스 렌더링
- fcntl.flock 파일 잠금 적용 (concurrent access 안전)

### 2. scheduler/publish_worker.py (수정)
- `get_scheduled_posts()` 추가: status="scheduled" + publish_time 15분 이내 포스트 탐색
- `_run_approval_mode_cycle()` 확장: approved 발행 → scheduled 생성 트리거 → pending 하위호환 순서로 처리
- content_type fallback에 WARNING 로그 추가 (기존 `post.get("content_type", "cardnews")` → None 체크 + 경고)

### 3. scheduler/daily_runner.py (최소 수정)
- `build_daily_queue()`: post dict에 `content_type` 필드 추가 (콘텐츠 있는 슬롯: content에서 추출, 없는 슬롯: None)

## 생성/수정 파일 목록

- **신규**: `scheduler/on_demand_runner.py`, `tests/test_on_demand_runner.py`
- **수정**: `scheduler/publish_worker.py`, `scheduler/daily_runner.py`, `tests/test_publish_worker.py`
- **원복**: `pyrightconfig.json` (서브태스크 작업 중 수정 → 원래 상태로 복원)

## 새 큐 상태 플로우
```
scheduled → generating → awaiting_approval → approved → published
                                           → rejected
```

## 테스트 결과

- **관련 모듈 pytest**: 204 passed / 0 failed (0.68s)
  - test_on_demand_runner.py: 24 passed (신규)
  - test_publish_worker.py: 72 passed (기존 60 + 신규 12)
  - test_daily_runner.py: 46 passed
  - test_topic_selector.py: 62 passed
- **전체 프로젝트 pytest**: 426 passed / 1 failed
  - ⚠️ 기존 테스트 실패 1건 (본 작업 범위 외): `test_cta_linebreak.py::TestFactDbContainsBusinessPage::test_fact_db_contains_business_page` — fact_db.md에 '사업단 페이지' 표기 누락 (기존 데이터 이슈)
- **pyright**: 기존 reportMissingImports만 (동적 sys.path 이슈), 새 타입 에러 0건
- **black + isort**: 적용 완료

## QC 자동 검증 결과
- file_check: PASS (6/6)
- tdd_check: PASS (테스트+구현 파일 모두 존재)
- pyright_check: WARN (기존 reportMissingImports only)
- style_check: WARN → 수정 후 재적용 완료
- data_integrity: FAIL (task-516 vs task-516.1 명명 불일치 — dispatch.py 자동 넘버링)
- test_runner: FAIL (기존 실패 1건 — 본 작업 범위 외)

## 발견된 이슈

1. **[WARN] `_load_cache` private 함수 import**: on_demand_runner.py에서 topic_selector._load_cache를 직접 import — 향후 public API로 전환 권장
2. **[WARN] select_daily_topics()의 사이드이펙트**: generate_empty_daily_queue()에서 빈 큐 생성 시 select_daily_topics()가 used_count를 갱신. 실제 콘텐츠 생성 시에는 select_single_topic()으로 별도 토픽을 선택하므로, 빈 큐의 카테고리 배정용으로만 사용됨. 의도적 설계이나 토픽 풀 소모가 빨라질 수 있음
3. **[INFO] daily_runner.py 보존**: 기존 배치 생성 코드 삭제하지 않고 보존 (백업용, 지시대로)
4. **[INFO] 기존 테스트 실패**: test_cta_linebreak.py 1건 기존 실패 — fact_db.md 데이터 이슈, 본 작업 범위 외

## 머지 판단
- **머지 필요**: Yes
- **브랜치**: task/task-516-dev2
- **워크트리 경로**: /home/jay/projects/ThreadAuto/.worktrees/task-516-dev2
- **머지 의견**: pytest 204건 전체 통과, 기존 테스트 회귀 0건, 하위호환 유지. 머지 후 daily_runner.py의 새벽 cron 대신 on_demand_runner를 사용하도록 cron 설정 변경 필요 (운영 전환은 별도 작업).
