INFINITH SaaS Database Schema V3
작성일: 2026-04-05
마이그레이션 파일: supabase/migrations/20260405_saas_schema_v3.sql
설계 원칙
- CLINIC-CENTRIC: 병원 1개 = 1행. URL이 달라도 같은 병원이면 같은 행
- TIME-SERIES: 채널 메트릭은 INSERT-only 스냅샷 (시계열 쿼리)
- SEPARATION: 원시 데이터 / 분석 리포트 / 콘텐츠 전략 분리
- LOOP-READY: 매 분석이 이전 데이터를 참조해 전략 자동 조정
- MULTI-TENANT: user_id 기반 접근 제어 (미래 auth)
ERD (Entity Relationship)
clinics (병원 마스터)
├─ analysis_runs (분석 실행 히스토리) ← 매주 1행씩 쌓임
│ ├─ channel_snapshots (채널별 시계열 메트릭) ← INSERT-only
│ ├─ screenshots (스크린샷 증거)
│ └─ performance_metrics (성과 메트릭)
│ └─ strategy_adjustments (전략 조정 근거)
├─ channel_configs (사용자 수동 채널 연결)
├─ content_plans (콘텐츠 기획 — 활성 1개)
│ └─ content_performance (개별 콘텐츠 성과)
└─ (marketing_reports) ← 레거시 호환
테이블 상세
1. clinics — 병원 마스터
| 컬럼 |
타입 |
설명 |
| id |
UUID PK |
|
| user_id |
UUID |
소유자 (미래 auth) |
| url |
TEXT UNIQUE |
대표 URL |
| name |
TEXT |
한국어 병원명 |
| name_en |
TEXT |
영문 병원명 |
| domain |
TEXT |
도메인 |
| address, phone |
TEXT |
기본 정보 |
| established_year |
INT |
개원 연도 |
| services |
TEXT[] |
시술 목록 |
| branding |
JSONB |
컬러, 폰트, 로고, 태그라인 |
| social_handles |
JSONB |
검증된 소셜 핸들 |
| verified_channels |
JSONB |
Phase 1 결과 캐시 |
| analysis_frequency |
TEXT |
'manual' / 'daily' / 'weekly' / 'monthly' |
| last_analyzed_at |
TIMESTAMPTZ |
마지막 분석 시간 |
2. analysis_runs — 분석 실행 히스토리
| 컬럼 |
타입 |
설명 |
| id |
UUID PK |
|
| clinic_id |
UUID FK → clinics |
|
| status |
TEXT |
pending/discovering/collecting/generating/complete/partial/error |
| scrape_data |
JSONB |
Firecrawl 원시 데이터 |
| raw_channel_data |
JSONB |
API 수집 원시 데이터 |
| analysis_data |
JSONB |
시장 분석 |
| vision_analysis |
JSONB |
Vision 분석 결과 |
| report |
JSONB |
AI 리포트 |
| channel_errors |
JSONB |
채널별 에러 기록 |
| trigger |
TEXT |
'manual' / 'scheduled' / 'webhook' |
3. channel_snapshots — 채널별 시계열 ⭐ 핵심
| 컬럼 |
타입 |
설명 |
| id |
UUID PK |
|
| clinic_id |
UUID FK → clinics |
|
| run_id |
UUID FK → analysis_runs |
|
| channel |
TEXT |
youtube/instagram/facebook/gangnamUnni/... |
| handle |
TEXT |
@handle 또는 URL |
| followers |
INT |
구독자/팔로워 |
| posts |
INT |
게시물/영상 수 |
| total_views |
BIGINT |
총 조회수 |
| rating |
NUMERIC(3,1) |
평점 |
| reviews |
INT |
리뷰 수 |
| health_score |
INT |
0-100 |
| details |
JSONB |
상세 (top videos, latest posts 등) |
| screenshot_url |
TEXT |
채널 랜딩 스크린샷 |
| captured_at |
TIMESTAMPTZ |
캡처 시간 |
핵심: INSERT-only. 절대 UPDATE 하지 않음. captured_at 기준 시계열 쿼리.
4. screenshots — 스크린샷 증거
| 컬럼 |
타입 |
설명 |
| id |
UUID PK |
|
| clinic_id, run_id |
UUID FK |
|
| channel |
TEXT |
어떤 채널 |
| page_type |
TEXT |
main/doctors/surgery/landing |
| url |
TEXT |
이미지 URL (Supabase Storage) |
| source_url |
TEXT |
원본 페이지 URL |
| vision_data |
JSONB |
Gemini Vision 추출 데이터 |
5. content_plans — 콘텐츠 기획
| 컬럼 |
타입 |
설명 |
| id |
UUID PK |
|
| clinic_id |
UUID FK → clinics |
|
| run_id |
UUID FK → analysis_runs |
어떤 분석 기반 |
| brand_guide |
JSONB |
브랜딩 가이드 |
| channel_strategies |
JSONB |
채널별 전략 |
| content_strategy |
JSONB |
필라, 타입, 워크플로우 |
| calendar |
JSONB |
4주 캘린더 |
| is_active |
BOOLEAN |
현재 활성 기획 |
6. channel_configs — 사용자 수동 채널 연결
| 컬럼 |
타입 |
설명 |
| id |
UUID PK |
|
| clinic_id |
UUID FK |
|
| channel |
TEXT |
채널 종류 |
| handle |
TEXT |
@handle |
| is_verified |
BOOLEAN |
사용자가 직접 확인 |
| access_token |
TEXT |
OAuth token (미래) |
7. performance_metrics — 성과 메트릭 (루프 핵심)
| 컬럼 |
타입 |
설명 |
| id |
UUID PK |
|
| clinic_id, run_id |
UUID FK |
|
| prev_run_id |
UUID FK |
이전 분석 (비교 기준) |
| channel_deltas |
JSONB |
채널별 변화량 |
| kpi_progress |
JSONB |
KPI 달성률 |
| top_performing_content |
JSONB |
성과 좋은 콘텐츠 |
| underperforming_channels |
TEXT[] |
성과 미달 채널 |
| strategy_suggestions |
JSONB |
AI 전략 조정 제안 |
8. content_performance — 개별 콘텐츠 성과
| 컬럼 |
타입 |
설명 |
| id |
UUID PK |
|
| clinic_id |
UUID FK |
|
| plan_id |
UUID FK |
어떤 기획의 콘텐츠 |
| channel, content_type |
TEXT |
채널 + 유형 |
| views, likes, comments, shares |
INT |
반응 지표 |
| engagement_rate |
NUMERIC |
참여율 |
| performance_score |
INT |
0-100 |
9. strategy_adjustments — 전략 조정 히스토리
| 컬럼 |
타입 |
설명 |
| id |
UUID PK |
|
| clinic_id, plan_id |
UUID FK |
|
| performance_id |
UUID FK |
어떤 성과 분석 기반 |
| adjustment_type |
TEXT |
frequency_change/pillar_shift/channel_add/... |
| description |
TEXT |
"YouTube Shorts 주 3회 → 5회로 증가" |
| reason |
TEXT |
"Shorts 조회수가 Long-form 대비 300% 높음" |
| before_value, after_value |
JSONB |
변경 전/후 값 |
Views (트렌드 쿼리용)
channel_latest
각 채널의 최신 스냅샷만 반환. DISTINCT ON + ORDER BY captured_at DESC.
channel_weekly_delta
현재 vs 7일 전 스냅샷을 LATERAL JOIN으로 비교. 팔로워 변화율(%) 자동 계산.
성과 → 기획 루프 플로우
Week N:
1. analysis_run 생성
2. channel_snapshots INSERT (현재 메트릭)
3. performance_metrics 생성 (이전 run과 비교)
→ channel_deltas: {youtube: +2%, instagram: +7.1%}
→ kpi_progress: [{metric: "YouTube 구독자", progress: 97.4%}]
→ strategy_suggestions: ["Instagram Reels 빈도 증가 권장"]
4. strategy_adjustments INSERT (조정 내역)
5. content_plans UPDATE (조정 반영)
→ Content Director가 strategy_suggestions를 기반으로 캘린더 자동 조정
6. report 생성 (이전 분석 대비 변화 포함)
Week N+1:
→ 2번부터 반복. channel_snapshots에 데이터가 쌓이면서 트렌드 정확도 ↑
기존 호환성
| 기존 테이블 |
상태 |
이관 계획 |
marketing_reports |
유지 (deprecated) |
기존 API 호환 유지, 새 데이터는 새 테이블에만 저장 |
scrape_results |
유지 |
캐시용 |
구현 체크리스트
Phase 1: DB 마이그레이션 (테이블 생성)
Phase 2: Edge Function 전환 — discover-channels
Phase 3: Edge Function 전환 — collect-channel-data
Phase 4: Edge Function 전환 — generate-report
Phase 5: 성과 분석 루프 구현
Phase 6: Frontend 전환
Phase 7: 스케줄링
Phase 8: 기존 데이터 이관