// FSRS4Anki v2.0.3 Scheduler
// The latest version will be released on https://github.com/open-spaced-repetition/fsrs4anki
// Default parameters of FSRS4Anki for global
var f_s = [0.4112,1.5968];
var f_d = [1.0199,-0.4685,-1.7075,0.0714];
var s_w = [3.5014,-1.3554,-0.138,1.7324,2.9985,-0.0531,0.9896,1.3442];
// The above parameters can be optimized via FSRS4Anki optimizer.
// Custom parameters for user
let requestRetention = 0.8; // recommended setting: 0.8 ~ 0.9
let maximumInterval = 3650;
let easyBonus = 1.5;
let hardInterval = 1.2;
const ratings = {
"again": 1,
"hard": 2,
"good": 3,
"easy": 4
};
debugger;
// get the name of the card's deck
// need to add
{{Deck}}
to your card's front template
if (document.getElementById('deck') !== null) {
const deck_name = document.getElementById('deck').innerHTML;
// parameters for a specific deck
if (deck_name == "Main::English::Advanced Vocabulary") {
var f_s = [0.7249,0.8026];
var f_d = [1.0007,-0.6561,-1.2081,0.0112];
var s_w = [3.2511,-0.8703,-0.0277,1.5211,2.1651,-0.2846,0.5221,1.2443];
requestRetention = 0.80;
maximumInterval = 3650;
easyBonus = 1.5;
hardInterval = 1.2;
}
}
// auto-calculate intervalModifier
const intervalModifier = Math.log(requestRetention) / Math.log(0.9);
// For new cards
if (is_new()) {
init_states();
states.easy.normal.review.scheduledDays = constrain_interval(customData.easy.s);
// For learning/relearning cards
} else if (is_learning()) {
// Init states if the card didn't contain customData
if (is_empty()) {
init_states();
}
const good_interval = constrain_interval(customData.good.s);
const easy_interval = Math.max(constrain_interval(customData.easy.s * easyBonus), good_interval + 1);
if (states.good.normal?.review) {
states.good.normal.review.scheduledDays = good_interval;
}
if (states.easy.normal?.review) {
states.easy.normal.review.scheduledDays = easy_interval;
}
// For review cards
} else if (is_review()) {
// Convert the interval and factor to stability and difficulty if the card didn't contain customData
if (is_empty()) {
convert_states();
}
const interval = states.current.normal?.review.elapsedDays ? states.current.normal.review.elapsedDays : states.current.filtered.rescheduling.originalState.review.elapsedDays;
const last_d = customData.again.d;
const last_s = customData.again.s;
const retrievability = Math.exp(Math.log(0.9) * interval / last_s);
const lapses = states.again.normal?.relearning.review.lapses ? states.again.normal.relearning.review.lapses : states.again.filtered.rescheduling.originalState.relearning.review.lapses;
customData.again.d = next_difficulty(last_d, "again");
customData.again.s = next_forget_stability(customData.again.d, last_s, retrievability);
customData.hard.d = next_difficulty(last_d, "hard");
customData.hard.s = next_recall_stability(customData.hard.d, last_s, retrievability);
customData.good.d = next_difficulty(last_d, "good");
customData.good.s = next_recall_stability(customData.good.d, last_s, retrievability);
customData.easy.d = next_difficulty(last_d, "easy");
customData.easy.s = next_recall_stability(customData.easy.d, last_s, retrievability);
const hard_interval = constrain_interval(last_s * hardInterval);
const good_interval = Math.max(constrain_interval(customData.good.s), hard_interval + 1);
const easy_interval = Math.max(constrain_interval(customData.easy.s * easyBonus), good_interval + 1);
if (states.hard.normal?.review) {
states.hard.normal.review.scheduledDays = hard_interval;
}
if (states.good.normal?.review) {
states.good.normal.review.scheduledDays = good_interval;
}
if (states.easy.normal?.review) {
states.easy.normal.review.scheduledDays = easy_interval;
}
}
function constrain_difficulty(difficulty) {
return Math.min(Math.max(difficulty.toFixed(2), 1), 10);
}
function constrain_interval(interval) {
return Math.min(Math.max(Math.round(interval * intervalModifier), 1), maximumInterval);
}
function next_difficulty(d, rating) {
let next_d = d + f_d[2] * (ratings[rating] - 3);
return constrain_difficulty(mean_reversion(f_d[0] * (- f_d[1] + 1), next_d));
}
function mean_reversion(init, current) {
return f_d[3] * init + (1 - f_d[3]) * current;
}
function next_recall_stability(d, s, r) {
return +(s * (1 + Math.exp(s_w[0]) * Math.pow(d, s_w[1]) * Math.pow(s, s_w[2]) * (Math.exp((1 - r) * s_w[3]) - 1))).toFixed(2);
}
function next_forget_stability(d, s, r) {
return +(s_w[4] * Math.pow(d, s_w[5]) * Math.pow(s, s_w[6]) * Math.exp((1 - r) * s_w[7])).toFixed(2);
}
function init_states() {
customData.again.d = init_difficulty("again");
customData.again.s = init_stability("again");
customData.hard.d = init_difficulty("hard");
customData.hard.s = init_stability("hard");
customData.good.d = init_difficulty("good");
customData.good.s = init_stability("good");
customData.easy.d = init_difficulty("easy");
customData.easy.s = init_stability("easy");
}
function init_difficulty(rating) {
return +(f_d[0] * (f_d[1] * (ratings[rating] - 4) + 1)).toFixed(2);
}
function init_stability(rating) {
return +(f_s[0] * (f_s[1] * (ratings[rating] - 1) + 1)).toFixed(2);
}
function convert_states() {
const scheduledDays = states.current.normal ? states.current.normal.review.scheduledDays : states.current.filtered.rescheduling.originalState.review.scheduledDays;
const easeFactor = states.current.normal ? states.current.normal.review.easeFactor : states.current.filtered.rescheduling.originalState.review.easeFactor;
const old_s = +Math.max(scheduledDays / intervalModifier, 0.1).toFixed(2);
const old_d = constrain_difficulty(Math.pow((easeFactor - 1) / (Math.exp(s_w[0]) * Math.pow(old_s, s_w[2]) * (Math.exp((1 - requestRetention) * s_w[3]) - 1)), 1 / s_w[1]));
customData.again.d = old_d;
customData.again.s = old_s;
customData.hard.d = old_d;
customData.hard.s = old_s;
customData.good.d = old_d;
customData.good.s = old_s;
customData.easy.d = old_d;
customData.easy.s = old_s;
}
function is_new() {
if (states.current.normal?.new !== undefined) {
if (states.current.normal?.new !== null) {
return true;
}
}
if (states.current.filtered?.rescheduling?.originalState !== undefined) {
if (Object.hasOwn(states.current.filtered?.rescheduling?.originalState, 'new')) {
return true;
}
}
return false;
}
function is_learning() {
if (states.current.normal?.learning !== undefined) {
if (states.current.normal?.learning !== null) {
return true;
}
}
if (states.current.filtered?.rescheduling?.originalState !== undefined) {
if (Object.hasOwn(states.current.filtered?.rescheduling?.originalState, 'learning')) {
return true;
}
}
if (states.current.normal?.relearning !== undefined) {
if (states.current.normal?.relearning !== null) {
return true;
}
}
if (states.current.filtered?.rescheduling?.originalState !== undefined) {
if (Object.hasOwn(states.current.filtered?.rescheduling?.originalState, 'relearning')) {
return true;
}
}
return false;
}
function is_review() {
if (states.current.normal?.review !== undefined) {
if (states.current.normal?.review !== null) {
return true;
}
}
if (states.current.filtered?.rescheduling?.originalState !== undefined) {
if (Object.hasOwn(states.current.filtered?.rescheduling?.originalState, 'review')) {
return true;
}
}
return false;
}
function is_empty() {
return !customData.again.d | !customData.again.s | !customData.hard.d | !customData.hard.s | !customData.good.d | !customData.good.s | !customData.easy.d | !customData.easy.s;
}