Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] Overall Processes, Logic and Anki's Card Handling #143

Open
benediktdertinger opened this issue Dec 24, 2024 · 5 comments
Open

Comments

@benediktdertinger
Copy link

Hello!

Thank you so much for creating and sharing this! I really appreciate it.

The package is quite impressive but I find it very hard to understand concepts if you haven't had any experience in this context yet. I read through all issues and checked out the demos. I will try to share my findings below for others and also ask a few questions to validate if my understanding was correct. Additionally I would like to build a logic that is similar to what Anki provides and I have a few questions here also.

At first I was assuming that you would create a database with all individual ratings and gather details for upcoming cards based on these ratings. But I realized that you only have a single object per card that basically defines the overall statistics for the interaction with this card per user over time.

General logic

  1. Generate a statistic for a card via const cardStatistic = createEmptyCard(new Date());
  2. Calculate the rating options for this card via const ratingOptions = f.repeat(cardStatistic, new Date());
ratingOptions
{
  '1': {
      // This is the new card statistic if user selects option 1
      card: {
        due: 2024-12-25T10:51:32.708Z,
        stability: 0.40255,
        difficulty: 7.1949,
        elapsed_days: 0,
        scheduled_days: 1,
        reps: 1,
        lapses: 0,
        state: 2,
        last_review: 2024-12-24T10:51:32.708Z
      },
      // This is the log for this card if user selects option 1
      log: {
        rating: 1,
        state: 0,
        due: 2024-12-24T10:51:32.708Z,
        stability: 0,
        difficulty: 0,
        elapsed_days: 0,
        last_elapsed_days: 0,
        scheduled_days: 0,
        review: 2024-12-24T10:51:32.708Z
      }
    },
    '2': {
      card: {
        due: 2024-12-26T10:51:32.708Z,
        stability: 1.18385,
        difficulty: 6.48830527,
        elapsed_days: 0,
        scheduled_days: 2,
        reps: 1,
        lapses: 0,
        state: 2,
        last_review: 2024-12-24T10:51:32.708Z
      },
      log: {
        rating: 2,
        state: 0,
        due: 2024-12-24T10:51:32.708Z,
        stability: 0,
        difficulty: 0,
        elapsed_days: 0,
        last_elapsed_days: 0,
        scheduled_days: 0,
        review: 2024-12-24T10:51:32.708Z
      }
    },
    '3': {
      card: {
        due: 2024-12-27T10:51:32.708Z,
        stability: 3.173,
        difficulty: 5.28243442,
        elapsed_days: 0,
        scheduled_days: 3,
        reps: 1,
        lapses: 0,
        state: 2,
        last_review: 2024-12-24T10:51:32.708Z
      },
      log: {
        rating: 3,
        state: 0,
        due: 2024-12-24T10:51:32.708Z,
        stability: 0,
        difficulty: 0,
        elapsed_days: 0,
        last_elapsed_days: 0,
        scheduled_days: 0,
        review: 2024-12-24T10:51:32.708Z
      }
    },
    '4': {
      card: {
        due: 2025-01-10T10:51:32.708Z,
        stability: 15.69105,
        difficulty: 3.22450159,
        elapsed_days: 0,
        scheduled_days: 17,
        reps: 1,
        lapses: 0,
        state: 2,
        last_review: 2024-12-24T10:51:32.708Z
      },
      log: {
        rating: 4,
        state: 0,
        due: 2024-12-24T10:51:32.708Z,
        stability: 0,
        difficulty: 0,
        elapsed_days: 0,
        last_elapsed_days: 0,
        scheduled_days: 0,
        review: 2024-12-24T10:51:32.708Z
      }
    }
}
  1. Let the user select an option const chosenRatingOption = ratingOptions[Rating.Again].card
  2. Store the chosenRatingOption as the next card statistic and load it in step 1 for next card interaction instead of creating an empty card (empty statistic).
  3. You can also store the log for further processing const chosenRatingLog = ratingOptions[Rating.Again].log (but this is not necessary).

Example for structuring your data

// Your custom card
type CardDetails = {
  id: string;
  front: string;
  back: string;
}

// From ts-fsrs. Created via createEmptyCard(new Date());
type Card = {
  due: 2022-02-01T09:00:00.000Z,
  stability: 0,
  difficulty: 0,
  elapsed_days: 0,
  scheduled_days: 0,
  reps: 0,
  lapses: 0,
  state: 0,
  last_review: undefined
}

// Object in your database that will be updated after each review / rating. You don't store results separately (but you can store log objects; see blow).
type UserCardStatistic = {
  id: string;
  createdBy: Ref<User>;
  card: Ref<CardDetails>;
  statistic: Card; // personally I find the naming quite confusing as this is not the Card but the statistics for the card to calculate due date and other relevant params for upcoming interactions
}

Picking and rating processes

Process A: Getting a card to show it to the user (based on previous reviews, new cards you want to show per day etc.)
Process B: Let the user review a card and store the result to be used for step 1

Process A: Picking next card

  1. You would go through your database and select one based on your criteria (balance of New, Learning, Review, Relearning). More on this in my questions.
  2. You can then either a) just return the card and let the user define a rating (e.g. button option Rating.Again) and then send this rating to your data handler (and database) and process it like described below or you b) already present the user with the options from f.repeat(cardToBeRated, new Date()); so the user can directly pick an option and you can for example display further details above the button and send this option to create a new one or overwrite the existing one.

Process B: Review / rating process

  1. Incoming user rating (e.g. from frontend to backend). Could also be the statistic if you chose A.2.b.
  2. Check if the user has stored a statistic (UserCardStatistic) for a card previously on your database.
  3. If there is an existing statistic, use it cardToBeRated. Otherwise create a new statistic (Card) via const cardToBeRated = createEmptyCard(new Date());.
  4. Create the rating options via const ratingsForCard = f.repeat(cardToBeRated, new Date());. -> So the important thing to understand here is that all calculations are done based on this statistic object (rather then multiple database entries of reviews)
  5. Get the wanted rating from the rating options const rating = ratingsForCard[Rating.Again].card;. -> This is the newly created statistic for this card and the respective user. You can now store it to your database.
  6. Additionally you can also store the log for this rating const log = ratingsForCard[Rating.Again].log; to your database for "...analysis, undoing the review, and optimization (WIP)."

Anki learning system

Cards
Learning Cards: Recently introduced or forgotten cards that are in the process of being learned.
Mature Cards: Cards that have been successfully reviewed several times and have longer intervals between reviews.
New Cards: These are cards you've added but haven't seen before.

What I learned abou their approach

  1. Learning Cards
  • Learning cards are those that are still in the early "learning phase" and are scheduled to reappear at short intervals (e.g., 1 minute, 10 minutes, etc.).
  • A learning card will only appear in your queue if its next step's due time has been reached.
  • If the card is not yet due (e.g., 5 minutes away from its next scheduled step), it won't appear until the time arrives.
  1. Mature Cards
  • Review cards are cards that have entered the "review phase," meaning they have been learned and are now scheduled to appear based on their spaced repetition interval (e.g., 1 day, 3 days, 7 days, etc.).
  • Review cards are shown only when their due date has arrived or passed.
  • If a review card’s due date is in the future, it will not appear in the current session.
  1. New Cards are introduced last (up to the daily limit), but they are also interleaved into the session in step 1 and 2.

My questions:

  1. Did I understand the overall logic correctly?
  2. I'm assuming that—using an open cards pool—I create all statistics for each user and the card on demand whenever a first interaction takes place as the algo calculates the next option not based on previous logs but the Card object and its details? Or if a deck approach is used the users add specific cards to their deck and for each card on the deck a statistic for this user and card is directly created (so I don't have to think about state.New cards and "not opened / unknown" cards for this user).
  3. How can I enable the Anki version of the algo?
  4. You are using states of New, Learning, Review, Relearning. How are they related to 'New, Learning, Mature' from Anki?
  5. Do I basically only have to rely on picking state.Learning, state.Relearning, state.Review and state.New (each sorted by due date) and intersperse "not opened / unknown" cards (or use deck approach described in question 3 and don't have to think about "new / unknown" cards).

Thank you so much in advance!

@L-M-Sherlock
Copy link
Member

Mature Cards: Cards that have been successfully reviewed several times and have longer intervals between reviews.

In Anki, Mature cards and young cards both belong to review cards. For details about the card stats of Anki, please read https://docs.ankiweb.net/getting-started.html?highlight=young#card-states

2. I'm assuming that—using an open cards pool—I create all statistics for each user and the card on demand whenever a first interaction takes place as the algo calculates the next option not based on previous logs but the Card object and its details?

Sorry, I don't understand what you mean. In Anki, the Card object is created when the user add it or import it from a pre-made deck. I guess it's the deck approach as you mentioned.

What do you mean by the Anki version? FSRS is the algorithm used by Anki. Anki is using FSRS-5 now, and ts-fsrs also only support FSRS-5.

  • You are using states of New, Learning, Review, Relearning. How are they related to 'New, Learning, Mature' from Anki?

Anki is also using states of New, Learning, Review, Relearning. For detail, see: https://github.com/ankidroid/Anki-Android/wiki/Database-Structure#cards

@ishiko732
Copy link
Collaborator

But I realized that you only have a single object per card that basically defines the overall statistics for the interaction with this card per user over time.

2. the algo calculates the next option not based on previous logs but the Card object and its details?

Usually, one note corresponds to one or more cards. When you refer to the overall statistics of card interactions, are you specifically referring to the current card object information? FSRS calculates the next review time using the given time,rating and the card's DSR. Since the current card already incorporates historical DS information, there is no need to retain or use historical records for the scheduling calculation.
image

3. If there is an existing statistic, use it cardToBeRated. Otherwise create a new statistic (Card) via const cardToBeRated = createEmptyCard(new Date());.

You need a card_id; otherwise, you won’t be able to locate the specific card.
You can refer to the following content:

  1. Incoming user rating (e.g. from frontend to backend). Could also be the statistic if you chose A.2.b.

In a web service, never send the entire card to the backend, as frontend data cannot be trusted. Instead, send only the card_id, rating, and review timing information (e.g., now, review_duration), and perform the scheduling on the backend. However, if you're using Electron or Tauri, it's acceptable to send the entire card.

@benediktdertinger
Copy link
Author

Thank you so much @L-M-Sherlock and @ishiko732! This was really helpful.

@benediktdertinger benediktdertinger changed the title Overall Processes, Logic and Anki's Card Handling [Question] Overall Processes, Logic and Anki's Card Handling Jan 6, 2025
@benediktdertinger
Copy link
Author

What is your recommended setup regarding generatorParameters to have a setup as close to Anki as possible?

@ishiko732
Copy link
Collaborator

What is your recommended setup regarding generatorParameters to have a setup as close to Anki as possible?

Since the (re)learning steps in the current ts-fsrs are hard code and can only approximate Anki; To closely match Anki’s scheduling results, you should input values for w and request_retention that are consistent with those used in Anki.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants