# InsuRo 트렌드 인사이트 — Phase 1: DB + 키워드 풀 선정

## 작업 레벨: Lv.4 (Phase 1/5)

## 프로젝트
- InsuRo 서버: `/home/jay/projects/InsuRo/server`

## 트렌드 인사이트 3문서 (필수 참조)
- 계획서: `/home/jay/workspace/memory/plans/insuro-trend-insight/plan.md`
- 맥락노트: `/home/jay/workspace/memory/plans/insuro-trend-insight/context-notes.md`
- 체크리스트: `/home/jay/workspace/memory/plans/insuro-trend-insight/checklist.md`
- 에이전트 미팅 기록: `/home/jay/workspace/memory/meetings/2026-04-29-keyword-ranking-redesign.md`

★ 위 3문서를 반드시 읽고 전체 맥락을 파악한 뒤 작업할 것.

## 이 Phase의 범위
체크리스트 항목: **1-6 DB 마이그레이션 + 1-1 키워드 풀 선정 파이프라인**

### 1. DB 5테이블 생성 (Supabase)

```sql
-- 1. 키워드 풀 마스터
CREATE TABLE keywords (
  id SERIAL PRIMARY KEY,
  keyword TEXT NOT NULL UNIQUE,
  category TEXT NOT NULL DEFAULT '기타',
  seed_origin TEXT,
  monthly_search_volume INTEGER DEFAULT 0,
  is_active BOOLEAN DEFAULT true,
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

-- 2. 일별 트렌드 데이터
CREATE TABLE keyword_trends (
  id SERIAL PRIMARY KEY,
  keyword_id INTEGER REFERENCES keywords(id) ON DELETE CASCADE,
  period_start DATE NOT NULL,
  period_end DATE NOT NULL,
  trend_ratio REAL NOT NULL,
  collected_at TIMESTAMPTZ DEFAULT now(),
  UNIQUE(keyword_id, period_start)
);

-- 3. 일별 블로그 포화도
CREATE TABLE keyword_saturation (
  id SERIAL PRIMARY KEY,
  keyword_id INTEGER REFERENCES keywords(id) ON DELETE CASCADE,
  total_blog_count INTEGER NOT NULL,
  collected_at DATE NOT NULL,
  UNIQUE(keyword_id, collected_at)
);

-- 4. 일별 랭킹 결과
CREATE TABLE keyword_rankings (
  id SERIAL PRIMARY KEY,
  keyword_id INTEGER REFERENCES keywords(id) ON DELETE CASCADE,
  rank_date DATE NOT NULL,
  surge_score REAL NOT NULL DEFAULT 0,
  saturation_score REAL NOT NULL DEFAULT 0,
  issue_score REAL NOT NULL DEFAULT 0,
  composite_score REAL NOT NULL DEFAULT 0,
  rank_position INTEGER,
  UNIQUE(keyword_id, rank_date)
);

-- 5. 수집 실패 로그
CREATE TABLE failed_batches (
  id SERIAL PRIMARY KEY,
  batch_type TEXT NOT NULL,
  keywords TEXT[] NOT NULL,
  error_message TEXT,
  retry_count INTEGER DEFAULT 0,
  resolved BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- 인덱스
CREATE INDEX idx_trends_keyword_period ON keyword_trends(keyword_id, period_start DESC);
CREATE INDEX idx_saturation_keyword_date ON keyword_saturation(keyword_id, collected_at DESC);
CREATE INDEX idx_rankings_date_rank ON keyword_rankings(rank_date DESC, rank_position ASC);
CREATE INDEX idx_keywords_active ON keywords(is_active) WHERE is_active = true;
```

★ Supabase SQL Editor에서 직접 실행. 기존 trend_keywords 테이블은 건드리지 않음.

### 2. 시드 키워드 30개 JSON 작성

파일: `server/config/keyword-seeds.json`

```json
{
  "생명보험": ["암보험", "종신보험", "정기보험", "변액보험", "저축보험", "연금보험"],
  "손해보험": ["자동차보험", "운전자보험", "화재보험", "배상책임보험", "여행자보험"],
  "건강보험": ["실손보험", "치아보험", "간병보험", "치매보험", "의료실비"],
  "어린이태아": ["어린이보험", "태아보험", "자녀보험", "교육보험"],
  "노후연금": ["퇴직연금", "개인연금", "IRP", "연금저축", "노후보험"],
  "기타": ["보험비교", "보험리모델링", "보험해지", "보험료절약", "보험설계사"]
}
```

### 3. 키워드 풀 선정 스크립트

파일: `server/scripts/keyword_pool_refresh.py`

기능:
1. `keyword-seeds.json`에서 시드 30개 로드
2. SearchAd API `getRelKeywordStat`으로 각 시드의 연관 키워드 확장 (30회 호출)
3. 중복 제거 (keyword 기준 exact match)
4. 총 검색량(PC+Mobile) 계산
5. 검색량 내림차순 정렬 → 상위 2,000개 컷오프 (하한 월 100회)
6. 노이즈 필터링:
   ```python
   BLOCKLIST_PATTERNS = [r'채용|연봉|시험', r'사기|먹튀|고소', r'대출|카드|주식']
   BRAND_LIST = ["삼성", "한화", "교보", "메리츠", "DB", "현대", "KB", "농협", "롯데", "라이나", "하나", "AIA"]
   ```
7. 카테고리 자동 분류 (규칙 기반):
   ```python
   CATEGORY_RULES = {
     "암/건강": ["암", "건강", "실손", "의료", "치아", "간병", "질병", "수술"],
     "자동차/운전": ["자동차", "운전", "차량", "교통"],
     "생명/종신": ["종신", "정기", "사망", "생명"],
     "저축/연금": ["저축", "연금", "퇴직", "IRP", "변액"],
     "어린이/태아": ["어린이", "태아", "자녀", "교육"],
     "화재/재산": ["화재", "주택", "재산", "배상"],
     "비교/행동": ["비교", "추천", "해지", "갈아타기", "리모델링", "가입"],
     "기타": []
   }
   ```
8. keywords 테이블에 INSERT/UPDATE (is_active 관리)
9. 결과 Telegram 알림: "키워드 풀 갱신 완료 — 신규 +N, 비활성 -N, 활성 N개"

★ SearchAd API 키: .env.keys의 NAVER_SEARCHAD_CUSTOMER_ID, NAVER_SEARCHAD_API_KEY, NAVER_SEARCHAD_SECRET_KEY
★ rate limit: 0.5초 간격으로 호출
★ 기존 `_get_naver_search_volume` 함수(main.py 라인 1921)의 인증 로직 참조

### 4. 크론 등록 (이 Phase에서는 등록만, 실행은 Phase 2)

```
# 매월 1일 03:00 — 키워드 풀 갱신
0 3 1 * * cd /home/jay/projects/InsuRo/server && python3 scripts/keyword_pool_refresh.py >> /tmp/keyword-pool-refresh.log 2>&1
```

## affected_files
- Supabase SQL (신규 — 5테이블 + 4인덱스)
- `server/config/keyword-seeds.json` (신규)
- `server/scripts/keyword_pool_refresh.py` (신규)

## 검증 시나리오
1. Supabase에서 5테이블 정상 생성 확인 (SQL 실행)
2. `keyword_pool_refresh.py` 실행 → keywords 테이블에 2,000개 내외 INSERT
3. 각 키워드에 category 정상 분류
4. 노이즈 키워드(채용/사기 등) 필터링 확인
5. 검색량 하한 100회 미만 키워드 제외 확인
6. 기존 trend_keywords 테이블 영향 없음
