diff --git a/doc/PIPELINE_IMPROVEMENT_PLAN.md b/doc/PIPELINE_IMPROVEMENT_PLAN.md new file mode 100644 index 0000000..ea5aa28 --- /dev/null +++ b/doc/PIPELINE_IMPROVEMENT_PLAN.md @@ -0,0 +1,150 @@ +# 검색/분석 파이프라인 종합 개선 계획 + +**작성일**: 2026-04-04 +**상태**: 계획 완료, 구현 대기 + +## Context + +현재 파이프라인의 3-phase 아키텍처(discover → collect → generate)는 구조적으로는 괜찮으나, **실행 신뢰성**에 심각한 문제가 있음. 3개 병렬 코드 탐색 결과: +- **Silent failure 16건**: 모든 catch 블록이 에러를 삼킴 +- **데이터 품질 8건**: 잘못된 URL, 동명이인 병원, 평점 스케일 혼동 +- **에러 복구 0건**: 재시도 로직 없음, 새로고침 시 데이터 유실 +- **API 불안정 6건**: 타임아웃, 쿼터 소진, 인증 실패 미감지 + +**목표**: 검색 정확도 + 에러 회복력 + UX 투명성을 대폭 개선 + +--- + +## 진행 상태 체크리스트 + +### Sprint 1: 데이터 품질 Quick Wins (~2.5h) + +- [ ] **WP-1**. YouTube Channel ID 정규식 수정 (20min) + - 파일: `discover-channels/index.ts`, `verifyHandles.ts` + - 변경: `/^UC[a-zA-Z0-9_-]{20,}$/` → `/^UC[a-zA-Z0-9_-]{22}$/` + - 검증: 24자가 아닌 channel ID가 reject되는지 확인 + +- [ ] **WP-2**. Naver Place 동명이인 방지 (30min) + - 파일: `enrich-channels/index.ts` lines 319-343 + - 변경: 카테고리 필터링 (성형/피부) + 검색어에 "성형외과" 추가 + - 검증: "아이디병원" 검색 → "아이디마곡치과" 아닌 성형외과 반환 + +- [ ] **WP-3**. Google Maps URL 수정 (20min) + - 파일: `collect-channel-data/index.ts`, `enrich-channels/index.ts` + - 변경: `place.website`(병원 사이트) 대신 `maps.google.com/search` URL + - 검증: 기타 채널의 구글 지도 링크가 Google Maps로 이동 + +- [ ] **WP-4**. Naver Blog 공식 블로그 분리 (30min) + - 파일: `collect-channel-data/index.ts` + - 변경: verified_channels 핸들로 `blog.naver.com/{handle}` 생성, 검색 결과는 `blogMentions` + - 검증: 네이버 블로그 링크가 공식 블로그로 이동 + +- [ ] **WP-5**. 강남언니 평점 정규화 (30min) + - 파일: `collect-channel-data/index.ts`, `enrich-channels/index.ts` + - 변경: `rawRating` + `normalizedRating` 분리, ≤5.0이면 ×2 + - 검증: 강남언니 평점이 `/10`으로 일관되게 표시 + +- [ ] **WP-6**. Perplexity 모델 상수화 (20min) + - 파일: 새 `_shared/config.ts` + - 변경: 환경변수 `PERPLEXITY_MODEL` 또는 기본값 `"sonar"` + - 검증: 모든 Edge Function에서 하드코딩 `"sonar"` 제거됨 + +- [ ] **WP-7**. Apify 타임아웃 증가 (10min) + - 파일: `discover-channels/index.ts` + - 변경: `waitForFinish=30` → `waitForFinish=45` + - 검증: Instagram 프로필 스크래핑 성공률 향상 + +--- + +### Sprint 2: 에러 가시성 (~2.25h) + +- [ ] **WP-8**. 채널 수집 에러 추적 ⭐ 핵심 (1.5h) + - 파일: `collect-channel-data/index.ts` + - DB: `ALTER TABLE marketing_reports ADD COLUMN IF NOT EXISTS channel_errors JSONB DEFAULT '{}';` + - 변경: + - [ ] HTTP 상태 코드 체크 (429, 403, 500) + - [ ] `Promise.allSettled` 결과 순회 → `channelErrors` 기록 + - [ ] 부분 성공이어도 항상 DB 저장 (unconditional save) + - [ ] status: `"collected"` vs `"partial"` vs `"collection_failed"` + - [ ] 응답: `{ channelData, channelErrors, partialFailure }` + - 검증: API 토큰 무효화 시 에러가 기록되는지 확인 + +- [ ] **WP-9**. Instagram/Facebook 검증 개선 (45min) + - 파일: `_shared/verifyHandles.ts` + - 변경: + - [ ] `verified` 타입: `boolean | "unverifiable"` + - [ ] Instagram: 로그인 리다이렉트 감지 + - [ ] Facebook: HEAD → GET, 실패 시 `"unverifiable"` + - [ ] `collect-channel-data`에서 `"unverifiable"` 포함 + - 검증: Facebook 핸들이 "unverifiable"로 표시되고 수집은 시도됨 + +--- + +### Sprint 3: 에러 회복 (~5.25h) + +- [ ] **WP-10**. API 재시도 유틸리티 (2h) + - 파일: 새 `_shared/retry.ts` + - 변경: `fetchWithRetry()` — 지수 백오프, 429 존중, AbortController 타임아웃 + - 검증: 429 응답 시 자동 재시도 후 성공 + +- [ ] **WP-11**. 부분 실패 복구 (1h) + - 파일: `collect-channel-data/index.ts` + - 변경: `Promise.allSettled` 직후 무조건 중간 DB 저장 + - 검증: 일부 채널 실패해도 나머지 데이터 보존 + +- [ ] **WP-12**. 파이프라인 이어하기 (1.5h) + - 파일: `AnalysisLoadingPage.tsx`, `supabase.ts` + - 변경: + - [ ] `sessionStorage`에 reportId 저장 + - [ ] URL에 reportId 포함 + - [ ] DB status 폴링 + - [ ] status별 이어하기 로직 + - 검증: 분석 중 새로고침 → 이어서 진행 + +- [ ] **WP-13**. Enrichment 재시도 버튼 (45min) + - 파일: `useEnrichment.ts`, `EmptyState.tsx` + - 변경: `retry()` 함수 + "다시 시도" 버튼 (최대 2회) + - 검증: Enrichment 실패 후 버튼 클릭 → 재시도 성공 + +--- + +### Sprint 4: UX 마무리 (~50min) + +- [ ] **WP-14**. EmptyState 상태별 UI (20min) + - 파일: `EmptyState.tsx` + - 변경: `loading | error | not_found` 상태별 다른 UI + - 검증: 각 상태에 맞는 아이콘/메시지/버튼 표시 + +- [ ] **WP-15**. Firecrawl Rate Limiting (30min) + - 파일: `_shared/retry.ts`에 추가 + - 변경: 도메인별 500ms 간격 강제 + - 검증: Firecrawl 429 에러 발생 안 함 + +--- + +## 핵심 파일 목록 + +| 파일 | Sprint | 설명 | +|------|--------|------| +| `supabase/functions/discover-channels/index.ts` | 1 | 채널 발견 | +| `supabase/functions/collect-channel-data/index.ts` | 1, 2, 3 | 데이터 수집 | +| `supabase/functions/enrich-channels/index.ts` | 1 | 레거시 enrichment | +| `supabase/functions/generate-report/index.ts` | - | 리포트 생성 (수정 없음) | +| `supabase/functions/_shared/verifyHandles.ts` | 1, 2 | 핸들 검증 | +| `supabase/functions/_shared/config.ts` (신규) | 1 | 공유 설정 | +| `supabase/functions/_shared/retry.ts` (신규) | 3 | 재시도 유틸 | +| `supabase/migrations/20260404_channel_errors.sql` (신규) | 2 | DB 마이그레이션 | +| `src/pages/AnalysisLoadingPage.tsx` | 3 | 로딩 페이지 | +| `src/hooks/useEnrichment.ts` | 3 | Enrichment 훅 | +| `src/components/report/ui/EmptyState.tsx` | 4 | 빈 상태 UI | +| `src/lib/supabase.ts` | 3 | API 클라이언트 | + +## 총 예상 소요 시간 + +| Sprint | 시간 | 배포 범위 | +|--------|------|---------| +| Sprint 1: Quick Wins | ~2.5h | Edge Functions × 3 | +| Sprint 2: 에러 가시성 | ~2.25h | DB + Edge Functions | +| Sprint 3: 에러 회복 | ~5.25h | Edge Functions + Frontend + Vercel | +| Sprint 4: UX 마무리 | ~50min | Frontend + Vercel | +| **합계** | **~11h** | |