# InsuRo 트렌드 인사이트 — Phase 1 패치 (17사이클 미팅 반영)

## 작업 레벨: Lv.2

## 프로젝트
- 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-30-keyword-pool-limit-overcome.md`

## 배경
17사이클 에이전트 미팅 결과를 Phase 1 코드에 반영하는 패치 작업. 기존 코드는 정상 동작 중이며, 설정값 변경 + 파일 추가가 핵심.

## 수정 사항 (7개)

### 1. keyword_pool_refresh.py — 설정값 변경

```python
# 변경 전
MAX_KEYWORDS = 2_000
MIN_MONTHLY_SEARCH = 100

# 변경 후
MAX_KEYWORDS = 3_500
MIN_MONTHLY_SEARCH = 50
```

### 2. keyword_pool_refresh.py — BLOCKLIST 2패턴 추가

기존 BLOCKLIST_PATTERNS 리스트 맨 뒤에 추가:

```python
r"살인|자살|폭행|성범죄|음주운전|마약",
r"파산|부도|횡령|배임",
```

### 3. keywords 테이블 — is_pinned 컬럼 추가

SQL 마이그레이션 파일 생성: `server/migrations/007_keywords_is_pinned.sql`

```sql
-- 007: keywords 테이블 is_pinned 컬럼 추가 (17사이클 미팅)
ALTER TABLE keywords ADD COLUMN IF NOT EXISTS is_pinned BOOLEAN DEFAULT FALSE;
```

★ Supabase Management API로 실행:
```python
from dotenv import load_dotenv
import os, requests
load_dotenv(dotenv_path='/home/jay/workspace/.env.keys')
url = os.environ.get('INSURO_NEW_SUPABASE_URL','')
ref = url.replace('https://','').split('.')[0]
mgmt_key = os.environ.get('SUPABASE_ACCESS_TOKEN','')
with open('server/migrations/007_keywords_is_pinned.sql') as f:
    sql = f.read()
resp = requests.post(
    f'https://api.supabase.com/v1/projects/{ref}/database/query',
    headers={'Authorization': f'Bearer {mgmt_key}', 'Content-Type': 'application/json'},
    json={'query': sql}, timeout=30
)
print(f"Status: {resp.status_code}")
```

### 4. 래퍼 스크립트 생성 — run_trend_pipeline.sh

파일: `server/scripts/run_trend_pipeline.sh`

```bash
#!/bin/bash
set -e

LOCKFILE="/tmp/trend-pipeline.lock"
LOGDIR="/tmp/trend-pipeline-logs"
mkdir -p "$LOGDIR"

exec 200>"$LOCKFILE"
flock -n 200 || { echo "이미 실행 중"; exit 1; }

log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOGDIR/pipeline-$(date +%F).log"; }

cd /home/jay/projects/InsuRo/server
source /home/jay/workspace/.env.keys 2>/dev/null

log "Step 1: daily_trend_collect.py 시작"
python3 scripts/daily_trend_collect.py >> "$LOGDIR/trend-$(date +%F).log" 2>&1
TREND_EXIT=$?
log "Step 1 완료 (exit=$TREND_EXIT)"

log "Step 2: daily_saturation_collect.py 시작"
python3 scripts/daily_saturation_collect.py >> "$LOGDIR/saturation-$(date +%F).log" 2>&1
SAT_EXIT=$?
log "Step 2 완료 (exit=$SAT_EXIT)"

if [ $TREND_EXIT -eq 0 ] && [ $SAT_EXIT -eq 0 ]; then
    log "Step 3: daily_ranking_calc.py 시작"
    python3 scripts/daily_ranking_calc.py >> "$LOGDIR/ranking-$(date +%F).log" 2>&1
    log "Step 3 완료 (exit=$?)"
else
    log "Step 3 SKIP — 선행 실패 (trend=$TREND_EXIT, sat=$SAT_EXIT)"
fi

log "Step 4: daily_health_check.py 시작"
python3 scripts/daily_health_check.py >> "$LOGDIR/health-$(date +%F).log" 2>&1
log "Step 4 완료 (exit=$?)"

log "파이프라인 종료"
```

chmod +x 필수.

### 5. 크론 교체 — 5개 → 2개

기존 크론 5개 제거 후 아래 2개로 교체:

```
0 3 1 * * cd /home/jay/projects/InsuRo/server && source /home/jay/workspace/.env.keys && python3 scripts/keyword_pool_refresh.py >> /tmp/keyword-pool-refresh.log 2>&1
0 6 * * * /home/jay/projects/InsuRo/server/scripts/run_trend_pipeline.sh
```

### 6. config 파일 2개 생성

파일: `server/config/trend-insight-config.json`

```json
{
  "pool_limits": {
    "core_max": 3000,
    "explore_max": 500,
    "total_max": 3500,
    "overflow_strategy": "evict_lowest_score",
    "pin_max": 100
  },
  "degradation_levels": {
    "NORMAL": "SearchAd + DataLab + Blog — 전체 기능",
    "LEVEL_1": "DataLab + Blog only — 검색량 바 스태일",
    "LEVEL_2": "Blog only — 포화도+delta 기반",
    "LEVEL_3": "캐시 only — 마지막 수집 데이터 표시"
  },
  "surge_detection": {
    "default_threshold": 200,
    "season_threshold": 300,
    "baseline": "4week_same_weekday_avg"
  },
  "news_pipeline": {
    "auto_approve": true,
    "weekly_limit": 10,
    "target_pool": "exploration",
    "archive_days": 30
  },
  "api": {
    "primary_env_prefix": "NAVER",
    "standby_env_suffix": "_STANDBY",
    "failover_threshold": 3,
    "recovery_check_minutes": 30
  }
}
```

파일: `server/config/season_calendar.json`

```json
{
  "version": "1.0",
  "seasons": [
    {
      "id": "tax_season",
      "name": "연말정산/세액공제",
      "period": {"start": "12-01", "end": "01-15"},
      "boost_multiplier": 2.0,
      "keywords_pattern": ["연말정산", "세액공제", "보장성보험.*공제", "연금저축"],
      "related_products": ["보장성보험", "연금저축"],
      "surge_threshold_override": 200
    },
    {
      "id": "enrollment_season",
      "name": "입학/신학기",
      "period": {"start": "02-01", "end": "03-15"},
      "boost_multiplier": 1.3,
      "keywords_pattern": ["자녀.*보험", "어린이보험", "학자금", "교육보험"],
      "related_products": ["어린이보험", "교육보험"]
    },
    {
      "id": "car_insurance_q1",
      "name": "자동차보험 갱신 시즌",
      "period": {"start": "03-01", "end": "03-31"},
      "boost_multiplier": 1.5,
      "keywords_pattern": ["자동차보험", "자동차보험비교", "다이렉트.*보험"],
      "related_products": ["자동차보험", "운전자보험"]
    },
    {
      "id": "vacation_summer",
      "name": "여름휴가/여행",
      "period": {"start": "06-15", "end": "08-31"},
      "boost_multiplier": 1.3,
      "keywords_pattern": ["여행.*보험", "해외.*보험", "여행자보험"],
      "related_products": ["여행자보험", "해외여행보험"]
    },
    {
      "id": "health_checkup",
      "name": "건강검진 시즌",
      "period": {"start": "09-01", "end": "11-30"},
      "boost_multiplier": 1.5,
      "keywords_pattern": ["건강검진", "실비.*갱신", "의료비", "실손보험"],
      "related_products": ["실손보험", "건강보험"],
      "surge_threshold_override": 250
    },
    {
      "id": "year_end",
      "name": "연말 재정비",
      "period": {"start": "11-01", "end": "12-31"},
      "boost_multiplier": 1.8,
      "keywords_pattern": ["보험.*리모델링", "보험.*정리", "보장분석", "보험.*갈아타기"],
      "related_products": ["종합보험", "보장분석"],
      "surge_threshold_override": 250
    }
  ],
  "global_config": {
    "default_surge_threshold": 200,
    "season_surge_threshold": 300,
    "overlap_strategy": "max_boost"
  }
}
```

### 7. keyword_pool_refresh.py 재실행

수정 완료 후 재실행하여 3,500개 키워드 수집:

```bash
cd /home/jay/projects/InsuRo/server
source /home/jay/workspace/.env.keys
python3 scripts/keyword_pool_refresh.py
```

실행 후 확인:
- keywords 테이블 활성 키워드 수 = 3,000~3,500개
- 상위 20개에 보험 무관 키워드 없음
- is_pinned 컬럼 존재 확인

## affected_files
- `server/scripts/keyword_pool_refresh.py` (수정 — MAX/MIN/BLOCKLIST)
- `server/migrations/007_keywords_is_pinned.sql` (신규)
- `server/scripts/run_trend_pipeline.sh` (신규)
- `server/config/trend-insight-config.json` (신규)
- `server/config/season_calendar.json` (신규)
- crontab (수정 — 5→2 교체)

## 검증 시나리오
1. MAX_KEYWORDS=3500, MIN_MONTHLY_SEARCH=50 확인 (grep)
2. BLOCKLIST_PATTERNS 10개 패턴 확인 (기존 8 + 신규 2)
3. keywords 테이블에 is_pinned 컬럼 존재 확인
4. run_trend_pipeline.sh 수동 실행 → 4단계 순차 실행 로그 확인
5. crontab -l → 2개 크론만 존재 (keyword_pool + pipeline)
6. config 2개 파일 존재 + JSON 파싱 정상
7. keyword_pool_refresh.py 재실행 → 활성 키워드 3,000~3,500개
8. 상위 20개 키워드 전부 보험 관련 확인
