📊 Reading Analytics
Understand your reading habits with speed tracking, mood analysis, streak monitoring, personalized insights, journaling, time budgets, session management, achievements, and full data export/import.
Overview
FeedReader includes nine analytics modules that work together to give you a complete picture of your reading life. Every module stores data locally on-device with no cloud dependency.
| Module | Purpose | Key Metric |
|---|---|---|
| Speed Tracker | Measure and predict reading speed | Words per minute (WPM) |
| Mood Tracker | Log mood and correlate with feeds | Mood level (1–5) |
| Streak Tracker | Daily reading consistency | Consecutive days reading |
| Insights Generator | Weekly/monthly reading summaries | Reader personality archetype |
| Reading Journal | Auto-generated daily reading diary | Entries with reflections |
| Time Budget | Set and track reading time limits | Minutes per day |
| Session Tracker | Individual reading session logging | Duration, articles, pauses |
| Achievements | Gamified reading milestones | 40 badges, 5 tiers |
| Data Export | Full backup and restore | JSON archive with validation |
📖 Speed Tracker
Tracks your words-per-minute (WPM) across articles, adjusted for article length, and provides personalized reading time estimates.
How It Works
When you finish an article, FeedReader records a speed sample with the article's word count, time spent, feed name, and category. Samples are filtered using IQR outlier detection (valid range: 50–1200 WPM) so unusually fast skims or long idle pauses don't skew your stats.
Length-Adjusted WPM
People read short and long articles at different speeds. The tracker maintains separate WPM buckets:
| Bucket | Word Count | Min Samples |
|---|---|---|
| Short | < 500 words | 3 |
| Medium | 500 – 1,500 words | 3 |
| Long | > 1,500 words | 3 |
estimatedReadingTime(wordCount:) picks the right bucket for the given article length, falling back to your global average if there aren't enough samples in that bucket yet.
API
let tracker = ReadingSpeedTracker.shared
// Record a reading sample
tracker.recordSample(
articleTitle: "AI in 2026",
feedName: "TechCrunch",
wordCount: 1200,
readingTimeSeconds: 300
)
// Get estimated reading time for a new article
let estimate = tracker.estimatedReadingTime(wordCount: 800) // TimeInterval
let formatted = tracker.formattedEstimate(wordCount: 800) // "3 min read"
// Trend analysis
let trend = tracker.speedTrend(days: 30) // .improving, .declining, .stable
// Breakdowns
let byFeed = tracker.feedBreakdown() // [SpeedBreakdown]
let byCat = tracker.categoryBreakdown() // [SpeedBreakdown]
// Where you stand
let percentile = tracker.populationPercentile() // 0-100
let label = tracker.speedLabel() // "Fast reader"
// Full profile
let profile = tracker.buildProfile() // SpeedProfile
Speed Labels
| WPM Range | Label |
|---|---|
| < 150 | Careful reader |
| 150 – 250 | Average reader |
| 250 – 400 | Fast reader |
| > 400 | Speed reader |
😊 Mood Tracker
Log how you feel while reading and discover which feeds boost or drain your mood.
Mood Levels
| Level | Value | Emoji |
|---|---|---|
| Very Low | 1 | 😢 |
| Low | 2 | 😕 |
| Neutral | 3 | 😐 |
| Good | 4 | 🙂 |
| Great | 5 | 😄 |
Feed Impact Analysis
The tracker correlates mood changes with what you were reading. feedMoodImpact() returns a per-feed breakdown showing average mood delta — positive means the feed tends to improve your mood, negative means the opposite. moodBoosterFeeds(minSessions:) filters to feeds with consistently positive impact.
API
let mood = ReadingMoodTracker.shared
// Log a mood entry
mood.logMood(
level: .good,
note: "Interesting AI article",
feedName: "Hacker News",
articleLink: "https://..."
)
// Query
let today = mood.todayEntries()
let current = mood.currentMood()
let avg = mood.averageMood(from: weekAgo) // Double?
let shifts = mood.moodShifts() // [MoodShift]
// Discover mood-boosting feeds
let impacts = mood.feedMoodImpact() // [FeedMoodImpact]
let boosters = mood.moodBoosterFeeds() // positive-delta feeds only
let suggestions = mood.moodBasedSuggestions() // feed suggestions by mood
// Export
let report = mood.moodReport(from: monthAgo, to: Date())
let export = mood.exportEntries() // JSON-compatible
🔥 Streak Tracker
Build a daily reading habit with streak tracking and weekly summaries.
How Streaks Work
A streak increments each consecutive day you read at least one article. Missing a day resets the streak counter to zero, but your best streak record is preserved. The optional goalTarget parameter lets you set a daily article count goal — the streak only counts days where you meet or exceed the target.
API
let streak = ReadingStreakTracker.shared
// Record reading activity (returns updated stats)
let stats = streak.recordArticleRead(goalTarget: 3)
// or record multiple at once:
let stats2 = streak.recordArticles(count: 5, goalTarget: 3)
// Current stats
let s = streak.getStats()
// s.currentStreak — consecutive days reading
// s.bestStreak — all-time longest streak
// s.totalArticlesRead — cumulative total
// s.todayCount — articles read today
// s.goalMetToday — whether today's goal is met
// Historical data
let record = streak.record(for: someDate) // DailyReadingRecord?
let range = streak.records(from: start, to: end)
// Weekly summaries (articles per day, average, best day)
let weeks = streak.weeklySummaries(weeks: 4)
// Goal-based streak (days in a row meeting the goal)
let goalStreak = streak.goalStreakDays(last: 30)
💡 Insights Generator
Generates personalized weekly and monthly reading reports with reader personality profiling.
Report Contents
- Volume: articles read, total words, time spent
- Velocity: average WPM, speed trend
- Diversity: how many different feeds/categories you read across
- Consistency: days active, streak data
- Top feeds and categories: ranked by article count
- Trend comparison: change versus previous period
- Reader personality: one of 8 archetypes
Reader Archetypes
| Archetype | Profile |
|---|---|
| 📚 Scholar | High volume, high diversity, consistent |
| 🎯 Specialist | High volume, low diversity (deep in one area) |
| 🦋 Explorer | Moderate volume, high diversity |
| ⚡ Speedster | Very high WPM, skims widely |
| 🧘 Contemplator | Low WPM, few articles, reads deeply |
| 📰 News Junkie | High volume, mostly news feeds |
| 🌊 Casual | Light reader, sporadic schedule |
| 🏃 Commuter | Reads in concentrated bursts (peak hours) |
API
let insights = ReadingInsightsGenerator.shared
// Generate a report
let report = insights.generateWeeklyInsight(
from: readEvents,
endDate: Date()
)
let monthly = insights.generateMonthlyInsight(
from: readEvents,
endDate: Date()
)
// Latest saved report
let last = insights.latestReport(for: .weekly)
// Export
let text = insights.exportAsText(report)
let json = insights.exportAsJSON(report)
📓 Reading Journal
An auto-generated daily diary of your reading activity — articles read, highlights saved, notes taken, and personal reflections.
Journal Entry Structure
- Articles read: title, feed, time spent, word count
- Highlights: excerpts saved from articles, with color codes
- Notes: personal annotations on articles
- Reflections: free-form daily thoughts
- Mood tags: emotional context for the day
- Statistics: total reading time, articles count, words read
Export Formats
Journal entries can be exported as Markdown (suitable for Obsidian, Notion, or GitHub) or JSON. Supports single-day, date-range, or full-history export.
API
let journal = ReadingJournalManager.shared
// Automatic recording (called by FeedReader internally)
journal.recordArticleRead(
link: url, title: title,
feedName: feed, timeSpent: seconds
)
journal.recordHighlight(text: excerpt, articleTitle: title)
journal.recordNote(text: note, articleTitle: title)
// Manual entries
journal.addReflection("Great deep-dive day")
journal.addMoodTag(.positive)
// Query
let today = journal.todayEntry()
let entry = journal.entry(for: someDate)
let range = journal.entries(from: start, to: end)
// Export
let md = journal.exportMarkdown(for: someDate)
let mdRange = journal.exportMarkdownRange(from: start, to: end)
let json = journal.exportJSON()
// Digests
let weekly = journal.weeklyDigest()
let monthly = journal.monthlyDigest()
// Streaks
let streak = journal.currentStreak // consecutive days with entries
⏱️ Time Budget
Set daily reading time goals and track how your time is distributed across feeds and categories.
Configuration
let budget = ReadingTimeBudget.shared
// Set a daily target (in minutes)
var config = budget.getConfig()
config.dailyTargetMinutes = 30
config.warningThresholdPercent = 80 // warn at 80% of budget
budget.updateConfig(config)
Session Tracking
Start a session when the user begins reading and stop it when they leave. Sessions automatically record duration, article context, and word count.
// Start/stop reading
budget.startSession(
articleTitle: "AI Safety in Practice",
feedName: "Alignment Forum",
wordCount: 2400
)
let entry = budget.stopSession()
// Query entries
let todayEntries = budget.entries(on: Date())
let weekEntries = budget.entries(from: weekAgo, to: Date())
// Time analysis
let todayTotal = budget.todayTotal() // TimeInterval
let remaining = budget.remainingToday() // TimeInterval
let progress = budget.dailyProgress() // 0.0 – 1.0+
let exceeded = budget.isOverBudget() // Bool
// Breakdowns
let byFeed = budget.feedBreakdown(from: weekAgo, to: Date())
let byCat = budget.categoryBreakdown(from: weekAgo, to: Date())
let byHour = budget.hourlyDistribution(days: 7)
let trend = budget.dailyTrend(days: 14)
// Suggestions
let suggestions = budget.budgetSuggestions() // personalized tips
🎧 Session Tracker
Track individual reading sessions with pause/resume support and per-article timing.
Session Lifecycle
startSession()— begins a new session (optional tag for context)startArticle(link:title:feedName:)— mark when an article is openedfinishArticle()— mark when the article is closed or scrolled pastpauseSession()/resumeSession()— handle app backgroundingendSession()— finalize and persist the session
Sessions record total duration, active reading time (excluding pauses), articles read with per-article time, and optional tags for categorization.
API
let sessions = ReadingSessionTracker.shared
// Basic flow
let session = sessions.startSession(tag: "morning-reading")
sessions.startArticle(link: url, title: title, feedName: feed)
sessions.finishArticle()
let completed = sessions.endSession()
// Query
let recent = sessions.sessions(lastDays: 7)
let tagged = sessions.sessions(withTag: "morning-reading")
let byId = sessions.session(byId: sessionId)
// Summaries
let summary = sessions.summary(for: recent)
// summary.totalDuration, summary.activeTime, summary.totalArticles,
// summary.avgArticlesPerSession, summary.avgSessionDuration
🏆 Achievements
Gamified reading milestones with 40 badges across 12 categories and 5 rarity tiers.
Rarity Tiers
| Tier | Points | Difficulty |
|---|---|---|
| 🟢 Common | 10 | Easy milestones (read 1 article) |
| 🔵 Uncommon | 25 | Regular effort (7-day streak) |
| 🟣 Rare | 50 | Dedicated readers (100 articles) |
| 🟠 Epic | 100 | Exceptional (30-day streak) |
| 🔴 Legendary | 250 | Elite (1000+ articles, 100-day streak) |
Categories
Achievements span reading volume, consistency (streaks), speed, diversity, bookmarking, sharing, time-of-day patterns, and more. Secret achievements are hidden until unlocked.
API
let achievements = ReadingAchievementsManager.shared
// Check progress
let all = achievements.allProgress()
let unlocked = achievements.unlockedAchievements()
let locked = achievements.lockedAchievements(includeSecrets: true)
// Filter
let streakBadges = achievements.achievements(in: .consistency)
let epics = achievements.achievements(ofRarity: .epic)
// Leveling
let level = achievements.currentLevel()
let points = achievements.totalPoints()
let toNext = achievements.pointsToNextLevel()
// Text summary
let summary = achievements.textSummary()
💾 Data Export & Import
Full backup and restore for all reading data — speed samples, mood entries, streaks, journal, time budget, sessions, achievements, bookmarks, highlights, notes, tags, and more.
Export Options
You can export everything (.all) or a specific subset:
| Option | Data Included |
|---|---|
.all | Every data category below |
.readingHistory | Articles read, timestamps, durations |
.bookmarks | Saved articles |
.highlights | Article highlights with colors |
.notes | Personal annotations |
.tags | Article tags |
.goals | Reading goals and progress |
.analytics | Speed, mood, streaks, sessions |
.journal | Journal entries with reflections |
.achievements | Badge progress and unlock dates |
Import Strategies
| Strategy | Behavior |
|---|---|
.skip | Keep existing data, skip conflicts |
.overwrite | Replace existing with imported data |
.merge | Combine both, preferring newer timestamps |
API
let exporter = ReadingDataExporter.shared
// Export
let (data, error) = exporter.exportData(options: .all)
let (fileSize, err) = exporter.exportToFile(url: backupUrl)
// Validate before importing
let warnings = exporter.validateArchive(data)
let preview = exporter.previewArchive(data)
// preview.version, preview.exportDate, preview.itemCounts
// Import
let result = exporter.importData(data, strategy: .merge)
// result.imported, result.skipped, result.errors