# task-3040 — 유튜브 Shorts 영상 수집 제외

**레벨**: Lv.2 · **팀**: dev2-team · **회장 승인 완료** (2026-08-27)
**저장소**: `Jeon-Jonghyuk/InsuWiki` (`/home/jay/projects/insuwiki`, 기본 브랜치 **master**)

## ★ 회장 지시 (원문)
> "shorts 영상은 대상에서 제외해야 할 듯. 왜냐면, **긴 원본 영상을 기준으로 일부만 잘라서
> 편집한게 shorts 영상이기 때문**이야"

**이유가 중요하다**: Shorts 는 원본 롱폼의 발췌이므로 **지식이 중복**된다. 같은 내용이 두 번
카드가 되면 검색 품질이 떨어지고 전사·요약 비용도 이중으로 든다.

---

## 현재 상태 (ANU 실측)

**Shorts 필터가 없다.** 모든 신규 영상을 전사·요약한다.

```python
# youtube_pipeline/main.py:250  — duration 은 이미 조회하고 있다
duration = get_video_duration(video_id, config.YOUTUBE_API_KEY)

# 그 아래 바로 전사로 들어간다
transcript = transcribe(video_id, title, description, duration_seconds=duration)
```

- `get_video_duration()` 이 **ISO 8601 문자열**(`PT25S`, `PT2H20M35S` 등)을 반환한다
- `transcriber.py` 에 `parse_duration_seconds()` 가 **이미 있다**(task-3018). 재사용하라
- ★ `youtube_knowledge` 컬렉션에는 **duration 이 저장되지 않는다**(ANU 확인: 185건 중 0건).
  그래서 **기존 카드 중 Shorts 가 몇 건인지 지금은 알 수 없다**

---

## 할 일

### ① Shorts 판별 후 스킵
`main.py` 에서 duration 조회 **직후, 전사 시작 전에** 판별해 건너뛴다.
- ★ **전사 전에** 걸러야 GPU·API 비용이 절약된다. 전사한 뒤 버리지 마라
- 판별 기준을 **상수 + 환경변수 override** 로 두어라 (예: `SHORTS_MAX_DURATION_SEC`, 기본 60)
- ★ **기준 근거를 조사해서 정하라.** YouTube Shorts 는 과거 60초였으나 정책이 바뀌었다.
  현재 기준을 확인하고, 우리 채널(보험명의정닥터)의 실제 영상 길이 분포를 표본으로 확인한 뒤
  **경계값을 근거와 함께** 정하라. 추측으로 60 을 박지 마라
- duration 조회 실패(`None`)일 때는 **스킵하지 말고 정상 처리**하라(fail-open).
  길이를 모른다고 롱폼을 버리면 손실이 크다

### ② 커서와의 상호작용 ★ 가장 조심할 것
`main.py` 에는 **`lastCrawledAt` fail-closed 커서 가드**(task-3021)가 있다.
처리 실패가 1건이라도 있으면 커서를 전진시키지 않고 다음 회차에 재시도한다.

**Shorts 스킵은 "실패"가 아니라 "정상적으로 처리된 것"이다.**
- Shorts 를 실패로 계수하면 **커서가 영원히 전진하지 못하고 매 회차 같은 구간을 재시도**한다
- 기존 `stats.skipped` 처리 방식(예: 이미 처리된 영상 스킵)을 따르되,
  **Shorts 스킵임을 구분할 수 있게** 로그·통계에 남겨라
- ★ 이 상호작용을 **테스트로 봉인하라**. "Shorts 만 있는 회차에서 커서가 정상 전진하는지"

### ③ 로그로 드러내기
- 몇 건이 Shorts 로 걸러졌는지 **회차 요약에 표시**하라
- 조용히 사라지면 나중에 "왜 이 영상이 안 들어왔지" 를 추적할 수 없다

### ④ 기존 Shorts 카드 — 조사만 하고 삭제하지 마라
- `youtube_knowledge` 에 duration 이 없어 소급 판별이 어렵다.
  **YouTube API 로 기존 영상들의 duration 을 조회해 Shorts 가 몇 건인지 집계**하라(조사)
- ★ **삭제·비활성화는 하지 마라.** 집계 결과와 정리 방안만 보고하고 ANU 판단을 받아라
- 향후를 위해 duration 을 `youtube_knowledge` 에 저장할지도 **제안**하라(이번에 구현할지는 판단)

---

## ★ 하지 말 것
- **커서 가드 완화·제거 금지** (t3021, 라이브 발화 확인됨)
- **fail-closed 완화 금지** (전사 없으면 카드 생성 안 함)
- **1회 처리량 상한 변경 금지** (t3028, OOM 재발 방지)
- **요약 프롬프트·출처 헤더 변경 금지** (t3039 결과물 — 방금 머지됨)
- `firestore_writer.py` 의 `properties` 저장 로직 변경 금지 (t3029)
- 기존 카드·지식 데이터 **삭제 금지**
- `nextapp/**` 변경 금지
- 채널 재활성화 금지 (인스킹은 회장 지시로 비활성)

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "scripts/youtube-pipeline/youtube_pipeline/main.py"
    - "scripts/youtube-pipeline/youtube_pipeline/config.py"
    - "scripts/youtube-pipeline/youtube_pipeline/youtube_api.py"
    - "scripts/youtube-pipeline/tests/**"
    - "scripts/youtube-pipeline/docs/**"
    - "memory/reports/task-3040.md"
  forbidden_paths:
    - "scripts/youtube-pipeline/youtube_pipeline/summarizer.py"
    - "scripts/youtube-pipeline/youtube_pipeline/firestore_writer.py"
    - "scripts/youtube-pipeline/youtube_pipeline/drive_uploader.py"
    - "nextapp/**"
    - ".github/workflows/**"
    - "functions/**"
  commands: ["bash", "python3", "pytest", "gh"]
  merge_policy: "tiered"
  ttl_hours: 24
```
★ `transcriber.py` 는 **읽기만** 하라(`parse_duration_seconds` 재사용). 수정 금지.

## 검증 (실측 강제)
1. **경계값 근거** — Shorts 기준을 무엇으로 정했는지, 근거와 함께.
   정닥터 채널 실제 영상 길이 분포를 **표본으로 제시**하라
2. **판별 정확도** — 실제 영상 ID 몇 건으로 Shorts/롱폼 판별이 맞는지 대조표.
   ★ ANU 가 확인한 실제 사례: `youtube-5wyjdrJNa5c` 는 **PT25S**(Shorts 로 추정)
3. **★ 커서 전진 확인** — Shorts 만 있는 회차에서 `lastCrawledAt` 이 **정상 전진**하는지.
   실패로 계수돼 커서가 묶이면 FAIL
4. **fail-open 확인** — duration 조회 실패 시 스킵되지 않고 정상 처리되는지
5. **비용 절감 확인** — Shorts 가 **전사 전에** 걸러지는지(전사 호출이 일어나지 않아야 한다)
6. **기존 Shorts 집계** — 몇 건인지 숫자로. 삭제하지 말 것
7. **봉인** — Shorts 필터를 무력화하는 변이, Shorts 를 실패로 계수하는 변이를 만들어
   테스트가 FAIL 하는지 확인·복원. 변이가 no-op 이 아님을 `assert` 로 먼저 증명하라
8. `pytest` 회귀 유지 (**base 재측정 기준선** — 현재 273 passed)

## 완료 조건 (DoD)
**Shorts 가 전사 전에 걸러지고, 그 사실이 로그에 드러나며, Shorts 스킵이 커서 전진을 막지 않고,
duration 미상 영상은 정상 처리된다. 기존 Shorts 건수가 집계되어 보고된다.**

## ★ 세션 조기 종료 대책
시작 즉시 브랜치 + 드래프트 PR 을 먼저 열어라. **단계마다 커밋하라.**
검증에서 GPU·RAM 을 장시간 점유하지 마라. 파이프라인 타이머(6시간 주기)와 겹치지 마라.
★ ANU 명세 전제가 틀렸다고 판단되면 구현하지 말고 **반증을 먼저 기록한 뒤 보고하라.**

## 운영 계약
- `origin/master`(=`d2e38b9`) 기준 `git pull --ff-only` 후 시작
- ★ **worktree 를 써라.** 메인 저장소 `/home/jay/projects/insuwiki` 는 **systemd 러너가 직접 실행**한다
- ★ `gh` 호출 시 `GH_TOKEN="$BOT_GITHUB_TOKEN"` 주입 필수 (회장 개인 PAT 금지 — 감사기록 오염)
- 워크플로우 `/home/jay/workspace/prompts/DIRECT-WORKFLOW.md` · QC `/home/jay/workspace/teams/shared/QC-RULES.md`
- `WORKSPACE_ROOT=/home/jay/workspace` · `CHAT_ID=6937032012` · 수집자 key `ANU_KEY=c119085addb0f8b7`
- 완료 경로는 `finish-task.sh` 실행이 유일하다. 수동 `.done` 금지.
- ★ PR Check `validate` 는 이 저장소에서 **상시 적색**이다(`scripts/*.ts` tsc). 교집합 없으면 선재로 판정하고 근거를 적어라

## 보고
**PR 생성까지가 범위다. 머지는 ANU 가 한다. 자동 머지 금지.**
`memory/reports/task-3040.md` 작성 후 표준 완료 콜백 등록. 콜백 프롬프트 **UTF-8 3900 bytes 이하**.
★ 봉투 첫 줄에 **"Shorts 전사 전 차단 여부 + 커서 정상 전진 여부"** 를 담아라.