package domain import ( "errors" "time" ) const ( KeyModePlatform = "platform" KeyModeByok = "byok" MeterAICopy = "ai_copy" MeterAIResearch = "ai_research" MeterWebSearch = "web_search" MeterAIImage = "ai_image" PlanFree = "free" PlanStarter = "starter" PlanPro = "pro" ) var ( ErrNotFound = errors.New("usage not found") ErrForbidden = errors.New("usage forbidden") ErrNoKey = errors.New("no api key configured (platform or byok)") ErrQuotaExceeded = errors.New("platform credits insufficient") ErrInvalidPlan = errors.New("invalid plan") ) type Event struct { ID string `bson:"_id" json:"id"` UID int64 `bson:"uid" json:"uid"` Meter string `bson:"meter" json:"meter"` Credits int `bson:"credits" json:"credits"` // platform only; byok always 0 KeyMode string `bson:"key_mode" json:"key_mode"` Label string `bson:"label" json:"label"` Source string `bson:"source" json:"source"` MonthKey string `bson:"month_key" json:"month_key"` CreatedAt int64 `bson:"created_at" json:"created_at"` } type MemberPrefs struct { UID int64 `bson:"uid" json:"uid"` PlanID string `bson:"plan_id" json:"plan_id"` Unlimited bool `bson:"unlimited" json:"unlimited"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } type Purchase struct { ID string `bson:"_id" json:"id"` UID int64 `bson:"uid" json:"uid"` PlanID string `bson:"plan_id" json:"plan_id"` MockRef string `bson:"mock_ref,omitempty" json:"mock_ref,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` } type PlanDef struct { ID string MonthlyCredits int } var Plans = map[string]PlanDef{ PlanFree: {ID: PlanFree, MonthlyCredits: 80}, PlanStarter: {ID: PlanStarter, MonthlyCredits: 500}, PlanPro: {ID: PlanPro, MonthlyCredits: 2000}, } var DefaultCreditCost = map[string]int{ MeterAICopy: 1, MeterAIResearch: 3, MeterWebSearch: 1, MeterAIImage: 5, } func NowNano() int64 { return time.Now().UTC().UnixNano() } func MonthKey(nano int64) string { t := time.Unix(0, nano).UTC() return t.Format("2006-01") } func CurrentMonthKey() string { return MonthKey(NowNano()) } // Summary split — never merge platform+byok into one total_calls only. type MeterCount struct { Count int `json:"count"` Credits int `json:"credits"` } type PlatformBlock struct { CreditsUsed int `json:"credits_used"` CreditsRemaining int `json:"credits_remaining"` CreditsTotal int `json:"credits_total"` CallCount int `json:"call_count"` ByMeter map[string]MeterCount `json:"by_meter"` } type ByokBlock struct { CallCount int `json:"call_count"` ByMeter map[string]MeterCount `json:"by_meter"` } type MonthSummary struct { MonthKey string `json:"month_key"` UID int64 `json:"uid"` PlanID string `json:"plan_id"` Unlimited bool `json:"unlimited"` Platform PlatformBlock `json:"platform"` Byok ByokBlock `json:"byok"` // Legacy FE bars: only platform credits (not mixed) TotalCredits int `json:"total_credits"` RemainingCredits int `json:"remaining_credits"` } type TenantRow struct { UID int64 `json:"uid"` PlanID string `json:"plan_id"` Unlimited bool `json:"unlimited"` PlatformCreditsUsed int `json:"platform_credits_used"` ByokCallCount int `json:"byok_call_count"` } type TenantSummary struct { MonthKey string `json:"month_key"` PlatformCreditsUsed int `json:"platform_credits_used"` ByokCallCount int `json:"byok_call_count"` Members []TenantRow `json:"members"` }