# task-2136: 3단 하이브리드 키워드 소싱 Phase 1 MVP

## ★ 프로젝트: `/home/jay/projects/InsuRo/`

## 배경
task-2133에서 구글 트렌드 파이프라인 기본 구축 완료 (고정 30개 + pytrends + DB).
10회 에이전트 미팅 합의로 3단 하이브리드 키워드 소싱 Phase 1 확정.
미팅 기록: `/home/jay/workspace/memory/meetings/2026-04-23-google-trends-architecture.md`

## Phase 1 MVP 8개 모듈

### 모듈 1: BaseCollector 추상 클래스
- 파일: `server/collectors/base.py`
- `collect()`, `normalize()`, `filter_negative()`, `run()` (템플릿 메서드)
- 모든 Collector가 상속

### 모듈 2: FixedCollector (L1 고정 30개)
- 파일: `server/collectors/fixed.py`
- 기존 `trend_collector.py` 래핑 (90% 재활용)
- `config/fixed_keywords.txt`에서 30개 로드

### 모듈 3: NaverCollector (L2 네이버 검색광고 API 70개)
- 파일: `server/collectors/naver.py`
- 네이버 검색광고 API v5 사용 (이미 InsuRo에 키 있음: NAVER_SEARCHAD_*)
- 인증: X-API-KEY, X-Customer, X-Timestamp, X-Signature(HMAC-SHA256)
- 엔드포인트: GET /keywordstool — hintKeywords에 시드 키워드 5개씩 배치
- relKeyword + monthlyPcQcCnt + monthlyMobileQcCnt 추출
- 주 1회 수집 (cron: 매주 월요일 03:00)
- 보험 도메인 사전으로 필터링 → 상위 70개 선별

### 모듈 4: KeywordNormalizer
- 파일: `server/utils/normalizer.py`
- 4단계: strip → 공백 정규화 → 특수문자 제거 → 동의어 치환
- 동의어 30쌍: 실비=실손, 종신보험=종신, CI보험=CI 등
- `config/synonyms.json`

### 모듈 5: NegativeFilter
- 파일: `server/utils/negative_filter.py`
- `config/negative_words.json` (50개)
- 법적 리스크: 보험사기, 소송, 거절
- 무관 키워드: 보험 뜻, 보험 영어
- 소비자 전용: 해지방법, 환급금 조회, 계산기

### 모듈 6: 키워드 3상태 모델
- DB: trend_keywords 테이블에 `source`, `status`, `naver_search_volume`, `score` 컬럼 추가
- active (현재 Top 100) / inactive (7일 연속 이탈) / retired (6개월)
- 과거 데이터 영구 보존, 삭제 없음

### 모듈 7: 스코어링 v1
- 네이버 검색량 기반 단일 스코어
- score = (pc_count + mobile_count) 정규화 (0~100)
- 급상승 태그: 구글 트렌드 변동률 기반 (Phase 2에서 합산)

### 모듈 8: 프론트엔드 리스트 뷰
- KeywordAnalysis.tsx에 "키워드 순위" 탭 추가
- TOP 20 카드형 리스트: 순위 | 키워드명 | 검색량 바 | 변동률 화살표 | Layer 태그
- Layer 컬러태그: L1 파랑, L2 초록
- 신뢰도 3단계: 초록(24h), 노랑(7일), 빨강(7일+)
- 미등록 키워드 안내: "트렌드 데이터를 준비 중입니다" + 유사 3개 추천 + "알림 받기" CTA
- inactive 키워드: opacity 50% + "마지막 갱신: YYYY-MM-DD" 표시

## DB 변경 (ALTER TABLE)
```sql
ALTER TABLE trend_keywords ADD COLUMN IF NOT EXISTS source VARCHAR(20) DEFAULT 'fixed';
ALTER TABLE trend_keywords ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'active';
ALTER TABLE trend_keywords ADD COLUMN IF NOT EXISTS naver_search_volume BIGINT DEFAULT 0;
ALTER TABLE trend_keywords ADD COLUMN IF NOT EXISTS score FLOAT DEFAULT 0.0;
ALTER TABLE trend_keywords ADD COLUMN IF NOT EXISTS last_scored_at TIMESTAMPTZ;
ALTER TABLE trend_keywords ADD COLUMN IF NOT EXISTS retired_at TIMESTAMPTZ;
CREATE INDEX IF NOT EXISTS idx_trend_keywords_status_score ON trend_keywords(status, score DESC);
```

## 설정 파일
```
config/
├── fixed_keywords.txt      # 30개 고정 키워드
├── synonyms.json           # 동의어 30쌍
├── negative_words.json     # 네거티브 50개
└── keyword_config.yaml     # 수집 주기, 스코어링 설정
```

## REST API 추가
- GET /api/insuro/keywords/top?limit=20 — TOP N 키워드 (스코어 정렬)
- GET /api/insuro/keywords/search?q=암보험 — 키워드 검색 (정규화 적용)
- GET /api/insuro/keywords/{id} — 키워드 상세 (시계열 + 메타데이터)

## ★ 먼저 읽을 파일
- `/home/jay/projects/InsuRo/server/trend_collector.py` — 기존 수집기 (FixedCollector 래핑 대상)
- `/home/jay/projects/InsuRo/server/main.py` — 기존 API (엔드포인트 추가 위치)
- `/home/jay/projects/InsuRo/src/pages/KeywordAnalysis.tsx` — 기존 프론트 (탭 추가)
- `/home/jay/workspace/memory/meetings/2026-04-23-google-trends-architecture.md` — 미팅 합의

## 검증 시나리오

### 시나리오 1: NaverCollector 수집
네이버 검색광고 API로 시드 키워드 → 연관 키워드 확장 → 검색량 정렬 → 상위 70개 DB 저장

### 시나리오 2: 정규화 + 네거티브 필터
"암 보험" → "암보험" (정규화), "보험사기방법" → 필터링 제외

### 시나리오 3: TOP 20 API
GET /api/insuro/keywords/top?limit=20 → score 내림차순 20개 반환

### 시나리오 4: 미등록 키워드 안내
프론트에서 "간병보험" 검색 (DB에 없음) → "트렌드 데이터를 준비 중입니다" + 유사 3개

### 시나리오 5: inactive 표시
7일 이상 수집 안 된 키워드 → opacity 50% + "마지막 갱신: YYYY-MM-DD"

## 완료 시그니처
- BaseCollector + FixedCollector + NaverCollector 구현
- 정규화 + 네거티브 필터 동작
- TOP 20 리스트 뷰 + 컬러태그 + 신뢰도
- 미등록/inactive 키워드 안내 UX
- 테스트 통과

## 레벨
- critical (Lv.4)

## 프로젝트
- insuro