# 발굴(Discovery) 전용 페이지 — 설계 작성일: 2026-05-30 상태: 승인됨 (구현 대기) ## 목적 수집된 영상(`ChannelVideo`) 중 "재가공할 만한 떡상 후보"를 빠르게 골라내는 일일 작업 화면을 제공한다. 파이프라인 "수집→**분류/발굴**→재가공→유통"에서 발굴 단계를 전용 화면으로 끌어올린다. **제약: 비용 0** — 새로운 외부 API 호출이나 신규 수집 없이, 이미 보유한 파생 지표(`viewsPerHour`, `viewsPerSubRatio`, `viewCount`, `publishedAt`)만으로 랭킹/필터한다. ## 배경 / 현재 상태 - 떡상 발굴은 현재 `GET /api/v1/channel-videos/outperformers` 하나뿐 — `viewsPerSubRatio >= minRatio`인 Shorts를 비율순으로만 반환하며, 대시보드 TOP 리스트로만 노출된다. **전용 작업 화면이 없다.** - 큐레이션 액션(북마크/상태변경/메모/재가공 이동)은 `ChannelVideoCurationController`(`/api/v1/channel-videos/...`)에 이미 구현되어 있다 — 재사용한다. - UI 패턴은 `collection.html`(필터 바 + 테이블 + 행별 인라인 액션)을 따른다. ## 비범위 (YAGNI) - 복합 "발굴 점수"(가중치 합산) — 도입하지 않음. 기존 단일 지표 정렬로 충분. - AI/외부 호출 — 없음. - 새 액션 엔드포인트 — 없음(기존 큐레이션 API 재사용). ## 아키텍처 / 컴포넌트 신규로 추가되는 단위는 다음 5개이며 각각 책임이 단일하다. ### 1. `ChannelVideoRepository.discover(...)` (신규 쿼리) 수집 영상을 발굴 조건으로 필터링하는 조회 쿼리. 입력(모두 nullable, null이면 해당 필터 미적용): - `publishedAfter: LocalDateTime` — 이 시각 이후 업로드분만 (최근성 필터) - `minRatio: BigDecimal` — `viewsPerSubRatio >= minRatio` - `shortsOnly: boolean` — true면 `isShorts = true` - `source: String` — `CHANNEL` | `SEARCH` - `unprocessedOnly: boolean` — true면 상태가 `NEW` 또는 `REVIEWING`인 것만 - `sort: Sort` — 정렬 기준 규칙: **`interestStatus = 'EXCLUDED'`는 항상 제외**한다. ### 2. `ChannelVideoCurationService.discover(...)` (신규 서비스 메서드) 파라미터 검증·기본값 적용 후 리포지토리 호출. - `periodDays`(Integer) → `publishedAfter = now - periodDays일` (null/0이면 전체) - `sortBy` → 기존 `ALLOWED_SORT` 화이트리스트(`viewsPerHour|viewsPerSubRatio|viewCount|publishedAt|durationSec`) 재사용, 기본 `viewsPerSubRatio` 내림차순 - `minRatio` 그대로 전달(null이면 미적용) - 결과 개수는 `limit`(기본 100, 최대 200)으로 캡 ### 3. `GET /api/v1/channel-videos/discover` (신규 엔드포인트) `ChannelVideoCurationController`에 추가. 쿼리 파라미터: `periodDays`, `minRatio`, `shortsOnly`(기본 false), `source`, `unprocessedOnly`(기본 false), `sortBy`, `limit`. 응답: `ApiResponse>`. ### 4. `discover.html` + `/discover` 라우트 - `WebController`에 `GET /discover` 추가 (`currentPage = "discover"`, 뷰 `discover`). - 템플릿은 `collection.html` 구조를 따른다(공유 `layout/base`). - **필터 바**: 기간(전체/7/14/30일), 최소 배율(전체/≥2x/≥3x/≥5x), Shorts만 토글, 출처(전체/CHANNEL/SEARCH), 미처리만 토글, 정렬 드롭다운(떡상속도/배율/조회수/최신). - **결과 테이블**: 썸네일 · 제목 · 채널 · 조회수 · 구독자 · 배율(viewsPerSubRatio) · 떡상속도(viewsPerHour) · 업로드일 · 상태뱃지. - **행별 액션**(기존 API 재사용): 북마크 토글(`POST /{id}/bookmark`), TARGET 지정(`POST /{id}/status`), 🪄 재가공(`/rework/{id}` 이동), YouTube 열기(외부 링크). ### 5. 사이드바 링크 `layout/sidebar.html`에 "발굴" 항목 추가(수집함과 보드 사이 권장). 아이콘 후보: lucide `radar` 또는 `telescope`. `currentPage == 'discover'`일 때 active. ## 데이터 흐름 ``` discover.html (필터 변경/진입) → GET /api/v1/channel-videos/discover?필터 → CurationService.discover() → Repository.discover() → 행 렌더링 → 행별 액션 → 기존 POST 엔드포인트(bookmark/status) → 낙관적 UI 갱신 ``` ## 기본값 (페이지 진입 시) - 기간: 전체 - 최소 배율: 전체(미적용) - Shorts만: off - 출처: 전체 - 미처리만: off (단, EXCLUDED는 항상 숨김) - 정렬: 구독자 대비 배율(viewsPerSubRatio) 내림차순 → 진입 즉시 "구독자 적은데 조회수 터진" 영상이 상단에 온다. ## 에러 / 빈 상태 - 결과 0건: "조건에 맞는 발굴 후보가 없습니다" 안내 행. - API 실패: 테이블에 에러 메시지(`ApiResponse.message`) 표시. (`collection.html` 방식과 동일) - 백필 안 된 옛 데이터(`viewsPerSubRatio = null`)는 배율 정렬/필터에서 자연히 하단·제외됨 — 별도 처리 불필요(기존 백필 기능으로 보정 가능). ## 검증 방법 이 프로젝트는 테스트 인프라가 없고(`src/test` 부재) 원격 운영 DB에 붙으므로, 기존 관행대로 검증한다: 1. `gradlew.bat clean compileJava` 통과. 2. `gradlew.bat bootRun` 기동(빈 와이어링·엔티티 매핑 정상 확인). 3. Swagger(`/swagger-ui.html`)에서 `/discover` 응답 확인 + 브라우저에서 `/discover` 페이지 필터/정렬/액션 동작 수동 확인. ## 영향 / 변경 파일 요약 신규: `discover.html`, 스펙 문서. 수정: `ChannelVideoRepository`(메서드 1), `ChannelVideoCurationService`(메서드 1), `ChannelVideoCurationController`(엔드포인트 1), `WebController`(라우트 1), `layout/sidebar.html`(링크 1). 기존 액션 API·엔티티·다른 페이지는 변경 없음.