o2o-infinith-demo/doc/PIPELINE_IMPROVEMENT_PLAN.md

253 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 검색/분석 파이프라인 종합 개선 계획
**작성일**: 2026-04-04
**최종 수정**: 2026-04-04 (Vision Analysis 추가)
**상태**: Sprint 0 + Sprint 1 구현 중
## Context
현재 파이프라인의 3-phase 아키텍처(discover → collect → generate)는 구조적으로는 괜찮으나, **실행 신뢰성**과 **정보 수집 범위**에 심각한 문제가 있음.
### 핵심 문제
- **Vision 분석 부재**: 텍스트만 수집 → 이미지 속 정보(개원 연도, 의료진 사진, 인증 마크, 시술 전후) 100% 누락. 전체 정보의 약 40%를 놓침
- **Silent failure 16건**: 모든 catch 블록이 에러를 삼킴
- **데이터 품질 8건**: 잘못된 URL, 동명이인 병원, 평점 스케일 혼동
- **에러 복구 0건**: 재시도 로직 없음, 새로고침 시 데이터 유실
- **API 불안정 6건**: 타임아웃, 쿼터 소진, 인증 실패 미감지
**목표**: Vision 분석 추가 + 검색 정확도 + 에러 회복력 + UX 투명성 대폭 개선
---
## 진행 상태 체크리스트
### Sprint 0: Vision Analysis 추가 ⭐ 최우선 (~2h)
성형외과 홈페이지의 핵심 정보가 이미지로 제공됨 (배너, 의료진 사진, 인증 마크 등).
Firecrawl screenshot + Gemini Vision으로 이미지 속 정보를 추출.
- [ ] **WP-V1**. 멀티페이지 스크린샷 캡처 + 저장 (45min)
- 파일: `collect-channel-data/index.ts`에 추가 (Phase 2 — 검증된 URL들이 있는 시점)
- 캡처 대상 (6+ 페이지):
1. **병원 메인 페이지** — 배너, 소셜 아이콘, 카카오톡 버튼
2. **의료진 페이지** — siteMap에서 `/doctor`, `/team`, `/staff` URL 자동 탐지
3. **시술 안내 페이지** — `/surgery`, `/service`, `/procedure` 탐지
4. **YouTube 채널 랜딩**`youtube.com/@{handle}` (구독자 수, 고정 영상)
5. **Instagram 프로필**`instagram.com/{handle}` (팔로워 수, 피드 미리보기)
6. **강남언니 페이지** — verified URL (평점, 리뷰 수, 의료진)
- API: Firecrawl `formats: ["screenshot"]` + `screenshotOptions: { fullPage: false, quality: 80, viewport: { width: 1280, height: 800 } }`
- 저장: Supabase Storage `screenshots/{reportId}/{channel}.png` → signed URL
- DB: `channel_data.screenshots[]``ScreenshotEvidence` 형태로 저장
- 프론트엔드: 이미 구현된 `ScreenshotProvider``EvidenceGallery``EvidenceLightbox` 에 바로 연결
- [ ] **WP-V2**. Gemini Vision 분석 (1h)
- 파일: 새 `_shared/visionAnalysis.ts`
- API: Google Gemini `gemini-2.0-flash` (GEMINI_API_KEY 이미 .env에 있음)
- 각 스크린샷별 분석 프롬프트:
- **메인 페이지**: 개원 연도, 소셜 아이콘, 카카오톡 버튼, 브랜드 컬러, 슬로건, 인증 마크
- **의료진 페이지**: 의사 이름 + 전문 분야 + 약력 (이미지 내 텍스트 OCR)
- **시술 페이지**: 시술 카테고리, 가격 정보, 전후 사진 유무
- **YouTube 랜딩**: 구독자 수, 영상 수, 최근 업로드 제목, 고정 영상
- **Instagram 프로필**: 팔로워 수, 게시물 수, 최근 피드 미리보기, 바이오
- **강남언니**: 평점(/10), 리뷰 수, 의료진 수, 시술 종류
- 응답: 페이지별 JSON → `channel_data.visionAnalysis`에 저장
- [ ] **WP-V3**. Vision 데이터 → 리포트 + 증거 통합 (45min)
- 파일: `generate-report/index.ts`, `transformReport.ts`
- 변경:
- 스크린샷 → `report.screenshots[]` (ScreenshotEvidence 형태)
- 프론트엔드의 기존 EvidenceGallery/Lightbox에 바로 표시
- Vision 추출 의료진 → `clinicSnapshot.doctors` 보강 (이미지에서 읽은 정보)
- Vision 추출 개원 연도 → `clinicSnapshot.established` 보강
- Vision 추출 YouTube/Instagram 수치 → KPI에 cross-reference
- 각 채널 진단 항목에 `evidenceIds` 연결 → 해당 채널 스크린샷이 증거로 표시
- 검증:
- 그랜드성형외과 분석 → "SINCE 2004" 개원 연도 추출 확인
- YouTube 분석 섹션 → 채널 랜딩 스크린샷 썸네일 표시 확인
- Instagram 분석 섹션 → 프로필 스크린샷 표시 확인
- 스크린샷 클릭 → 라이트박스 모달에서 풀사이즈 보기 확인
### Vision Analysis 프롬프트 설계
```
System: You are a medical clinic website visual analyst. Extract structured
information from website screenshots. Respond ONLY with valid JSON.
User: Analyze this Korean plastic surgery clinic homepage screenshot.
Extract:
1. Founding year or operation duration (e.g., "SINCE 2004", "21년 무사고")
2. Doctor names and specialties shown in profile photos
3. Certification badges/marks (JCI, 보건복지부, medical tourism)
4. Main service categories from navigation menu
5. Social media icons/buttons visible (Instagram, YouTube, Blog, KakaoTalk, etc.)
6. Floating consultation buttons (KakaoTalk, LINE, WhatsApp)
7. Brand colors (primary, accent) from visual elements
8. Any promotional text or slogans in banners
{
"foundingYear": "2004",
"operationYears": 21,
"doctors": [{"name": "김OO", "specialty": "안면윤곽", "position": "대표원장"}],
"certifications": ["JCI", "보건복지부 인증"],
"serviceCategories": ["눈성형", "코성형", "가슴성형", "안면윤곽"],
"socialIcons": [{"platform": "instagram", "visible": true}, ...],
"floatingButtons": ["kakaotalk", "line"],
"brandColors": {"primary": "#C4A882", "accent": "#FF1493"},
"slogans": ["끊임없이 의료성형 뷰티 트렌드를 연구하는 그랜드 의료진"]
}
```
### Vision Analysis에 필요한 API/리소스
| API | 용도 | 비용 |
|-----|------|------|
| Firecrawl `formats: ["screenshot"]` | 페이지 스크린샷 캡처 | 기존 요금에 포함 |
| Gemini `gemini-2.0-flash-exp` | 이미지 분석 (Vision) | ~$0.002/이미지 |
| Gemini `GEMINI_API_KEY` | 이미 설정됨 (.env) | ✅ 사용 가능 |
### Vision으로 수집 가능한 추가 정보 (현재 누락)
| 정보 | 현재 수집 | Vision 추가 후 |
|------|---------|-------------|
| 개원 연도 | ❌ AI 추측 (자주 틀림) | ✅ 배너에서 직접 읽기 |
| 의료진 수/이름 | ⚠️ 강남언니에서만 | ✅ 홈페이지 프로필에서 추출 |
| 인증 마크 | ❌ 미수집 | ✅ JCI, 보건복지부 등 인식 |
| 시술 카테고리 | ⚠️ Firecrawl JSON | ✅ 네비게이션 메뉴에서 확인 |
| 카카오톡 상담 버튼 | ❌ JS 렌더링이라 못 잡음 | ✅ 플로팅 버튼 감지 |
| 브랜드 컬러 | ⚠️ CSS 추출 (부정확) | ✅ 실제 비주얼에서 추출 |
| 슬로건/태그라인 | ⚠️ 이미지 내 텍스트 누락 | ✅ 배너 텍스트 OCR |
---
### Sprint 1: 데이터 품질 Quick Wins (~2.5h) ✅ 완료
- [x] **WP-1**. YouTube Channel ID 정규식 수정 (20min)
- [x] **WP-2**. Naver Place 동명이인 방지 (30min)
- [x] **WP-3**. Google Maps URL 수정 (20min)
- [x] **WP-4**. Naver Blog 공식 블로그 분리 (30min)
- [x] **WP-5**. 강남언니 평점 정규화 (30min)
- [x] **WP-6**. Perplexity 모델 상수화 (20min)
- [x] **WP-7**. Apify 타임아웃 증가 (10min)
**추가 완료:**
- [x] 소셜 버튼 직접 추출 (Firecrawl actions + JS 렌더링 후 href 추출)
---
### 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` | 0, 1 | 채널 발견 + Vision |
| `supabase/functions/collect-channel-data/index.ts` | 0, 1, 2, 3 | 데이터 수집 |
| `supabase/functions/_shared/visionAnalysis.ts` (신규) | 0 | Vision 분석 유틸 |
| `supabase/functions/_shared/config.ts` | 1 | 공유 설정 |
| `supabase/functions/_shared/verifyHandles.ts` | 1, 2 | 핸들 검증 |
| `supabase/functions/_shared/retry.ts` (신규) | 3 | 재시도 유틸 |
| `supabase/functions/enrich-channels/index.ts` | 1 | 레거시 enrichment |
| `supabase/functions/generate-report/index.ts` | 0 | 리포트에 Vision 데이터 반영 |
| `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 0: Vision Analysis | ~2h | Edge Functions + Gemini | 🔜 다음 |
| Sprint 1: Quick Wins | ~2.5h | Edge Functions × 5 | ✅ 완료 |
| Sprint 2: 에러 가시성 | ~2.25h | DB + Edge Functions | 대기 |
| Sprint 3: 에러 회복 | ~5.25h | Edge Functions + Frontend + Vercel | 대기 |
| Sprint 4: UX 마무리 | ~50min | Frontend + Vercel | 대기 |
| **합계** | **~13h** | | |
---
## Vision Analysis 아키텍처
```
discover-channels (Stage A)
├─ A1. Firecrawl scrape (JSON + links) ← 기존
├─ A2. Firecrawl map ← 기존
├─ A3. Firecrawl branding ← 기존
├─ A4. Firecrawl social buttons (JS actions) ← 방금 추가
└─ A5. Firecrawl screenshot + Gemini Vision ← Sprint 0 신규
├─ 메인 페이지 스크린샷 캡처
├─ Gemini Vision 분석 (개원 연도, 의료진, 인증, 소셜 아이콘)
└─ 결과를 clinic 정보 + socialHandles에 병합
collect-channel-data
└─ Vision 데이터를 channel_data.visionAnalysis에 저장
generate-report
└─ Vision 데이터를 리포트 프롬프트에 포함 (실제 데이터 기반)
```