UNPKG

fsrs-algorithm

Version:

Free Spaced Repetition Scheduler (FSRS) algorithm implementation in TypeScript

394 lines (296 loc) 10.6 kB
# FSRS - Free Spaced Repetition Scheduler A TypeScript/JavaScript implementation of the FSRS (Free Spaced Repetition Scheduler) algorithm for optimizing memory retention through spaced repetition learning. ## What is FSRS? FSRS is a modern spaced repetition algorithm that schedules review sessions for flashcards or learning materials based on memory research. It dynamically adjusts review intervals based on your performance, helping you learn more efficiently by showing difficult cards more frequently and easy cards less often. ## Features - 🧠 **Scientific Algorithm**: Based on memory research and forgetting curves - ⚙️ **Configurable Parameters**: Customize the algorithm for your learning style - 📊 **Difficulty Tracking**: Automatically adjusts card difficulty based on performance - 🔄 **State Management**: Handles different learning states (New, Learning, Review, Relearning) - 📈 **Retrievability Calculation**: Track memory strength over time - 🎯 **Retention Optimization**: Optimize for your target retention rate ## Installation ```bash npm install fsrs-algorithm # or yarn add fsrs-algorithm ``` ## Quick Start ```typescript import { FSRS, Rating, State } from "fsrs-algorithm"; // Initialize FSRS with default parameters const fsrs = new FSRS(); // Create a new card const card = fsrs.createEmptyCard(); // Schedule the card based on your rating const now = new Date(); const schedulingOptions = fsrs.schedule(card, now); // Rate your performance (1=Again, 2=Hard, 3=Good, 4=Easy) const yourRating = Rating.Good; const scheduledCard = schedulingOptions.good.card; const reviewLog = schedulingOptions.good.reviewLog; console.log(`Next review: ${scheduledCard.due}`); console.log(`Interval: ${scheduledCard.scheduledDays} days`); ``` ## API Reference ### Constructor ```typescript const fsrs = new FSRS(parameters?: Partial<FSRSParameters>); ``` **Parameters:** - `requestRetention` (default: 0.9): Target retention rate (0.0-1.0) - `maximumInterval` (default: 3650): Maximum review interval in days - `w`: Array of 19 algorithm weights (uses optimized defaults) ### Methods #### `createEmptyCard(now?: Date): Card` Creates a new flashcard ready for learning. ```typescript const card = fsrs.createEmptyCard(); ``` #### `schedule(card: Card, now?: Date): SchedulingCards` Returns scheduling options for all possible ratings. ```typescript const options = fsrs.schedule(card, new Date()); // Access different rating outcomes const againOption = options.again; // Rating.Again (1) const hardOption = options.hard; // Rating.Hard (2) const goodOption = options.good; // Rating.Good (3) const easyOption = options.easy; // Rating.Easy (4) ``` #### `getRetrievability(card: Card, now?: Date): number` Calculate the current probability of successfully recalling the card (0.0-1.0). ```typescript const retrievability = fsrs.getRetrievability(card); console.log(`${Math.round(retrievability * 100)}% chance of recall`); ``` #### `updateParameters(newParameters: Partial<FSRSParameters>): void` Update algorithm parameters. ```typescript fsrs.updateParameters({ requestRetention: 0.85, // Target 85% retention maximumInterval: 1825, // Max 5 years }); ``` ## Data Types ### Card ```typescript interface Card { due: Date; // When to review next stability: number; // Memory strength difficulty: number; // Card difficulty (1-10) elapsedDays: number; // Days since last review scheduledDays: number; // Scheduled interval reps: number; // Total reviews lapses: number; // Number of times forgotten state: State; // Current learning state lastReview?: Date; // Last review date } ``` ### Rating ```typescript enum Rating { Again = 1, // Forgot the card Hard = 2, // Remembered with difficulty Good = 3, // Remembered easily Easy = 4, // Too easy } ``` ### State ```typescript enum State { New = 0, // Never studied Learning = 1, // First time learning Review = 2, // Regular review Relearning = 3, // Relearning after forgetting } ``` ## Usage Examples ### Pseudocode flow ``` // 1. You'd create an empty card for new cards -- structure as below interface Card { due: Date; // When to show this card next stability: number; // How "sticky" the memory is difficulty: number; // How hard this card is (1-10) elapsedDays: number; // Days since last review scheduledDays: number; // How long it was scheduled for reps: number; // Total times reviewed lapses: number; // Times you failed/forgot state: State; // New/Learning/Review/Relearning lastReview?: Date; // When you last saw it } cosnt today = new Date(); const newCard = fsrs.createEmptyCard(today); // new empty card // 2. get hypothetical outcomes for empty card -- structure is below export interface SchedulingInfo { card: Card; reviewLog: ReviewLog; } export interface SchedulingCards { again: SchedulingInfo; hard: SchedulingInfo; good: SchedulingInfo; easy: SchedulingInfo; } const cardPossibilities = fsrs.schedule(newCard, today); // this would return SchedulingCards // 3. Show user possibilities // 4. On user selection save new selection in database/cache for next iteration. // ReviewLog is for historical data on specific card to view overall history. // 5. On next "study" session you would pull these cards // and get hypothetical outcomes per card show them and repeat the steps, as below. // Note the different method, this takes an object like below, converts/validates and returns possible outcomes like the 'schedule' method. const reviewCard: RawCardData = { id: "cmdauwe2l0001vexwplv74vjf", userId: "5NANdkHyQ1p0riBtDeHPOfdRwdv7y1DM", cardId: "cmd4fgv3f0001veq4loisxygg", due: "2025-07-25T22:52:36.294Z", // These are strings not Date objects stability: 5.8, difficulty: 3.99, elapsedDays: 0, reps: 1, lapses: 0, state: "REVIEW", lastReview: "2025-07-19T22:52:36.294Z", // These are strings not Date objects createdAt: "2025-07-19T23:05:41.949Z", updatedAt: "2025-07-19T23:05:41.949Z", }; const hypotheticalOutcomes = fsrs.scheduleRawCard(rawData, now); ``` ### Basic Learning Session ```typescript import { FSRS, Rating } from "fsrs-algorithm"; const fsrs = new FSRS(); let card = fsrs.createEmptyCard(); // Study session function reviewCard(rating: Rating) { const options = fsrs.schedule(card, new Date()); switch (rating) { case Rating.Again: card = options.again.card; break; case Rating.Hard: card = options.hard.card; break; case Rating.Good: card = options.good.card; break; case Rating.Easy: card = options.easy.card; break; } console.log(`Next review: ${card.due.toLocaleDateString()}`); console.log(`Difficulty: ${card.difficulty.toFixed(2)}`); console.log(`Stability: ${card.stability.toFixed(2)} days`); } // User rates the card as "Good" reviewCard(Rating.Good); ``` ### Using Card Objects from your Database ``` // Note the different method, this takes an object like below, converts/validates and returns possible outcomes like the 'schedule' method. const reviewCard: RawCardData = { id: "cmdauwe2l0001vexwplv74vjf", userId: "5NANdkHyQ1p0riBtDeHPOfdRwdv7y1DM", cardId: "cmd4fgv3f0001veq4loisxygg", due: "2025-07-25T22:52:36.294Z", // These are strings not Date objects stability: 5.8, difficulty: 3.99, elapsedDays: 0, reps: 1, lapses: 0, state: "REVIEW", lastReview: "2025-07-19T22:52:36.294Z", // These are strings not Date objects createdAt: "2025-07-19T23:05:41.949Z", updatedAt: "2025-07-19T23:05:41.949Z", }; const hypotheticalOutcomes = fsrs.scheduleRawCard(rawData, now); // Now you can render these for the user again. ``` ### Custom Parameters ```typescript // Optimize for different learning scenarios const fsrs = new FSRS({ requestRetention: 0.95, // Higher retention for important material maximumInterval: 365, // Review at least annually }); ``` ### Tracking Multiple Cards ```typescript interface StudyCard extends Card { id: string; front: string; back: string; } class StudySession { private fsrs = new FSRS(); private cards: StudyCard[] = []; addCard(front: string, back: string): StudyCard { const card: StudyCard = { ...this.fsrs.createEmptyCard(), id: crypto.randomUUID(), front, back, }; this.cards.push(card); return card; } getDueCards(now = new Date()): StudyCard[] { return this.cards.filter((card) => card.due <= now); } reviewCard(cardId: string, rating: Rating): void { const cardIndex = this.cards.findIndex((c) => c.id === cardId); if (cardIndex === -1) return; const options = this.fsrs.schedule(this.cards[cardIndex]); const ratingKey = this.getRatingKey(rating); this.cards[cardIndex] = { ...this.cards[cardIndex], ...options[ratingKey].card, }; } private getRatingKey(rating: Rating): keyof SchedulingCards { const map = { [Rating.Again]: "again", [Rating.Hard]: "hard", [Rating.Good]: "good", [Rating.Easy]: "easy", }; return map[rating]; } } ``` ## Configuration ### Algorithm Parameters The FSRS algorithm uses 19 weight parameters (`w[0]` to `w[18]`) that control different aspects of the scheduling: - `w[0-3]`: Initial stability for each rating - `w[4-5]`: Initial difficulty calculation - `w[6-7]`: Difficulty adjustment and mean reversion - `w[8-10]`: Stability calculation for successful reviews - `w[11-14]`: Stability calculation for failed reviews (lapses) - `w[15-16]`: Hard/Easy rating penalties and bonuses The default weights are optimized for general use, but you can customize them: ```typescript const customWeights = [ 0.4, 0.6, 2.4, 5.8, 4.93, 0.94, 0.86, 0.01, 1.49, 0.14, 0.94, 2.18, 0.05, 0.34, 1.26, 0.29, 2.61, 0.0, 0.0, ]; const fsrs = new FSRS({ w: customWeights }); ``` ### Retention Rate Adjust the target retention rate based on your needs: ```typescript // Conservative (more frequent reviews) const conservative = new FSRS({ requestRetention: 0.95 }); // Aggressive (less frequent reviews, more forgetting) const aggressive = new FSRS({ requestRetention: 0.8 }); ``` ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. ## License MIT License - see LICENSE file for details. ## References - [FSRS Algorithm Paper](https://www.nature.com/articles/s41598-024-57552-7) - [FSRS GitHub Repository](https://github.com/open-spaced-repetition/fsrs4anki) - [Spaced Repetition Research](https://www.gwern.net/Spaced-repetition) --- ##### PS. This is an Alpha release, I've got some cleanup to do and perhaps allow different ways to incorporate this various application structures.