docs(spec): 추천 채널 발굴(떡상 Shorts) 설계 추가

지역(KR,JP,US) 인기 Shorts 주기 검색 → 작은구독자·고배율 떡상 채널 발굴 →
RecommendedChannel 저장 → /recommend 페이지에서 등록/제외. 쿼터 가드·기존 검색 재사용.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hehihoho3@gmail.com 2026-06-26 10:07:23 +09:00
parent 279cb4c9d0
commit 9fed1e04b9

View File

@ -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<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)
- 구독자 증가율 추적(스냅샷 누적 필요)
- 키워드 기반 검색(이번엔 지역 광범위)
- 자동 등록(수동 검토 유지)
- 추천 사유 다중 지표(이번엔 배율 단일 기준)