Skip to content

도메인: Quota (주 AI 분석 한도)

항목
주 무료 quota10
짧은 공시 임계값1000자 미만 무료 노출
Unavailable 임계 retry_count90 (FAILED 상태에서 도달 시 영구 부재)

상수는 internal/ai/policy.goDefaultRevealPolicy() 에 캡처. 이전 버전은 WEEKLY_FREE_QUOTA / SHORT_DOC_THRESHOLD 환경변수로 매 호출마다 hot-reload 가능했으나 prod 미사용으로 폐기 (2026-05-12). 정책 변형 필요 시 RevealPolicyFromEnv() 추가하거나 DefaultRevealPolicy() 의 값 수정.

가입 보너스는 V4 (2026-05-06) 에서 폐기됨. 첫 경험 절벽 (10 → 5) 거부감이 더 컸음.

정책 모듈 구조

RevealPolicy = QuotaPolicy + AnalysisPolicy. Classify 메서드가 분석 + quota 룰의 단일 진입점.

객체책임호출자
QuotaPolicy { WeeklyLimit }한도 + 주 경계 (CurrentWeekStart) + Remaining(Quota)RevealService, QuotaRepository, admob.Handler
AnalysisPolicy { ShortDocThreshold, UnavailableRetryThreshold }IsShort(docLen), IsUnavailable(status, retryCount)RevealService
RevealPolicy { Quota, Analysis }Classify(analysis, alreadyRevealed, quota) → OutcomeRevealService

main.go 가 ai.DefaultRevealPolicy() 1회 생성 → 의존 모듈에 주입. admob 패키지는 revealPolicy.Quota 만 받아 SSV reward 시 quota_week_start 를 동일 룰로 set (이전엔 currentWeekStart 패키지 로컬 복제 — import cycle 회피용이었으나 deepening 후 ai → admob 단방향 import 로 해소).

비용 모델 (오해 방지)

LLM 호출은 공시당 1회 (스케줄러가 분석 시) — 사용자 reveal 은 analysis_cache 캐시 조회. 사용자 reveal 횟수는 LLM 비용과 무관. quota cap 의 목적은:

  1. ad_free_pass IAP funnel hook
  2. 다중 계정 어뷰즈 방어 보조 (App Check + SSV 1차, cap 으로 ROI ↓)
  3. 인지 가치 — 무제한이면 "이 분석 가치 있나?" 신호 약화

상세 정책 결정 맥락: ../policies/monetization.md.

알고리즘

remaining = weekly_limit - used_count

(weekly_limit = WEEKLY_FREE_QUOTA. used_count 는 음수 허용 — 광고 시청 시 -1.)

별도 ad_bonus 컬럼 없음. 광고 보상은 used_count -= 1 로 표현. ad_reward_transactions 테이블은 SSV idempotency (중복 콜백 차단) 전용 — quota 계산에 참조 안 함.

차감 결정은 internal/ai/reveal.goClassify() 가 단일 진실:

  1. analysis == nil 또는 status != COMPLETEDPending
  2. is_short (doc_length < SHORT_DOC_THRESHOLD) → FreeReveal
  3. already_revealed (user_revealed_disclosures 에 row 있음) → FreeReveal
  4. remaining <= 0QuotaExhausted (429 QUOTA_EXHAUSTED + can_watch_ad: true)
  5. 정상 → PaidReveal (used_count +1 + reveal 기록, 단일 트랜잭션)

자세한 race-safe 트랜잭션 시퀀스: docs/policies/lazy-reveal.md.

Edge case

  • 주 경계: quota_week_start != current_week_start (월요일 KST) 이면 used_count 0 으로 리셋 (음수였어도 0)
  • 광고 시청: used_count -= 1, 무제한. used_count=0 인 상태에서 광고 보면 -1 되어 "선납" 처럼 작동하나, 주 reset 시 0 으로 초기화 — 주말에 광고 미리 봐두는 식 이월은 불가 (의도된 동작)
  • SSV 중복 콜백: ad_reward_transactions.transaction_id PK 로 idempotency. 같은 transaction_id 두 번째 호출은 INSERT 충돌 → 보상 미지급
  • 동시 reveal: user_revealed_disclosures INSERT ON CONFLICT + quota 차감 트랜잭션으로 중복 차감 방지

관련 테이블

  • dartbrief.user_analysis_quota — user_id, used_count, quota_week_start
  • dartbrief.ad_reward_transactions — transaction_id (PK), user_id (idempotency 전용)
  • dartbrief.user_revealed_disclosures — user_id, rcept_no, revealed_at

관련 API