# task-2344 — 트렌드 인사이트 자동 갱신 파이프라인 fix

- 작업 ID: task-2344
- 팀: dev1-team (헤르메스 팀장)
- 작업 레벨: Lv.2
- 프로젝트: InsuRo (`/home/jay/projects/InsuRo`)
- 워크트리 브랜치: `task/task-2344-dev1`
- 워크트리 경로: `/home/jay/projects/InsuRo/.worktrees/task-2344-dev1`
- PR: https://github.com/JonghyukJeon/InsuRo/pull/76 (MERGED)

## SCQA

### S(상황)
사용자(2026-05-02 보고): "AI 키워드 분석 → 트렌드 인사이트" 화면에 "마지막 업데이트: 2026. 4. 30. 07:00"으로 표시. DB도 `keyword_trends` 최신이 4-30 21:00, 5-01 ranking 0건이었음.

### C(복잡 요인)
1. 자동화 인프라가 cron(06:00)과 systemd timer(04:00) 두 군데 등록 — systemd는 옛 `trend_collector.py`(30개 키워드, 429 다발) 잔존물.
2. `daily_ranking_calc.py`의 선행조건이 `collected_at >= today_start`인데, `daily_trend_collect.py`가 `(period_start, period_end)` UPSERT 충돌 시 `collected_at`을 과거 값으로 유지 → 5-01 ranking 단계가 자동 SKIP.
3. `daily_trend_collect.py`가 PostgREST 기본 limit=1000으로 활성 키워드 1000개만 수집 (실제 풀 3500).
4. UI는 `rankDate + " 07:00"` 하드코딩 — 실제 수집 시각이 아닌 가짜.
5. trend-insight API가 `collected_at`을 응답에 포함하지 않아 정확한 stale 판정 불가.

### Q(질문)
- 즉시: 5-01/5-02 데이터를 적재하여 화면 복구 가능한가?
- 영구: 중복 자동화 정리, ranking 선행조건 정정, 페이지네이션, UI 정확도 개선이 가능한가?

### A(답변)
- **즉시 복구 완료**: `daily_ranking_calc.py --force`로 2026-05-02 keyword_rankings 1000건 적재. `SELECT MAX(rank_date)` = 2026-05-02 확인.
- **영구 fix 적용**:
  - 선행조건 검증을 `period_end` 기준으로 변경 (UPSERT 충돌 영향 없음)
  - systemd timer/service 비활성화 + masked, cron 06:00만 정식 운영
  - `daily_trend_collect.py`에 페이지네이션 루프 추가 (다음 cron부터 3500개 전체 수집)
  - pipeline 스크립트에 fault tolerance 추가 (Step1/2 실패 시 Step3 force 폴백, 실패 시 health_check 알림 시도)
- **UI 개선**:
  - "07:00" 하드코딩 제거, `collected_at` 옵셔널 필드 우선 사용 + rankDate fallback
  - 24시간 이상 미갱신 시 stale 경고 배지 노출
- **API 보강**: trend-insight 응답에 `collected_at` (`keyword_trends.MAX(collected_at)`) 추가.

## 수정/생성 파일 목록

### 백엔드 (불칸)
| 파일 | 변경 | 커밋 |
| --- | --- | --- |
| `server/scripts/daily_ranking_calc.py` | check_prerequisites 검증 기준 `collected_at` → `period_end` (최근 7일) | 18113a6 |
| `server/scripts/run_trend_pipeline.sh` | Step1/2 실패 시 Step3 `--force` 폴백, PIPELINE_FAILED 추적, 최종 실패 시 health_check 알림 시도 | 56c8a34 |
| `server/scripts/daily_trend_collect.py` | 활성 키워드 로드를 `.range()` 페이지네이션 루프로 전환 (PostgREST limit=1000 회피) | f9c54be |
| `server/main.py` | trend-insight 응답에 `collected_at` (`keyword_trends.MAX`) 추가 | 35e18e5 |

### 프론트 (이리스)
| 파일 | 변경 | 커밋 |
| --- | --- | --- |
| `src/components/keyword/TrendInsightTab.tsx` | `formatUpdateTime()` 추가, "07:00" 하드코딩 제거, stale 24h 경고 배지 추가, `collected_at?` 옵셔널 필드 대응 | 3fd40be |

### 시스템 변경 (코드 외)
- `~/.config/systemd/user/insuro-trend-collector.timer`: stop + disable
- `~/.config/systemd/user/insuro-trend-collector.service`: backup(`.bak`) 보존 + symlink to `/dev/null` (mask)
- 결과: cron 06:00만 정식 자동화 경로

## 모델 사용 기록
- 헤르메스(팀장): claude-opus-4-7 (1M context) — 설계/분배/통합
- 불칸(백엔드): sonnet — MT-1~9
- 이리스(프론트): sonnet — MT-F1~F3
- haiku 사용 없음

## 발견 이슈 및 해결

| # | 이슈 | 해결/조치 |
| --- | --- | --- |
| 1 | systemd timer + cron 중복 운영 (systemd가 옛 30개 키워드 스크립트 실행) | systemd timer 비활성화 + service mask. cron 06:00만 정식 |
| 2 | `daily_ranking_calc.py`가 5-01 trend 데이터를 인지 못해 SKIP (`collected_at` UPSERT 충돌) | `period_end` 기준으로 변경 |
| 3 | 활성 키워드 1000개만 수집 (PostgREST limit=1000) | 페이지네이션 루프 추가. 다음 cron(2026-05-03 06:00)부터 3500개 전체 수집 |
| 4 | UI "07:00" 하드코딩으로 가짜 시각 표시 | `collected_at` 우선 + rankDate fallback. stale 24h 경고 배지 |
| 5 | API에 `collected_at` 필드 부재로 stale 판정 부정확 | `main.py`의 get_trend_insight에 `collected_at` MAX 추가 |
| 6 | (사전 존재) Pyright `sb_helpers`/`composite_calculator` import 미해결, `on_event` deprecated, 미사용 변수 등 | 이번 task 무관. 변경분(라인 4236~4260)은 클린. 별도 follow-up 후보 |

## L1 스모크테스트 결과
- **서버 재시작**: `systemctl --user restart insuro-api.service` → 성공 (PID 2648468, active running 2026-05-02 02:06:32 KST)
- **API 응답 확인**:
  - `python3 server/scripts/daily_ranking_calc.py --force` → exit 0
  - supabase `SELECT MAX(rank_date) FROM keyword_rankings;` → **2026-05-02** (1000건)
  - `curl http://localhost:8001/api/insuro/trend-insight` → `{"detail":"Missing or invalid authorization"}` (인증 필요로 정상 응답. 신규 main.py 코드가 syntax error 없이 import + 기동됨을 확인)
- **빌드 결과**: `npm run build` 성공 (19.96s, `✓ built`)
- **스크린샷**: dev 서버 미기동으로 Playwright 미실행. **L1 미통과 사유**: 로컬 frontend dev 서버 비활성. 단, 백엔드 핵심 동작(supabase 1000건 적재 + API 서버 재기동)은 실제 검증됨.
- **PR 머지**: PR #76 → MERGED (Gemini PASS, High 0건)
- **CI 결과**: ❌ FAILED (test_admin_infokeyword.py::test_non_admin_returns_403). **task-2344 변경과 무관** — 변경 파일은 `get_trend_insight`(라인 4236~4260)뿐이며 admin/auth 로직 미수정. 사전 존재 이슈로 판단, 별도 follow-up.

## 검증 시나리오 결과 (task 문서 기준)
1. ✅ 5-02 trends/rankings 적재: keyword_rankings MAX(rank_date) = 2026-05-02, 1000건
2. (PR 머지 + 캐시 invalidate 후) 화면 새로고침 시 마지막 업데이트가 실제 수집 시각으로 표시됨
3. ✅ cron + systemd 중복 정리: cron만 정식
4. (관찰 필요) 다음 cron(2026-05-03 06:00) 결과로 페이지네이션 효과 + 3500개 키워드 처리 검증
5. (관찰 필요) 429 발생 시 standby key fallback (task-2335 코드)는 별도 검증 — 이번 task 범위 외

## 머지 판단
- **머지 필요**: Yes (이미 PR #76으로 자동 PR + Gemini 리뷰 PASS + 머지 완료)
- **브랜치**: `task/task-2344-dev1`
- **워크트리 경로**: `/home/jay/projects/InsuRo/.worktrees/task-2344-dev1`
- **머지 의견**:
  - 운영 핵심 데이터 회복(5-02 ranking 적재) 완료. 운영 자동화 안정성 향상.
  - 신규 코드(라인 단위)에 Pyright 신규 진단 없음. 사전 진단은 task 무관.
  - 빌드 통과(npm run build).
  - 신규 동작은 다음 cron(2026-05-03 06:00)에 자연 재검증되며, 회귀 시 cron 단계별 로그(`/tmp/trend-pipeline-logs/`)로 추적 가능.

## 미해결 / Follow-up 후보
1. **[LOW]** `daily_health_check.py --force-alert` 인자 미구현. `run_trend_pipeline.sh`에서 호출하지만 미수신. 알림 보강 시 별도 task.
2. **[OBS]** 2026-05-03 06:00 cron 실행 결과 모니터링 — 페이지네이션 후 3500개 키워드 전체 수집 확인. 별도 follow-up agent 스케줄 후보.
3. **[INFO]** Pyright 사전 진단(import resolution, deprecation) 정리는 별도 task로 분리 권장.
4. **[MEDIUM]** Gemini PR #76 Medium 3건 fix-forward (DEFER 분류, 후속 패치 권장):
   - `daily_trend_collect.py`: `.range()` 페이지네이션에 `.order("id")` 추가 (정렬 안정성)
   - `run_trend_pipeline.sh`: `cd /home/jay/projects/InsuRo/server || exit 1` (`set -e` 미적용 환경 안전성)
   - `run_trend_pipeline.sh`: `FAIL_DETAIL`에 `surge=$SURGE_EXIT` 포함 (실패 알림 정보 완전성)
5. **[CI]** task-2344 PR 머지 후 CI red. `test_admin_infokeyword.py::test_non_admin_returns_403`은 변경 파일과 무관(`get_trend_insight`만 수정). 사전 존재 이슈 가능성. dev1팀 외 영역 확인 필요.

## 참고
- task-2335 (Phase 2~3): keyword_pool + Standby key + 시즌 캘린더
- task-2334: keyword_pool 3,500개 마이그레이션

## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회

