2탄 권3 제3장
2탄 권3 — 제3장

Day 43~49: 마이페이지·통계·Sentry 시작

7주차 페이스 2.5h/일 — 운영자 의식보다 자동 트리거가 강하다

📑 이 챕터에서 다룰 내용
들어가며 7주차 페이스 추가 축소 적용

권3 제2장 (Day 42 G3 PASSED) 에서 6주차 끝 4주 누적 77h → 7주차 페이스 추가 축소 (2.5h/일) 결정. 이번 주는 "매일 2.5h" 페이스로 마이페이지·통계·Sentry 시작합니다.

사전 지식 체크이 장의 목적완료 후 결과물
Day 30~42 / G3 PASSED / 7주차 페이스 2.5h/일 / G4 통과 조건 (Sentry 7일 무장애) Day 43 G3 휴식 + Day 44~48 마이페이지·통계·KPI·Sentry + Day 49 회고. 페이스 보호 강화 마이페이지 + 통계 차트 + Phase 1 KPI 어드민 + Sentry 통합 + 7일 무장애 관측 시작
3-1 Day 43 — ★ G3 통과 휴식 의식
💻 BUILD.md Day 43 entry
## Phase 1.0 Day 43 — ★ G3 통과 후 휴식 ★

[계획]
- 작업 X (G3 통과 의식 + R4 보호)

[실행]
- 작업 X
- 가족·자유 시간
- BUILD.md·CLAUDE.md 노출 X (강제 단절)

[E2 자동 보호 효과]
- G3 통과 의식 (1일 휴식)
- R4 4주 누적 77h → Day 43 휴식 → 다음 4주 시작점
- 7주차 페이스 2.5h/일 (지금까지 최저)

[누적] 126h (Day 43 변동 없음)
3-2 Day 44 — 마이페이지 (DB 집계)

작업 (2.5h — 페이스 추가 축소)

💻 lib/profile-stats.ts (약 60줄)
// lib/profile-stats.ts
export interface UserStats {
  total_jupjups: number;
  total_success: number;
  total_failed: number;
  total_in_progress: number;
  avg_days_taken: number | null;  // SUCCESS만
  recent_jupjups: JupjupItem[];   // 최근 10건
}

export async function getUserStats(userId: string): Promise {
  // Supabase RPC 호출 (단일 쿼리 + 인덱스 활용)
  const { data, error } = await supabase.rpc('get_user_stats', {
    user_id: userId,
  });
  if (error) throw error;
  return data;
}
💻 supabase/migrations/0005_user_stats_rpc.sql
CREATE OR REPLACE FUNCTION get_user_stats(user_id UUID)
RETURNS JSONB AS $$
DECLARE
  result JSONB;
BEGIN
  SELECT jsonb_build_object(
    'total_jupjups', (SELECT COUNT(*) FROM jupjups j WHERE j.user_id = $1),
    'total_success', (SELECT COUNT(*) FROM jupjups j WHERE j.user_id = $1
                       AND j.result = 'SUCCESS'),
    'total_failed', (SELECT COUNT(*) FROM jupjups j WHERE j.user_id = $1
                      AND j.result = 'FAILED'),
    'total_in_progress', (SELECT COUNT(*) FROM jupjups j
                           WHERE j.user_id = $1 AND j.result = 'IN_PROGRESS'),
    'avg_days_taken', (SELECT AVG(days_taken) FROM jupjups j
                        WHERE j.user_id = $1 AND j.result = 'SUCCESS'),
    'recent_jupjups', (
      SELECT jsonb_agg(jsonb_build_object(
        'id', j.id,
        'benefit_id', j.benefit_id,
        'benefit_name', b.name,
        'result', j.result,
        'created_at', j.created_at
      ) ORDER BY j.created_at DESC)
      FROM jupjups j JOIN benefits b ON b.id = j.benefit_id
      WHERE j.user_id = $1 LIMIT 10
    )
  ) INTO result;
  RETURN result;
END;
$$ LANGUAGE plpgsql STABLE;
📘 LogOnTable 트레이스 — RPC vs 클라이언트 집계

결정: get_user_stats RPC 함수 (서버 측 집계)

근거: 1탄 v2 새 9장 9-5 절 사례 적용. 클라이언트 집계 = N+1 쿼리 위험. RPC 단일 쿼리 = 인덱스 활용 + 1 round trip.

대안: 클라이언트 집계 (5 SELECT 호출) — 성능 문제

누적: 126h + Day 44 (2.5h) = 128.5h

3-3 Day 45 — 통계 차트 (성공률·평균 일수)
💻 Claude Code — StatsChart 컴포넌트
요구사항:
1. components/StatsChart.tsx (90줄):
   - 마이페이지: 성공률 도넛 + 결과별 막대
   - 상세 페이지: benefit별 성공률 + 평균 소요 일수
2. recharts 또는 Victory Native 사용 (Expo 호환)
3. CLAUDE.md §5 [2] 톤 분기:
   - 사장님 톤: '성공률 N% (사장님 N명)'
   - 시민 톤: '받은 비율 N% (시민 N명)'
4. 데이터 부족 (jupjups < 3건) 시: '아직 충분한 데이터 없음' 표시
5. ⚖️ E1: 차트 옆 면책 1줄
   ('통계는 사용자 후기 기반, 신청 결과 보장 X')
📘 LogOnTable 트레이스 — 통계 데이터 부족 fallback

결정: jupjups < 3건 benefit는 차트 노출 X (텍스트 fallback)

근거: 통계 신뢰성 = 표본 크기. 1~2건으로 도넛 차트 = 오해 유발 (예: 1명 성공 → 100% 성공률 잘못 인식)

대안: 모든 benefit 차트 노출 — 통계 오해 위험 = E1 변호 논리 흔들림

부작용: 신규 benefit 통계 부족 → Phase 2 데이터 누적 후 자연 해결

누적: 128.5h + Day 45 (2.5h) = 131h

3-4 Day 46 — Phase 1 KPI 측정 어드민
💻 Claude Code — KPI 어드민 페이지
요구사항:
1. src/app/admin/kpi/page.tsx (160줄):
   - [1] 단계적 기여 부담 곡선:
     · 부담 0 (체크박스) 도달 사용자: COUNT
     · 부담 0 → 낮음 (3택) 전환율: %
     · 부담 낮음 → 선택 (자유 후기) 전환율: %
   - [2] LLM 분류 confidence 분포:
     · auto_active 비중 / admin_queue 비중
     · 검수 SLA 7일 준수율
   - [3] 사용자 신뢰 메트릭:
     · 신고 발생 비율 (건/100 jupjups)
     · 후기 작성 비율 (8%+ 목표)

2. 자동 갱신: 5분 캐시 (Supabase realtime 미사용)
🎉 첫 측정 결과 (시뮬 데이터 기반)
KPI 지표목표시뮬 결과판정
부담 0 → 낮음 전환율≥10%70%✅ 통과
부담 낮음 → 선택 전환율≥30%60%✅ 통과
auto_active 비중정상 분포65%✅ 정상
검수 SLA 7일 준수율100%100%✅ 통과
후기 작성 비율≥8%80% (시뮬)✅ 통과

주의: 시뮬 데이터 기반. 실제 출시 후 (Day 73~) 사용자 데이터로 본격 검증합니다.

누적: 131h + Day 46 (2.5h) = 133.5h

3-5 Day 47~48 — Sentry 통합 (G4 통과 조건 P2)

Day 47 작업 (2.5h)

💻 Sentry 통합 설정
요구사항:
1. Expo + @sentry/react-native 설치
2. Sentry 프로젝트 생성 + DSN 받음 (.env.local)
3. src/lib/sentry.ts (50줄):
   - Sentry.init() — 앱 시작 시
   - 에러 capture 자동
   - 사용자 컨텍스트 (user_id, nickname) 자동 포함
4. supabase/functions/_shared/sentry.ts:
   - Edge Function 에러도 Sentry 보고
5. G4 통과 조건: '7일 무장애 관측' 시작 (Day 56 까지)
📘 LogOnTable 트레이스 — Sentry DSN 공개 가능

결정: Sentry DSN은 NEXT_PUBLIC_ / EXPO_PUBLIC_ 환경변수로 공개

근거: Sentry DSN = 공개 가능 키 (악용 시 Sentry 알림 다 받기만, 데이터 유출 X). CLAUDE.md §3 절대 준수 — _SECRET_ 패턴 X.

부작용: 누구나 DSN 보고 가짜 에러 보낼 수 있음 → Sentry 무료 한도 (5K 에러/월) 소비 가능. 발생 시 Sentry inbound filter 설정

누적: 133.5h + Day 47 (2.5h) = 136h

Day 48 작업 (2.5h)

💻 Sentry 에러 그룹화 룰
에러 그룹화 룰:
- LLM 분류 실패 (G-1) → 'llm-classification' 그룹
- IP rate limit 차단 → 정상 동작 (Sentry 보고 X)
- DB 트리거 실패 → 'db-trigger' 그룹
- 카카오 토큰 갱신 실패 → 'auth-kakao' 그룹

Slack 통합 — Critical 에러 발생 시 즉시 Slack 알림
★ Day 48 자정부터 7일 무장애 관측 시작 (Day 55 까지)
📘 LogOnTable 트레이스 — IP rate limit 차단 = Sentry 보고 X

결정: IP rate limit 차단은 정상 동작 → Sentry 보고 X

근거: 의도된 보안 동작. 사용자 잘못된 동작에 의한 정상 reject. Sentry에 보내면 "실제 에러"와 혼선

대안: 모든 reject Sentry 보고 — 노이즈 ↑ → 진짜 에러 놓침

누적: 136h + Day 48 (2.5h) = 138.5h

3-6 Day 49 — 일요일 회고 (7주차)
💻 BUILD.md Day 49 entry — 7주차 회고
## Phase 1.0 Day 49 — 7주차 회고

[E2 4 트리거 점검]
☐ 누적 60h+/4주 — 4주 누적 (4~7주차): 21+24+18+12.5 = 75.5h ⚠️
   (7주차 페이스 축소로 회복 진행)
☐ 회고 부재 2주+ — No
☐ 토요일 4주 연속 — No (일관 유지)
☐ "피곤하다" 등장 — No

[★ 자동 결정]
- 8주차 (Day 50~56) 페이스 2.5h/일 유지 (G4 통과 의무 우선)
- Day 56 G4 통과 후 추가 휴식 (Day 57 휴식)

## 7주차 회고 (Day 43~49)

### 산출물 (Day 44~48, 5일)
1. src/app/profile/index.tsx (140줄, 마이페이지)
2. lib/profile-stats.ts (60줄)
3. supabase/migrations/0005_user_stats_rpc.sql (45줄)
4. components/StatsChart.tsx (90줄, 통계 차트 + 두 톤)
5. src/app/admin/kpi/page.tsx (160줄, Phase 1 KPI)
6. lib/admin/kpi-stats.ts (70줄)
7. src/lib/sentry.ts + _shared/sentry.ts (90줄)

총: 7개 파일 + 1 SQL = ~655줄

### Sentry 7일 무장애 관측 시작
- Day 48 자정 ~ Day 55 자정
- Critical 에러 발견 시 Slack 즉시 알림
- 7일 무장애 = G4 통과 조건 (Day 56)

[누적] 138.5h (Day 49 변동 없음 — 회고만)
[7주차 누적] 12.5h (Day 44~48, 페이스 축소 효과)
3-7 7주차 점검 — Phase 1.0 후반 절반의 절반
💻 7주차 누적 현황
[권1·권2·권3 5·6주차] 126h
[권3 7주차] 12.5h (페이스 추가 축소 + 휴식 1일)
[누적 합계] 138.5h
[4주 누적 (4~7주차)] 21+24+18+12.5 = 75.5h ⚠️ (회복 진행)

[★ 자동 보호 의식 본격 입증 사이클]
- 5주차 끝 73h → 6주차 페이스 25% 축소 (4h → 3h)
- 6주차 끝 77h → 7주차 페이스 추가 축소 (3h → 2.5h)
- 7주차 끝 75.5h → 8주차 유지 + Day 57 휴식 의무
"피곤하다" 신호 0회이지만 의식적 자동 보호 작동.
💡 Phase 1 KPI 첫 측정 (시뮬 데이터)
  • 부담 0 → 낮음: 70% (목표 ≥10% 통과)
  • 부담 낮음 → 선택: 60% (목표 ≥30% 통과)
  • auto_active 비중: 65%
  • 검수 SLA 준수율: 100%
  • 후기 작성 비율: 80% (시뮬, 실제 출시 후 검증)

📌 권3 제3장 정리

  • 핵심 한 줄: Day 43~49 = G3 휴식 + 마이페이지·통계·Phase 1 KPI + Sentry 통합 + 7일 무장애 관측 시작. 페이스 2.5h/일 (지금까지 최저).
  • 7주차 산출물: 마이페이지 (140줄) + RPC 함수 (45줄) + 통계 차트 (90줄) + KPI 어드민 (160줄) + Sentry 통합 (90줄)
  • ★ E2 자동 보호 의식 작동 사이클: 5주차 끝 73h → 페이스 -25% / 6주차 끝 77h → 페이스 추가 -17% / 7주차 끝 75.5h → 회복 진행. "피곤하다" 신호 0회, 자동 트리거가 보호
  • Sentry 7일 무장애 관측: Day 48 ~ Day 55 / Critical 에러 → Slack 즉시 / G4 통과 조건 (Day 56)
  • 누적: 138.5h / 4주 누적 75.5h
  • 다음 장: 권3 제4장 — Day 50~56 (G4 통과: FCM 알림 + 공유 카드 + 1순위 커뮤니티 측정)