h-lab/docs/superpowers/specs/2026-06-25-recommended-channel-discovery-design.md
hehihoho3@gmail.com 9fed1e04b9 docs(spec): 추천 채널 발굴(떡상 Shorts) 설계 추가
지역(KR,JP,US) 인기 Shorts 주기 검색 → 작은구독자·고배율 떡상 채널 발굴 →
RecommendedChannel 저장 → /recommend 페이지에서 등록/제외. 쿼터 가드·기존 검색 재사용.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:07:23 +09:00

126 lines
6.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 추천 채널 발굴 (떡상 Shorts 기반) 설계
- 날짜: 2026-06-25
- 상태: 설계 확정(구현 대기)
## 1. 목적
사이트가 **주기적으로(일 1회) 지역별 인기 Shorts 를 광범위 검색**해, "구독자는 적은데 조회수가
폭발한"(떡상) 채널을 자동 발굴하고, **상위 N개를 추천 채널로 모아** 사용자가 `/recommend` 페이지에서
접속해 보고, 마음에 들면 내 채널로 등록하거나 제외할 수 있게 한다.
기존 `/discover` 발굴은 이미 수집된 **영상** 중 떡상 후보를 찾는 것이고, 이 기능은 새로운 **채널**을
능동적으로 발굴해 추천하는 별도 기능이다.
## 2. 배경 / 재사용 대상
- `service/YoutubeSearchService.searchYoutubeVideos(YoutubeSearchCondition)`: 지역(regionCode) 단위로
YouTube search.list 검색 + 각 영상의 채널 구독자/메타 조회. 결과에 channelId·channelTitle·구독자·조회수 포함.
**광범위 Shorts 검색에 재사용**(type=video, videoDuration=short, order=viewCount, regionCode 별).
- `global/schedule/YoutubeQuotaGuard`: 일일 쿼터 예산 가드. → 발굴도 이 가드 안에서 동작.
- `global/schedule/ScheduledCollectionService`: 기존 일별 스케줄. → 발굴 작업을 여기에 추가(또는 병행).
- `domain/channel/Channel` + `ChannelService.saveChannelFromUrl`/채널 저장: "내 채널 등록" 시 재사용.
- 배율(조회수÷구독자) 개념: 기존 `viewsPerSubRatio` 와 동일 기준.
## 3. 데이터 흐름
```
[스케줄러 일1회 (cron 설정)] enabled & 쿼터 여유?
└─ regions(KR,JP,US) 각각:
YoutubeSearchService 로 인기 Shorts 검색(order=viewCount, short, pages 설정)
→ 영상별 채널 구독자 → 배율 계산
채널 단위 집계: 채널별 '최고 배율 영상' 1건 선정
필터: 구독자 ≤ maxSubscribers AND 배율 ≥ minRatio
AND 이미 등록된 채널(Channel) 아님
AND RecommendedChannel.status = EXCLUDED 아님
RecommendedChannel upsert(channelId 기준): 메타(대표영상·배율·구독자·발견일) 저장/갱신
[/recommend 페이지] status=NEW 를 배율 desc 정렬, 상위 N 표시
카드: 썸네일·채널명·구독자·대표 떡상영상(제목·조회수·배율)·샘플 링크
액션: [내 채널 등록] → Channel 생성 + status=REGISTERED
[제외] → status=EXCLUDED (다음 발굴에서 제외)
```
## 4. 컴포넌트
### 4.1 `RecommendedChannel` 엔티티 + `RecommendedChannelRepository` (신규, `domain/channel`)
- 필드: `id`, `channelId`(unique), `channelTitle`, `thumbnailUrl`, `subscriberCount`,
`status`(NEW|REGISTERED|EXCLUDED), 대표영상(`topVideoId`,`topVideoTitle`,`topVideoViewCount`,`ratio`),
`region`(발견 지역), `discoveredAt`, `updatedAt`(@CreationTimestamp/@UpdateTimestamp).
- Repo: `findByChannelId`, `findByStatusOrderByRatioDesc`, 존재여부 조회 등.
### 4.2 `ChannelDiscoveryService` (신규, `domain/channel` 또는 `service`)
- **검색 API 선택(구현 시 확정)**: 키워드 없는 "광범위 인기 Shorts"는 search.list(q 필요, 100units)보다
**videos.list `chart=mostPopular` + `regionCode`(1 unit, 키워드 불필요)** 가 적합하다. mostPopular 결과를
duration ≤ 60s(Shorts)로 필터링해 후보로 삼는다. 단 mostPopular 는 카테고리 제한이 있을 수 있어,
결과가 빈약하면 search.list(`order=viewCount`, `videoDuration=short`, 일반 q 또는 카테고리)로 보강한다.
`YoutubeSearchService` 에 region 기반 인기영상 조회 메서드를 추가하거나 신규 호출 경로를 둔다.
- `Map<String,Object> runDiscovery()`:
1. regions 별 인기 Shorts 조회(쿼터 가드로 지역마다 잔여 확인 후 진행).
2. 결과 영상 → 채널별 최고 배율 집계(`Map<channelId, Candidate>`).
3. 필터(구독자 상한·배율 하한·등록됨 제외·EXCLUDED 제외).
4. `RecommendedChannel` upsert(있으면 더 좋은 메트릭으로 갱신, status 가 NEW 일 때만).
5. 요약 반환(검색 지역수, 후보수, 신규 저장수, 소비 쿼터).
- 쿼터 부족 시 해당 지역 건너뛰고 로그(기존 패턴).
### 4.3 스케줄 연결
- `ScheduledCollectionService``@Scheduled(cron=${hlab.scheduler.channel-discovery.cron})` 추가 →
`channelDiscoveryService.runDiscovery()`. enabled 플래그로 on/off. 수동 실행용 메서드도 제공.
### 4.4 컨트롤러 (`RecommendedChannelController`, 신규)
- `GET /api/recommended-channels?limit=` → status=NEW, 배율 desc 상위 목록(ApiResponse).
- `POST /api/recommended-channels/{id}/register` → 내 채널 등록(Channel 생성) + status=REGISTERED.
- `POST /api/recommended-channels/{id}/exclude` → status=EXCLUDED.
- `POST /api/recommended-channels/run` → 수동 발굴 트리거(선택, 관리용).
### 4.5 웹 페이지 `/recommend` (신규 `recommend.html` + `WebController` 라우트)
- 사이드바에 "추천 채널" 메뉴 추가(`currentPage="recommend"`).
- 추천 채널 카드 그리드: 썸네일·채널명·구독자·대표 떡상영상(배율 배지)·YouTube 링크.
- 각 카드 [내 채널 등록]/[제외] 버튼 → 위 API 호출 후 목록 갱신.
- 빈 상태/로딩/사용법 안내(기존 페이지 톤 따름).
## 5. 설정 (`application.yml`, 기본값 有)
```yaml
hlab:
scheduler:
channel-discovery:
enabled: ${CHANNEL_DISCOVERY_ENABLED:true}
cron: ${CHANNEL_DISCOVERY_CRON:0 30 4 * * *} # 매일 04:30 (기존 수집 04:00 이후)
discovery:
regions: ${DISCOVERY_REGIONS:KR,JP,US}
max-subscribers: ${DISCOVERY_MAX_SUBS:100000} # 작은 채널 상한
min-ratio: ${DISCOVERY_MIN_RATIO:5.0} # 떡상 배율 하한
pages-per-region: ${DISCOVERY_PAGES:1} # 지역당 검색 페이지 수
top-n: ${DISCOVERY_TOP_N:30} # 추천 페이지 노출 상한
```
## 6. 쿼터/성능
- search.list = 지역·페이지당 약 100 units, channels.list(구독자) = 50채널당 1 unit.
KR,JP,US × 1페이지 ≈ 300 units + 수십 units → 일일 한도(기본 10000) 내 충분.
- `YoutubeQuotaGuard.remaining()` 확인 후 지역 단위로 진행/건너뜀.
## 7. 에러 처리
- 특정 지역 검색 실패 → 그 지역만 건너뛰고 나머지 진행(로그).
- 채널 등록 실패(이미 존재 등) → 사용자에게 메시지, 추천 항목은 REGISTERED 로 정리.
- 쿼터 소진 → 발굴 중단, 다음 주기 재시도.
## 8. 테스트 (`src/test`)
순수 로직 위주:
- 집계/랭킹: 영상 목록 → 채널별 최고 배율 선정, 구독자 상한·배율 하한 필터.
- dedup: 등록된 channelId·EXCLUDED 제외.
- 실제 YouTube/스케줄은 라이브/수동 검증.
## 9. 범위 제외 (YAGNI)
- 구독자 증가율 추적(스냅샷 누적 필요)
- 키워드 기반 검색(이번엔 지역 광범위)
- 자동 등록(수동 검토 유지)
- 추천 사유 다중 지표(이번엔 배율 단일 기준)