Skip to main content

Analytics

Record structured gamification events and query aggregated metrics — completion rates, streak trends, badge unlocks, and more.

Event Types

Seven event types cover all gamification activity:
PrismGamificationAnalyticsEvent
public enum PrismGamificationAnalyticsEvent: Sendable {
    case challengeStarted(challengeID: String, at: Date)
    case challengeCompleted(challengeID: String, at: Date, duration: TimeInterval?)
    case challengeProgressed(challengeID: String, progress: Double)
    case streakExtended(streakID: String, currentStreak: Int)
    case streakBroken(streakID: String, previousStreak: Int)
    case badgeUnlocked(badgeID: String, tier: String)
    case leaderboardScoreSubmitted(userID: String, score: Int)
}
Each event exposes eventType (storage key) and entityID (related entity).

Recording Events

Record Events
// Challenge lifecycle
try await manager.recordAnalyticsEvent(
    .challengeStarted(challengeID: "tenWorkouts", at: .now)
)

try await manager.recordAnalyticsEvent(
    .challengeCompleted(
        challengeID: "tenWorkouts",
        at: .now,
        duration: 86400 * 5 // 5 days
    )
)

// Streak activity
try await manager.recordAnalyticsEvent(
    .streakExtended(streakID: "daily", currentStreak: 14)
)

// Badge unlock
try await manager.recordAnalyticsEvent(
    .badgeUnlocked(badgeID: "fitnessFreak", tier: "silver")
)

Aggregated Snapshots

Query aggregated metrics for any time period:
Analytics Snapshot
let snapshot = try await manager.analyticsSnapshot(
    from: Calendar.current.date(byAdding: .day, value: -30, to: .now)!,
    to: .now
)

print(snapshot.totalChallengesStarted)    // 12
print(snapshot.totalChallengesCompleted)  // 8
print(snapshot.completionRate)            // 0.666...
print(snapshot.averageTimeToComplete)     // 172800.0 (2 days)
print(snapshot.totalStreakDays)           // 25
print(snapshot.totalBadgesUnlocked)      // 3
print(snapshot.eventCount)               // 48

PrismAnalyticsSnapshot

PropertyTypeDescription
totalChallengesStartedIntChallenges started in period
totalChallengesCompletedIntChallenges completed in period
completionRateDoubleCompletion ratio (0.0–1.0)
averageTimeToCompleteTimeInterval?Mean completion time
totalStreakDaysIntStreak-extended events
totalBadgesUnlockedIntBadges unlocked in period
eventCountIntTotal events in period
periodStartDatePeriod start date
periodEndDatePeriod end date

Querying Events

Fetch events for a specific entity:
Entity Events
let events = try await manager.analyticsEvents(
    for: "tenWorkouts",
    limit: 50
)

for event in events {
    print("\(event.eventType) at \(event.timestamp)")
}
Returns [PrismAnalyticsRecordSnapshot] sorted by timestamp descending. Default limit is 100.

PrismAnalyticsRecordSnapshot

PropertyTypeDescription
recordIDStringRecord identifier
eventTypeStringEvent type key
entityIDStringRelated entity
timestampDateWhen event occurred
metadataStringJSON-encoded extra data
completionDurationDouble?Duration for completion events

Cleanup

Remove old analytics data to manage storage:
Clear Old Data
let cutoff = Calendar.current.date(byAdding: .month, value: -6, to: .now)!
try await manager.clearAnalytics(before: cutoff)
Schedule periodic cleanup — analytics records accumulate over time. Keep recent data for dashboards and clear older records that are no longer needed.

Dashboard Pattern

Weekly Dashboard
func weeklyDashboard() async throws -> String {
    let weekAgo = Calendar.current.date(
        byAdding: .day, value: -7, to: .now
    )!
    let stats = try await manager.analyticsSnapshot(
        from: weekAgo, to: .now
    )

    return """
    This Week:
\(stats.totalChallengesCompleted)/\(stats.totalChallengesStarted) challenges (\(Int(stats.completionRate * 100))%)
\(stats.totalStreakDays) streak days
\(stats.totalBadgesUnlocked) badges unlocked
    """
}