package domain import ( "context" "errors" "time" "github.com/google/uuid" ) var ( ErrNotFound = errors.New("growth not found") ErrForbidden = errors.New("growth access denied") ErrValidation = errors.New("growth validation") ErrThrottle = errors.New("account health throttle") ErrReviewPending = errors.New("draft review required") ErrRateLimited = errors.New("checkup regenerate limit") ) const ( SourceScoutOutreach = "scout_outreach" SourceOutboxStep = "outbox_step" KindReach = "reach" KindReply = "reply" KindFollow = "follow" KindConversion = "conversion" KindInteraction = "interaction" ConfidenceConfirmed = "confirmed" ConfidencePossible = "possible" StatusObserving = "observing" StatusConfirmed = "confirmed" StatusPossible = "possible" StatusExpired = "expired" StatusAmended = "amended" CheckupGenerating = "generating" CheckupReady = "ready" CheckupFailed = "failed" HealthNormal = "normal" HealthWarn = "warn" HealthThrottle = "throttle" ReviewNone = "none" ReviewPending = "pending" ReviewApproved = "approved" ReviewRejected = "rejected" RewardCredited = "credited" RewardCapped = "capped" RewardSkipped = "skipped" ObserveWindow = 72 * time.Hour ) func NowNano() int64 { return time.Now().UTC().UnixNano() } func NewID() string { return uuid.NewString() } type OutcomeEvent struct { ID string `bson:"_id" json:"id"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` SourceType string `bson:"source_type" json:"source_type"` SourceID string `bson:"source_id" json:"source_id"` ThreadsAccountID string `bson:"threads_account_id,omitempty" json:"threads_account_id,omitempty"` Kind string `bson:"kind" json:"kind"` Confidence string `bson:"confidence" json:"confidence"` Status string `bson:"status" json:"status"` ReplyCountDelta int `bson:"reply_count_delta,omitempty" json:"reply_count_delta,omitempty"` LikeCountDelta int `bson:"like_count_delta,omitempty" json:"like_count_delta,omitempty"` FollowersDelta int `bson:"followers_delta,omitempty" json:"followers_delta,omitempty"` ConversionAmount float64 `bson:"conversion_amount,omitempty" json:"conversion_amount,omitempty"` ConversionNote string `bson:"conversion_note,omitempty" json:"conversion_note,omitempty"` ConversionCurrency string `bson:"conversion_currency,omitempty" json:"conversion_currency,omitempty"` HasConversion bool `bson:"has_conversion" json:"has_conversion"` ClickCount int `bson:"click_count,omitempty" json:"click_count,omitempty"` WindowEndsAt int64 `bson:"window_ends_at" json:"window_ends_at"` SentAt int64 `bson:"sent_at" json:"sent_at"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } const ( KindClick = "click" PlaybookBrief = "brief" PlaybookPersona = "persona" PlaybookPlay = "play" WSRoleOwner = "owner" WSRoleEditor = "editor" WSRoleReviewer = "reviewer" ) type Playbook struct { ID string `bson:"_id" json:"id"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` Kind string `bson:"kind" json:"kind"` Title string `bson:"title" json:"title"` Niche string `bson:"niche" json:"niche"` Body string `bson:"body" json:"body"` Anonymous bool `bson:"anonymous" json:"anonymous"` ImportCount int `bson:"import_count" json:"import_count"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } type UtmLink struct { ID string `bson:"_id" json:"id"` Code string `bson:"code" json:"code"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` DestinationURL string `bson:"destination_url" json:"destination_url"` OutcomeID string `bson:"outcome_id,omitempty" json:"outcome_id,omitempty"` Label string `bson:"label,omitempty" json:"label,omitempty"` Clicks int `bson:"clicks" json:"clicks"` CreatedAt int64 `bson:"created_at" json:"created_at"` } type WorkspaceMember struct { ID string `bson:"_id" json:"id"` WorkspaceID string `bson:"workspace_id" json:"workspace_id"` UID int64 `bson:"uid" json:"uid"` Role string `bson:"role" json:"role"` JoinedAt int64 `bson:"joined_at" json:"joined_at"` } // BenchmarkSample — anonymized contribution for niche medians type BenchmarkSample struct { ID string `bson:"_id" json:"id"` Niche string `bson:"niche" json:"niche"` EngRate float64 `bson:"eng_rate" json:"eng_rate"` AvgViews float64 `bson:"avg_views" json:"avg_views"` OwnerHash string `bson:"owner_hash" json:"owner_hash"` // one sample per owner per niche UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } type OutcomeSummary struct { From int64 To int64 Reach int Conversations int FollowsPossible int FollowsConfirmed int Conversions int ConversionAmount float64 } type CheckupFinding struct { Claim string `bson:"claim" json:"claim"` Evidence []string `bson:"evidence" json:"evidence"` } type CheckupAction struct { Title string `bson:"title" json:"title"` Reason string `bson:"reason" json:"reason"` Deeplink string `bson:"deeplink" json:"deeplink"` } type WeeklyCheckup struct { ID string `bson:"_id" json:"id"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` WeekKey string `bson:"week_key" json:"week_key"` Status string `bson:"status" json:"status"` Summary string `bson:"summary" json:"summary"` Findings []CheckupFinding `bson:"findings" json:"findings"` Actions []CheckupAction `bson:"actions" json:"actions"` Error string `bson:"error,omitempty" json:"error,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } type AccountHealth struct { ThreadsAccountID string `bson:"_id" json:"threads_account_id"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` Score int `bson:"score" json:"score"` Level string `bson:"level" json:"level"` Factors []string `bson:"factors" json:"factors"` Advice string `bson:"advice" json:"advice"` ComputedAt int64 `bson:"computed_at" json:"computed_at"` } type Workspace struct { ID string `bson:"_id" json:"id"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` Name string `bson:"name" json:"name"` ReviewRequired bool `bson:"review_required" json:"review_required"` IsDefault bool `bson:"is_default" json:"is_default"` Archived bool `bson:"archived" json:"archived"` FooterText string `bson:"footer_text,omitempty" json:"footer_text,omitempty"` HidePoweredBy bool `bson:"hide_powered_by,omitempty" json:"hide_powered_by,omitempty"` BrandName string `bson:"brand_name,omitempty" json:"brand_name,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } type MemberWorkspaceState struct { OwnerUID int64 `bson:"_id" json:"owner_uid"` CurrentWorkspaceID string `bson:"current_workspace_id" json:"current_workspace_id"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } type DraftReview struct { ID string `bson:"_id" json:"id"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` WorkspaceID string `bson:"workspace_id" json:"workspace_id"` RefType string `bson:"ref_type" json:"ref_type"` RefID string `bson:"ref_id" json:"ref_id"` Status string `bson:"status" json:"status"` Reason string `bson:"reason,omitempty" json:"reason,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } type InviteReward struct { ID string `bson:"_id" json:"id"` InviterUID int64 `bson:"inviter_uid" json:"inviter_uid"` InviteeUID int64 `bson:"invitee_uid" json:"invitee_uid"` PlanID string `bson:"plan_id" json:"plan_id"` Points int `bson:"points" json:"points"` Status string `bson:"status" json:"status"` CreatedAt int64 `bson:"created_at" json:"created_at"` } type OutcomeListFilter struct { Kind string Confidence string SourceType string From int64 To int64 Page int PageSize int } type Repository interface { // Outcomes UpsertOutcomeBySource(ctx context.Context, e *OutcomeEvent) error GetOutcome(ctx context.Context, id string) (*OutcomeEvent, error) ListOutcomes(ctx context.Context, ownerUID int64, f OutcomeListFilter) ([]*OutcomeEvent, int64, error) SaveOutcome(ctx context.Context, e *OutcomeEvent) error ListObservingDue(ctx context.Context, now int64, limit int) ([]*OutcomeEvent, error) // Checkups SaveCheckup(ctx context.Context, c *WeeklyCheckup) error GetCheckup(ctx context.Context, id string) (*WeeklyCheckup, error) LatestCheckup(ctx context.Context, ownerUID int64) (*WeeklyCheckup, error) ListCheckups(ctx context.Context, ownerUID int64, page, pageSize int) ([]*WeeklyCheckup, int64, error) CountCheckupGenerations(ctx context.Context, ownerUID int64, sinceNano int64) (int64, error) // Health SaveHealth(ctx context.Context, h *AccountHealth) error GetHealth(ctx context.Context, accountID string) (*AccountHealth, error) ListHealth(ctx context.Context, ownerUID int64) ([]*AccountHealth, error) // Workspace SaveWorkspace(ctx context.Context, w *Workspace) error GetWorkspace(ctx context.Context, id string) (*Workspace, error) ListWorkspaces(ctx context.Context, ownerUID int64, includeArchived bool) ([]*Workspace, error) CountActiveWorkspaces(ctx context.Context, ownerUID int64) (int64, error) GetMemberWSState(ctx context.Context, ownerUID int64) (*MemberWorkspaceState, error) SaveMemberWSState(ctx context.Context, s *MemberWorkspaceState) error // Reviews SaveReview(ctx context.Context, r *DraftReview) error GetReview(ctx context.Context, id string) (*DraftReview, error) FindReviewByRef(ctx context.Context, ownerUID int64, refType, refID string) (*DraftReview, error) ListReviews(ctx context.Context, ownerUID int64, status string, page, pageSize int) ([]*DraftReview, int64, error) // Invite rewards SaveInviteReward(ctx context.Context, r *InviteReward) error ListInviteRewards(ctx context.Context, inviterUID int64, limit int) ([]*InviteReward, error) SumInviteRewardPointsMonth(ctx context.Context, inviterUID int64, monthStartNano int64) (int, error) SumInviteRewardPointsTotal(ctx context.Context, inviterUID int64) (int, error) HasInviteRewardForInvitee(ctx context.Context, inviteeUID int64) (bool, error) // P2 Playbooks SavePlaybook(ctx context.Context, p *Playbook) error GetPlaybook(ctx context.Context, id string) (*Playbook, error) ListPlaybooks(ctx context.Context, kind, niche string, ownerOnlyUID int64, page, pageSize int) ([]*Playbook, int64, error) DeletePlaybook(ctx context.Context, id string) error IncPlaybookImport(ctx context.Context, id string) error // P2 UTM SaveUtmLink(ctx context.Context, u *UtmLink) error GetUtmByCode(ctx context.Context, code string) (*UtmLink, error) ListUtmLinks(ctx context.Context, ownerUID int64) ([]*UtmLink, error) IncUtmClick(ctx context.Context, code string) (*UtmLink, error) // P2 Workspace members SaveWorkspaceMember(ctx context.Context, m *WorkspaceMember) error ListWorkspaceMembers(ctx context.Context, workspaceID string) ([]*WorkspaceMember, error) RemoveWorkspaceMember(ctx context.Context, workspaceID string, uid int64) error IsWorkspaceMember(ctx context.Context, workspaceID string, uid int64) (bool, string, error) // P2 Benchmark UpsertBenchmarkSample(ctx context.Context, s *BenchmarkSample) error ListBenchmarkSamples(ctx context.Context, niche string, limit int) ([]*BenchmarkSample, error) }