도메인: Analytics (Firebase Analytics 이벤트)
룰
| 항목 | 값 |
|---|---|
| SDK | firebase_analytics (Flutter) |
| Firebase 프로젝트 | dartbrief (dev/prod 단일 — 프로젝트 분리 X) |
| Dev 격리 | setAnalyticsCollectionEnabled(flavor.isProd) — dev 빌드는 수집 OFF |
| 사용자 식별자 | setUserId(user.userId.toString()) — auth/me 성공 시 |
왜 단일 Firebase 프로젝트: Google 로그인 / FCM / App Check 가 모두 같은 GoogleService-Info.plist 에 묶여 있어 dev/prod 프로젝트 분리는 마찰만 큼. dev 메트릭 오염은 collection 토글로 처리.
도입 배경 (2026-05-07 5명 패널 토론)
lazy auth (비로그인 reveal 게이트) + 웹 부활 검토 중, DEVIL 의 핵심 지적:
"전환 측정 없이 lazy 풀면 게이트 통과율 / 중도 이탈을 못 봄. 출시 후 회복 불가능한 데이터 공백."
→ 5-PR 계획 1번으로 analytics 사전 셋업 (PR #54 머지 완료, 2026-05-07).
단일 진입점
mobile/lib/shared/analytics/analytics_service.dart 의 AnalyticsService 만 사용. 호출 측은 typed 메서드만 — 이벤트명/파라미터 키 문자열이 코드 전반에 흩어지지 않도록.
final analytics = ref.read(analyticsServiceProvider);
await analytics.logRevealAttempt(rceptNo: rceptNo);모든 메서드는 try/catch 로 감싸 native 채널 실패 시에도 앱 흐름을 막지 않음 (분석 누락 < 사용자 액션 차단).
이벤트 taxonomy
인증 (Firebase 표준)
| 이벤트 | 메서드 | 파라미터 | 송출 시점 |
|---|---|---|---|
login | logLogin | loginMethod: 'kakao'|'google'|'apple' | AuthController.handleAuthToken 안에서 refetch() 후 (login_page 의 mounted 가드 외) |
sign_up | logSignUp | signUpMethod | 현재 미사용 — 백엔드가 신규/기존 구분 응답 안 함. 시그니처만 준비 |
logout | logLogout | — | AuthController.logout() 안에서 setUserId(null) 보다 먼저 |
setUserId 직렬화 — login 흐름은 AuthController.handleAuthToken 이 refetch() 안에서 setUserId(user.userId.toString()) 를 await 한 뒤 같은 컨트롤러에서 logLogin 직접 송출 (page-level 호출 X — 게이트 모달 dispose/race 회피, 단일 책임 지점). 401 콜백 vs 명시적 logout race 는 _isLoggingOut 플래그로 우회 (자세히는 flutter-gotchas §7).
공시 / 분석 funnel
| 이벤트 | 파라미터 | 송출 시점 |
|---|---|---|
disclosure_view | rcept_no, corp_code? | 공시 상세 페이지 진입 |
analyze_inspect | rcept_no, status, is_short?, already_revealed? | Inspect 메타 도착 — 폴링 dedup (아래) |
reveal_attempt | rcept_no | "AI 분석 보기" 버튼 클릭 직전 |
reveal_result | rcept_no, outcome, newly_revealed?, can_watch_ad? | reveal API 응답 / 예외 / 화면 이탈 |
status 값 (analyze_inspect): 'completed' | 'pending' | 'unavailable' — Flutter AnalysisMetaState 타입 기준. 백엔드 QUOTA_EXHAUSTED 는 HTTP 200 으로 내려와 AnalysisMetaCompleted → 'completed' 로 기록됨. 'unavailable' 은 202+UNAVAILABLE 응답 시 송출 (현재 백엔드 미생성, reserved).
폴링 dedup — disclosure_detail_controller._lastInspectStatus 가 직전 status 를 기억해 같은 status 가 폴링으로 5회 들어와도 1번만 송출. status 가 바뀌면 다시 송출.
RevealOutcome enum
reveal_result.outcome 차원. 모든 종결 분기가 단일 이벤트 — funnel 분기 분석 용이.
| 값 | 의미 | 백엔드 매핑 |
|---|---|---|
success | reveal 성공 (PaidReveal 또는 FreeReveal) | Outcome.PaidReveal / FreeReveal |
quota_exhausted | 주 quota 소진 | 429 QUOTA_EXHAUSTED |
not_ready | 분석 아직 안 됨 | Outcome.Pending |
unavailable | 영구 불가 (FAILED + retry≥3) | reserved — 현재 Flutter reveal 경로 미송출 (별도 PR scope) |
error | 클라이언트 측 예외 (네트워크/서버 장애) | — |
cancelled | reveal 응답 도착 전 controller dispose | — |
error / cancelled 는 reveal_attempt 대비 reveal_result 의 구조적 누락 방지 — 모든 attempt 가 어떤 outcome 으로든 종결되어야 funnel 깨짐을 알 수 있음.
Engagement
| 이벤트 | 파라미터 | 송출 시점 |
|---|---|---|
watchlist_add | corp_code | 종목 추가 성공 (unawaited 로 화면 흐름 차단 X) |
광고 (현재 미송출)
| 이벤트 | 메서드 | 상태 |
|---|---|---|
ad_reward_earned | logAdRewardEarned | 시그니처만 유지, 클라이언트 송출 X |
왜 클라이언트 송출 안 하나 — prod 는 AdMob SSV 가 백엔드를 직접 호출하므로 클라이언트는 검증 통과 여부를 모름. 클라이언트에서 송출하면 SSV 실패해도 보상 funnel 이 +1 → false-positive. 정확한 측정은 백엔드 SSV 핸들러에서 Firebase Measurement Protocol / BigQuery 로 별도 기록 예정 (별도 PR scope).
lazy auth
| 이벤트 | 파라미터 | 의도 |
|---|---|---|
auth_gate_shown | action: 'reveal'|'watchlist'|'feedback'|'sign_in'|'subscribe' | 게이트 노출 — 비로그인 사용자가 어떤 액션으로 게이트에 닿는지. sign_in 은 명시적 로그인 진입점 (설정 → 로그인 메뉴) 으로 후속 액션 없이 로그인 자체가 목적. subscribe 는 페이월 CTA 탭 (구매=로그인 필수) |
auth_gate_dismissed | action, reason: 'close'|'swipe' | 게이트 닫힘 (취소) — 게이트 통과율 분모. reason 으로 X 명시 닫기 vs swipe down/system back 구분 → 풀스크린 vs 모달 A/B 결정 차원 |
AuthGateActionenum 으로 액션 차원 고정.- 게이트 통과 (로그인 성공) 경로는 별도 이벤트 X — 기존
login(provider)이벤트가 게이트 직후 송출돼 timestamp correlation 으로 측정. reason분리는 모달 vs 풀스크린 결정 입력값. swipe 비율이 높으면 풀스크린이 dismiss 마찰 추가 가치, close (X) 비율이 높으면 추가 가치 낮음.
구독 / 페이월 (현재 미송출 — 구현 PR scope)
screen-paywall 구독 funnel. 결제·복원이 미구현이라 시그니처/이벤트 명세만 둠.
| 이벤트 | 파라미터 | 송출 시점 |
|---|---|---|
paywall_view | source: 'settings'|'quota_soldout'|'watchlist_full' | 페이월 진입 — 어느 진입점이 전환을 만드는지 |
subscribe_attempt | plan: 'pro'|'max' | CTA 탭 (스토어 결제 시작 직전) — 어느 티어를 고르는지 |
subscribe_result | plan: 'pro'|'max', outcome: 'success'|'cancelled'|'error' | 결제 + verify 종결. 모든 attempt 가 어떤 outcome 으로든 종결 (reveal funnel 과 동일 원칙) |
restore_attempt | — | "구매 복원" 탭 |
restore_result | outcome: 'restored'|'none'|'error' | 복원 종결 (none = 활성 구독 없음) |
source차원으로 진입점별 전환율 비교 → 페이월 노출 위치 우선순위 결정.- 구매 성공의 매출 측정은 클라이언트 이벤트가 아니라 스토어 웹훅/
verify백엔드 기록이 진실 (광고 SSV 와 동일 — 클라이언트 false-positive 방지).
Edge case
- dev 빌드 → prod 메트릭 오염:
bootstrap.dart가setAnalyticsCollectionEnabled(flavor.isProd)로 dev 는 수집 OFF. flavor 기준 (kDebugMode아님 —make run-dev가--release라kDebugMode=false). - 사용자 식별자 race —
setUserId(null)이logLogout보다 먼저 발생하면 logout 이 익명 귀속되어 이탈 funnel 깨짐.AuthController.logout()은_isLoggingOut플래그 + 순차 await 로 보장. - 401 콜백 vs logout() — 토큰 만료 시
onUnauthorized콜백이setUserId(null)호출. 그러나 명시적 logout 흐름에서는 logout 이벤트가 익명 귀속될 위험 →_isLoggingOut=true면 콜백이 setUserId(null) 우회. - Controller dispose 중 reveal:
disclosure_detail_controller.reveal()은analytics를 함수 진입 시 캡처 (ref.read를 await 후 호출하면 dispose 후 crash). dispose 후엔RevealOutcome.cancelled송출. - 폴링 중 같은 이벤트 폭주 — Inspect 메타가 pending 인 동안 폴링 (5→10→15→20s backoff, 이후 60s 고정) →
_lastInspectStatusdedup 으로 1회만 송출.
관련 코드
mobile/lib/shared/analytics/analytics_service.dart— 단일 진입점mobile/lib/app/bootstrap.dart— collection togglemobile/lib/shared/auth/auth_controller.dart— login/logout/setUserId 직렬화mobile/lib/features/disclosure/application/disclosure_detail_controller.dart— Inspect/reveal funnel + dispose 가드mobile/lib/features/watchlist/application/watchlist_controller.dart— watchlist_add
법적 페이지 영향
Firebase Analytics 는 패키지 추가만으로 App Instance ID + 자동 수집 이벤트(session, screen_view, app_open 등) + 기기/OS/언어/국가 가 Google 로 송신된다. 따라서 다음 페이지에 Google LLC (Firebase Analytics) 위탁/국외이전 명시 필수:
frontend/src/app/privacy/page.tsx— 제2조 자동 수집 정보, 제5조 위탁, 제6조 국외 이전frontend/src/app/terms/page.tsx— 제9조·제12조 외부 서비스 목록
이벤트 taxonomy 추가 (logEvent 신규) 자체는 법적 페이지 영향 없음 (이미 "앱 사용 이벤트" 로 포괄). 단, 새로운 자동 수집 식별자 / 디바이스 정보 수집 SDK 추가 시 동시 갱신.
관련 PR
- PR #54 (2026-05-07) —
firebase_analytics도입 + 핵심 funnel 이벤트 wiring - PR #71 (2026-05-11) — 법적 페이지에 Firebase Analytics 위탁 명시 (PR #54 누락분 정정)