diff --git a/docs/superpowers/specs/2026-06-25-recommended-channel-discovery-design.md b/docs/superpowers/specs/2026-06-25-recommended-channel-discovery-design.md new file mode 100644 index 0000000..1842f91 --- /dev/null +++ b/docs/superpowers/specs/2026-06-25-recommended-channel-discovery-design.md @@ -0,0 +1,125 @@ +# 추천 채널 발굴 (떡상 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 runDiscovery()`: + 1. regions 별 인기 Shorts 조회(쿼터 가드로 지역마다 잔여 확인 후 진행). + 2. 결과 영상 → 채널별 최고 배율 집계(`Map`). + 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) + +- 구독자 증가율 추적(스냅샷 누적 필요) +- 키워드 기반 검색(이번엔 지역 광범위) +- 자동 등록(수동 검토 유지) +- 추천 사유 다중 지표(이번엔 배율 단일 기준)