title: content-orchestration 성과 분석 대시보드 구현 플랜 date: 2026-02-26 type: implementation-plan project: content-orchestration phase: 3-analytics status: draft
content-orchestration 성과 분석 대시보드 구현 플랜
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: content-orchestration 대시보드에 성과 분석 탭을 추가하여 콘텐츠 발행 현황, 파이프라인 효율, 채널 분포, 에러 추이를 시각적으로 파악할 수 있게 한다.
Architecture: 기존 content-orchestration Next.js 15 앱에 /[project]/analytics 라우트를 추가. DB 집계 쿼리를 content-db.ts에 확장하고, 순수 Tailwind CSS 기반 차트 컴포넌트(bar/line/donut)를 자체 구현한다. 외부 차트 라이브러리(recharts 등) 의존성 없이 SVG + Tailwind로 경량 구현.
Tech Stack: Next.js 15 (App Router, RSC), Turso (LibSQL), Tailwind CSS 4, SVG 차트 (자체 구현)
코드베이스 분석 요약
기존 구조 (재사용 가능)
src/lib/content-db.ts: Turso 클라이언트 + 7개 조회 함수 (newsletters, queue, logs, pipeline_logs, rss_stats, news_stats, plf_schedule)src/lib/projects.ts: 프로젝트 설정 (apppro/richbukae/ai-architect)src/app/[project]/layout.tsx: 네비게이션 바 (개요/캘린더/RSS/로그) -- 여기에 "성과 분석" 탭 추가- UI 패턴: StatCard, StatusBadge, EmptyState, 테이블 패턴 일관 사용
- 스타일: Tailwind CSS만 사용 (shadcn/ui 미사용, 외부 UI 라이브러리 없음)
DB 테이블 (analytics 데이터 소스)
| 테이블 | 주요 데이터 | analytics 활용 |
|---|---|---|
collected_news | 수집일, 소스, 등급, 사용여부 | 일별 수집량, 소스별 분포 |
newsletters | 생성일, 상태, 발송일 | 발행 추이, 상태 분포 |
content_queue | 타입, 필라, 상태, 우선순위, 프로젝트, 채널 | 타입별/필라별 분포, 파이프라인 상태 |
content_logs | 타입, 플랫폼, 상태, metrics(JSON), 발행일 | 채널별 성과, 일별 발행량 |
pipeline_logs | 파이프라인명, 상태, 소요시간, 처리건수, metadata(JSON) | 파이프라인 효율, 실패율 |
content_distributions | 채널ID, 상태, 발행URL | 채널별 배포 추적 |
error_logs | 컴포넌트, 에러타입, 자동교정, 해결여부 | 에러 추이, 자동교정 성공률 |
channels | 채널명, 타입, 플랫폼, 활성여부 | 채널 목록 참조 |
의존성 제약
- package.json에 차트 라이브러리 없음 (recharts 등 미설치)
- 결정: SVG + Tailwind로 경량 차트 자체 구현 -- 의존성 추가 없이 빌드 사이즈 최소화
- 대안: 데이터가 복잡해지면 recharts 추가 가능하지만 MVP에서는 불필요
Task 1: DB 집계 쿼리 함수 추가 (content-db.ts 확장)
파일: src/lib/content-db.ts
추가할 함수 6개:
// 1. 일별 콘텐츠 발행 수 (최근 30일)
export async function getDailyPublishStats(days: number = 30): Promise<DailyPublishStat[]>
// SQL: SELECT date(published_at/1000, 'unixepoch') as day, content_type, COUNT(*) as count
// FROM content_logs WHERE published_at >= ? GROUP BY day, content_type ORDER BY day
// 2. 필라별 콘텐츠 분포
export async function getPillarDistribution(): Promise<PillarStat[]>
// SQL: SELECT pillar, COUNT(*) as count, status FROM content_queue
// WHERE pillar IS NOT NULL GROUP BY pillar, status
// 3. 파이프라인 효율 통계 (최근 30일)
export async function getPipelineEfficiency(days: number = 30): Promise<PipelineEfficiency[]>
// SQL: SELECT pipeline_name, COUNT(*) as runs,
// SUM(CASE WHEN status='completed' THEN 1 ELSE 0 END) as success,
// SUM(CASE WHEN status='failed' THEN 1 ELSE 0 END) as failed,
// AVG(duration_ms) as avg_duration, SUM(items_processed) as total_items
// FROM pipeline_logs WHERE created_at >= ? GROUP BY pipeline_name
// 4. 에러 추이 (최근 30일, 컴포넌트별)
export async function getErrorTrends(days: number = 30): Promise<ErrorTrend[]>
// SQL: SELECT component, error_type, COUNT(*) as count,
// SUM(CASE WHEN auto_fix_result='success' THEN 1 ELSE 0 END) as auto_fixed
// FROM error_logs WHERE occurred_at >= ? GROUP BY component, error_type
// 5. 채널별 배포 현황 (content_distributions 기반)
export async function getChannelPerformance(): Promise<ChannelPerformance[]>
// SQL: SELECT cd.channel_id, c.name, c.platform, cd.platform_status, COUNT(*) as count
// FROM content_distributions cd LEFT JOIN channels c ON cd.channel_id = c.id
// GROUP BY cd.channel_id, cd.platform_status
// 6. 주간 요약 (이번 주 vs 지난 주)
export async function getWeeklySummary(): Promise<WeeklySummary>
// SQL: 복합 쿼리 — 이번 주/지난 주 발행수, 수집수, 에러수 비교
인터페이스 정의:
export interface DailyPublishStat { day: string; content_type: string; count: number; }
export interface PillarStat { pillar: string; status: string; count: number; }
export interface PipelineEfficiency { pipeline_name: string; runs: number; success: number; failed: number; avg_duration: number; total_items: number; }
export interface ErrorTrend { component: string; error_type: string; count: number; auto_fixed: number; }
export interface ChannelPerformance { channel_id: string; name: string; platform: string; status: string; count: number; }
export interface WeeklySummary { this_week: { published: number; collected: number; errors: number }; last_week: { published: number; collected: number; errors: number }; }
완료 기준: npm run build 타입 에러 없음.
Task 2: SVG 차트 컴포넌트 구현
파일: src/components/charts.tsx (신규)
구현할 차트 3종:
2-1. BarChart (수직 막대 차트)
- Props:
{ data: {label: string; value: number; color?: string}[]; height?: number; showValues?: boolean } - 순수 SVG + Tailwind 클래스
- 반응형: 컨테이너 너비 100%
- 용도: 일별 발행량, 필라별 분포
2-2. MiniLineChart (간단 라인 차트)
- Props:
{ data: number[]; labels?: string[]; height?: number; color?: string } - SVG polyline으로 구현
- 용도: 일별 수집 추이, 에러 추이
2-3. DonutChart (도넛 차트)
- Props:
{ data: {label: string; value: number; color: string}[]; size?: number } - SVG circle + stroke-dasharray로 구현
- 중앙에 총계 표시
- 용도: 채널별 분포, 상태 분포
스타일 규칙:
- 기존 코드의 Tailwind 컬러 팔레트 사용 (blue-500, green-500, orange-500, purple-500, red-500)
- 빈 데이터 시 EmptyState 패턴과 동일한 fallback 표시
- 서버 컴포넌트 호환 (RSC 렌더링 가능)
완료 기준: 각 차트가 빈 데이터, 1개 데이터, 다수 데이터에서 정상 렌더링. npm run build 성공.
Task 3: 네비게이션 업데이트 + 라우트 생성
파일 수정: src/app/[project]/layout.tsx
변경 내용:
// NAV_ITEMS에 추가
{ href: '/analytics', label: '성과 분석', icon: '◆' },
파일 생성: src/app/[project]/analytics/page.tsx
페이지 구조:
/[project]/analytics
├── 주간 요약 카드 (이번 주 vs 지난 주, 증감 표시)
├── 일별 발행 추이 (BarChart, 최근 14일)
├── 필라별 콘텐츠 분포 (DonutChart)
├── 파이프라인 효율 (테이블 + 성공률 바)
├── 채널별 배포 현황 (BarChart)
├── 에러 추이 (MiniLineChart + 자동교정 성공률)
└── 수집 효율 (수집/사용 비율 바)
props 데이터: 모든 데이터는 Task 1에서 만든 집계 함수로 서버사이드 fetch (RSC).
완료 기준: /apppro/analytics 라우트 접속 시 페이지 렌더링, 네비게이션에서 "성과 분석" 링크 활성.
Task 4: 성과 분석 대시보드 페이지 구현
파일: src/app/[project]/analytics/page.tsx
세부 섹션 구현:
4-1. 주간 요약 카드 (Hero Section)
- 4개 StatCard: 이번 주 발행수, 이번 주 수집수, 미해결 에러, 파이프라인 성공률
- 각 카드에 지난 주 대비 증감 표시 (화살표 + 퍼센트)
- 기존 StatCard 컴포넌트 확장 (delta prop 추가)
4-2. 일별 발행 추이
- BarChart: X축=날짜(14일), Y축=발행수, 색상=content_type별
- 날짜 포맷: "2/26" 형식 (한국어)
4-3. 필라별 콘텐츠 분포
- DonutChart: 5대 필라별 비중
- 옆에 범례 + 건수 표시
4-4. 파이프라인 효율
- 테이블: 파이프라인명, 실행 횟수, 성공률(%), 평균 소요시간, 총 처리건수
- 성공률은 가로 바로 시각화 (기존 ChannelDistribution 패턴 재사용)
4-5. 채널별 배포 현황
- BarChart: 채널별 배포 수
- 상태별 색상 구분 (published=green, pending=orange, failed=red)
4-6. 에러 추이
- MiniLineChart: 일별 에러 발생 수
- 하단에 컴포넌트별 에러 수 + 자동교정 성공률 표시
4-7. 수집 효율
- 진행 바: 수집/사용 비율 (기존 RSS 페이지 패턴 재사용)
- 소스별 수집량 상위 5개
완료 기준: 모든 섹션이 실데이터(Turso DB)로 렌더링. 빈 테이블에서도 에러 없이 빈 상태 표시. npm run build 성공.
Task 5: 빌드 검증 + 통합 테스트
검증 항목:
npm run build성공 (타입 에러/린트 에러 없음)/apppro/analytics페이지 접속 확인- 네비게이션 바에 "성과 분석" 탭 표시 확인
- 빈 데이터 상태에서 모든 차트/테이블이 graceful하게 빈 상태 표시
- 기존 페이지들 (
/,/apppro,/apppro/calendar,/apppro/rss,/apppro/logs) 깨지지 않음 확인
수정 사항:
- 빌드 에러 발견 시 즉시 수정
- 타입 불일치 수정
- 린트 경고 처리
완료 기준: npm run build 클린 성공 + 기존 페이지 영향 없음.
구현 순서 의존성
Task 1 (DB 쿼리) → Task 2 (차트 컴포넌트) → Task 3 (라우트 생성) → Task 4 (페이지 구현) → Task 5 (빌드 검증)
Task 1, 2는 독립적이므로 병렬 가능. Task 3은 Task 1+2 완료 후. Task 4는 Task 3 완료 후. Task 5는 최종.
CEO 블로킹: 없음
- DB 스키마 변경 없음 (기존 테이블의 SELECT 쿼리만 추가)
- 환경변수 추가 없음 (기존 CONTENT_OS_DB_URL/TOKEN 사용)
- 외부 의존성 추가 없음 (SVG 차트 자체 구현)
- Vercel 배포는 GitHub push 시 자동 (기존 설정 유지)
예상 구현 시간
- Task 1: ~3분 (DB 함수 6개)
- Task 2: ~5분 (SVG 차트 3종)
- Task 3: ~2분 (레이아웃 수정 + 빈 페이지)
- Task 4: ~8분 (7개 섹션 구현)
- Task 5: ~2분 (빌드 + 검증)
- 총: ~20분