nextav/docs/active/recommendations/SURPRISE_ME_RECOMMENDATION_...

19 KiB
Raw Blame History

Surprise Me - Video Recommendation Feature Design

Overview

The "Surprise Me" feature is a lightweight video recommendation system designed for personal video management. Unlike enterprise systems like YouTube, this focuses on rediscovery and variety for a single user's personal collection, helping users find interesting videos they might have forgotten about or haven't watched recently.

Design Philosophy

Core Principles

  1. Discovery Over Prediction: Help users rediscover their own collection rather than predict preferences
  2. Lightweight: No machine learning infrastructure required
  3. Variety-Focused: Ensure diverse recommendations across different libraries/folders
  4. Personal: Leverage user's own bookmarks, ratings, and viewing patterns
  5. Serendipity: Balance between smart recommendations and pleasant surprises

Key Difference from YouTube-Style Recommendations

  • No collaborative filtering: Single user system
  • No external data: Works entirely with user's personal library
  • Focus on forgotten content: Help surface older or unwatched videos
  • Simplicity: Easy to understand and implement

Architecture

UI Integration

Sidebar Navigation

├── Home
├── Settings
├── Videos
├── Photos
├── Texts
├── Bookmarks
├── 🎲 Surprise Me          ← NEW ITEM
├── Clusters
│   ├── Cluster 1
│   └── Cluster 2
└── Folder Viewer
    ├── Library 1
    └── Library 2

Important: "Surprise Me" is a standalone sidebar item, NOT a tab within the Videos page.

Page Layout (/surprise-me)

When users click "Surprise Me" in the sidebar, they navigate to a dedicated page showing:

┌─────────────────────────────────────────────────────────┐
│  🎲 Surprise Me                                         │
│                                                          │
│  Discover videos from your collection                   │
│                                                          │
│  [🔄 Refresh Recommendations]  [⚙️ Algorithm: Smart Mix]│
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│  Recommended for You                                     │
│  ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐         │
│  │Video │ │Video │ │Video │ │Video │ │Video │         │
│  │ #1   │ │ #2   │ │ #3   │ │ #4   │ │ #5   │         │
│  └──────┘ └──────┘ └──────┘ └──────┘ └──────┘         │
│                                                          │
│  ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐         │
│  │Video │ │Video │ │Video │ │Video │ │Video │         │
│  │ #6   │ │ #7   │ │ #8   │ │ #9   │ │ #10  │         │
│  └──────┘ └──────┘ └──────┘ └──────┘ └──────┘         │
└─────────────────────────────────────────────────────────┘

Database Schema Extensions

New Table: media_access

Tracks user interactions with media for better recommendations.

CREATE TABLE media_access (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  media_id INTEGER NOT NULL,
  access_type TEXT NOT NULL CHECK (access_type IN ('view', 'play', 'bookmark', 'rate')),
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
);

-- Indexes for performance
CREATE INDEX idx_media_access_media_id ON media_access(media_id);
CREATE INDEX idx_media_access_created_at ON media_access(created_at);
CREATE INDEX idx_media_access_type_created_at ON media_access(access_type, created_at);

Usage:

  • view: When video modal/player is opened
  • play: When video playback actually starts
  • bookmark: When video is bookmarked
  • rate: When video is rated

API Endpoints

GET /api/videos/recommendations

Query Parameters:

  • algorithm (optional): Recommendation algorithm to use
    • smart_mix (default): Balanced mix of all algorithms
    • weighted_random: Smart randomization with recency weighting
    • forgotten_gems: Bookmarked/rated but not recently viewed
    • similar_to_favorites: From same folders as highly-rated videos
    • time_based: Based on video creation/addition time
    • pure_random: Complete randomization
    • unwatched_first: Prioritize never-accessed videos
  • limit (optional, default: 20): Number of recommendations
  • exclude_recent_days (optional, default: 30): Exclude videos viewed in last N days

Response:

{
  "recommendations": [
    {
      "id": 123,
      "title": "Movie Title",
      "path": "/mnt/videos/movie.mp4",
      "size": 1073741824,
      "thumbnail": "/api/thumbnails/...",
      "type": "video",
      "bookmark_count": 0,
      "star_count": 1,
      "avg_rating": 4.5,
      "created_at": "2024-01-15T10:30:00Z",
      "recommendation_reason": "From your favorite library",
      "recommendation_score": 0.85
    }
  ],
  "algorithm": "smart_mix",
  "total_videos": 1500,
  "total_recommended": 20
}

POST /api/media-access/:id

Track media access for recommendation improvement.

Body:

{
  "accessType": "view" | "play" | "bookmark" | "rate"
}

Response:

{
  "success": true,
  "mediaId": 123
}

Recommendation Algorithms

1. Smart Mix (Default)

Combines multiple strategies for balanced recommendations:

  • 30% Weighted Random Discovery
  • 25% Forgotten Gems
  • 20% Similar to Favorites
  • 15% Unwatched First
  • 10% Time-Based Variety

Implementation:

async function getSmartMixRecommendations(limit: number) {
  const recommendations = [];
  
  // Calculate distribution
  const distribution = {
    weightedRandom: Math.floor(limit * 0.3),
    forgottenGems: Math.floor(limit * 0.25),
    similarToFavorites: Math.floor(limit * 0.2),
    unwatchedFirst: Math.floor(limit * 0.15),
    timeBased: Math.floor(limit * 0.1)
  };
  
  // Get from each algorithm
  recommendations.push(...await getWeightedRandom(distribution.weightedRandom));
  recommendations.push(...await getForgottenGems(distribution.forgottenGems));
  recommendations.push(...await getSimilarToFavorites(distribution.similarToFavorites));
  recommendations.push(...await getUnwatchedFirst(distribution.unwatchedFirst));
  recommendations.push(...await getTimeBased(distribution.timeBased));
  
  // Shuffle and deduplicate
  return shuffleAndDeduplicate(recommendations).slice(0, limit);
}

2. Weighted Random Discovery

Smart randomization that considers:

  • Recency penalty: Videos viewed recently get lower weight
  • Rating boost: Higher-rated videos get slight preference
  • Library diversity: Ensure videos from different sources

Scoring Formula:

score = base_score × recency_multiplier × rating_multiplier × diversity_bonus

where:
  base_score = random(0.5, 1.0)
  recency_multiplier = min(1.0, days_since_last_view / 90)
  rating_multiplier = 1.0 + (avg_rating / 10)
  diversity_bonus = 1.2 if from underrepresented library

SQL Query:

SELECT 
  m.*,
  RANDOM() as random_factor,
  JULIANDAY('now') - JULIANDAY(COALESCE(ma.last_access, m.created_at)) as days_since_view,
  COALESCE(m.avg_rating, 0) as rating
FROM media m
LEFT JOIN (
  SELECT media_id, MAX(created_at) as last_access
  FROM media_access
  WHERE access_type IN ('view', 'play')
  GROUP BY media_id
) ma ON m.id = ma.media_id
WHERE m.type = 'video'
  AND (ma.last_access IS NULL OR ma.last_access < datetime('now', '-30 days'))
ORDER BY (
  random_factor * 
  MIN(1.0, (JULIANDAY('now') - JULIANDAY(COALESCE(ma.last_access, m.created_at))) / 90.0) *
  (1.0 + COALESCE(m.avg_rating, 0) / 10.0)
) DESC
LIMIT ?

3. Forgotten Gems

Surface videos that you've shown interest in but haven't watched recently:

  • Bookmarked videos not viewed in 30+ days
  • Highly-rated videos (4+ stars) not viewed in 60+ days
  • Videos from folders you've bookmarked

Target Criteria:

  • Has bookmark OR rating >= 4
  • Not accessed in last 30-60 days
  • Sorted by rating and bookmark combination

SQL Query:

SELECT DISTINCT m.*,
  CASE 
    WHEN b.id IS NOT NULL THEN 'You bookmarked this'
    WHEN m.avg_rating >= 4 THEN 'You rated this highly'
    ELSE 'From a favorite folder'
  END as recommendation_reason
FROM media m
LEFT JOIN bookmarks b ON m.id = b.media_id
LEFT JOIN (
  SELECT media_id, MAX(created_at) as last_access
  FROM media_access
  WHERE access_type IN ('view', 'play')
  GROUP BY media_id
) ma ON m.id = ma.media_id
WHERE m.type = 'video'
  AND (
    b.id IS NOT NULL OR 
    m.avg_rating >= 4 OR
    m.path LIKE (SELECT folder_path || '%' FROM folder_bookmarks LIMIT 1)
  )
  AND (ma.last_access IS NULL OR ma.last_access < datetime('now', '-30 days'))
ORDER BY 
  (COALESCE(m.avg_rating, 0) * 0.7 + 
   CASE WHEN b.id IS NOT NULL THEN 3 ELSE 0 END) DESC,
  RANDOM()
LIMIT ?

4. Similar to Favorites

Find videos from the same location as your highly-rated content:

  • Same parent folder as 4+ star videos
  • Same library as frequently accessed content
  • Similar file size/characteristics

Logic:

  1. Find user's top-rated videos (4+ stars)
  2. Extract their parent folders
  3. Find other videos in those folders
  4. Exclude already-watched videos

SQL Query:

WITH favorite_folders AS (
  SELECT DISTINCT 
    SUBSTR(path, 1, LENGTH(path) - LENGTH(SUBSTR(path, INSTR(path, '/', -1) + 1))) as folder_path
  FROM media
  WHERE type = 'video' AND avg_rating >= 4
  LIMIT 10
)
SELECT m.*,
  'From a folder with your favorites' as recommendation_reason
FROM media m
LEFT JOIN media_access ma ON m.id = ma.media_id AND ma.access_type IN ('view', 'play')
WHERE m.type = 'video'
  AND EXISTS (
    SELECT 1 FROM favorite_folders ff
    WHERE m.path LIKE ff.folder_path || '%'
  )
  AND m.avg_rating < 4  -- Not already a favorite
  AND (ma.id IS NULL OR ma.created_at < datetime('now', '-60 days'))
ORDER BY RANDOM()
LIMIT ?

5. Time-Based Recommendations

Recommendations based on temporal patterns:

  • Seasonal: Videos added around the same time last year
  • New discoveries: Recently added to library but not yet viewed
  • Old but gold: Videos added long ago that you might have missed

Categories:

type TimeBasedCategory = 
  | 'recently_added'      // Added in last 7 days, not viewed
  | 'one_month_old'       // Added ~30 days ago
  | 'seasonal'            // Added ~365 days ago
  | 'archive_discovery'   // Added >1 year ago, never viewed

6. Pure Random

Complete randomization with minimal filters:

  • Only filter: video type and exclude corrupted files
  • Pure serendipity mode
  • No bias towards ratings or access patterns

7. Unwatched First

Prioritize videos that have never been accessed:

  • Never appeared in media_access table
  • Sorted by date added (newest first)
  • Ensures new content gets discovered

SQL Query:

SELECT m.*,
  'Never watched before' as recommendation_reason
FROM media m
LEFT JOIN media_access ma ON m.id = ma.media_id
WHERE m.type = 'video'
  AND ma.id IS NULL
ORDER BY m.created_at DESC
LIMIT ?

Implementation Plan

Phase 1: Foundation (Priority: P0)

  • Design documentation
  • Database schema migration (add media_access table)
  • Media access tracking utility functions
  • Basic recommendation service structure

Phase 2: Core Algorithms (Priority: P0)

  • Implement Weighted Random algorithm
  • Implement Unwatched First algorithm
  • Implement Pure Random algorithm
  • Create API endpoint /api/videos/recommendations
  • Create media access tracking endpoint

Phase 3: Advanced Algorithms (Priority: P1)

  • Implement Forgotten Gems algorithm
  • Implement Similar to Favorites algorithm
  • Implement Time-Based algorithm
  • Implement Smart Mix algorithm

Phase 4: UI Components (Priority: P0)

  • Create SurpriseMePage component (/src/app/surprise-me/page.tsx)
  • Add "Surprise Me" item to sidebar navigation
  • Implement recommendation grid display
  • Add algorithm selector dropdown
  • Add refresh button functionality

Phase 5: Integration & Polish (Priority: P1)

  • Track video views automatically when player opens
  • Add recommendation reasons/badges to video cards
  • Implement smooth transitions and loading states
  • Add empty state when no recommendations available
  • Performance testing with large libraries

Phase 6: Advanced Features (Priority: P2)

  • Save preferred algorithm in user preferences
  • "Not interested" button to exclude videos
  • Recommendation history tracking
  • Export/share recommendations
  • Statistics dashboard (most recommended, never recommended, etc.)

File Structure

/root/workspace/nextav/
├── src/
│   ├── app/
│   │   ├── api/
│   │   │   ├── media-access/
│   │   │   │   └── [id]/
│   │   │   │       └── route.ts          ← NEW: Track media access
│   │   │   └── videos/
│   │   │       └── recommendations/
│   │   │           └── route.ts          ← NEW: Get recommendations
│   │   └── surprise-me/
│   │       └── page.tsx                  ← NEW: Surprise Me page
│   ├── components/
│   │   ├── recommendation-grid.tsx       ← NEW: Display recommendations
│   │   └── sidebar.tsx                   ← MODIFY: Add Surprise Me item
│   ├── db/
│   │   └── index.ts                      ← MODIFY: Add media_access table
│   └── lib/
│       └── recommendation-service.ts     ← NEW: Recommendation algorithms
└── docs/
    └── SURPRISE_ME_RECOMMENDATION_DESIGN.md  ← This file

User Experience Flow

Scenario 1: First Time User

  1. User clicks "🎲 Surprise Me" in sidebar
  2. Page shows loading state
  3. Default "Smart Mix" algorithm runs
  4. 20 diverse videos displayed
  5. User can click any video to watch
  6. User can click "Refresh" for new recommendations

Scenario 2: Regular User

  1. User navigates to Surprise Me
  2. System recognizes viewing patterns
  3. Shows mix of:
    • Unwatched videos from favorite libraries
    • Old favorites not seen recently
    • Random discoveries for variety
  4. Each card shows reason: "From your favorite library" or "Never watched"

Scenario 3: Algorithm Exploration

  1. User selects "Forgotten Gems" from dropdown
  2. System shows only bookmarked/highly-rated unwatched content
  3. User finds an old favorite they forgot about
  4. User watches and enjoys rediscovery

Performance Considerations

Database Optimization

  • All queries use indexed columns (media_id, created_at, type)
  • Limit queries to reasonable batch sizes (20-50 videos)
  • Use RANDOM() efficiently with proper ORDER BY
  • Cache recommendation results for 5-10 minutes

Caching Strategy

// Cache recommendations for short period
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
const recommendationCache = new Map<string, {
  data: Video[],
  timestamp: number
}>();

Scalability

  • Works efficiently with 1,000 to 100,000+ videos
  • Lazy loading for recommendation display
  • Pagination for large result sets
  • Background precomputation for complex algorithms (future enhancement)

Configuration Options

User Preferences (Future)

interface RecommendationPreferences {
  defaultAlgorithm: RecommendationAlgorithm;
  excludeRecentDays: number;        // Default: 30
  recommendationLimit: number;       // Default: 20
  enableAutoRefresh: boolean;        // Auto-refresh on page visit
  preferredLibraries: number[];      // Boost these libraries
  excludedFolders: string[];         // Never recommend from these
}

System Settings

const RECOMMENDATION_CONFIG = {
  MAX_RECOMMENDATIONS: 100,
  DEFAULT_LIMIT: 20,
  MIN_RATING_FOR_FAVORITES: 4,
  RECENT_VIEW_THRESHOLD_DAYS: 30,
  DIVERSITY_BOOST_FACTOR: 1.2,
  CACHE_TTL_SECONDS: 300
};

Testing Strategy

Unit Tests

  • Test each algorithm independently
  • Verify SQL query correctness
  • Test edge cases (empty library, all videos watched, etc.)

Integration Tests

  • Test API endpoint responses
  • Verify media access tracking
  • Test algorithm switching

User Acceptance Testing

  • Test with small library (<100 videos)
  • Test with medium library (100-1000 videos)
  • Test with large library (>10,000 videos)
  • Verify recommendations are diverse and interesting

Future Enhancements

Phase 2 Features (Post-MVP)

  1. Playlist Generation: Create playlists from recommendations
  2. Mood-Based: Filter by video characteristics (length, genre from filename)
  3. Social: Share recommendation sets with other users
  4. Learning: Track which recommendations user clicks to improve algorithms
  5. Filters: By library, rating range, size, date added
  6. Smart Scheduling: "Video of the Day" feature
  7. Collections: "Similar to this video" on video player
  8. Statistics: Dashboard showing recommendation effectiveness

Advanced Algorithms (Future)

  1. Content-Based Filtering: Use filename patterns, folder structure
  2. Collaborative Patterns: If multiple users, find correlation
  3. Temporal Patterns: Learn user's viewing time preferences
  4. Sequence Learning: Detect video series/collections
  5. Quality Scoring: Prefer higher quality encodes

Success Metrics

Key Performance Indicators

  • Engagement: % of recommendations clicked
  • Discovery Rate: # of previously unwatched videos discovered
  • Diversity Score: # of different libraries in recommendations
  • User Satisfaction: Time spent on Surprise Me page
  • Rediscovery: % of forgotten favorites surfaced

Target Goals

  • 80%+ of users try Surprise Me feature
  • 30%+ click-through rate on recommendations
  • 50%+ of recommendations are from unwatched videos
  • 70%+ user satisfaction with variety

Appendix

Recommendation Reason Badges

Visual indicators showing why a video was recommended:

Badge Meaning Color
🌟 Favorite From folder with 4+ star videos Gold
🔖 Bookmarked You bookmarked this Blue
New Added recently, never watched Green
🕰️ Forgotten Gem Rated highly but not watched in 60+ days Purple
🎲 Random Pure random selection Gray
📁 Same Folder From folder with your favorites Orange
🎯 Unwatched Never accessed before Teal

Example UI Component Props

interface SurpriseMePageProps {
  initialAlgorithm?: RecommendationAlgorithm;
  initialLimit?: number;
}

interface RecommendationCardProps {
  video: Video;
  reason: string;
  score: number;
  onVideoClick: (video: Video) => void;
}

Document Version: 1.0
Last Updated: 2025-10-12
Status: Design Phase
Next Steps: Begin Phase 1 implementation