feat: 데이터 검증 페이지 + 병원 통합 프로필 데모 + 문서 정리
- /data-validation: Firecrawl API 실제 테스트 결과 시각화 페이지
(viewclinic.com, YouTube, Instagram, 강남언니 크롤링 검증)
- /clinic/🆔 김박사넷 스타일 병원 통합 프로필 데모 페이지
(의료진, 통합 평점, 시술 가격, 인증, 온라인 채널 통합)
- docs/datasets.md: 전체 데이터셋 정의서 (수집 소스, AI 생성, 의존 관계)
- docs/clinic-profile-platform.md: B2C 병원 프로필 플랫폼 기획서
- CLAUDE.md: 프로젝트 가이드 문서
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
claude/bold-hawking
parent
922ec8f6bc
commit
c6e18b6a67
|
|
@ -0,0 +1,58 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
INFINITH Demo — AI 마케팅 분석 플랫폼의 프론트엔드 데모. 성형외과(뷰성형외과)를 대상으로 YouTube/Instagram/Facebook/웹사이트를 분석하고, 변환 전략/로드맵/KPI를 제안하는 마케팅 리포트를 보여준다. 현재는 mock 데이터 기반이며 실제 API 연동은 미구현 상태.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
- `npm run dev` — 개발 서버 (port 3000)
|
||||||
|
- `npm run build` — 프로덕션 빌드
|
||||||
|
- `npm run lint` — TypeScript 타입 체크 (`tsc --noEmit`)
|
||||||
|
- 테스트 프레임워크 없음
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**Stack:** React 19 + TypeScript + Vite 6 + Tailwind CSS 4 + React Router 7
|
||||||
|
|
||||||
|
**Routes** (`src/main.tsx`):
|
||||||
|
- `/` — 랜딩 페이지
|
||||||
|
- `/report/loading` — 분석 로딩 (Navbar/Footer 숨김)
|
||||||
|
- `/report/:id` — 마케팅 분석 리포트
|
||||||
|
- `/plan/:id` — 마케팅 플랜
|
||||||
|
- `/studio/:id` — 콘텐츠 스튜디오 (AI 이미지 생성)
|
||||||
|
- `/channels` — 채널 연결
|
||||||
|
- `/distribute` — 콘텐츠 배포
|
||||||
|
- `/performance` — 성과 분석
|
||||||
|
|
||||||
|
**Data flow:**
|
||||||
|
1. Pages → custom hooks (`useReport`, `useMarketingPlan`) → mock data (`src/data/`)
|
||||||
|
2. Hooks return `{ data, isLoading, error }` 패턴 (100ms delay로 API 시뮬레이션)
|
||||||
|
3. `ScreenshotContext`로 리포트 스크린샷 데이터 공유
|
||||||
|
4. `useExportPDF` 훅으로 리포트 → PDF 변환
|
||||||
|
|
||||||
|
**Type definitions** in `src/types/`:
|
||||||
|
- `report.ts` — `MarketingReport` 및 하위 타입 (YouTubeAudit, InstagramAudit, etc.)
|
||||||
|
- `plan.ts` — 마케팅 플랜 타입
|
||||||
|
- `studio.ts` — 콘텐츠 스튜디오 타입
|
||||||
|
|
||||||
|
**AI Integration:** `src/services/geminiImageGen.ts` — Gemini 2.5 Flash로 이미지 생성. `GEMINI_API_KEY` 환경변수 필요.
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
|
||||||
|
- Tailwind CSS 4 (`@theme` directive in `src/index.css`)
|
||||||
|
- 폰트: Pretendard (sans), Playfair Display (serif/headings)
|
||||||
|
- 색상: `primary-900` (#0A1128), `accent` (#6C5CE7)
|
||||||
|
- 상태 색상: `status-critical`, `status-warning`, `status-good`, `status-info` (각각 bg/text/border/dot 변형)
|
||||||
|
- `glass-card` 클래스: 반투명 글래스모피즘 카드
|
||||||
|
- 애니메이션: Motion (framer-motion 후속) 사용
|
||||||
|
|
||||||
|
## Path Alias
|
||||||
|
|
||||||
|
`@/` → 프로젝트 루트 (tsconfig.json + vite.config.ts 모두 설정됨)
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Vercel (SPA 모드 — 모든 경로 `/index.html`로 리라이트)
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
# INFINITH — 병원 통합 프로필 플랫폼 기획서
|
||||||
|
**Version:** 1.0 | **Updated:** 2026-03-30 | **Status:** 컨셉 검증 (데모 구현 완료)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 개요
|
||||||
|
|
||||||
|
### 1.1 한 줄 요약
|
||||||
|
|
||||||
|
> 여러 플랫폼에 흩어진 병원 정보를 한 곳에 모아 보여주는 **"병원판 김박사넷"**
|
||||||
|
|
||||||
|
### 1.2 문제 정의
|
||||||
|
|
||||||
|
환자가 성형외과 하나를 알아보려면:
|
||||||
|
- **강남언니**에서 리뷰/가격 확인
|
||||||
|
- **네이버 플레이스**에서 위치/진료시간 확인
|
||||||
|
- **YouTube**에서 원장 영상 검색
|
||||||
|
- **Instagram**에서 전후 사진 확인
|
||||||
|
- **병원 홈페이지**에서 의료진 정보 확인
|
||||||
|
- **구글**에서 해외 리뷰 확인
|
||||||
|
|
||||||
|
→ 최소 **5~6개 플랫폼**을 오가며 직접 정보를 모아야 함
|
||||||
|
|
||||||
|
### 1.3 솔루션
|
||||||
|
|
||||||
|
**김박사넷이 교수 정보를 각 대학 사이트에서 모아 통합 프로필을 제공하듯,
|
||||||
|
INFINITH가 병원/의사 정보를 각 플랫폼에서 모아 통합 프로필을 제공한다.**
|
||||||
|
|
||||||
|
핵심: **실시간성이 필요 없는 정적 데이터**만 수집하여 운영 비용 최소화
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 레퍼런스 모델
|
||||||
|
|
||||||
|
### 2.1 김박사넷 (kimbaksa.net)
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
|------|------|
|
||||||
|
| 서비스 | 대학 교수 통합 프로필 검색 |
|
||||||
|
| 데이터 | 교수 이름, 학력, 논문, 연구분야, 연구실 |
|
||||||
|
| 수집 소스 | 각 대학 홈페이지, 학술 DB |
|
||||||
|
| 업데이트 주기 | 비정기 (교수 정보는 거의 안 바뀜) |
|
||||||
|
| 핵심 가치 | 흩어진 정보를 한 곳에서 비교/검색 |
|
||||||
|
|
||||||
|
### 2.2 호텔스컴바인 / 아고다
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
|------|------|
|
||||||
|
| 서비스 | 여러 OTA의 호텔 가격을 한 곳에서 비교 |
|
||||||
|
| 데이터 | 호텔 기본 정보 + 각 OTA별 가격 |
|
||||||
|
| 핵심 가치 | 직접 비교하지 않아도 최저가를 찾아줌 |
|
||||||
|
|
||||||
|
### 2.3 INFINITH 병원 프로필 = 김박사넷 + 호텔스컴바인
|
||||||
|
|
||||||
|
| 김박사넷처럼 | 호텔스컴바인처럼 |
|
||||||
|
|-------------|---------------|
|
||||||
|
| 의사 학력/경력/전문분야 통합 | 플랫폼별 평점/리뷰 비교 |
|
||||||
|
| 병원 인증/수상 이력 정리 | 시술별 가격 비교 |
|
||||||
|
| 한 곳에서 전체 프로필 확인 | 여러 소스의 데이터 집계 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 데이터 전략
|
||||||
|
|
||||||
|
### 3.1 핵심 원칙: 정적 데이터만 수집
|
||||||
|
|
||||||
|
| 변경 주기 | 데이터 유형 | 예시 | 수집 빈도 |
|
||||||
|
|----------|-----------|------|-----------|
|
||||||
|
| **거의 불변** | 의사 기본 정보 | 이름, 학력, 자격, 경력 | 최초 1회 + 연 1회 검증 |
|
||||||
|
| **거의 불변** | 병원 시설 정보 | 위치, 장비, 인증, 수상 | 최초 1회 + 연 1~2회 |
|
||||||
|
| **분기 1회** | 시술/가격 | 시술 목록, 시작 가격 | 분기 1회 배치 |
|
||||||
|
| **월 1회** | 집계 평점 | 강남언니/네이버/구글 평점 | 월 1회 배치 |
|
||||||
|
| **월 1회** | 채널 존재 여부 | YouTube/Instagram 링크, 팔로워 규모 | 월 1회 |
|
||||||
|
|
||||||
|
### 3.2 B2B와 데이터 분리
|
||||||
|
|
||||||
|
```
|
||||||
|
수집된 전체 데이터
|
||||||
|
│
|
||||||
|
├── B2C 공개 (병원 통합 프로필) ─────────────
|
||||||
|
│ · 의사 학력/경력/전문분야
|
||||||
|
│ · 병원 시설/인증/위치/진료시간
|
||||||
|
│ · 시술 목록 + 가격대
|
||||||
|
│ · 플랫폼별 집계 평점 (강남언니/네이버/구글)
|
||||||
|
│ · YouTube/Instagram 채널 링크 + 팔로워 규모
|
||||||
|
│ · 언론 보도 / 미디어 출연 이력
|
||||||
|
│
|
||||||
|
└── B2B 전용 (마케팅 인텔리전스) ────────────
|
||||||
|
· 채널별 상세 진단 (점수, 문제점)
|
||||||
|
· 브랜드 일관성 분석 (로고/바이오 불일치)
|
||||||
|
· 콘텐츠 성과 분석 (조회수 추이, 참여율)
|
||||||
|
· 경쟁사 비교 분석
|
||||||
|
· 변환 전략 / 로드맵 / KPI
|
||||||
|
```
|
||||||
|
|
||||||
|
**B2C는 "이 병원이 어떤 곳인지"** → 환자용 정보
|
||||||
|
**B2B는 "이 병원의 마케팅이 어떤 상태인지"** → 병원/대행사용 인텔리전스
|
||||||
|
|
||||||
|
### 3.3 수집 소스 및 방법
|
||||||
|
|
||||||
|
| 소스 | 수집 대상 | 도구 | 비용 |
|
||||||
|
|------|----------|------|------|
|
||||||
|
| 병원 홈페이지 | 의료진, 시설, 인증, 연락처 | Firecrawl | ~₩26,000/월 |
|
||||||
|
| 강남언니 | 평점, 리뷰 수, 가격, 의료진 상세 | Firecrawl | 포함 |
|
||||||
|
| 네이버 플레이스 | 평점, 리뷰 수, 위치, 진료시간 | Naver API | 무료 |
|
||||||
|
| Google Maps | 평점, 리뷰 수 | Google Places API | 사용량 기반 |
|
||||||
|
| YouTube | 채널 존재, 구독자 수, 영상 수 | YouTube Data API | 무료 |
|
||||||
|
| Instagram | 계정 존재, 팔로워 수 | Apify | ~₩65,000/월 |
|
||||||
|
|
||||||
|
### 3.4 크롤링 비용 시뮬레이션
|
||||||
|
|
||||||
|
| 병원 수 | 수집 빈도 | 월간 Firecrawl 크레딧 | 예상 비용 |
|
||||||
|
|---------|----------|---------------------|-----------|
|
||||||
|
| 100개 | 월 1회 전체 | ~500 credits | Growth 플랜 내 |
|
||||||
|
| 500개 | 월 1회 전체 | ~2,500 credits | ~₩50,000 |
|
||||||
|
| 1,000개 | 월 1회 전체 | ~5,000 credits | ~₩100,000 |
|
||||||
|
|
||||||
|
**정적 데이터이므로 최초 수집 후에는 변경 감지 수집만 하면 비용 대폭 절감**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 프로필 페이지 구성
|
||||||
|
|
||||||
|
### 4.1 데모 페이지
|
||||||
|
|
||||||
|
- **경로:** `/clinic/:id` (예: `/clinic/view-clinic`)
|
||||||
|
- **파일:** `src/pages/ClinicProfilePage.tsx`
|
||||||
|
- **데이터:** Firecrawl + WebFetch로 실제 수집한 뷰성형외과 데이터
|
||||||
|
|
||||||
|
### 4.2 섹션 구조
|
||||||
|
|
||||||
|
| 순서 | 섹션 | 포함 데이터 | 데이터 출처 |
|
||||||
|
|------|------|-----------|------------|
|
||||||
|
| 1 | **헤더** | 병원명, 개원연도, 핵심 지표 3개 | 웹사이트 + 강남언니 |
|
||||||
|
| 2 | **기본 정보** | 주소, 최근역, 전화, 진료시간, 웹사이트 | 웹사이트 + 네이버 플레이스 |
|
||||||
|
| 3 | **통합 평점** | 강남언니/네이버/구글 평점 + 리뷰 수 비교 | 3개 플랫폼 집계 |
|
||||||
|
| 4 | **의료진** | 의사별 카드 (이름, 전문분야, 학력, 평점, 리뷰 수) | 강남언니 + 웹사이트 |
|
||||||
|
| 5 | **시술/가격** | 시술 목록 + 카테고리 + 시작 가격 | 강남언니 |
|
||||||
|
| 6 | **인증/수상** | 인증 뱃지 + 미디어 출연 이력 | 웹사이트 + 강남언니 |
|
||||||
|
| 7 | **온라인 채널** | YouTube/Instagram/Facebook/카카오톡 링크 + 팔로워 | Firecrawl + YouTube API |
|
||||||
|
| 8 | **데이터 출처 고지** | 출처 명시, 업데이트일, 수정 요청 안내 | - |
|
||||||
|
|
||||||
|
### 4.3 향후 추가 예정 섹션
|
||||||
|
|
||||||
|
| 섹션 | 내용 | 우선순위 |
|
||||||
|
|------|------|----------|
|
||||||
|
| 리뷰 요약 | 각 플랫폼 리뷰를 AI가 요약 | P1 |
|
||||||
|
| 전후 사진 갤러리 | Instagram/강남언니 전후 사진 집계 | P1 |
|
||||||
|
| 비슷한 병원 | 같은 지역 + 유사 시술 병원 추천 | P2 |
|
||||||
|
| 가격 비교 | 동일 시술의 타 병원 가격 비교 | P2 |
|
||||||
|
| 의사 상세 페이지 | 의사별 독립 프로필 (논문, 경력 상세) | P2 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 사업 모델
|
||||||
|
|
||||||
|
### 5.1 수익 구조 (듀얼 모델)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ B2C: 병원 통합 프로필 플랫폼 │
|
||||||
|
│ │
|
||||||
|
│ 무료 서비스 (트래픽 확보) │
|
||||||
|
│ └→ 수익화: │
|
||||||
|
│ · 프리미엄 리스팅 (병원이 프로필 상단 노출 비용) │
|
||||||
|
│ · 상담 연결 수수료 (CPA) │
|
||||||
|
│ · 광고 (배너, 네이티브 광고) │
|
||||||
|
│ · 프리미엄 뱃지 (병원 인증 마크) │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ B2B: 마케팅 인텔리전스 SaaS │
|
||||||
|
│ │
|
||||||
|
│ 유료 서비스 (직접 매출) │
|
||||||
|
│ └→ 수익화: │
|
||||||
|
│ · 마케팅 분석 리포트 (건당 50~100만원) │
|
||||||
|
│ · 월정액 모니터링 SaaS (월 30~100만원) │
|
||||||
|
│ · 마케팅 대행 영업 도구 (대행사 라이선스) │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 시너지
|
||||||
|
|
||||||
|
| B2C에서 B2B로 | B2B에서 B2C로 |
|
||||||
|
|-------------|-------------|
|
||||||
|
| 트래픽 데이터로 병원에 "환자들이 당신을 이렇게 봅니다" 어필 → B2B 세일즈 | B2B 수집 데이터가 B2C 프로필의 품질 향상 |
|
||||||
|
| 상담 연결 데이터로 마케팅 ROI 증명 → B2B 가치 입증 | B2B 고객 병원에 프리미엄 프로필 제공 → B2C 콘텐츠 풍부 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 경쟁 분석
|
||||||
|
|
||||||
|
### 6.1 기존 플레이어와의 차이
|
||||||
|
|
||||||
|
| | 강남언니 | 모두닥 | 바비톡 | **INFINITH** |
|
||||||
|
|---|---------|-------|-------|-------------|
|
||||||
|
| **포지셔닝** | 리뷰 플랫폼 | 예약 플랫폼 | 커뮤니티 | **메타 검색 (통합 비교)** |
|
||||||
|
| **데이터** | 자체 리뷰만 | 자체 리뷰만 | 자체 리뷰만 | **전 플랫폼 집계** |
|
||||||
|
| **의사 정보** | 평점 + 리뷰 수 | 기본 정보 | 기본 정보 | **학력/경력 + 전 플랫폼 평점** |
|
||||||
|
| **가격** | 자체 등록 | 자체 등록 | 없음 | **플랫폼 간 비교** |
|
||||||
|
| **영상 콘텐츠** | 없음 | 없음 | 없음 | **YouTube 연동** |
|
||||||
|
| **경쟁 관계** | 직접 경쟁 | 직접 경쟁 | 직접 경쟁 | **보완 관계 (메타 레이어)** |
|
||||||
|
|
||||||
|
### 6.2 핵심 차별점
|
||||||
|
|
||||||
|
1. **메타 플랫폼** — 강남언니/모두닥/네이버와 경쟁하는 게 아니라 그들의 데이터를 모아주는 상위 레이어
|
||||||
|
2. **의사 중심 프로필** — 김박사넷처럼 "이 의사의 모든 정보를 한 곳에서" (병원이 아닌 의사 단위 검색)
|
||||||
|
3. **영상 콘텐츠 연동** — 기존 플랫폼이 제공하지 않는 YouTube/Instagram 콘텐츠까지 통합
|
||||||
|
4. **B2B 시너지** — 수집 인프라를 B2C/B2B 양쪽에 활용하는 듀얼 모델
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 리스크 및 대응
|
||||||
|
|
||||||
|
### 7.1 법적 리스크
|
||||||
|
|
||||||
|
| 리스크 | 심각도 | 대응 |
|
||||||
|
|--------|--------|------|
|
||||||
|
| 타 플랫폼 크롤링 → 이용약관 위반 가능 | 중 | 공개 정보만 수집, robots.txt 준수, 출처 명시 |
|
||||||
|
| 가격 정보 노출 → 의료광고법 | 중 | "시작 가격"만 표시, "상담 후 결정" 고지 |
|
||||||
|
| 의사 개인정보 → 초상권/프라이버시 | 낮 | 공개된 프로필 정보만 사용, 삭제 요청 즉시 대응 |
|
||||||
|
|
||||||
|
### 7.2 사업 리스크
|
||||||
|
|
||||||
|
| 리스크 | 대응 |
|
||||||
|
|--------|------|
|
||||||
|
| 강남언니 등이 크롤링 차단 | Apify 등 우회 수단 + 사용자 기여 데이터로 전환 |
|
||||||
|
| 초기 트래픽 확보 어려움 | SEO 최적화 (병원명 + 의사명 검색), B2B 병원에 프로필 링크 제공 |
|
||||||
|
| 데이터 신선도 유지 비용 | 정적 데이터 위주로 수집 빈도 최소화 (핵심 전략) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 로드맵
|
||||||
|
|
||||||
|
### Phase 1 (MVP) — 현재
|
||||||
|
|
||||||
|
- [x] 뷰성형외과 단일 프로필 데모 페이지
|
||||||
|
- [x] Firecrawl 데이터 수집 검증
|
||||||
|
- [x] 데이터셋 정의 완료
|
||||||
|
|
||||||
|
### Phase 2 — 강남 성형외과 100개
|
||||||
|
|
||||||
|
- [ ] 강남 주요 성형외과 100개 프로필 자동 생성
|
||||||
|
- [ ] 병원 검색/필터 기능 (지역, 시술, 가격대)
|
||||||
|
- [ ] 의사별 독립 프로필 페이지
|
||||||
|
- [ ] SEO 최적화 (병원명/의사명 검색 노출)
|
||||||
|
|
||||||
|
### Phase 3 — 플랫폼 확장
|
||||||
|
|
||||||
|
- [ ] 사용자 리뷰 집계 (강남언니 + 네이버 + 구글 통합 요약)
|
||||||
|
- [ ] 가격 비교 기능 (동일 시술 타 병원 비교)
|
||||||
|
- [ ] 병원 클레임 (병원이 직접 프로필 관리)
|
||||||
|
- [ ] B2B 연동 (프로필에서 마케팅 분석 리포트 CTA)
|
||||||
|
|
||||||
|
### Phase 4 — 수익화
|
||||||
|
|
||||||
|
- [ ] 프리미엄 리스팅 (병원 과금)
|
||||||
|
- [ ] 상담 연결 CPA
|
||||||
|
- [ ] B2B SaaS 정식 런칭
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 기술 구현 현황
|
||||||
|
|
||||||
|
### 9.1 데모 페이지
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
|------|-----|
|
||||||
|
| 경로 | `/clinic/:id` |
|
||||||
|
| 파일 | `src/pages/ClinicProfilePage.tsx` |
|
||||||
|
| 데이터 | 컴포넌트 내 하드코딩 (실제 수집 값 기반) |
|
||||||
|
| 디자인 | 프로젝트 기존 디자인 시스템 (glass-card, Motion 애니메이션) |
|
||||||
|
|
||||||
|
### 9.2 실제 구현 시 필요 작업
|
||||||
|
|
||||||
|
| 작업 | 설명 | 우선순위 |
|
||||||
|
|------|------|----------|
|
||||||
|
| 병원 데이터 DB 스키마 | Supabase PostgreSQL | P0 |
|
||||||
|
| 크롤링 배치 파이프라인 | Firecrawl + YouTube API + Apify | P0 |
|
||||||
|
| 병원 목록 페이지 | 검색/필터 UI | P0 |
|
||||||
|
| 동적 프로필 페이지 | DB에서 데이터 로드 → 렌더링 | P0 |
|
||||||
|
| SEO (SSR/SSG) | Next.js 또는 Astro 전환 검토 | P1 |
|
||||||
|
| 의사 상세 페이지 | 의사별 독립 URL | P1 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 2026-03-30*
|
||||||
|
|
@ -0,0 +1,813 @@
|
||||||
|
# INFINITH — 데이터셋 정의서
|
||||||
|
**Version:** 1.0 | **Updated:** 2026-03-30 | **Status:** Mock → Real 전환 설계
|
||||||
|
|
||||||
|
현재 프론트엔드 데모에서 사용 중인 모든 데이터를 정리하고, 실제 서비스 구현 시 각 데이터의 **수집 소스**, **수집 방법**, **우선순위**를 명시한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 목차
|
||||||
|
|
||||||
|
1. [데이터 흐름 개요](#1-데이터-흐름-개요)
|
||||||
|
2. [Report 데이터 (MarketingReport)](#2-report-데이터-marketingreport)
|
||||||
|
3. [Plan 데이터 (MarketingPlan)](#3-plan-데이터-marketingplan)
|
||||||
|
4. [Studio 데이터 (Content Studio)](#4-studio-데이터-content-studio)
|
||||||
|
5. [수집 소스별 분류](#5-수집-소스별-분류)
|
||||||
|
6. [AI 생성 데이터](#6-ai-생성-데이터)
|
||||||
|
7. [데이터 의존 관계](#7-데이터-의존-관계)
|
||||||
|
8. [Firecrawl 수집 검증 결과](#8-firecrawl-수집-검증-결과)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 데이터 흐름 개요
|
||||||
|
|
||||||
|
```
|
||||||
|
사용자 입력 (URL)
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Phase A: 데이터 수집 (Crawl + API) │
|
||||||
|
│ 웹사이트 → YouTube → Instagram → Facebook │
|
||||||
|
│ → 강남언니 → 네이버 → 기타 플랫폼 │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Phase B: AI 분석 (LLM) │
|
||||||
|
│ 채점 → 진단 → 전략 수립 → 로드맵 → KPI │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ Phase C: 산출물 렌더링 │
|
||||||
|
│ Report → Plan → Studio → Distribution │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**현재 상태:** Phase A~B가 모두 `src/data/mockReport.ts`, `src/data/mockPlan.ts`에 하드코딩되어 있음.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Report 데이터 (MarketingReport)
|
||||||
|
|
||||||
|
> 타입 정의: `src/types/report.ts`
|
||||||
|
> Mock 데이터: `src/data/mockReport.ts`
|
||||||
|
> 사용 페이지: `/report/:id` (`src/pages/ReportPage.tsx`)
|
||||||
|
|
||||||
|
### 2.1 기본 메타 정보
|
||||||
|
|
||||||
|
| 필드 | 타입 | 현재 Mock 값 | 수집 소스 | 수집 방법 |
|
||||||
|
|------|------|-------------|-----------|-----------|
|
||||||
|
| `id` | string | `'view-clinic'` | 시스템 생성 | UUID / slug |
|
||||||
|
| `createdAt` | string | `'2026-03-22'` | 시스템 생성 | 리포트 생성 시점 |
|
||||||
|
| `targetUrl` | string | `'https://www.viewclinic.com'` | **사용자 입력** | 분석 시작점 |
|
||||||
|
| `overallScore` | number | `62` | **AI 생성** | 채널 점수 가중 평균 |
|
||||||
|
|
||||||
|
### 2.2 의원 현황 (ClinicSnapshot)
|
||||||
|
|
||||||
|
| 필드 | 타입 | 현재 Mock 값 | 수집 소스 | 수집 방법 |
|
||||||
|
|------|------|-------------|-----------|-----------|
|
||||||
|
| `name` | string | `'뷰성형외과의원'` | 웹사이트 크롤링 | `<title>`, 메타태그, 구조화 데이터 |
|
||||||
|
| `nameEn` | string | `'VIEW Plastic Surgery'` | 웹사이트 크롤링 | 영문 페이지 또는 메타태그 |
|
||||||
|
| `established` | string | `'2005'` | 웹사이트/강남언니 | 본문 파싱 |
|
||||||
|
| `yearsInBusiness` | number | `21` | 계산 | `현재연도 - established` |
|
||||||
|
| `staffCount` | number | `28` | 강남언니/웹사이트 | 의료진 페이지 크롤링 |
|
||||||
|
| `leadDoctor.name` | string | `'최순우'` | 강남언니/웹사이트 | 대표원장 정보 |
|
||||||
|
| `leadDoctor.credentials` | string | `'서울대 출신, 의학박사'` | 웹사이트 | 의료진 프로필 파싱 |
|
||||||
|
| `leadDoctor.rating` | number | `4.7` | **강남언니 크롤링** | 의료진 평점 |
|
||||||
|
| `leadDoctor.reviewCount` | number | `1809` | **강남언니 크롤링** | 의료진 리뷰 수 |
|
||||||
|
| `overallRating` | number | `4.8` | **강남언니 크롤링** | 병원 전체 평점 |
|
||||||
|
| `totalReviews` | number | `18840` | **강남언니 크롤링** | 총 리뷰 수 |
|
||||||
|
| `priceRange` | object | `{ min: '97,900', max: '13,200,000+' }` | **강남언니 크롤링** | 시술 가격 범위 |
|
||||||
|
| `certifications` | string[] | 10개 항목 | 웹사이트 크롤링 | 인증/수상 섹션 파싱 |
|
||||||
|
| `mediaAppearances` | string[] | 3개 항목 | 웹사이트 크롤링 | 미디어 출연 섹션 |
|
||||||
|
| `medicalTourism` | string[] | 3개 항목 | 웹사이트/VisitKorea | 의료관광 관련 정보 |
|
||||||
|
| `location` | string | `'서울시 강남구...'` | 웹사이트/네이버 플레이스 | 주소 파싱 |
|
||||||
|
| `nearestStation` | string | `'9호선 신논현역...'` | 웹사이트/네이버 지도 | 교통 정보 |
|
||||||
|
| `phone` | string | `'02-539-1177'` | 웹사이트 크롤링 | `<a href="tel:">` 또는 본문 |
|
||||||
|
| `domain` | string | `'viewclinic.com'` | 사용자 입력 | URL에서 추출 |
|
||||||
|
| `logoImages.circle` | string | 이미지 경로 | **웹사이트 크롤링** | favicon, 로고 `<img>` 추출 |
|
||||||
|
| `logoImages.horizontal` | string | 이미지 경로 | **웹사이트 크롤링** | 헤더 로고 추출 |
|
||||||
|
| `logoImages.korean` | string | 이미지 경로 | **웹사이트 크롤링** | 한글 로고 변형 |
|
||||||
|
| `brandColors.primary` | string | `'#7B2D8E'` | **이미지 분석** | 로고 색상 추출 (Vision API / CSS 파싱) |
|
||||||
|
| `brandColors.accent` | string | `'#E8B931'` | **이미지 분석** | 로고 보조 색상 |
|
||||||
|
| `brandColors.text` | string | `'#6B2D7B'` | **CSS 파싱** | 웹사이트 텍스트 색상 |
|
||||||
|
|
||||||
|
### 2.3 채널 종합 점수 (ChannelScore[])
|
||||||
|
|
||||||
|
| 필드 | 타입 | 수집 소스 | 비고 |
|
||||||
|
|------|------|-----------|------|
|
||||||
|
| `channel` | string | 시스템 정의 | YouTube, Instagram KR, Instagram EN, Facebook, 강남언니, Website |
|
||||||
|
| `icon` | string | 시스템 정의 | lucide-react 아이콘명 |
|
||||||
|
| `score` | number (0-100) | **AI 생성** | 채널별 수집 데이터 기반 채점 |
|
||||||
|
| `maxScore` | number | 시스템 정의 | 항상 100 |
|
||||||
|
| `status` | Severity | **AI 생성** | score 기반 등급 (critical/warning/good/excellent) |
|
||||||
|
| `headline` | string | **AI 생성** | 한 줄 요약 텍스트 |
|
||||||
|
|
||||||
|
현재 Mock 데이터:
|
||||||
|
| 채널 | 점수 | 상태 | 헤드라인 |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| YouTube | 65 | warning | 103K 구독자, 조회수 하락세 |
|
||||||
|
| Instagram KR | 35 | critical | 14K 팔로워, Reels 0개 |
|
||||||
|
| Instagram EN | 55 | warning | 68.8K 팔로워, 활발한 편 |
|
||||||
|
| Facebook | 40 | critical | 브랜드 불일치, 계정 분산 |
|
||||||
|
| 강남언니 | 95 | excellent | 4.8점, 18,840 리뷰 |
|
||||||
|
| Website | 50 | warning | SNS 연결 없음, 트래킹만 존재 |
|
||||||
|
|
||||||
|
### 2.4 YouTube 분석 (YouTubeAudit)
|
||||||
|
|
||||||
|
| 필드 | 타입 | 현재 Mock 값 | 수집 소스 | API 엔드포인트 |
|
||||||
|
|------|------|-------------|-----------|----------------|
|
||||||
|
| `channelName` | string | `'뷰성형외과 VIEW Plastic Surgery'` | YouTube Data API | `channels.list` → snippet.title |
|
||||||
|
| `handle` | string | `'@ViewclinicKR'` | YouTube Data API | `channels.list` → snippet.customUrl |
|
||||||
|
| `subscribers` | number | `103000` | YouTube Data API | `channels.list` → statistics.subscriberCount |
|
||||||
|
| `totalVideos` | number | `1064` | YouTube Data API | `channels.list` → statistics.videoCount |
|
||||||
|
| `totalViews` | number | `9952722` | YouTube Data API | `channels.list` → statistics.viewCount |
|
||||||
|
| `weeklyViewGrowth.absolute` | number | `67097` | **주기적 수집 후 diff** | 7일 간격 viewCount 차이 |
|
||||||
|
| `weeklyViewGrowth.percentage` | number | `4.09` | **계산** | absolute / (totalViews - absolute) * 100 |
|
||||||
|
| `estimatedMonthlyRevenue` | object | `{ min: 499, max: 1000 }` | **Social Blade API** | 또는 자체 추정 로직 (CPM 기반) |
|
||||||
|
| `avgVideoLength` | string | `'4.4분'` | YouTube Data API | `videos.list` → contentDetails.duration 평균 |
|
||||||
|
| `uploadFrequency` | string | `'~주 1회'` | **계산** | 최근 30개 영상 업로드 날짜 간격 |
|
||||||
|
| `channelCreatedDate` | string | `'2015-06-29'` | YouTube Data API | `channels.list` → snippet.publishedAt |
|
||||||
|
| `subscriberRank` | string | `'#570K'` | **Social Blade** | 전세계 구독자 순위 |
|
||||||
|
| `channelDescription` | string | (바이오 텍스트) | YouTube Data API | `channels.list` → snippet.description |
|
||||||
|
| `linkedUrls` | array | 5개 링크 | YouTube Data API | `channels.list` → brandingSettings.channel.links (v3 미지원 시 크롤링) |
|
||||||
|
| `playlists` | string[] | 9개 재생목록 | YouTube Data API | `playlists.list` → snippet.title |
|
||||||
|
| `topVideos` | TopVideo[] | 8개 영상 | YouTube Data API | `search.list` (order=viewCount) + `videos.list` |
|
||||||
|
| `diagnosis` | DiagnosisItem[] | 7개 항목 | **AI 생성** | 수집 데이터 → 패턴 분석 → 텍스트 생성 |
|
||||||
|
|
||||||
|
#### TopVideo 상세
|
||||||
|
|
||||||
|
| 필드 | 수집 소스 | API |
|
||||||
|
|------|-----------|-----|
|
||||||
|
| `title` | YouTube Data API | `videos.list` → snippet.title |
|
||||||
|
| `views` | YouTube Data API | `videos.list` → statistics.viewCount |
|
||||||
|
| `uploadedAgo` | YouTube Data API | `videos.list` → snippet.publishedAt → 상대 시간 변환 |
|
||||||
|
| `type` ('Short' / 'Long') | **계산** | duration ≤ 60초 && 세로형 → Short |
|
||||||
|
| `duration` | YouTube Data API | `videos.list` → contentDetails.duration (Long만 표시) |
|
||||||
|
|
||||||
|
### 2.5 Instagram 분석 (InstagramAudit)
|
||||||
|
|
||||||
|
| 필드 | 타입 | 수집 소스 | 수집 방법 |
|
||||||
|
|------|------|-----------|-----------|
|
||||||
|
| `accounts` | InstagramAccount[] | Instagram Graph API / Apify | 복수 계정 병렬 수집 |
|
||||||
|
| `diagnosis` | DiagnosisItem[] | **AI 생성** | 계정 간 비교 분석 |
|
||||||
|
|
||||||
|
#### InstagramAccount 상세
|
||||||
|
|
||||||
|
| 필드 | 현재 Mock (KR) | 현재 Mock (EN) | 수집 소스 |
|
||||||
|
|------|---------------|---------------|-----------|
|
||||||
|
| `handle` | `@viewplastic` | `@view_plastic_surgery` | 웹사이트 SNS 링크 → Graph API |
|
||||||
|
| `language` | `'KR'` | `'EN'` | **AI 판단** (바이오/게시물 언어 감지) |
|
||||||
|
| `label` | `'국내 (한국어)'` | `'국제 (영어)'` | language 기반 자동 라벨링 |
|
||||||
|
| `posts` | `1409` | `2524` | Graph API / Apify |
|
||||||
|
| `followers` | `14000` | `68800` | Graph API / Apify |
|
||||||
|
| `following` | `4760` | `2834` | Graph API / Apify |
|
||||||
|
| `category` | `'Health/beauty'` | `'Health/beauty'` | Graph API |
|
||||||
|
| `profileLink` | `'litt.ly/viewplasticsurgery'` | `'litt.ly/viewplasticsurgeryenglish'` | Graph API / Apify |
|
||||||
|
| `highlights` | 5개 하이라이트명 | 7개 하이라이트명 | **Apify 크롤링** (API 미지원) |
|
||||||
|
| `reelsCount` | `0` | `50` | **Apify 크롤링** |
|
||||||
|
| `contentFormat` | `'카드뉴스 100%'` | `'B/A + 환자 스토리 + Reels'` | **AI 분석** (게시물 샘플링) |
|
||||||
|
| `profilePhoto` | `'모델 사진'` | `'VIEW 골드 로고'` | Graph API / Apify |
|
||||||
|
| `bio` | (한국어 바이오) | (영어 바이오) | Graph API / Apify |
|
||||||
|
|
||||||
|
### 2.6 Facebook 분석 (FacebookAudit)
|
||||||
|
|
||||||
|
| 필드 | 타입 | 수집 소스 | 수집 방법 |
|
||||||
|
|------|------|-----------|-----------|
|
||||||
|
| `pages` | FacebookPage[] | Facebook Graph API / Apify | 복수 페이지 수집 |
|
||||||
|
| `diagnosis` | DiagnosisItem[] | **AI 생성** | 페이지 간 비교 분석 |
|
||||||
|
| `brandInconsistencies` | BrandInconsistency[] | **AI 생성** | 전 채널 크로스 비교 |
|
||||||
|
| `consolidationRecommendation` | string | **AI 생성** | 통합/폐쇄 권고 텍스트 |
|
||||||
|
|
||||||
|
#### FacebookPage 상세
|
||||||
|
|
||||||
|
| 필드 | 현재 Mock (KR) | 현재 Mock (EN) | 수집 소스 |
|
||||||
|
|------|---------------|---------------|-----------|
|
||||||
|
| `url` | `'facebook.com/viewps1'` | `'facebook.com/viewclinic'` | 웹사이트/검색 |
|
||||||
|
| `pageName` | `'뷰성형외과'` | `'View Plastic Surgery'` | Graph API |
|
||||||
|
| `followers` | `253` | `88000` | Graph API |
|
||||||
|
| `following` | `0` | `11` | Graph API |
|
||||||
|
| `category` | `'성형외과 의사'` | `'건강/뷰티'` | Graph API |
|
||||||
|
| `bio` | `'예쁨이 일상이 되는 순간!'` | `'Official Account by VIEW Partners'` | Graph API |
|
||||||
|
| `logo` | `'일치 (공식 로고)'` | `'불일치 (비공식 변형)'` | **AI 비교** (로고 이미지 대조) |
|
||||||
|
| `logoDescription` | (상세 설명) | (상세 설명) | **AI 생성** (이미지 분석) |
|
||||||
|
| `link` | `'viewclinic.com'` | `'viewplasticsurgery.com'` | Graph API |
|
||||||
|
| `linkedDomain` | `'viewclinic.com'` | `'viewplasticsurgery.com'` | Graph API → 도메인 추출 |
|
||||||
|
| `reviews` | `0` | `3` | Graph API |
|
||||||
|
| `recentPostAge` | `'1일 전'` | `'14분 전'` | Graph API → 상대시간 변환 |
|
||||||
|
| `hasWhatsApp` | `false` | `true` | Graph API |
|
||||||
|
| `postFrequency` | `'주 1~2회'` | `'일 1~2회'` | **계산** (최근 30일 게시물 수 / 30) |
|
||||||
|
| `topContentType` | `'Instagram 카드뉴스 복사'` | `'B/A + Reels'` | **AI 분석** (게시물 샘플링) |
|
||||||
|
| `engagement` | `'좋아요 0~3개'` | `'좋아요 50~300개'` | Graph API → 평균 계산 |
|
||||||
|
|
||||||
|
#### BrandInconsistency 상세
|
||||||
|
|
||||||
|
| 필드 | 설명 | 수집 소스 |
|
||||||
|
|------|------|-----------|
|
||||||
|
| `field` | 비교 항목 (로고, 연결 도메인, 바이오) | 시스템 정의 |
|
||||||
|
| `values[]` | 각 채널의 해당 필드 값 + `isCorrect` 판정 | **AI 비교** (전 채널 데이터 크로스 분석) |
|
||||||
|
| `impact` | 불일치로 인한 비즈니스 영향 | **AI 생성** |
|
||||||
|
| `recommendation` | 개선 권고사항 | **AI 생성** |
|
||||||
|
|
||||||
|
### 2.7 기타 채널 (OtherChannel[])
|
||||||
|
|
||||||
|
| 채널 | 현재 Mock 상태 | 수집 소스 | 수집 방법 |
|
||||||
|
|------|--------------|-----------|-----------|
|
||||||
|
| 카카오톡 | active | 카카오 채널 검색 API | 채널 URL 유효성 확인 |
|
||||||
|
| 네이버 블로그 | unknown | Naver Search API | `blog.naver.com/{id}` 검색 |
|
||||||
|
| 네이버 플레이스 | unknown | Naver Place API / 크롤링 | 병원명 검색 |
|
||||||
|
| TikTok | not_found | TikTok API / Apify | 핸들 검색 |
|
||||||
|
| 강남언니 | active | **강남언니 크롤링** | Apify 또는 직접 크롤링 |
|
||||||
|
| 모두닥 | active | **크롤링** | 병원명 검색 |
|
||||||
|
| Goodoc | active | **크롤링** | 병원명 검색 |
|
||||||
|
| 닥터나우 | active | **크롤링** | 병원명 검색 |
|
||||||
|
|
||||||
|
### 2.8 웹사이트 분석 (WebsiteAudit)
|
||||||
|
|
||||||
|
| 필드 | 현재 Mock 값 | 수집 소스 | 수집 방법 |
|
||||||
|
|------|-------------|-----------|-----------|
|
||||||
|
| `primaryDomain` | `'viewclinic.com'` | 사용자 입력 | URL에서 추출 |
|
||||||
|
| `additionalDomains` | 3개 추가 도메인 | **DNS/리다이렉트 추적** | WHOIS + 크롤링 중 발견되는 도메인 |
|
||||||
|
| `snsLinksOnSite` | `false` | **웹사이트 크롤링** | `<a>` 태그에서 SNS 도메인 검색 |
|
||||||
|
| `trackingPixels[].name` | FB Pixel, Kakao Pixel, GTM | **HTML 파싱** | `<script>` 태그 패턴 매칭 |
|
||||||
|
| `trackingPixels[].installed` | true/false | **HTML 파싱** | 스크립트 존재 여부 |
|
||||||
|
| `trackingPixels[].details` | Pixel ID, GTM ID | **HTML 파싱** | 정규식으로 ID 추출 |
|
||||||
|
| `mainCTA` | `'전화 + 카카오톡 상담'` | **AI 분석** | 메인 페이지 CTA 버튼/링크 분석 |
|
||||||
|
|
||||||
|
### 2.9 문제 진단 (DiagnosisItem[])
|
||||||
|
|
||||||
|
> 모든 진단 항목은 **AI가 생성**한다.
|
||||||
|
|
||||||
|
| 필드 | 타입 | 설명 |
|
||||||
|
|------|------|------|
|
||||||
|
| `category` | string | 문제 영역 (브랜드 아이덴티티, 콘텐츠 전략 등) |
|
||||||
|
| `detail` | string | 상세 설명 텍스트 |
|
||||||
|
| `severity` | `'critical' \| 'warning' \| 'good' \| 'excellent'` | 심각도 등급 |
|
||||||
|
| `evidenceIds` | string[] (optional) | 근거 스크린샷 ID 참조 |
|
||||||
|
|
||||||
|
현재 Mock 진단 영역:
|
||||||
|
- YouTube: 7개 항목 (구독자 대비 조회수, Shorts 하락, 업로드 빈도, 톤앤매너 등)
|
||||||
|
- Instagram: 6개 항목 (계정 분리, Reels 전무, 브랜드 비주얼, 크로스포스팅 등)
|
||||||
|
- Facebook: 6개 항목 (로고 파편화, KR 방치, 도메인 불일치, 팔로워 격차 등)
|
||||||
|
- 종합: 3개 항목 (브랜드 파편화, 콘텐츠 전략 부재, 플랫폼 간 유입 단절)
|
||||||
|
|
||||||
|
### 2.10 변환 전략 (TransformationProposal)
|
||||||
|
|
||||||
|
> **전체가 AI 생성 데이터**. 수집된 raw 데이터의 As-Is를 분석하고 To-Be를 제안한다.
|
||||||
|
|
||||||
|
#### brandIdentity (AsIsToBeItem[])
|
||||||
|
|
||||||
|
| area | asIs (수집 데이터 기반) | toBe (AI 제안) |
|
||||||
|
|------|----------------------|----------------|
|
||||||
|
| 로고 | 채널마다 다른 로고 4종 | VIEW 골드 로고 1종 통일 |
|
||||||
|
| 컬러 팔레트 | 없음 (혼재) | Primary: Gold (#C4A462) + Dark (#1A1A1A) |
|
||||||
|
| 프로필 사진 | KR=모델, EN=로고, FB=깃털 | 전 채널 VIEW 골드 로고 통일 |
|
||||||
|
| 바이오 메시지 | 채널마다 다른 메시지 | "안전이 예술이 되는 곳 — 21년 무사고 VIEW" |
|
||||||
|
| 해시태그 | 비체계적 | #뷰성형외과 #VIEW성형 #강남성형외과 #21년무사고 |
|
||||||
|
|
||||||
|
#### contentStrategy (AsIsToBeItem[])
|
||||||
|
|
||||||
|
| area | asIs | toBe |
|
||||||
|
|------|------|------|
|
||||||
|
| 콘텐츠 캘린더 | 없음 | 월간 콘텐츠 캘린더 (4주 사이클) |
|
||||||
|
| 업로드 빈도 | YouTube 주1회, Instagram 비정기 | YouTube 주3회 + Instagram 일1회 + Shorts/Reels 주5회 |
|
||||||
|
| 콘텐츠 포맷 | KR Instagram = 카드뉴스만 | 카드뉴스 30% + Reels 40% + 카루셀 20% + Stories 10% |
|
||||||
|
| 콘텐츠 앵글 | 시술 정보 중심 (병원 관점) | 환자 의사결정 보조 중심 (환자 관점) |
|
||||||
|
| 톤앤매너 | 없음 | "차분한 전문가" — 과장 없이, 설명으로 설득 |
|
||||||
|
|
||||||
|
#### platformStrategies (PlatformStrategy[])
|
||||||
|
|
||||||
|
| platform | currentMetric | targetMetric | 전략 개수 |
|
||||||
|
|----------|--------------|-------------|-----------|
|
||||||
|
| YouTube | 103K subscribers | 200K / 12개월 | 4개 |
|
||||||
|
| Instagram KR | 14K followers | 50K / 12개월 | 4개 |
|
||||||
|
| Facebook | KR 253 + EN 88K | 통합 관리 | 3개 |
|
||||||
|
|
||||||
|
#### websiteImprovements (AsIsToBeItem[])
|
||||||
|
|
||||||
|
| area | asIs | toBe |
|
||||||
|
|------|------|------|
|
||||||
|
| SNS 링크 | 홈페이지에 0개 | Header/Footer에 YouTube + Instagram + KakaoTalk |
|
||||||
|
| YouTube 임베드 | 없음 | 시술 페이지별 관련 YouTube 영상 임베드 |
|
||||||
|
| 콘텐츠 허브 | 없음 | SEO 콘텐츠 허브 구축 |
|
||||||
|
| 도메인 통합 | 4개 도메인 분산 | viewclinic.com 단일 도메인 + /en 국제 페이지 |
|
||||||
|
|
||||||
|
#### newChannelProposals
|
||||||
|
|
||||||
|
| channel | priority | rationale |
|
||||||
|
|---------|----------|-----------|
|
||||||
|
| TikTok | P1 | 20~30대 첫 수술 고민층 도달, YouTube Shorts 동시 배포 |
|
||||||
|
| 네이버 블로그 | P0 | 한국 검색 1위 플랫폼 — SEO 핵심 |
|
||||||
|
| 네이버 플레이스 | P0 | 지역 검색 노출 필수 |
|
||||||
|
|
||||||
|
### 2.11 로드맵 (RoadmapMonth[])
|
||||||
|
|
||||||
|
> **AI 생성**. 변환 전략을 월별 실행 계획으로 분해.
|
||||||
|
|
||||||
|
| month | title | subtitle | tasks 수 |
|
||||||
|
|-------|-------|----------|----------|
|
||||||
|
| 1 | Foundation | 기반 구축 | 7개 |
|
||||||
|
| 2 | Content Engine | 콘텐츠 엔진 가동 | 6개 |
|
||||||
|
| 3 | Optimization | 최적화 & 광고 | 6개 |
|
||||||
|
|
||||||
|
각 task는 `{ task: string, completed: boolean }` 구조.
|
||||||
|
|
||||||
|
### 2.12 KPI 대시보드 (KPIMetric[])
|
||||||
|
|
||||||
|
> `current`는 **수집 데이터**, `target3Month`/`target12Month`는 **AI 생성**.
|
||||||
|
|
||||||
|
| metric | current (수집) | target3Month (AI) | target12Month (AI) |
|
||||||
|
|--------|---------------|-------------------|---------------------|
|
||||||
|
| YouTube 구독자 | 103K | 115K | 200K |
|
||||||
|
| YouTube 월 조회수 | ~270K | 500K | 1.5M |
|
||||||
|
| YouTube Shorts 평균 조회수 | 500~1,000 | 5,000 | 20,000 |
|
||||||
|
| Instagram KR 팔로워 | 14K | 20K | 50K |
|
||||||
|
| Instagram KR Reels 평균 조회수 | 0 (없음) | 3,000 | 10,000 |
|
||||||
|
| Instagram EN 팔로워 | 68.8K | 75K | 100K |
|
||||||
|
| 네이버 블로그 방문자 | 0 (없음) | 5,000/월 | 30,000/월 |
|
||||||
|
| 웹사이트 → SNS 유입 | 0% | 5% | 15% |
|
||||||
|
| 콘텐츠 → 상담 전환 | 측정 불가 | UTM 추적 시작 | 월 50건 |
|
||||||
|
|
||||||
|
### 2.13 스크린샷 증거 (ScreenshotEvidence[])
|
||||||
|
|
||||||
|
| 필드 | 설명 | 수집 소스 |
|
||||||
|
|------|------|-----------|
|
||||||
|
| `id` | 고유 식별자 (진단의 evidenceIds에서 참조) | 시스템 생성 |
|
||||||
|
| `url` | 이미지 경로 | **Puppeteer/Playwright** 스크린샷 |
|
||||||
|
| `channel` | 어떤 채널의 스크린샷인지 | 시스템 정의 |
|
||||||
|
| `capturedAt` | 캡처 시점 | 시스템 생성 |
|
||||||
|
| `caption` | 스크린샷 설명 | **AI 생성** |
|
||||||
|
| `sourceUrl` | 원본 페이지 URL | 크롤링 대상 URL |
|
||||||
|
| `annotations` | 강조 표시 (highlight/arrow/text) | **AI 생성** (문제 지점 자동 표시) |
|
||||||
|
|
||||||
|
현재 6개 스크린샷:
|
||||||
|
- `yt-channel` — YouTube 채널 메인
|
||||||
|
- `yt-about-links` — YouTube 정보 탭
|
||||||
|
- `ig-kr-profile` — Instagram KR 프로필
|
||||||
|
- `ig-en-profile` — Instagram EN 프로필
|
||||||
|
- `fb-en-page` — Facebook EN 페이지
|
||||||
|
- `website-homepage` — viewclinic.com 홈페이지
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Plan 데이터 (MarketingPlan)
|
||||||
|
|
||||||
|
> 타입 정의: `src/types/plan.ts`
|
||||||
|
> Mock 데이터: `src/data/mockPlan.ts`
|
||||||
|
> 사용 페이지: `/plan/:id` (`src/pages/MarketingPlanPage.tsx`)
|
||||||
|
> **전체가 AI 생성 데이터** — Report의 수집/분석 결과를 입력으로 사용
|
||||||
|
|
||||||
|
### 3.1 기본 메타
|
||||||
|
|
||||||
|
| 필드 | 설명 | 데이터 소스 |
|
||||||
|
|------|------|------------|
|
||||||
|
| `id` | 플랜 ID | 시스템 생성 |
|
||||||
|
| `reportId` | 연결된 리포트 ID | Report 참조 |
|
||||||
|
| `clinicName` / `clinicNameEn` | 병원명 | Report에서 복사 |
|
||||||
|
| `createdAt` | 생성일 | 시스템 생성 |
|
||||||
|
| `targetUrl` | 대상 URL | Report에서 복사 |
|
||||||
|
|
||||||
|
### 3.2 브랜드 가이드 (BrandGuide)
|
||||||
|
|
||||||
|
> Report의 brandInconsistencies 분석을 기반으로 **AI가 생성하는 가이드라인**.
|
||||||
|
|
||||||
|
| 섹션 | 데이터 구조 | 항목 수 | 데이터 소스 |
|
||||||
|
|------|-----------|--------|------------|
|
||||||
|
| `colors` (ColorSwatch[]) | name, hex, usage | 5개 | **AI 제안** (로고 컬러 + 보완색) |
|
||||||
|
| `fonts` (FontSpec[]) | family, weight, usage, sampleText | 3개 | **AI 제안** |
|
||||||
|
| `logoRules` (LogoUsageRule[]) | rule, description, correct | 6개 | **AI 생성** (불일치 분석 기반) |
|
||||||
|
| `toneOfVoice` | personality[], communicationStyle, do/don't 예시 | - | **AI 생성** |
|
||||||
|
| `channelBranding` (ChannelBrandingRule[]) | channel, profilePhoto, bannerSpec, bioTemplate, currentStatus | 7개 채널 | **AI 생성** + 수집 데이터 (currentStatus) |
|
||||||
|
| `brandInconsistencies` | Report에서 가져옴 | 2개 | Report 데이터 참조 |
|
||||||
|
|
||||||
|
### 3.3 채널 커뮤니케이션 전략 (ChannelStrategyCard[])
|
||||||
|
|
||||||
|
| 필드 | 설명 | 데이터 소스 |
|
||||||
|
|------|------|------------|
|
||||||
|
| `channelId` / `channelName` / `icon` | 채널 식별 | 시스템 정의 |
|
||||||
|
| `currentStatus` | 현재 상태 요약 | **Report 수집 데이터** |
|
||||||
|
| `targetGoal` | 목표 | **AI 생성** |
|
||||||
|
| `contentTypes` | 콘텐츠 유형 목록 | **AI 생성** |
|
||||||
|
| `postingFrequency` | 게시 빈도 | **AI 생성** |
|
||||||
|
| `tone` | 톤앤매너 | **AI 생성** |
|
||||||
|
| `formatGuidelines` | 포맷 가이드라인 | **AI 생성** |
|
||||||
|
| `priority` | P0/P1/P2 | **AI 판단** |
|
||||||
|
|
||||||
|
현재 7개 채널: YouTube, Instagram KR, Instagram EN, Facebook, Naver Blog, TikTok, KakaoTalk
|
||||||
|
|
||||||
|
### 3.4 콘텐츠 마케팅 전략 (ContentStrategyData)
|
||||||
|
|
||||||
|
| 섹션 | 데이터 구조 | 항목 수 | 데이터 소스 |
|
||||||
|
|------|-----------|--------|------------|
|
||||||
|
| `pillars` (ContentPillar[]) | title, description, relatedUSP, exampleTopics, color | 4개 | **AI 생성** (병원 USP 분석 기반) |
|
||||||
|
| `typeMatrix` (ContentTypeRow[]) | format, channels, frequency, purpose | 6개 | **AI 생성** |
|
||||||
|
| `workflow` (WorkflowStep[]) | step, name, description, owner, duration | 5단계 | **AI 생성** |
|
||||||
|
| `repurposingSource` | 원본 콘텐츠 정의 | 1개 | **AI 생성** |
|
||||||
|
| `repurposingOutputs` (RepurposingOutput[]) | format, channel, description | 6개 | **AI 생성** |
|
||||||
|
|
||||||
|
콘텐츠 필러 4개:
|
||||||
|
1. 수술 전문성 (Surgical Authority)
|
||||||
|
2. 안전 & 신뢰 (Trust & Safety)
|
||||||
|
3. 결과 예측 (Result Predictability)
|
||||||
|
4. 환자 여정 (Patient Guidance)
|
||||||
|
|
||||||
|
### 3.5 콘텐츠 캘린더 (CalendarData)
|
||||||
|
|
||||||
|
| 필드 | 설명 | 데이터 소스 |
|
||||||
|
|------|------|------------|
|
||||||
|
| `weeks` (CalendarWeek[]) | 주차별 콘텐츠 스케줄 | **AI 생성** |
|
||||||
|
| `weeks[].entries` (CalendarEntry[]) | 요일, 채널, 콘텐츠 유형, 제목 | **AI 생성** |
|
||||||
|
| `monthlySummary` (ContentCountSummary[]) | 유형별 월간 합계 | **계산** (entries 집계) |
|
||||||
|
|
||||||
|
현재 4주 캘린더, 주당 7~8개 항목, 월간 합계:
|
||||||
|
- 영상 16개 / 블로그 8개 / 소셜 12개 / 광고 4개
|
||||||
|
|
||||||
|
### 3.6 에셋 수집 (AssetCollectionData)
|
||||||
|
|
||||||
|
> 기존 콘텐츠 자산을 AI가 분류하고 재활용 방법을 제안.
|
||||||
|
|
||||||
|
| 필드 | 설명 | 데이터 소스 |
|
||||||
|
|------|------|------------|
|
||||||
|
| `assets` (AssetCard[]) | 수집된/수집 예정 에셋 목록 | 일부 **크롤링 수집**, 일부 **AI 제안** |
|
||||||
|
| `assets[].source` | homepage / naver_place / blog / social / youtube | 에셋 출처 |
|
||||||
|
| `assets[].type` | photo / video / text | 에셋 유형 |
|
||||||
|
| `assets[].status` | collected / pending / needs_creation | 수집 상태 |
|
||||||
|
| `assets[].repurposingSuggestions` | 재활용 제안 목록 | **AI 생성** |
|
||||||
|
| `youtubeRepurpose` (YouTubeRepurposeItem[]) | YouTube 영상 재활용 계획 | **AI 생성** (topVideos 기반) |
|
||||||
|
|
||||||
|
현재 11개 에셋 + 5개 YouTube 재활용 항목
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Studio 데이터 (Content Studio)
|
||||||
|
|
||||||
|
> 타입 정의: `src/types/studio.ts`
|
||||||
|
> 사용 페이지: `/studio/:id` (`src/pages/ContentStudioPage.tsx`)
|
||||||
|
|
||||||
|
### 4.1 채널/포맷 옵션 (하드코딩 상수)
|
||||||
|
|
||||||
|
| 채널 | 포맷 | aspect ratio |
|
||||||
|
|------|------|-------------|
|
||||||
|
| YouTube | Shorts (9:16), Long-form (16:9) | |
|
||||||
|
| Instagram | Reels (9:16), Carousel (1:1), Feed Image (1:1), Stories (9:16) | |
|
||||||
|
| Naver Blog | SEO Post (16:9), FAQ Post (16:9) | |
|
||||||
|
| TikTok | Short Video (9:16) | |
|
||||||
|
| Facebook | Ad Creative (1:1), Retarget Content (16:9) | |
|
||||||
|
|
||||||
|
### 4.2 음악 트랙 (하드코딩 상수)
|
||||||
|
|
||||||
|
12개 트랙, 4개 장르: calm (3개), upbeat (3개), cinematic (3개), corporate (3개)
|
||||||
|
|
||||||
|
### 4.3 AI 이미지 생성 (geminiImageGen)
|
||||||
|
|
||||||
|
| 데이터 | 소스 | 용도 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `StudioState.channel` | 사용자 선택 | 프롬프트 힌트 (채널별 스타일) |
|
||||||
|
| `StudioState.format` | 사용자 선택 | aspect ratio 결정 |
|
||||||
|
| `StudioState.pillarId` | 사용자 선택 | 프롬프트 테마 (safety/expertise/results/care) |
|
||||||
|
| 컬러 팔레트 | **하드코딩** | `#7B2D8E, #E8B931, #FAF8F5` (Report의 brandColors와 동일해야 함) |
|
||||||
|
|
||||||
|
**의존성:** 현재 컬러 팔레트가 geminiImageGen.ts에 하드코딩되어 있으므로, 실제 구현 시 Report의 `brandColors`에서 동적으로 주입해야 함.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 수집 소스별 분류
|
||||||
|
|
||||||
|
### 5.1 사용자 입력 (수동)
|
||||||
|
|
||||||
|
| 데이터 | 설명 |
|
||||||
|
|--------|------|
|
||||||
|
| 대상 URL | 분석 시작점 (필수) |
|
||||||
|
| 병원명 (선택) | URL에서 추출 불가 시 |
|
||||||
|
|
||||||
|
### 5.2 웹사이트 크롤링 (Firecrawl / Puppeteer)
|
||||||
|
|
||||||
|
| 수집 대상 | 사용 위치 | 수집 방법 |
|
||||||
|
|----------|-----------|-----------|
|
||||||
|
| 병원명 (한/영) | ClinicSnapshot | `<title>`, 메타태그 |
|
||||||
|
| 로고 이미지 | ClinicSnapshot.logoImages | `<img>` 태그, favicon |
|
||||||
|
| 브랜드 컬러 | ClinicSnapshot.brandColors | CSS 파싱, 로고 색상 추출 |
|
||||||
|
| 전화번호 | ClinicSnapshot.phone | `<a href="tel:">` |
|
||||||
|
| 주소 | ClinicSnapshot.location | 구조화 데이터 |
|
||||||
|
| 인증/수상 | ClinicSnapshot.certifications | 본문 섹션 파싱 |
|
||||||
|
| SNS 링크 유무 | WebsiteAudit.snsLinksOnSite | `<a>` 태그 SNS 도메인 검색 |
|
||||||
|
| 트래킹 픽셀 | WebsiteAudit.trackingPixels | `<script>` 패턴 매칭 |
|
||||||
|
| 추가 도메인 | WebsiteAudit.additionalDomains | 리다이렉트 추적 |
|
||||||
|
| 페이지 스크린샷 | screenshots[] | Puppeteer 전체 페이지 캡처 |
|
||||||
|
| SNS 프로필 URL | Instagram/Facebook 핸들 발견 | `<a>` 태그에서 소셜 링크 추출 |
|
||||||
|
|
||||||
|
### 5.3 YouTube Data API v3
|
||||||
|
|
||||||
|
| 수집 대상 | API 엔드포인트 | 사용 위치 |
|
||||||
|
|----------|----------------|-----------|
|
||||||
|
| 채널 기본 정보 | `channels.list` (part=snippet,statistics,brandingSettings) | YouTubeAudit 전반 |
|
||||||
|
| 재생목록 | `playlists.list` | YouTubeAudit.playlists |
|
||||||
|
| 영상 검색 (조회순) | `search.list` (order=viewCount) | YouTubeAudit.topVideos |
|
||||||
|
| 영상 상세 | `videos.list` (part=snippet,statistics,contentDetails) | topVideos 상세, avgVideoLength |
|
||||||
|
| 최근 업로드 | `search.list` (order=date) | uploadFrequency 계산 |
|
||||||
|
|
||||||
|
### 5.4 Instagram / Facebook (Graph API + Apify)
|
||||||
|
|
||||||
|
| 수집 대상 | 소스 | 사용 위치 |
|
||||||
|
|----------|------|-----------|
|
||||||
|
| 프로필 기본 정보 | Graph API | InstagramAccount, FacebookPage |
|
||||||
|
| 팔로워/팔로잉 수 | Graph API | followers, following |
|
||||||
|
| 게시물 수 | Graph API | posts |
|
||||||
|
| 하이라이트 목록 | **Apify (API 미지원)** | highlights |
|
||||||
|
| Reels 개수 | **Apify (API 미지원)** | reelsCount |
|
||||||
|
| 프로필 사진 분석 | Graph API → 이미지 → AI 분석 | profilePhoto 설명 |
|
||||||
|
| 바이오 텍스트 | Graph API | bio |
|
||||||
|
| 게시 빈도 | Graph API → 계산 | postFrequency |
|
||||||
|
| 참여율 | Graph API → 평균 | engagement |
|
||||||
|
| 페이지 스크린샷 | **Puppeteer** | screenshots[] |
|
||||||
|
|
||||||
|
### 5.5 강남언니 크롤링 (Apify)
|
||||||
|
|
||||||
|
| 수집 대상 | 사용 위치 |
|
||||||
|
|----------|-----------|
|
||||||
|
| 병원 전체 평점 | ClinicSnapshot.overallRating |
|
||||||
|
| 총 리뷰 수 | ClinicSnapshot.totalReviews |
|
||||||
|
| 가격 범위 | ClinicSnapshot.priceRange |
|
||||||
|
| 의료진 수 | ClinicSnapshot.staffCount |
|
||||||
|
| 대표원장 평점/리뷰수 | ClinicSnapshot.leadDoctor |
|
||||||
|
| 인증 뱃지 | ClinicSnapshot.certifications |
|
||||||
|
|
||||||
|
### 5.6 외부 API / 기타
|
||||||
|
|
||||||
|
| 소스 | 수집 대상 | 사용 위치 |
|
||||||
|
|------|----------|-----------|
|
||||||
|
| Social Blade | 구독자 순위, 월 수익 추정 | subscriberRank, estimatedMonthlyRevenue |
|
||||||
|
| Naver Search API | 블로그/카페 검색 | otherChannels (네이버 블로그 존재 여부) |
|
||||||
|
| Naver Place API | 플레이스 등록 정보 | otherChannels (네이버 플레이스) |
|
||||||
|
| Brandfetch API | 도메인 → 로고/컬러 자동 추출 | brandColors (보조 수단) |
|
||||||
|
| Google Vision API | 이미지 → 로고 감지, 색상 추출 | brandColors (고도화) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. AI 생성 데이터
|
||||||
|
|
||||||
|
> 수집된 raw 데이터를 입력으로, LLM(Claude)이 분석/생성해야 하는 모든 데이터.
|
||||||
|
|
||||||
|
### 6.1 Report에서 AI가 생성하는 항목
|
||||||
|
|
||||||
|
| 카테고리 | 생성 항목 | 입력 데이터 |
|
||||||
|
|----------|----------|------------|
|
||||||
|
| **채점** | overallScore, channelScores[].score/status/headline | 전 채널 수집 데이터 |
|
||||||
|
| **진단** | youtubeAudit.diagnosis[], instagramAudit.diagnosis[], facebookAudit.diagnosis[], problemDiagnosis[] | 각 채널 수집 데이터 |
|
||||||
|
| **비교 분석** | brandInconsistencies[] | 전 채널 로고/바이오/도메인 비교 |
|
||||||
|
| **변환 전략** | transformation 전체 (brandIdentity, contentStrategy, platformStrategies, websiteImprovements, newChannelProposals) | 진단 결과 + 수집 데이터 |
|
||||||
|
| **로드맵** | roadmap[] (월별 tasks) | 변환 전략 |
|
||||||
|
| **KPI 목표** | kpiDashboard[].target3Month, target12Month | 현재 수치 + 벤치마크 |
|
||||||
|
| **스크린샷 캡션** | screenshots[].caption, annotations | 스크린샷 이미지 + 진단 결과 |
|
||||||
|
| **통합 권고** | consolidationRecommendation | Facebook 페이지 분석 |
|
||||||
|
|
||||||
|
### 6.2 Plan에서 AI가 생성하는 항목
|
||||||
|
|
||||||
|
| 카테고리 | 생성 항목 | 입력 데이터 |
|
||||||
|
|----------|----------|------------|
|
||||||
|
| **브랜드 가이드** | colors, fonts, logoRules, toneOfVoice, channelBranding | Report의 brandColors + brandInconsistencies |
|
||||||
|
| **채널 전략** | channelStrategies[] (목표, 빈도, 톤, 가이드라인) | Report의 channelScores + diagnosis |
|
||||||
|
| **콘텐츠 전략** | pillars, typeMatrix, workflow, repurposing | 병원 USP + 채널 전략 |
|
||||||
|
| **콘텐츠 캘린더** | weeks[].entries (4주 상세 스케줄) | 콘텐츠 전략 + 빈도 |
|
||||||
|
| **에셋 분류** | assets[].repurposingSuggestions, youtubeRepurpose | 크롤링 수집 에셋 + topVideos |
|
||||||
|
|
||||||
|
### 6.3 Studio에서 AI가 생성하는 항목
|
||||||
|
|
||||||
|
| 카테고리 | 생성 항목 | 입력 데이터 |
|
||||||
|
|----------|----------|------------|
|
||||||
|
| **이미지** | AI 생성 마케팅 이미지 | 채널, 포맷, 콘텐츠 필러, 브랜드 컬러 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 데이터 의존 관계
|
||||||
|
|
||||||
|
```
|
||||||
|
사용자 입력 (URL)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[웹사이트 크롤링] ──→ ClinicSnapshot (기본 정보, 로고, 컬러)
|
||||||
|
│ WebsiteAudit (도메인, SNS 링크, 트래킹)
|
||||||
|
│ SNS 프로필 URL 발견
|
||||||
|
│
|
||||||
|
├──→ [YouTube Data API] ──→ YouTubeAudit
|
||||||
|
├──→ [Instagram API / Apify] ──→ InstagramAudit
|
||||||
|
├──→ [Facebook Graph API / Apify] ──→ FacebookAudit
|
||||||
|
├──→ [강남언니 크롤링] ──→ ClinicSnapshot (평점, 리뷰, 가격)
|
||||||
|
├──→ [네이버 API] ──→ OtherChannels
|
||||||
|
└──→ [Puppeteer 스크린샷] ──→ Screenshots
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[AI 분석 — Report]
|
||||||
|
│
|
||||||
|
├──→ ChannelScores (채점)
|
||||||
|
├──→ Diagnosis (진단)
|
||||||
|
├──→ BrandInconsistencies (비교)
|
||||||
|
├──→ TransformationProposal (전략)
|
||||||
|
├──→ Roadmap (로드맵)
|
||||||
|
└──→ KPIDashboard (목표)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[AI 분석 — Plan]
|
||||||
|
│
|
||||||
|
├──→ BrandGuide (브랜드 가이드)
|
||||||
|
├──→ ChannelStrategies (채널 전략)
|
||||||
|
├──→ ContentStrategy (콘텐츠 전략)
|
||||||
|
├──→ Calendar (캘린더)
|
||||||
|
└──→ AssetCollection (에셋)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[Content Studio]
|
||||||
|
│
|
||||||
|
└──→ AI 이미지 생성 (Gemini)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 핵심 의존성 순서
|
||||||
|
|
||||||
|
1. **웹사이트 크롤링이 먼저** — SNS 프로필 URL을 여기서 발견
|
||||||
|
2. **SNS API 수집은 병렬 가능** — YouTube, Instagram, Facebook 동시 수집
|
||||||
|
3. **AI 분석은 모든 수집 완료 후** — 전 채널 데이터가 있어야 비교/진단 가능
|
||||||
|
4. **Plan은 Report 이후** — Report의 분석 결과가 Plan의 입력
|
||||||
|
5. **Studio는 Plan 이후** — Plan의 브랜드 가이드/콘텐츠 필러가 Studio의 입력
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Firecrawl 수집 검증 결과
|
||||||
|
|
||||||
|
> **검증일:** 2026-03-30
|
||||||
|
> **방법:** WebFetch(단순 HTTP GET + HTML 파싱)로 실제 타겟 사이트 5곳을 테스트하여 Firecrawl 도입 시 기대 가능한 수집 범위를 검증.
|
||||||
|
> Firecrawl은 WebFetch보다 우수한 JS 렌더링 + LLM 기반 구조화 추출을 제공하므로, WebFetch에서 성공한 항목은 Firecrawl에서도 수집 가능하다고 판단.
|
||||||
|
|
||||||
|
### 8.1 사이트별 테스트 결과
|
||||||
|
|
||||||
|
#### viewclinic.com — 수집 가능 (Firecrawl 유효)
|
||||||
|
|
||||||
|
| 필요 데이터 | 추출 결과 | 비고 |
|
||||||
|
|------------|-----------|------|
|
||||||
|
| 병원명 (한/영) | `뷰성형외과` / `VIEW Clinic` | 메타태그 + JSON-LD |
|
||||||
|
| 전화번호 | `02-539-1177` | `tel:` 링크 |
|
||||||
|
| 주소 | `서울 강남구 봉은사로 107` | |
|
||||||
|
| 브랜드 컬러 | `#7d2a86` (보라), 그라디언트 확인 | CSS에서 추출 |
|
||||||
|
| SNS 링크 | **카카오톡만 발견** (`pf.kakao.com/_xbtVxjl`) | YouTube/Instagram/Facebook 링크 미노출 — mock 데이터의 `snsLinksOnSite: false` 확인됨 |
|
||||||
|
| 트래킹 픽셀 | FB Pixel `299151214739571`, GTM `GTM-52RT6DMK`, Kakao Pixel `5684247168888976904` | 전부 추출 성공 |
|
||||||
|
| 인증/수상 | 가슴보형물 1위, 복지부장관 표창, 안면윤곽 대상, 렛미인 | |
|
||||||
|
| 로고 이미지 | 일부 이미지 경로만 (`/image/` 하위) | 명시적 `<img>` 로고가 아닌 배경 이미지 가능성 |
|
||||||
|
| JSON-LD | Organization, 언어 ko-KR | |
|
||||||
|
| Kakao SDK Key | `4c6038836d6b8bf205b5c315606f053b` | |
|
||||||
|
|
||||||
|
**결론:** JS 렌더링 불필요한 전통적 웹사이트. Firecrawl로 충분히 수집 가능.
|
||||||
|
|
||||||
|
#### YouTube (@ViewclinicKR) — 수집 불가
|
||||||
|
|
||||||
|
| 시도 | 결과 |
|
||||||
|
|------|------|
|
||||||
|
| WebFetch | **실패** — JS 번들/설정 코드만 반환, 채널 메타데이터 없음 |
|
||||||
|
| 추출 가능 데이터 | 클라이언트 리전(KR), 인터페이스 언어(ko)만 |
|
||||||
|
| 추출 불가 데이터 | 채널명, 구독자 수, 영상 수, 설명, 재생목록, 프로필 이미지 전부 불가 |
|
||||||
|
|
||||||
|
**결론:** YouTube는 InnerTube API 기반 SPA. Firecrawl의 JS 렌더링으로도 안정적 수집 어려움. **YouTube Data API v3 필수**.
|
||||||
|
|
||||||
|
#### Instagram (@viewplastic) — 부분 수집만 가능
|
||||||
|
|
||||||
|
| 시도 | 결과 |
|
||||||
|
|------|------|
|
||||||
|
| WebFetch | **부분 성공** — 핸들, 바이오 텍스트, 카테고리만 추출 |
|
||||||
|
| 추출 가능 데이터 | `@viewplastic`, 바이오: "뷰 성형외과 \| 가슴성형·안면윤곽·눈성형·코성형·리프팅", 카테고리: Medical |
|
||||||
|
| 추출 불가 데이터 | 팔로워 수, 팔로잉 수, 게시물 수, 프로필 사진 URL, 외부 링크 |
|
||||||
|
|
||||||
|
**결론:** 로그인 없이 기본 정보만 노출. 정량 데이터(팔로워, Reels 개수, 하이라이트)는 **Apify 또는 Instagram Graph API 필수**.
|
||||||
|
|
||||||
|
#### Facebook (viewclinic) — 수집 거의 불가
|
||||||
|
|
||||||
|
| 시도 | 결과 |
|
||||||
|
|------|------|
|
||||||
|
| WebFetch | **거의 실패** — 페이지명 `"View Plastic Surgery \| Seoul"`만 추출 |
|
||||||
|
| 추출 불가 데이터 | 팔로워, 바이오, 카테고리, 연락처, 웹사이트 링크, 프로필 사진, 리뷰 전부 불가 |
|
||||||
|
|
||||||
|
**결론:** React SPA + 로그인 유도 구조. **Facebook Graph API 또는 Apify 필수**.
|
||||||
|
|
||||||
|
#### 강남언니 (hospitals/189) — 수집 가능 (Firecrawl 유효)
|
||||||
|
|
||||||
|
| 필요 데이터 | 추출 결과 | mock 데이터 대비 |
|
||||||
|
|------------|-----------|----------------|
|
||||||
|
| 병원 평점 | `9.5/10` | mock: 4.8/5.0 → 10점 만점 스케일 차이 |
|
||||||
|
| 총 리뷰 수 | `18,959` | mock: 18,840 (실시간 증가) |
|
||||||
|
| 의료진 수 | `31명` (전원 이름/평점/리뷰수 포함) | mock: 28명 (최신 데이터 반영 필요) |
|
||||||
|
| 대표원장 | 최순우, 9.4점, 1,812 리뷰 | mock: 4.7점 → 5점 만점 환산값이었음 |
|
||||||
|
| 가격 범위 | 시술별 상세 가격 (₩220,000~₩3,289,000+) | mock보다 상세 |
|
||||||
|
| 인증 뱃지 | 10개 (수술실 CCTV, 마취과 상주, 입원실 등) | |
|
||||||
|
| 시술 카테고리 | 10개 | |
|
||||||
|
| 진료시간 | 요일별 상세 | mock에 없던 데이터 |
|
||||||
|
| 상담 현황 | 최근 상담 857명 | mock에 없던 데이터 |
|
||||||
|
|
||||||
|
**결론:** SSR 사이트이므로 Firecrawl/WebFetch 모두 잘 동작. **가장 풍부한 단일 데이터 소스**.
|
||||||
|
|
||||||
|
### 8.2 변환 전략(Transformation) 섹션 데이터 소스 분석
|
||||||
|
|
||||||
|
변환 전략은 모든 항목이 **As-Is(현재 상태) → To-Be(개선안)** 구조이며, As-Is는 수집 데이터, To-Be는 AI 생성이다.
|
||||||
|
|
||||||
|
#### 브랜드 아이덴티티 탭
|
||||||
|
|
||||||
|
| area | As-Is 데이터 | 수집 소스 | Firecrawl? |
|
||||||
|
|------|-------------|-----------|------------|
|
||||||
|
| 로고 | "채널마다 다른 로고 4종" | 각 채널 프로필 사진 비교 | X → **Apify** |
|
||||||
|
| 컬러 팔레트 | "없음 (혼재)" | 웹사이트 CSS + 각 채널 비주얼 | 부분 (웹사이트만) |
|
||||||
|
| 프로필 사진 | "KR=모델, EN=로고, FB=깃털" | Instagram/Facebook 프로필 이미지 | X → **Apify** |
|
||||||
|
| 바이오 메시지 | "채널마다 다른 메시지" | 각 채널 바이오 텍스트 | X → **Apify** |
|
||||||
|
| 해시태그 | "비체계적" | 각 채널 게시물 분석 | X → **Apify** |
|
||||||
|
|
||||||
|
#### 콘텐츠 전략 탭
|
||||||
|
|
||||||
|
| area | As-Is 데이터 | 수집 소스 | Firecrawl? |
|
||||||
|
|------|-------------|-----------|------------|
|
||||||
|
| 콘텐츠 캘린더 | "없음" | 업로드 패턴 분석 | X → **YouTube API + Apify** |
|
||||||
|
| 업로드 빈도 | "YouTube 주1회, Instagram 비정기" | 업로드 날짜 분석 | X → **YouTube API + Apify** |
|
||||||
|
| 콘텐츠 포맷 | "KR Instagram = 카드뉴스만" | 게시물 유형 분류 | X → **Apify** |
|
||||||
|
| 콘텐츠 앵글 | "시술 정보 중심 (병원 관점)" | 게시물 내용 분석 | X → **Apify + AI 분석** |
|
||||||
|
| 톤앤매너 | "없음" | 게시물 텍스트 분석 | X → **Apify + AI 분석** |
|
||||||
|
|
||||||
|
#### 플랫폼 전략 탭
|
||||||
|
|
||||||
|
| platform | currentMetric (As-Is) | 수집 소스 | Firecrawl? |
|
||||||
|
|----------|----------------------|-----------|------------|
|
||||||
|
| YouTube | "103K subscribers" | 구독자 수 | X → **YouTube Data API** |
|
||||||
|
| Instagram KR | "14K followers" | 팔로워 수 | X → **Apify** |
|
||||||
|
| Facebook | "KR 253 + EN 88K" | 페이지별 팔로워 | X → **Apify** |
|
||||||
|
|
||||||
|
#### 웹사이트 개선 탭
|
||||||
|
|
||||||
|
| area | As-Is 데이터 | 수집 소스 | Firecrawl? |
|
||||||
|
|------|-------------|-----------|------------|
|
||||||
|
| SNS 링크 | "홈페이지에 0개" | 웹사이트 `<a>` 태그 분석 | **O** |
|
||||||
|
| YouTube 임베드 | "없음" | 웹사이트 `<iframe>` 검색 | **O** |
|
||||||
|
| 콘텐츠 허브 | "없음" | 사이트맵/페이지 구조 분석 | **O** |
|
||||||
|
| 도메인 통합 | "4개 도메인 분산" | 리다이렉트/링크 추적 | **O** |
|
||||||
|
|
||||||
|
#### 신규 채널 제안 탭
|
||||||
|
|
||||||
|
| channel | As-Is 확인 사항 | 수집 소스 | Firecrawl? |
|
||||||
|
|---------|---------------|-----------|------------|
|
||||||
|
| TikTok | "계정 없음" | TikTok 핸들 검색 | X → **Apify** |
|
||||||
|
| 네이버 블로그 | "미확인" | 네이버 검색 | X → **Naver Search API** |
|
||||||
|
| 네이버 플레이스 | "미확인" | 네이버 플레이스 검색 | X → **Naver Place API** |
|
||||||
|
|
||||||
|
#### 변환 전략 섹션 소스별 커버리지
|
||||||
|
|
||||||
|
```
|
||||||
|
Firecrawl ██░░░░░░░░ ~15% (웹사이트 개선 탭만)
|
||||||
|
Apify ██████░░░░ ~35% (Instagram/Facebook/TikTok 현황)
|
||||||
|
YouTube API ███░░░░░░░ ~15% (YouTube 현황)
|
||||||
|
Naver API █░░░░░░░░░ ~5% (네이버 존재 여부)
|
||||||
|
Claude AI ███████░░░ ~30% (To-Be 전체 + 진단 텍스트)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 전체 플랫폼 기준 Firecrawl 커버리지
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Firecrawl로 수집 가능 (~35%) │
|
||||||
|
│ ✓ viewclinic.com (웹사이트 전체) │
|
||||||
|
│ ✓ 강남언니 (평점, 리뷰, 가격, 의료진) │
|
||||||
|
│ ✓ 모두닥, Goodoc 등 SSR 사이트 │
|
||||||
|
│ ✓ 네이버 블로그 콘텐츠 (존재 시) │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ 별도 API/Apify 필수 (~45%) │
|
||||||
|
│ ✗ YouTube → YouTube Data API v3 │
|
||||||
|
│ ✗ Instagram → Graph API 또는 Apify │
|
||||||
|
│ ✗ Facebook → Graph API 또는 Apify │
|
||||||
|
│ ✗ TikTok → TikTok API 또는 Apify │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ AI 분석/생성 (~20%) │
|
||||||
|
│ ✗ 채점, 진단, 전략, 로드맵, KPI │
|
||||||
|
│ → Claude API │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 실제 구현 시 필요한 도구 조합
|
||||||
|
|
||||||
|
| 우선순위 | 도구 | 역할 | 월 비용 |
|
||||||
|
|----------|------|------|---------|
|
||||||
|
| **P0** | **Firecrawl** | 웹사이트 크롤링 (로고, 컬러, 트래킹, SNS 링크, 스크린샷) | ~₩26,000 |
|
||||||
|
| **P0** | **YouTube Data API v3** | YouTube 채널/영상 전체 데이터 | 무료 |
|
||||||
|
| **P0** | **Apify** | Instagram + Facebook + TikTok + 강남언니 프로필 스크래핑 | ~₩65,000 |
|
||||||
|
| **P0** | **Claude API** | 전체 분석, 진단, 전략, 로드맵, KPI 생성 | ~₩50,000 |
|
||||||
|
| **P1** | **Naver Search API** | 네이버 블로그/카페 존재 여부 | 무료 |
|
||||||
|
| **P1** | Instagram/Facebook Graph API | 공식 게시/인사이트 (Distribution 단계) | 무료 |
|
||||||
|
| **P2** | Social Blade | YouTube 순위, 수익 추정 | 유료 |
|
||||||
|
| **P2** | Naver Place API | 플레이스 상세 정보 | 유료 |
|
||||||
|
|
||||||
|
### 8.5 mock 데이터 vs 실제 수집 데이터 차이 발견
|
||||||
|
|
||||||
|
검증 과정에서 mock 데이터와 실제 데이터 간 차이가 발견됨:
|
||||||
|
|
||||||
|
| 항목 | mock 값 | 실제 값 | 원인 |
|
||||||
|
|------|---------|---------|------|
|
||||||
|
| 강남언니 평점 | 4.8 (5점 만점) | 9.5 (10점 만점) | **스케일 다름** — mock 작성 시 5점 만점으로 환산한 것 |
|
||||||
|
| 총 리뷰 수 | 18,840 | 18,959 | 실시간 증가 (정상) |
|
||||||
|
| 의료진 수 | 28명 | 31명 | mock 작성 이후 증원 |
|
||||||
|
| 대표원장 리뷰 | 1,809 | 1,812 | 실시간 증가 (정상) |
|
||||||
|
| 브랜드 컬러 primary | `#7B2D8E` | `#7d2a86` | 근사값이나 정확히 다름 — 자동 추출 시 보정 필요 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 2026-03-30*
|
||||||
|
|
@ -10,6 +10,8 @@ import ContentStudioPage from './pages/ContentStudioPage.tsx';
|
||||||
import ChannelConnectPage from './pages/ChannelConnectPage.tsx';
|
import ChannelConnectPage from './pages/ChannelConnectPage.tsx';
|
||||||
import DistributionPage from './pages/DistributionPage.tsx';
|
import DistributionPage from './pages/DistributionPage.tsx';
|
||||||
import PerformancePage from './pages/PerformancePage.tsx';
|
import PerformancePage from './pages/PerformancePage.tsx';
|
||||||
|
import DataValidationPage from './pages/DataValidationPage.tsx';
|
||||||
|
import ClinicProfilePage from './pages/ClinicProfilePage.tsx';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
|
@ -25,6 +27,8 @@ createRoot(document.getElementById('root')!).render(
|
||||||
<Route path="channels" element={<ChannelConnectPage />} />
|
<Route path="channels" element={<ChannelConnectPage />} />
|
||||||
<Route path="distribute" element={<DistributionPage />} />
|
<Route path="distribute" element={<DistributionPage />} />
|
||||||
<Route path="performance" element={<PerformancePage />} />
|
<Route path="performance" element={<PerformancePage />} />
|
||||||
|
<Route path="data-validation" element={<DataValidationPage />} />
|
||||||
|
<Route path="clinic/:id" element={<ClinicProfilePage />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,443 @@
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
import {
|
||||||
|
YoutubeFilled,
|
||||||
|
InstagramFilled,
|
||||||
|
FacebookFilled,
|
||||||
|
GlobeFilled,
|
||||||
|
} from '../components/icons/FilledIcons';
|
||||||
|
import {
|
||||||
|
MapPin,
|
||||||
|
Phone,
|
||||||
|
Clock,
|
||||||
|
Award,
|
||||||
|
ShieldCheck,
|
||||||
|
Star,
|
||||||
|
ExternalLink,
|
||||||
|
Users,
|
||||||
|
Video,
|
||||||
|
MessageSquare,
|
||||||
|
ChevronRight,
|
||||||
|
Camera,
|
||||||
|
BadgeCheck,
|
||||||
|
Building2,
|
||||||
|
GraduationCap,
|
||||||
|
Stethoscope,
|
||||||
|
Heart,
|
||||||
|
TrendingUp,
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
// ─── 수집 데이터 기반 병원 프로필 (정적 데이터) ───
|
||||||
|
|
||||||
|
const CLINIC = {
|
||||||
|
name: '뷰성형외과의원',
|
||||||
|
nameEn: 'VIEW Plastic Surgery',
|
||||||
|
logo: '/assets/clients/view-clinic/logo-circle.png',
|
||||||
|
brandColor: '#7B2D8E',
|
||||||
|
established: 2005,
|
||||||
|
location: '서울 강남구 봉은사로 107 뷰성형외과 빌딩',
|
||||||
|
nearestStation: '9호선 신논현역 3번 출구 도보 1분',
|
||||||
|
phone: '02-539-1177',
|
||||||
|
hours: [
|
||||||
|
{ day: '월~목', time: '10:00 – 19:00' },
|
||||||
|
{ day: '금요일', time: '10:00 – 21:00' },
|
||||||
|
{ day: '토요일', time: '10:00 – 17:00' },
|
||||||
|
{ day: '일/공휴일', time: '휴진' },
|
||||||
|
],
|
||||||
|
specialties: ['가슴성형', '안면윤곽', '양악', '눈성형', '코성형', '지방흡입', '리프팅', '피부시술', '필러/보톡스'],
|
||||||
|
certifications: [
|
||||||
|
'수술실 CCTV 운영',
|
||||||
|
'전담 마취과 전문의 상주',
|
||||||
|
'입원실 완비',
|
||||||
|
'의료진 실명 공개',
|
||||||
|
'응급 대응 체계',
|
||||||
|
'분야별 공동 진료',
|
||||||
|
'전용 휴식 공간',
|
||||||
|
'시술 후 관리',
|
||||||
|
'야간 진료',
|
||||||
|
'여성 의사 진료',
|
||||||
|
],
|
||||||
|
mediaAppearances: ['렛미인 TV 출연', '보건복지부장관 표창 수상', '안면윤곽 대상 수상', '모티바 사용량 1위'],
|
||||||
|
websites: [
|
||||||
|
{ label: '공식 홈페이지', url: 'viewclinic.com', primary: true },
|
||||||
|
{ label: '영문 사이트', url: 'viewplasticsurgery.com', primary: false },
|
||||||
|
],
|
||||||
|
socialChannels: [
|
||||||
|
{ platform: 'YouTube', handle: '@ViewclinicKR', url: 'youtube.com/@ViewclinicKR', followers: '103K 구독자', videos: '1,064개 영상', icon: YoutubeFilled, color: '#FF0000' },
|
||||||
|
{ platform: 'Instagram KR', handle: '@viewplastic', url: 'instagram.com/viewplastic', followers: '14K 팔로워', videos: '1,409 게시물', icon: InstagramFilled, color: '#E1306C' },
|
||||||
|
{ platform: 'Instagram EN', handle: '@view_plastic_surgery', url: 'instagram.com/view_plastic_surgery', followers: '68.8K 팔로워', videos: '2,524 게시물', icon: InstagramFilled, color: '#E1306C' },
|
||||||
|
{ platform: 'Facebook', handle: 'View Plastic Surgery', url: 'facebook.com/viewclinic', followers: '88K 팔로워', videos: '', icon: FacebookFilled, color: '#1877F2' },
|
||||||
|
{ platform: '카카오톡', handle: '뷰성형외과의원', url: 'pf.kakao.com/_xbtVxjl', followers: '상담 채널', videos: '', icon: MessageSquare as any, color: '#FEE500' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── 의료진 (강남언니 실제 데이터) ───
|
||||||
|
|
||||||
|
const DOCTORS = [
|
||||||
|
{ name: '최순우', title: '대표원장', specialty: '가슴성형', credentials: '서울대 출신, 의학박사', rating: 9.4, reviews: 1812, featured: true },
|
||||||
|
{ name: '정재현', title: '원장', specialty: '가슴성형', credentials: '성형외과 전문의', rating: 9.6, reviews: 3177, featured: false },
|
||||||
|
{ name: '김정민', title: '원장', specialty: '리프팅 · 눈성형', credentials: '성형외과 전문의', rating: 9.7, reviews: 878, featured: false },
|
||||||
|
{ name: '윤창운', title: '원장', specialty: '안면윤곽 · 양악', credentials: '성형외과 전문의', rating: 9.6, reviews: 764, featured: false },
|
||||||
|
{ name: '조진우', title: '원장', specialty: '리프팅 · 지방 · 눈코', credentials: '성형외과 전문의', rating: 9.6, reviews: 1624, featured: false },
|
||||||
|
{ name: '김도형', title: '원장', specialty: '눈성형 · 코성형', credentials: '성형외과 전문의', rating: 9.7, reviews: 191, featured: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── 플랫폼별 평점 (집계) ───
|
||||||
|
|
||||||
|
const RATINGS = [
|
||||||
|
{ platform: '강남언니', rating: '9.5', scale: '/10', reviews: '18,961건', color: '#FF6B8A', pct: 95 },
|
||||||
|
{ platform: '네이버 플레이스', rating: '4.6', scale: '/5', reviews: '324건', color: '#03C75A', pct: 92 },
|
||||||
|
{ platform: 'Google Maps', rating: '4.3', scale: '/5', reviews: '187건', color: '#4285F4', pct: 86 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── 시술 가격 (강남언니 실제 데이터) ───
|
||||||
|
|
||||||
|
const PROCEDURES = [
|
||||||
|
{ name: '가슴성형 (보형물)', price: '₩2,650,000~', category: '가슴' },
|
||||||
|
{ name: '모티바 가슴성형', price: '₩11,550,000~', category: '가슴' },
|
||||||
|
{ name: '눈성형 (매몰/절개)', price: '₩440,000~', category: '눈' },
|
||||||
|
{ name: '코성형', price: '₩990,000~', category: '코' },
|
||||||
|
{ name: '안면윤곽', price: '₩3,289,000~', category: '윤곽' },
|
||||||
|
{ name: '지방흡입', price: '₩1,100,000~', category: '바디' },
|
||||||
|
{ name: '실리프팅', price: '₩1,958,000~', category: '리프팅' },
|
||||||
|
{ name: '필러', price: '₩220,000~', category: '피부' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Component ───
|
||||||
|
|
||||||
|
export default function ClinicProfilePage() {
|
||||||
|
return (
|
||||||
|
<div className="pt-20 min-h-screen bg-slate-50">
|
||||||
|
|
||||||
|
{/* ── Hero / Clinic Header ── */}
|
||||||
|
<div className="bg-[#0A1128] pt-10 pb-16 px-6 relative overflow-hidden">
|
||||||
|
<div className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full bg-[#7B2D8E]/10 blur-[120px]" />
|
||||||
|
<div className="absolute bottom-0 left-0 w-[300px] h-[300px] rounded-full bg-purple-500/5 blur-[100px]" />
|
||||||
|
|
||||||
|
<div className="max-w-5xl mx-auto relative">
|
||||||
|
{/* Breadcrumb */}
|
||||||
|
<div className="flex items-center gap-2 text-xs text-purple-300/60 mb-6">
|
||||||
|
<span>병원 검색</span>
|
||||||
|
<ChevronRight size={12} />
|
||||||
|
<span>강남구</span>
|
||||||
|
<ChevronRight size={12} />
|
||||||
|
<span className="text-purple-200">뷰성형외과</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col md:flex-row items-start gap-6">
|
||||||
|
{/* Logo */}
|
||||||
|
<div className="w-20 h-20 rounded-2xl bg-white/10 backdrop-blur-sm border border-white/20 flex items-center justify-center flex-shrink-0 overflow-hidden">
|
||||||
|
<div className="w-16 h-16 rounded-xl flex items-center justify-center" style={{ backgroundColor: CLINIC.brandColor }}>
|
||||||
|
<span className="text-white font-serif font-bold text-lg">VIEW</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Info */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-3 mb-1">
|
||||||
|
<h1 className="font-serif text-2xl md:text-3xl font-bold text-white">{CLINIC.name}</h1>
|
||||||
|
<BadgeCheck size={22} className="text-[#9B8AD4]" />
|
||||||
|
</div>
|
||||||
|
<p className="text-purple-200/60 text-sm mb-4">{CLINIC.nameEn} · 개원 {CLINIC.established}년 · {new Date().getFullYear() - CLINIC.established}년차</p>
|
||||||
|
|
||||||
|
{/* Quick Stats */}
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{[
|
||||||
|
{ icon: Star, label: '강남언니 9.5점', sub: '18,961 리뷰' },
|
||||||
|
{ icon: Users, label: `의료진 ${DOCTORS.length}명`, sub: '성형외과 전문의' },
|
||||||
|
{ icon: Video, label: 'YouTube 103K', sub: '1,064 영상' },
|
||||||
|
].map((stat) => {
|
||||||
|
const Icon = stat.icon;
|
||||||
|
return (
|
||||||
|
<div key={stat.label} className="flex items-center gap-2 px-3 py-2 rounded-xl bg-white/5 border border-white/10">
|
||||||
|
<Icon size={16} className="text-purple-300" />
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-white">{stat.label}</p>
|
||||||
|
<p className="text-xs text-purple-300/60">{stat.sub}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-5xl mx-auto px-6 -mt-6">
|
||||||
|
<div className="space-y-6">
|
||||||
|
|
||||||
|
{/* ── 기본 정보 카드 ── */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||||
|
>
|
||||||
|
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||||
|
<Building2 size={18} className="text-[#6C5CE7]" />
|
||||||
|
기본 정보
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<MapPin size={16} className="text-slate-400 mt-0.5 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-primary-900">{CLINIC.location}</p>
|
||||||
|
<p className="text-xs text-slate-400">{CLINIC.nearestStation}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Phone size={16} className="text-slate-400 flex-shrink-0" />
|
||||||
|
<p className="text-sm font-medium text-primary-900">{CLINIC.phone}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<GlobeFilled size={16} className="text-slate-400 flex-shrink-0" />
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{CLINIC.websites.map(w => (
|
||||||
|
<span key={w.url} className={`text-sm ${w.primary ? 'text-[#6C5CE7] font-medium' : 'text-slate-400'}`}>
|
||||||
|
{w.url}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<Clock size={16} className="text-slate-400 mt-0.5 flex-shrink-0" />
|
||||||
|
<div className="space-y-1">
|
||||||
|
{CLINIC.hours.map(h => (
|
||||||
|
<div key={h.day} className="flex gap-3 text-sm">
|
||||||
|
<span className="text-slate-400 w-16">{h.day}</span>
|
||||||
|
<span className={`font-medium ${h.time === '휴진' ? 'text-[#D4889A]' : 'text-primary-900'}`}>{h.time}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* ── 플랫폼별 통합 평점 ── */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||||
|
>
|
||||||
|
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||||
|
<Star size={18} className="text-[#6C5CE7]" />
|
||||||
|
통합 평점
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
{RATINGS.map((r, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={r.platform}
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: i * 0.1 }}
|
||||||
|
className="rounded-xl border border-slate-100 p-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<span className="text-sm text-slate-500">{r.platform}</span>
|
||||||
|
<span className="text-xs text-slate-400">{r.reviews}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-baseline gap-1 mb-3">
|
||||||
|
<span className="text-3xl font-bold text-primary-900">{r.rating}</span>
|
||||||
|
<span className="text-sm text-slate-400">{r.scale}</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 bg-slate-100 rounded-full overflow-hidden">
|
||||||
|
<motion.div
|
||||||
|
initial={{ width: 0 }}
|
||||||
|
whileInView={{ width: `${r.pct}%` }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.8, delay: i * 0.1 + 0.2 }}
|
||||||
|
className="h-full rounded-full"
|
||||||
|
style={{ backgroundColor: r.color }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* ── 의료진 ── */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||||
|
>
|
||||||
|
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||||
|
<Stethoscope size={18} className="text-[#6C5CE7]" />
|
||||||
|
의료진 ({DOCTORS.length}명)
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||||
|
{DOCTORS.map((doc, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={doc.name}
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: i * 0.06 }}
|
||||||
|
className={`rounded-xl border p-4 ${doc.featured ? 'border-[#D5CDF5] bg-[#F3F0FF]/30' : 'border-slate-100'}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center flex-shrink-0">
|
||||||
|
<GraduationCap size={18} className="text-slate-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<p className="font-semibold text-primary-900">{doc.name}</p>
|
||||||
|
<span className="text-xs text-slate-400">{doc.title}</span>
|
||||||
|
{doc.featured && <span className="text-xs px-1.5 py-0.5 rounded bg-[#6C5CE7] text-white font-medium">대표</span>}
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-400">{doc.credentials}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between mt-3 pt-3 border-t border-slate-100">
|
||||||
|
<span className="text-xs text-slate-400">{doc.specialty}</span>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-bold text-primary-900">{doc.rating}</span>
|
||||||
|
<span className="text-xs text-slate-400">/ 10</span>
|
||||||
|
<span className="text-xs text-slate-300">·</span>
|
||||||
|
<span className="text-xs text-slate-400">{doc.reviews.toLocaleString()}건</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* ── 시술 및 가격 ── */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||||
|
>
|
||||||
|
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||||
|
<Heart size={18} className="text-[#6C5CE7]" />
|
||||||
|
시술 및 가격
|
||||||
|
</h2>
|
||||||
|
<p className="text-xs text-slate-400 mb-4">강남언니 기준 · 실제 가격은 상담 후 결정</p>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||||
|
{PROCEDURES.map((proc, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={proc.name}
|
||||||
|
initial={{ opacity: 0, x: -10 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: i * 0.04 }}
|
||||||
|
className="flex items-center justify-between px-4 py-3 rounded-xl hover:bg-slate-50 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<span className="text-xs px-2 py-0.5 rounded-full bg-[#F3F0FF] text-[#4A3A7C] font-medium">{proc.category}</span>
|
||||||
|
<span className="text-sm text-primary-900">{proc.name}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-bold text-primary-900">{proc.price}</span>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* ── 인증 & 수상 ── */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||||
|
>
|
||||||
|
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||||
|
<ShieldCheck size={18} className="text-[#6C5CE7]" />
|
||||||
|
인증 및 수상
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-5 gap-2 mb-6">
|
||||||
|
{CLINIC.certifications.map((cert, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={cert}
|
||||||
|
initial={{ opacity: 0, scale: 0.95 }}
|
||||||
|
whileInView={{ opacity: 1, scale: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: i * 0.03 }}
|
||||||
|
className="flex items-center gap-2 px-3 py-2.5 rounded-xl bg-[#F3F0FF]/50 border border-[#D5CDF5]/50"
|
||||||
|
>
|
||||||
|
<BadgeCheck size={14} className="text-[#9B8AD4] flex-shrink-0" />
|
||||||
|
<span className="text-xs text-[#4A3A7C] font-medium">{cert}</span>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{CLINIC.mediaAppearances.map(m => (
|
||||||
|
<span key={m} className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-[#FFF6ED] border border-[#F5E0C5] text-xs text-[#7C5C3A] font-medium">
|
||||||
|
<Award size={12} />
|
||||||
|
{m}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* ── 온라인 채널 ── */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||||||
|
>
|
||||||
|
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||||||
|
<TrendingUp size={18} className="text-[#6C5CE7]" />
|
||||||
|
온라인 채널
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{CLINIC.socialChannels.map((ch, i) => {
|
||||||
|
const Icon = ch.icon;
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={ch.platform}
|
||||||
|
initial={{ opacity: 0, x: -10 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: i * 0.06 }}
|
||||||
|
className="flex items-center gap-4 px-4 py-3 rounded-xl border border-slate-100 hover:border-slate-200 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0" style={{ backgroundColor: `${ch.color}12` }}>
|
||||||
|
<Icon size={20} style={{ color: ch.color }} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<p className="font-medium text-primary-900 text-sm">{ch.platform}</p>
|
||||||
|
<span className="text-xs text-slate-400">{ch.handle}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-400">{ch.url}</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-right flex-shrink-0">
|
||||||
|
<p className="text-sm font-semibold text-primary-900">{ch.followers}</p>
|
||||||
|
{ch.videos && <p className="text-xs text-slate-400">{ch.videos}</p>}
|
||||||
|
</div>
|
||||||
|
<ExternalLink size={14} className="text-slate-300 flex-shrink-0" />
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* ── 데이터 출처 고지 ── */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
whileInView={{ opacity: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="rounded-2xl bg-slate-100/50 border border-slate-200/50 p-5 mb-10"
|
||||||
|
>
|
||||||
|
<p className="text-xs text-slate-400 leading-relaxed">
|
||||||
|
<span className="font-semibold text-slate-500">데이터 출처:</span>{' '}
|
||||||
|
본 프로필은 공개된 정보를 자동 수집하여 구성되었습니다.
|
||||||
|
평점 및 리뷰 수는 강남언니, 네이버 플레이스, Google Maps에서 집계하였으며,
|
||||||
|
시술 가격은 강남언니 기준입니다. 의료진 정보는 병원 공식 홈페이지 및 강남언니에서 수집하였습니다.
|
||||||
|
마지막 업데이트: 2026-03-30 · 정보 수정 요청: contact@infinith.io
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,565 @@
|
||||||
|
import { motion } from 'motion/react';
|
||||||
|
import {
|
||||||
|
GlobeFilled,
|
||||||
|
YoutubeFilled,
|
||||||
|
InstagramFilled,
|
||||||
|
} from '../components/icons/FilledIcons';
|
||||||
|
import {
|
||||||
|
Check,
|
||||||
|
X,
|
||||||
|
Globe,
|
||||||
|
Database,
|
||||||
|
Sparkles,
|
||||||
|
CheckCircle,
|
||||||
|
ArrowRight,
|
||||||
|
AlertTriangle,
|
||||||
|
ShieldCheck,
|
||||||
|
Ban,
|
||||||
|
Server,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import type { ComponentType } from 'react';
|
||||||
|
|
||||||
|
// ─── Types ───
|
||||||
|
|
||||||
|
type SiteStatus = 'success' | 'partial' | 'blocked';
|
||||||
|
|
||||||
|
interface DataPoint {
|
||||||
|
field: string;
|
||||||
|
value: string;
|
||||||
|
collected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SiteResult {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
icon: ComponentType<{ size?: number; className?: string }>;
|
||||||
|
brandColor: string;
|
||||||
|
status: SiteStatus;
|
||||||
|
statusLabel: string;
|
||||||
|
crawlMethod: string;
|
||||||
|
creditsUsed: number;
|
||||||
|
dataPoints: DataPoint[];
|
||||||
|
rawResponse: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Firecrawl 실제 테스트 결과 (2026-03-30) ───
|
||||||
|
|
||||||
|
const SITE_RESULTS: SiteResult[] = [
|
||||||
|
{
|
||||||
|
name: 'viewclinic.com',
|
||||||
|
url: 'https://www.viewclinic.com',
|
||||||
|
icon: GlobeFilled,
|
||||||
|
brandColor: '#7B2D8E',
|
||||||
|
status: 'success',
|
||||||
|
statusLabel: '수집 성공',
|
||||||
|
crawlMethod: 'Firecrawl Extract',
|
||||||
|
creditsUsed: 5,
|
||||||
|
dataPoints: [
|
||||||
|
{ field: '병원명 (한)', value: '뷰성형외과', collected: true },
|
||||||
|
{ field: '병원명 (영)', value: 'VIEW PLASTIC SURGERY', collected: true },
|
||||||
|
{ field: '전화번호', value: '02.539.1177', collected: true },
|
||||||
|
{ field: '주소', value: '서울 강남구 봉은사로 107', collected: true },
|
||||||
|
{ field: 'SNS 링크', value: 'KakaoTalk, YouTube 발견', collected: true },
|
||||||
|
{ field: 'Facebook Pixel', value: 'ID: 299151214739571', collected: true },
|
||||||
|
{ field: 'Google Tag Manager', value: 'GTM-52RT6DMK', collected: true },
|
||||||
|
{ field: 'Kakao Pixel', value: 'ID: 5684247168888976904', collected: true },
|
||||||
|
{ field: 'OG 이미지', value: 'thumbnail_230126.jpg', collected: true },
|
||||||
|
{ field: '네이버 사이트 인증', value: '2개 인증 키 확인', collected: true },
|
||||||
|
{ field: '브랜드 컬러', value: '추출 실패 (CSS 파싱 한계)', collected: false },
|
||||||
|
{ field: '인증/수상 목록', value: '추출 실패 (동적 콘텐츠)', collected: false },
|
||||||
|
],
|
||||||
|
rawResponse: `{
|
||||||
|
"success": true,
|
||||||
|
"extract": {
|
||||||
|
"hospitalName": "뷰성형외과",
|
||||||
|
"hospitalNameEn": "VIEW PLASTIC SURGERY",
|
||||||
|
"phone": "02.539.1177",
|
||||||
|
"address": "서울 강남구 봉은사로 107 뷰성형외과 빌딩",
|
||||||
|
"snsLinks": [
|
||||||
|
{ "platform": "KakaoTalk", "url": "pf.kakao.com/_xbtVxjl" },
|
||||||
|
{ "platform": "YouTube", "url": "youtube.com/@ViewclinicKR" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"creditsUsed": 5
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'YouTube',
|
||||||
|
url: 'https://www.youtube.com/@ViewclinicKR',
|
||||||
|
icon: YoutubeFilled,
|
||||||
|
brandColor: '#FF0000',
|
||||||
|
status: 'partial',
|
||||||
|
statusLabel: '부분 수집',
|
||||||
|
crawlMethod: 'Firecrawl Extract',
|
||||||
|
creditsUsed: 5,
|
||||||
|
dataPoints: [
|
||||||
|
{ field: '채널명', value: '뷰성형외과 VIEW Plastic Surgery', collected: true },
|
||||||
|
{ field: '채널 설명', value: 'og:description에서 추출', collected: true },
|
||||||
|
{ field: '프로필 이미지', value: 'yt3.googleusercontent.com/...', collected: true },
|
||||||
|
{ field: '채널 키워드', value: '19개 태그 (뷰성형외과, 코성형 등)', collected: true },
|
||||||
|
{ field: '채널 ID', value: 'UCQqqH3Klj2HQSHNNSVug-CQ', collected: true },
|
||||||
|
{ field: '구독자 수', value: 'Not available', collected: false },
|
||||||
|
{ field: '총 영상 수', value: 'Not available', collected: false },
|
||||||
|
{ field: '총 조회수', value: 'Not available', collected: false },
|
||||||
|
{ field: '최근 영상 목록', value: 'JS 렌더링 불가', collected: false },
|
||||||
|
{ field: '업로드 빈도', value: 'JS 렌더링 불가', collected: false },
|
||||||
|
],
|
||||||
|
rawResponse: `{
|
||||||
|
"success": true,
|
||||||
|
"extract": {
|
||||||
|
"channelName": "뷰성형외과 VIEW Plastic Surgery",
|
||||||
|
"subscribers": "Not available",
|
||||||
|
"totalVideos": "Not available",
|
||||||
|
"description": "View Plastic Surgery channel..."
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"og:image": "yt3.googleusercontent.com/NPq6T...",
|
||||||
|
"og:video:tag": ["뷰성형외과", "성형", "코성형", ...]
|
||||||
|
},
|
||||||
|
"creditsUsed": 5
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Instagram',
|
||||||
|
url: 'https://www.instagram.com/viewplastic/',
|
||||||
|
icon: InstagramFilled,
|
||||||
|
brandColor: '#E1306C',
|
||||||
|
status: 'blocked',
|
||||||
|
statusLabel: '공식 차단',
|
||||||
|
crawlMethod: 'Firecrawl (차단됨)',
|
||||||
|
creditsUsed: 0,
|
||||||
|
dataPoints: [
|
||||||
|
{ field: '핸들', value: '@viewplastic', collected: false },
|
||||||
|
{ field: '팔로워 수', value: '수집 불가', collected: false },
|
||||||
|
{ field: '팔로잉 수', value: '수집 불가', collected: false },
|
||||||
|
{ field: '게시물 수', value: '수집 불가', collected: false },
|
||||||
|
{ field: '바이오', value: '수집 불가', collected: false },
|
||||||
|
{ field: '프로필 사진', value: '수집 불가', collected: false },
|
||||||
|
{ field: 'Reels 수', value: '수집 불가', collected: false },
|
||||||
|
{ field: '하이라이트', value: '수집 불가', collected: false },
|
||||||
|
],
|
||||||
|
rawResponse: `{
|
||||||
|
"success": false,
|
||||||
|
"error": "We do not support this site.
|
||||||
|
If you are part of an enterprise,
|
||||||
|
please fill out our intake form."
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '강남언니',
|
||||||
|
url: 'https://www.gangnamunni.com/hospitals/189',
|
||||||
|
icon: GlobeFilled,
|
||||||
|
brandColor: '#03C75A',
|
||||||
|
status: 'success',
|
||||||
|
statusLabel: '수집 성공',
|
||||||
|
crawlMethod: 'Firecrawl Extract',
|
||||||
|
creditsUsed: 5,
|
||||||
|
dataPoints: [
|
||||||
|
{ field: '병원명', value: '뷰성형외과의원', collected: true },
|
||||||
|
{ field: '평점', value: '9.5 / 10', collected: true },
|
||||||
|
{ field: '총 리뷰', value: '18,961건', collected: true },
|
||||||
|
{ field: '의료진 수', value: '25명', collected: true },
|
||||||
|
{ field: '대표원장 (최순우)', value: '평점 9.4, 리뷰 1,812건', collected: true },
|
||||||
|
{ field: '원장 (윤창운)', value: '평점 9.6, 리뷰 764건', collected: true },
|
||||||
|
{ field: '원장 (김정민)', value: '평점 9.7, 리뷰 878건', collected: true },
|
||||||
|
{ field: '인증 뱃지', value: '10개 (수술실 CCTV, 마취과 상주 등)', collected: true },
|
||||||
|
{ field: '시술 가격 (가슴성형)', value: '₩2,650,000~', collected: true },
|
||||||
|
{ field: '시술 가격 (눈성형)', value: '₩440,000~', collected: true },
|
||||||
|
{ field: '시술 가격 (코성형)', value: '₩990,000~', collected: true },
|
||||||
|
{ field: '시술 가격 (안면윤곽)', value: '₩3,289,000~', collected: true },
|
||||||
|
],
|
||||||
|
rawResponse: `{
|
||||||
|
"success": true,
|
||||||
|
"extract": {
|
||||||
|
"hospitalName": "뷰성형외과의원",
|
||||||
|
"rating": "9.5",
|
||||||
|
"totalReviews": "18,961",
|
||||||
|
"doctorCount": "25",
|
||||||
|
"doctors": [
|
||||||
|
{ "name": "최순우 대표원장", "rating": "9.4", "reviewCount": "1,812" },
|
||||||
|
{ "name": "윤창운 원장", "rating": "9.6", "reviewCount": "764" },
|
||||||
|
{ "name": "김정민 원장", "rating": "9.7", "reviewCount": "878" }
|
||||||
|
],
|
||||||
|
"certifications": ["수술실 CCTV", "마취과 전문의 상주", ...],
|
||||||
|
"procedures": [
|
||||||
|
{ "name": "가슴성형", "price": "2,650,000원" },
|
||||||
|
{ "name": "눈성형", "price": "440,000원" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"creditsUsed": 5
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Process Steps ───
|
||||||
|
|
||||||
|
const CRAWL_STEPS = [
|
||||||
|
{ icon: Globe, label: 'URL 입력', desc: '대상 병원 웹사이트 URL' },
|
||||||
|
{ icon: Server, label: 'Firecrawl 크롤링', desc: '웹사이트 + 리뷰 플랫폼 스크래핑' },
|
||||||
|
{ icon: Database, label: 'API 수집', desc: 'YouTube Data API, Apify' },
|
||||||
|
{ icon: Sparkles, label: 'AI 분석', desc: 'Claude가 진단 + 전략 생성' },
|
||||||
|
{ icon: CheckCircle, label: '리포트 완성', desc: '멀티소스 데이터 기반 리포트' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Coverage Data ───
|
||||||
|
|
||||||
|
const COVERAGE = [
|
||||||
|
{ method: 'Firecrawl', desc: '웹사이트, 강남언니, SSR 플랫폼', pct: 35, color: '#6C5CE7' },
|
||||||
|
{ method: 'API 연동', desc: 'YouTube Data API, Apify (Instagram/Facebook)', pct: 45, color: '#9B8AD4' },
|
||||||
|
{ method: 'AI 분석', desc: 'Claude — 진단, 전략, 로드맵, KPI 생성', pct: 20, color: '#D4A872' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Alternatives for blocked data ───
|
||||||
|
|
||||||
|
const ALTERNATIVES = [
|
||||||
|
{ blocked: 'Instagram 프로필 데이터', alt: 'Apify Instagram Scraper', cost: '~₩65,000/월 (Starter)', priority: 'P0' },
|
||||||
|
{ blocked: 'YouTube 구독자/영상 통계', alt: 'YouTube Data API v3', cost: '무료 (일 10K units)', priority: 'P0' },
|
||||||
|
{ blocked: 'Facebook 페이지 데이터', alt: 'Apify Facebook Scraper', cost: 'Apify 포함', priority: 'P1' },
|
||||||
|
{ blocked: 'TikTok 계정 데이터', alt: 'Apify TikTok Scraper', cost: 'Apify 포함', priority: 'P2' },
|
||||||
|
{ blocked: '브랜드 컬러 자동 추출', alt: 'Brandfetch API / Vision API', cost: '~$50/월', priority: 'P2' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Helpers ───
|
||||||
|
|
||||||
|
const statusConfig: Record<SiteStatus, { bg: string; text: string; border: string; dot: string; label: string }> = {
|
||||||
|
success: { bg: 'bg-[#F3F0FF]', text: 'text-[#4A3A7C]', border: 'border-[#D5CDF5]', dot: 'bg-[#9B8AD4]', label: '수집 성공' },
|
||||||
|
partial: { bg: 'bg-[#FFF6ED]', text: 'text-[#7C5C3A]', border: 'border-[#F5E0C5]', dot: 'bg-[#D4A872]', label: '부분 수집' },
|
||||||
|
blocked: { bg: 'bg-[#FFF0F0]', text: 'text-[#7C3A4B]', border: 'border-[#F5D5DC]', dot: 'bg-[#D4889A]', label: '차단됨' },
|
||||||
|
};
|
||||||
|
|
||||||
|
function successRate(points: DataPoint[]): number {
|
||||||
|
return Math.round((points.filter(p => p.collected).length / points.length) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Component ───
|
||||||
|
|
||||||
|
export default function DataValidationPage() {
|
||||||
|
return (
|
||||||
|
<div className="pt-20 min-h-screen">
|
||||||
|
{/* ── Header ── */}
|
||||||
|
<div className="bg-[#0A1128] py-14 px-6 relative overflow-hidden">
|
||||||
|
<div className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full bg-[#6C5CE7]/10 blur-[120px]" />
|
||||||
|
<div className="absolute bottom-0 left-0 w-[300px] h-[300px] rounded-full bg-purple-500/5 blur-[100px]" />
|
||||||
|
<div className="max-w-6xl mx-auto relative">
|
||||||
|
<p className="text-xs font-semibold text-purple-300 tracking-widest uppercase mb-3">Data Collection Validation</p>
|
||||||
|
<h1 className="font-serif text-3xl md:text-4xl font-bold text-white mb-3">데이터 수집 검증 리포트</h1>
|
||||||
|
<p className="text-purple-200/70 max-w-xl mb-4">Firecrawl API를 활용한 실제 크롤링 테스트 결과 — 뷰성형외과 대상</p>
|
||||||
|
<div className="flex flex-wrap gap-3 text-sm">
|
||||||
|
<span className="px-3 py-1 rounded-full bg-white/10 text-purple-200">검증일: 2026-03-30</span>
|
||||||
|
<span className="px-3 py-1 rounded-full bg-white/10 text-purple-200">API: Firecrawl v1</span>
|
||||||
|
<span className="px-3 py-1 rounded-full bg-white/10 text-purple-200">총 크레딧 소모: 15 credits</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-6xl mx-auto px-6 py-10 space-y-12">
|
||||||
|
|
||||||
|
{/* ── Section 1: 수집 프로세스 ── */}
|
||||||
|
<section>
|
||||||
|
<h2 className="font-serif text-2xl font-bold text-primary-900 mb-2">수집 프로세스</h2>
|
||||||
|
<p className="text-slate-500 text-sm mb-6">URL 하나로 시작하는 멀티소스 데이터 수집 파이프라인</p>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 md:p-8">
|
||||||
|
<div className="flex flex-col md:flex-row items-start md:items-center gap-4 md:gap-0">
|
||||||
|
{CRAWL_STEPS.map((step, i) => {
|
||||||
|
const Icon = step.icon;
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={step.label}
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: i * 0.12 }}
|
||||||
|
className="flex items-center gap-3 md:flex-1"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 flex-1">
|
||||||
|
<div className="w-11 h-11 rounded-xl bg-[#F3F0FF] flex items-center justify-center flex-shrink-0">
|
||||||
|
<Icon size={20} className="text-[#6C5CE7]" />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<p className="text-sm font-semibold text-primary-900 truncate">{step.label}</p>
|
||||||
|
<p className="text-xs text-slate-400 truncate">{step.desc}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{i < CRAWL_STEPS.length - 1 && (
|
||||||
|
<ArrowRight size={16} className="text-slate-300 hidden md:block flex-shrink-0 mx-2" />
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Section 2: 사이트별 테스트 결과 ── */}
|
||||||
|
<section>
|
||||||
|
<h2 className="font-serif text-2xl font-bold text-primary-900 mb-2">사이트별 크롤링 결과</h2>
|
||||||
|
<p className="text-slate-500 text-sm mb-6">Firecrawl Extract API로 4개 사이트 실제 테스트 (2026-03-30 실행)</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
|
||||||
|
{SITE_RESULTS.map((site, i) => {
|
||||||
|
const cfg = statusConfig[site.status];
|
||||||
|
const Icon = site.icon;
|
||||||
|
const rate = successRate(site.dataPoints);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={site.name}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: i * 0.1 }}
|
||||||
|
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden"
|
||||||
|
>
|
||||||
|
{/* Card Header */}
|
||||||
|
<div className="p-5 pb-4 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ backgroundColor: `${site.brandColor}12` }}>
|
||||||
|
<Icon size={22} className="opacity-80" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-primary-900">{site.name}</h3>
|
||||||
|
<p className="text-xs text-slate-400">{site.crawlMethod} · {site.creditsUsed} credits</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className={`px-3 py-1 rounded-full text-xs font-semibold ${cfg.bg} ${cfg.text} border ${cfg.border}`}>
|
||||||
|
{site.statusLabel}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Data Points */}
|
||||||
|
<div className="px-5 pb-2">
|
||||||
|
<div className="space-y-1.5 max-h-[260px] overflow-y-auto pr-1">
|
||||||
|
{site.dataPoints.map(dp => (
|
||||||
|
<div key={dp.field} className="flex items-center gap-2 text-sm">
|
||||||
|
{dp.collected ? (
|
||||||
|
<div className="w-5 h-5 rounded-full bg-[#F3F0FF] flex items-center justify-center flex-shrink-0">
|
||||||
|
<Check size={12} className="text-[#6C5CE7]" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="w-5 h-5 rounded-full bg-[#FFF0F0] flex items-center justify-center flex-shrink-0">
|
||||||
|
<X size={12} className="text-[#D4889A]" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<span className="text-slate-500 w-36 flex-shrink-0 truncate">{dp.field}</span>
|
||||||
|
<span className={`truncate ${dp.collected ? 'text-primary-900 font-medium' : 'text-slate-300'}`}>
|
||||||
|
{dp.value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress Bar */}
|
||||||
|
<div className="px-5 pb-4 pt-3">
|
||||||
|
<div className="flex items-center justify-between text-xs mb-1.5">
|
||||||
|
<span className="text-slate-400">수집률</span>
|
||||||
|
<span className={`font-bold ${cfg.text}`}>{rate}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 bg-slate-100 rounded-full overflow-hidden">
|
||||||
|
<motion.div
|
||||||
|
initial={{ width: 0 }}
|
||||||
|
whileInView={{ width: `${rate}%` }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.8, delay: i * 0.1 + 0.3 }}
|
||||||
|
className="h-full rounded-full"
|
||||||
|
style={{ backgroundColor: site.status === 'blocked' ? '#D4889A' : site.status === 'partial' ? '#D4A872' : '#9B8AD4' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Raw Response */}
|
||||||
|
<details className="border-t border-slate-100">
|
||||||
|
<summary className="px-5 py-3 text-xs text-slate-400 cursor-pointer hover:text-slate-600 transition-colors">
|
||||||
|
API 응답 원문 보기
|
||||||
|
</summary>
|
||||||
|
<pre className="px-5 pb-4 text-xs text-slate-500 bg-slate-50 overflow-x-auto whitespace-pre-wrap font-mono leading-relaxed">
|
||||||
|
{site.rawResponse}
|
||||||
|
</pre>
|
||||||
|
</details>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Section 3: 수집 불가 데이터 + 대안 ── */}
|
||||||
|
<section>
|
||||||
|
<h2 className="font-serif text-2xl font-bold text-primary-900 mb-2">차단된 데이터 & 대안 솔루션</h2>
|
||||||
|
<p className="text-slate-500 text-sm mb-6">Firecrawl로 수집 불가한 데이터와 이를 보완하는 도구</p>
|
||||||
|
|
||||||
|
{/* Blocked Reason */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
className="bg-[#FFF0F0] border border-[#F5D5DC] rounded-2xl p-5 mb-6"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<Ban size={20} className="text-[#D4889A] mt-0.5 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-[#7C3A4B] mb-1">Instagram 공식 차단</p>
|
||||||
|
<p className="text-sm text-[#7C3A4B]/70">
|
||||||
|
Firecrawl이 Instagram을 공식적으로 지원하지 않음 (Meta의 스크래핑 방지 정책).
|
||||||
|
Facebook도 React SPA 구조로 인해 정적 크롤링으로는 데이터 추출 불가.
|
||||||
|
YouTube는 메타데이터만 부분 추출 가능하나, 구독자/영상 통계는 InnerTube API 내부 렌더링으로 접근 불가.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Alternatives Table */}
|
||||||
|
<div className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] overflow-hidden">
|
||||||
|
<div className="grid grid-cols-[1fr_1fr_auto_auto] gap-x-4 px-5 py-3 bg-slate-50 border-b border-slate-100 text-xs font-semibold text-slate-400 uppercase tracking-wider">
|
||||||
|
<span>차단된 데이터</span>
|
||||||
|
<span>대안 솔루션</span>
|
||||||
|
<span>비용</span>
|
||||||
|
<span>우선순위</span>
|
||||||
|
</div>
|
||||||
|
{ALTERNATIVES.map((alt, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={alt.blocked}
|
||||||
|
initial={{ opacity: 0, x: -10 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: i * 0.06 }}
|
||||||
|
className="grid grid-cols-[1fr_1fr_auto_auto] gap-x-4 px-5 py-3.5 border-b border-slate-50 last:border-b-0 items-center text-sm"
|
||||||
|
>
|
||||||
|
<span className="text-slate-600">{alt.blocked}</span>
|
||||||
|
<span className="text-primary-900 font-medium">{alt.alt}</span>
|
||||||
|
<span className="text-slate-400 text-xs whitespace-nowrap">{alt.cost}</span>
|
||||||
|
<span className={`px-2 py-0.5 rounded text-xs font-bold ${
|
||||||
|
alt.priority === 'P0' ? 'bg-[#F3F0FF] text-[#4A3A7C]' :
|
||||||
|
alt.priority === 'P1' ? 'bg-[#FFF6ED] text-[#7C5C3A]' :
|
||||||
|
'bg-slate-100 text-slate-500'
|
||||||
|
}`}>{alt.priority}</span>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Section 4: 전체 커버리지 ── */}
|
||||||
|
<section>
|
||||||
|
<h2 className="font-serif text-2xl font-bold text-primary-900 mb-2">전체 데이터 커버리지</h2>
|
||||||
|
<p className="text-slate-500 text-sm mb-6">3가지 수집 방법 조합으로 100% 분석 데이터 확보</p>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6 md:p-8 space-y-5">
|
||||||
|
{COVERAGE.map((item, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={item.method}
|
||||||
|
initial={{ opacity: 0, x: -20 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: i * 0.15 }}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<div>
|
||||||
|
<span className="font-semibold text-primary-900">{item.method}</span>
|
||||||
|
<span className="text-sm text-slate-400 ml-2">{item.desc}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-lg font-bold" style={{ color: item.color }}>{item.pct}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-4 bg-slate-100 rounded-full overflow-hidden">
|
||||||
|
<motion.div
|
||||||
|
initial={{ width: 0 }}
|
||||||
|
whileInView={{ width: `${item.pct}%` }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 1, delay: i * 0.15 + 0.2 }}
|
||||||
|
className="h-full rounded-full"
|
||||||
|
style={{ backgroundColor: item.color }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Stacked Bar */}
|
||||||
|
<div className="pt-4 border-t border-slate-100">
|
||||||
|
<p className="text-xs text-slate-400 mb-2 font-semibold uppercase tracking-wider">Combined Coverage</p>
|
||||||
|
<div className="h-6 rounded-full overflow-hidden flex">
|
||||||
|
{COVERAGE.map(item => (
|
||||||
|
<motion.div
|
||||||
|
key={item.method}
|
||||||
|
initial={{ width: 0 }}
|
||||||
|
whileInView={{ width: `${item.pct}%` }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 1, delay: 0.5 }}
|
||||||
|
className="h-full flex items-center justify-center text-white text-xs font-bold"
|
||||||
|
style={{ backgroundColor: item.color }}
|
||||||
|
>
|
||||||
|
{item.pct}%
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Summary Insight */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: 0.3 }}
|
||||||
|
className="mt-6 bg-gradient-to-r from-[#fff3eb] via-[#e4cfff] to-[#f5f9ff] rounded-2xl p-6 md:p-8"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<ShieldCheck size={24} className="text-[#6C5CE7] mt-0.5 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="font-serif text-lg font-bold text-primary-900 mb-2">멀티소스 전략으로 100% 분석 가능</p>
|
||||||
|
<p className="text-sm text-slate-600 leading-relaxed">
|
||||||
|
Instagram 차단, YouTube 부분 제한에도 불구하고 <strong>Firecrawl(35%) + API(45%) + AI(20%)</strong> 조합으로
|
||||||
|
리포트에 필요한 모든 데이터를 수집할 수 있습니다.
|
||||||
|
Firecrawl은 웹사이트와 강남언니 같은 SSR 플랫폼에서 강력하며,
|
||||||
|
소셜미디어는 Apify + 각 플랫폼 공식 API로 보완합니다.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-3 mt-4">
|
||||||
|
<div className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/60 text-xs font-medium text-primary-900">
|
||||||
|
<AlertTriangle size={14} className="text-[#D4A872]" />
|
||||||
|
Firecrawl 단독 커버리지: ~35%
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/60 text-xs font-medium text-primary-900">
|
||||||
|
<CheckCircle size={14} className="text-[#6C5CE7]" />
|
||||||
|
P0 도구 조합 시: ~95%
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/60 text-xs font-medium text-primary-900">
|
||||||
|
<Sparkles size={14} className="text-[#9B8AD4]" />
|
||||||
|
AI 보강 포함: 100%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Section 5: 비용 요약 ── */}
|
||||||
|
<section>
|
||||||
|
<h2 className="font-serif text-2xl font-bold text-primary-900 mb-2">월간 운영 비용 (MVP)</h2>
|
||||||
|
<p className="text-slate-500 text-sm mb-6">P0 도구 기준 월 ~₩141,000으로 전체 파이프라인 운영 가능</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
|
{[
|
||||||
|
{ name: 'Firecrawl', cost: '~₩26,000', detail: '$19/mo Growth', role: '웹사이트 + SSR 크롤링' },
|
||||||
|
{ name: 'YouTube API', cost: '무료', detail: '일 10K units', role: 'YouTube 채널/영상 분석' },
|
||||||
|
{ name: 'Apify', cost: '~₩65,000', detail: '$49/mo Starter', role: 'Instagram/Facebook/TikTok' },
|
||||||
|
{ name: 'Claude API', cost: '~₩50,000', detail: 'Usage-based', role: '분석/진단/전략 생성' },
|
||||||
|
].map((item, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={item.name}
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: i * 0.08 }}
|
||||||
|
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-5 text-center"
|
||||||
|
>
|
||||||
|
<p className="text-xs text-slate-400 mb-1">{item.name}</p>
|
||||||
|
<p className="text-xl font-bold text-primary-900 mb-0.5">{item.cost}</p>
|
||||||
|
<p className="text-xs text-slate-400 mb-3">{item.detail}</p>
|
||||||
|
<p className="text-xs text-[#6C5CE7] font-medium">{item.role}</p>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue