ts-fsrs 5.2.0 → 5.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +1986 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +153 -152
- package/dist/index.mjs +1939 -1
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +2060 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +16 -19
package/dist/index.umd.js
CHANGED
|
@@ -1,2 +1,2061 @@
|
|
|
1
|
-
(function(o,c){typeof exports=="object"&&typeof module!="undefined"?c(exports):typeof define=="function"&&define.amd?define(["exports"],c):(o=typeof globalThis!="undefined"?globalThis:o||self,c(o.FSRS={}))})(this,function(o){"use strict";var c=(i=>(i[i.New=0]="New",i[i.Learning=1]="Learning",i[i.Review=2]="Review",i[i.Relearning=3]="Relearning",i))(c||{}),l=(i=>(i[i.Manual=0]="Manual",i[i.Again=1]="Again",i[i.Hard=2]="Hard",i[i.Good=3]="Good",i[i.Easy=4]="Easy",i))(l||{}),pt=Object.defineProperty,vt=Object.defineProperties,wt=Object.getOwnPropertyDescriptors,U=Object.getOwnPropertySymbols,bt=Object.prototype.hasOwnProperty,St=Object.prototype.propertyIsEnumerable,k=(i,t,e)=>t in i?pt(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,q=(i,t)=>{for(var e in t||(t={}))bt.call(t,e)&&k(i,e,t[e]);if(U)for(var e of U(t))St.call(t,e)&&k(i,e,t[e]);return i},Y=(i,t)=>vt(i,wt(t));class d{static card(t){return Y(q({},t),{state:d.state(t.state),due:d.time(t.due),last_review:t.last_review?d.time(t.last_review):void 0})}static rating(t){if(typeof t=="string"){const e=t.charAt(0).toUpperCase(),r=t.slice(1).toLowerCase(),a=l[`${e}${r}`];if(a===void 0)throw new Error(`Invalid rating:[${t}]`);return a}else if(typeof t=="number")return t;throw new Error(`Invalid rating:[${t}]`)}static state(t){if(typeof t=="string"){const e=t.charAt(0).toUpperCase(),r=t.slice(1).toLowerCase(),a=c[`${e}${r}`];if(a===void 0)throw new Error(`Invalid state:[${t}]`);return a}else if(typeof t=="number")return t;throw new Error(`Invalid state:[${t}]`)}static time(t){if(typeof t=="object"&&t instanceof Date)return t;if(typeof t=="string"){const e=Date.parse(t);if(isNaN(e))throw new Error(`Invalid date:[${t}]`);return new Date(e)}else if(typeof t=="number")return new Date(t);throw new Error(`Invalid date:[${t}]`)}static review_log(t){return Y(q({},t),{due:d.time(t.due),rating:d.rating(t.rating),state:d.state(t.state),review:d.time(t.review)})}}Date.prototype.scheduler=function(i,t){return p(this,i,t)},Date.prototype.diff=function(i,t){return b(this,i,t)},Date.prototype.format=function(){return W(this)},Date.prototype.dueFormat=function(i,t,e){return X(this,i,t,e)};function p(i,t,e){return new Date(e?d.time(i).getTime()+t*24*60*60*1e3:d.time(i).getTime()+t*60*1e3)}function b(i,t,e){if(!i||!t)throw new Error("Invalid date");const r=d.time(i).getTime()-d.time(t).getTime();let a=0;switch(e){case"days":a=Math.floor(r/(24*60*60*1e3));break;case"minutes":a=Math.floor(r/(60*1e3));break}return a}function W(i){const t=d.time(i),e=t.getFullYear(),r=t.getMonth()+1,a=t.getDate(),s=t.getHours(),n=t.getMinutes(),u=t.getSeconds();return`${e}-${M(r)}-${M(a)} ${M(s)}:${M(n)}:${M(u)}`}function M(i){return i<10?`0${i}`:`${i}`}const N=[60,60,24,31,12],O=["second","min","hour","day","month","year"];function X(i,t,e,r=O){i=d.time(i),t=d.time(t),r.length!==O.length&&(r=O);let a=i.getTime()-t.getTime(),s;for(a/=1e3,s=0;s<N.length&&!(a<N[s]);s++)a/=N[s];return`${Math.floor(a)}${e?r[s]:""}`}function xt(i){return d.time(i)}function Mt(i){return d.state(i)}function Et(i){return d.rating(i)}const B=Object.freeze([l.Again,l.Hard,l.Good,l.Easy]),Rt=[{start:2.5,end:7,factor:.15},{start:7,end:20,factor:.1},{start:20,end:1/0,factor:.05}];function V(i,t,e){let r=1;for(const n of Rt)r+=n.factor*Math.max(Math.min(i,n.end)-n.start,0);i=Math.min(i,e);let a=Math.max(2,Math.round(i-r));const s=Math.min(Math.round(i+r),e);return i>t&&(a=Math.max(a,t+1)),a=Math.min(a,s),{min_ivl:a,max_ivl:s}}function y(i,t,e){return Math.min(Math.max(i,t),e)}function J(i,t){const e=Date.UTC(i.getUTCFullYear(),i.getUTCMonth(),i.getUTCDate()),r=Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate());return Math.floor((r-e)/864e5)}const $t="5.2.0",K=.9,Q=36500,Z=!1,tt=!0,et=Object.freeze(["1m","10m"]),it=Object.freeze(["10m"]),Ft=`v${$t} using FSRS-6.0`,v=.001,At=36500,E=100,P=.5,rt=.1542,j=Object.freeze([.212,1.2931,2.3065,8.2956,6.4133,.8334,3.0194,.001,1.8722,.1666,.796,1.4835,.0614,.2629,1.6483,.6014,1.8729,.5425,.0912,.0658,rt]),at=2,st=i=>[[v,E],[v,E],[v,E],[v,E],[1,10],[.001,4],[.001,4],[.001,.75],[0,4.5],[0,.8],[.001,3.5],[.001,5],[.001,.25],[.001,.9],[0,4],[0,1],[1,6],[0,i],[0,i],[0,.8],[.1,.8]],$=(i,t)=>{let e=at;if(Math.max(0,t)>1){const r=-(Math.log(i[11])+Math.log(Math.pow(2,i[13])-1)+i[14]*.3)/t;e=y(+r.toFixed(8),.01,2)}return st(e).map(([r,a],s)=>y(i[s],r,a))},Dt=i=>{if(i.find(t=>!isFinite(t)&&!isNaN(t))!==void 0)throw Error(`Non-finite or NaN value in parameters ${i}`);if(![17,19,21].includes(i.length))throw Error(`Invalid parameter length: ${i.length}. Must be 17, 19 or 21 for FSRSv4, 5 and 6 respectively.`);return i},F=i=>{if(i===void 0)return[...j];switch(i.length){case 21:return[...i];case 19:return console.debug("[FSRS-6]auto fill w from 19 to 21 length"),[...i,0,P];case 17:{const t=[...i];return t[4]=+(t[5]*2+t[4]).toFixed(8),t[5]=+(Math.log(t[5]*3+1)/3).toFixed(8),t[6]=+(t[6]+.5).toFixed(8),console.debug("[FSRS-6]auto fill w from 17 to 21 length"),t.concat([0,0,0,P])}default:return console.warn("[FSRS]Invalid parameters length, using default parameters"),[...j]}},C=i=>{var t,e;const r=Array.isArray(i==null?void 0:i.learning_steps)?i.learning_steps:et,a=Array.isArray(i==null?void 0:i.relearning_steps)?i.relearning_steps:it,s=$(F(i==null?void 0:i.w),a.length);return{request_retention:(i==null?void 0:i.request_retention)||K,maximum_interval:(i==null?void 0:i.maximum_interval)||Q,w:s,enable_fuzz:(t=i==null?void 0:i.enable_fuzz)!=null?t:Z,enable_short_term:(e=i==null?void 0:i.enable_short_term)!=null?e:tt,learning_steps:r,relearning_steps:a}};function A(i,t){const e={due:i?d.time(i):new Date,stability:0,difficulty:0,elapsed_days:0,scheduled_days:0,reps:0,lapses:0,learning_steps:0,state:c.New,last_review:void 0};return t&&typeof t=="function"?t(e):e}var Lt=Object.defineProperty,It=(i,t,e)=>t in i?Lt(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,D=(i,t,e)=>It(i,typeof t!="symbol"?t+"":t,e);let Nt=class{constructor(t){D(this,"c"),D(this,"s0"),D(this,"s1"),D(this,"s2");const e=Ot();this.c=1,this.s0=e(" "),this.s1=e(" "),this.s2=e(" "),t==null&&(t=+new Date),this.s0-=e(t),this.s0<0&&(this.s0+=1),this.s1-=e(t),this.s1<0&&(this.s1+=1),this.s2-=e(t),this.s2<0&&(this.s2+=1)}next(){const t=2091639*this.s0+this.c*23283064365386963e-26;return this.s0=this.s1,this.s1=this.s2,this.s2=t-(this.c=t|0),this.s2}set state(t){this.c=t.c,this.s0=t.s0,this.s1=t.s1,this.s2=t.s2}get state(){return{c:this.c,s0:this.s0,s1:this.s1,s2:this.s2}}};function Ot(){let i=4022871197;return function(t){t=String(t);for(let e=0;e<t.length;e++){i+=t.charCodeAt(e);let r=.02519603282416938*i;i=r>>>0,r-=i,r*=i,i=r>>>0,r-=i,i+=r*4294967296}return(i>>>0)*23283064365386963e-26}}function Pt(i){const t=new Nt(i),e=()=>t.next();return e.int32=()=>t.next()*4294967296|0,e.double=()=>e()+(e()*2097152|0)*11102230246251565e-32,e.state=()=>t.state,e.importState=r=>(t.state=r,e),e}var jt=Object.defineProperty,Ct=(i,t,e)=>t in i?jt(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,L=(i,t,e)=>Ct(i,typeof t!="symbol"?t+"":t,e);const H=i=>{const t=typeof i=="number"?-i:-i[20],e=Math.exp(Math.pow(t,-1)*Math.log(.9))-1;return{decay:t,factor:+e.toFixed(8)}};function I(i,t,e){const{decay:r,factor:a}=H(i);return+Math.pow(1+a*t/e,r).toFixed(8)}class nt{constructor(t){L(this,"param"),L(this,"intervalModifier"),L(this,"_seed"),L(this,"forgetting_curve"),this.param=new Proxy(C(t),this.params_handler_proxy()),this.intervalModifier=this.calculate_interval_modifier(this.param.request_retention),this.forgetting_curve=I.bind(this,this.param.w)}get interval_modifier(){return this.intervalModifier}set seed(t){this._seed=t}calculate_interval_modifier(t){if(t<=0||t>1)throw new Error("Requested retention rate should be in the range (0,1]");const{decay:e,factor:r}=H(this.param.w);return+((Math.pow(t,1/e)-1)/r).toFixed(8)}get parameters(){return this.param}set parameters(t){this.update_parameters(t)}params_handler_proxy(){const t=this;return{set:function(e,r,a){return r==="request_retention"&&Number.isFinite(a)?t.intervalModifier=t.calculate_interval_modifier(Number(a)):r==="w"&&(a=$(F(a),e.relearning_steps.length),t.forgetting_curve=I.bind(this,a),t.intervalModifier=t.calculate_interval_modifier(Number(e.request_retention))),Reflect.set(e,r,a),!0}}}update_parameters(t){const e=C(t);for(const r in e)if(r in this.param){const a=r;this.param[a]=e[a]}}init_stability(t){return Math.max(this.param.w[t-1],.1)}init_difficulty(t){return+(this.param.w[4]-Math.exp((t-1)*this.param.w[5])+1).toFixed(8)}apply_fuzz(t,e){if(!this.param.enable_fuzz||t<2.5)return Math.round(t);const r=Pt(this._seed)(),{min_ivl:a,max_ivl:s}=V(t,e,this.param.maximum_interval);return Math.floor(r*(s-a+1)+a)}next_interval(t,e){const r=Math.min(Math.max(1,Math.round(t*this.intervalModifier)),this.param.maximum_interval);return this.apply_fuzz(r,e)}linear_damping(t,e){return+(t*(10-e)/9).toFixed(8)}next_difficulty(t,e){const r=-this.param.w[6]*(e-3),a=t+this.linear_damping(r,t);return y(this.mean_reversion(this.init_difficulty(l.Easy),a),1,10)}mean_reversion(t,e){return+(this.param.w[7]*t+(1-this.param.w[7])*e).toFixed(8)}next_recall_stability(t,e,r,a){const s=l.Hard===a?this.param.w[15]:1,n=l.Easy===a?this.param.w[16]:1;return+y(e*(1+Math.exp(this.param.w[8])*(11-t)*Math.pow(e,-this.param.w[9])*(Math.exp((1-r)*this.param.w[10])-1)*s*n),v,36500).toFixed(8)}next_forget_stability(t,e,r){return+y(this.param.w[11]*Math.pow(t,-this.param.w[12])*(Math.pow(e+1,this.param.w[13])-1)*Math.exp((1-r)*this.param.w[14]),v,36500).toFixed(8)}next_short_term_stability(t,e){const r=Math.pow(t,-this.param.w[19])*Math.exp(this.param.w[17]*(e-3+this.param.w[18])),a=e>=3?Math.max(r,1):r;return+y(t*a,v,36500).toFixed(8)}next_state(t,e,r){const{difficulty:a,stability:s}=t!=null?t:{difficulty:0,stability:0};if(e<0)throw new Error(`Invalid delta_t "${e}"`);if(r<0||r>4)throw new Error(`Invalid grade "${r}"`);if(a===0&&s===0)return{difficulty:y(this.init_difficulty(r),1,10),stability:this.init_stability(r)};if(r===0)return{difficulty:a,stability:s};if(a<1||s<v)throw new Error(`Invalid memory state { difficulty: ${a}, stability: ${s} }`);const n=this.forgetting_curve(e,s),u=this.next_recall_stability(a,s,n,r),h=this.next_forget_stability(a,s,n),f=this.next_short_term_stability(s,r);let _=u;if(r===1){let[m,w]=[0,0];this.param.enable_short_term&&(m=this.param.w[17],w=this.param.w[18]);const g=s/Math.exp(m*w);_=y(+g.toFixed(8),v,h)}return e===0&&this.param.enable_short_term&&(_=f),{difficulty:this.next_difficulty(a,r),stability:_}}}function lt(){const i=this.review_time.getTime(),t=this.current.reps,e=this.current.difficulty*this.current.stability;return`${i}_${t}_${e}`}function Ht(i){return function(){var t;const e=(t=Reflect.get(this.current,i))!=null?t:0,r=this.current.reps;return String(e+r||0)}}const ut=i=>{const t=i.slice(-1),e=parseInt(i.slice(0,-1),10);if(isNaN(e)||!Number.isFinite(e)||e<0)throw new Error(`Invalid step value: ${i}`);switch(t){case"m":return e;case"h":return e*60;case"d":return e*1440;default:throw new Error(`Invalid step unit: ${i}, expected m/h/d`)}},ot=(i,t,e)=>{const r=t===c.Relearning||t===c.Review?i.relearning_steps:i.learning_steps,a=r.length;if(a===0||e>=a)return{};const s=r[0],n=ut,u=()=>n(s),h=()=>{if(a===1)return Math.round(n(s)*1.5);const g=r[1];return Math.round((n(s)+n(g))/2)},f=g=>g<0||g>=a?null:r[g],_=g=>n(g),m={},w=f(Math.max(0,e));if(t===c.Review)return m[l.Again]={scheduled_minutes:n(w),next_step:0},m;{m[l.Again]={scheduled_minutes:u(),next_step:0},m[l.Hard]={scheduled_minutes:h(),next_step:e};const g=f(e+1);if(g){const x=_(g);x&&(m[l.Good]={scheduled_minutes:Math.round(x),next_step:e+1})}}return m};var R=(i=>(i.SCHEDULER="Scheduler",i.LEARNING_STEPS="LearningSteps",i.SEED="Seed",i))(R||{}),Tt=Object.defineProperty,Gt=(i,t,e)=>t in i?Tt(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,S=(i,t,e)=>Gt(i,typeof t!="symbol"?t+"":t,e);class T{constructor(t,e,r,a){S(this,"last"),S(this,"current"),S(this,"review_time"),S(this,"next",new Map),S(this,"algorithm"),S(this,"strategies"),S(this,"elapsed_days",0),this.algorithm=r,this.last=d.card(t),this.current=d.card(t),this.review_time=d.time(e),this.strategies=a,this.init()}checkGrade(t){if(!Number.isFinite(t)||t<0||t>4)throw new Error(`Invalid grade "${t}",expected 1-4`)}init(){const{state:t,last_review:e}=this.current;let r=0;t!==c.New&&e&&(r=J(e,this.review_time)),this.current.last_review=this.review_time,this.elapsed_days=r,this.current.elapsed_days=r,this.current.reps+=1;let a=lt;if(this.strategies){const s=this.strategies.get(R.SEED);s&&(a=s)}this.algorithm.seed=a.call(this)}preview(){return{[l.Again]:this.review(l.Again),[l.Hard]:this.review(l.Hard),[l.Good]:this.review(l.Good),[l.Easy]:this.review(l.Easy),[Symbol.iterator]:this.previewIterator.bind(this)}}*previewIterator(){for(const t of B)yield this.review(t)}review(t){const{state:e}=this.last;let r;switch(this.checkGrade(t),e){case c.New:r=this.newState(t);break;case c.Learning:case c.Relearning:r=this.learningState(t);break;case c.Review:r=this.reviewState(t);break}return r}buildLog(t){const{last_review:e,due:r,elapsed_days:a}=this.last;return{rating:t,state:this.current.state,due:e||r,stability:this.current.stability,difficulty:this.current.difficulty,elapsed_days:this.elapsed_days,last_elapsed_days:a,scheduled_days:this.current.scheduled_days,learning_steps:this.current.learning_steps,review:this.review_time}}}var zt=Object.defineProperty,Ut=(i,t,e)=>t in i?zt(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,kt=(i,t,e)=>Ut(i,t+"",e);class dt extends T{constructor(t,e,r,a){super(t,e,r,a),kt(this,"learningStepsStrategy");let s=ot;if(this.strategies){const n=this.strategies.get(R.LEARNING_STEPS);n&&(s=n)}this.learningStepsStrategy=s}getLearningInfo(t,e){var r,a,s,n;const u=this.algorithm.parameters;t.learning_steps=t.learning_steps||0;const h=this.learningStepsStrategy(u,t.state,this.current.state===c.Learning?t.learning_steps+1:t.learning_steps),f=Math.max(0,(a=(r=h[e])==null?void 0:r.scheduled_minutes)!=null?a:0),_=Math.max(0,(n=(s=h[e])==null?void 0:s.next_step)!=null?n:0);return{scheduled_minutes:f,next_steps:_}}applyLearningSteps(t,e,r){const{scheduled_minutes:a,next_steps:s}=this.getLearningInfo(this.current,e);if(a>0&&a<1440)t.learning_steps=s,t.scheduled_days=0,t.state=r,t.due=p(this.review_time,Math.round(a),!1);else if(t.state=c.Review,a>=1440)t.learning_steps=s,t.due=p(this.review_time,Math.round(a),!1),t.scheduled_days=Math.floor(a/1440);else{t.learning_steps=0;const n=this.algorithm.next_interval(t.stability,this.elapsed_days);t.scheduled_days=n,t.due=p(this.review_time,n,!0)}}newState(t){const e=this.next.get(t);if(e)return e;const r=d.card(this.current);r.difficulty=y(this.algorithm.init_difficulty(t),1,10),r.stability=this.algorithm.init_stability(t),this.applyLearningSteps(r,t,c.Learning);const a={card:r,log:this.buildLog(t)};return this.next.set(t,a),a}learningState(t){const e=this.next.get(t);if(e)return e;const{state:r,difficulty:a,stability:s}=this.last,n=d.card(this.current);n.difficulty=this.algorithm.next_difficulty(a,t),n.stability=this.algorithm.next_short_term_stability(s,t),this.applyLearningSteps(n,t,r);const u={card:n,log:this.buildLog(t)};return this.next.set(t,u),u}reviewState(t){const e=this.next.get(t);if(e)return e;const r=this.elapsed_days,{difficulty:a,stability:s}=this.last,n=this.algorithm.forgetting_curve(r,s),u=d.card(this.current),h=d.card(this.current),f=d.card(this.current),_=d.card(this.current);this.next_ds(u,h,f,_,a,s,n),this.next_interval(h,f,_,r),this.next_state(h,f,_),this.applyLearningSteps(u,l.Again,c.Relearning),u.lapses+=1;const m={card:u,log:this.buildLog(l.Again)},w={card:h,log:super.buildLog(l.Hard)},g={card:f,log:super.buildLog(l.Good)},x={card:_,log:super.buildLog(l.Easy)};return this.next.set(l.Again,m),this.next.set(l.Hard,w),this.next.set(l.Good,g),this.next.set(l.Easy,x),this.next.get(t)}next_ds(t,e,r,a,s,n,u){t.difficulty=this.algorithm.next_difficulty(s,l.Again);const h=n/Math.exp(this.algorithm.parameters.w[17]*this.algorithm.parameters.w[18]),f=this.algorithm.next_forget_stability(s,n,u);t.stability=y(+h.toFixed(8),v,f),e.difficulty=this.algorithm.next_difficulty(s,l.Hard),e.stability=this.algorithm.next_recall_stability(s,n,u,l.Hard),r.difficulty=this.algorithm.next_difficulty(s,l.Good),r.stability=this.algorithm.next_recall_stability(s,n,u,l.Good),a.difficulty=this.algorithm.next_difficulty(s,l.Easy),a.stability=this.algorithm.next_recall_stability(s,n,u,l.Easy)}next_interval(t,e,r,a){let s,n;s=this.algorithm.next_interval(t.stability,a),n=this.algorithm.next_interval(e.stability,a),s=Math.min(s,n),n=Math.max(n,s+1);const u=Math.max(this.algorithm.next_interval(r.stability,a),n+1);t.scheduled_days=s,t.due=p(this.review_time,s,!0),e.scheduled_days=n,e.due=p(this.review_time,n,!0),r.scheduled_days=u,r.due=p(this.review_time,u,!0)}next_state(t,e,r){t.state=c.Review,t.learning_steps=0,e.state=c.Review,e.learning_steps=0,r.state=c.Review,r.learning_steps=0}}let ht=class extends T{newState(t){const e=this.next.get(t);if(e)return e;this.current.scheduled_days=0,this.current.elapsed_days=0;const r=d.card(this.current),a=d.card(this.current),s=d.card(this.current),n=d.card(this.current);return this.init_ds(r,a,s,n),this.next_interval(r,a,s,n,0),this.next_state(r,a,s,n),this.update_next(r,a,s,n),this.next.get(t)}init_ds(t,e,r,a){t.difficulty=y(this.algorithm.init_difficulty(l.Again),1,10),t.stability=this.algorithm.init_stability(l.Again),e.difficulty=y(this.algorithm.init_difficulty(l.Hard),1,10),e.stability=this.algorithm.init_stability(l.Hard),r.difficulty=y(this.algorithm.init_difficulty(l.Good),1,10),r.stability=this.algorithm.init_stability(l.Good),a.difficulty=y(this.algorithm.init_difficulty(l.Easy),1,10),a.stability=this.algorithm.init_stability(l.Easy)}learningState(t){return this.reviewState(t)}reviewState(t){const e=this.next.get(t);if(e)return e;const r=this.elapsed_days,{difficulty:a,stability:s}=this.last,n=this.algorithm.forgetting_curve(r,s),u=d.card(this.current),h=d.card(this.current),f=d.card(this.current),_=d.card(this.current);return this.next_ds(u,h,f,_,a,s,n),this.next_interval(u,h,f,_,r),this.next_state(u,h,f,_),u.lapses+=1,this.update_next(u,h,f,_),this.next.get(t)}next_ds(t,e,r,a,s,n,u){t.difficulty=this.algorithm.next_difficulty(s,l.Again);const h=this.algorithm.next_forget_stability(s,n,u);t.stability=y(n,v,h),e.difficulty=this.algorithm.next_difficulty(s,l.Hard),e.stability=this.algorithm.next_recall_stability(s,n,u,l.Hard),r.difficulty=this.algorithm.next_difficulty(s,l.Good),r.stability=this.algorithm.next_recall_stability(s,n,u,l.Good),a.difficulty=this.algorithm.next_difficulty(s,l.Easy),a.stability=this.algorithm.next_recall_stability(s,n,u,l.Easy)}next_interval(t,e,r,a,s){let n,u,h,f;n=this.algorithm.next_interval(t.stability,s),u=this.algorithm.next_interval(e.stability,s),h=this.algorithm.next_interval(r.stability,s),f=this.algorithm.next_interval(a.stability,s),n=Math.min(n,u),u=Math.max(u,n+1),h=Math.max(h,u+1),f=Math.max(f,h+1),t.scheduled_days=n,t.due=p(this.review_time,n,!0),e.scheduled_days=u,e.due=p(this.review_time,u,!0),r.scheduled_days=h,r.due=p(this.review_time,h,!0),a.scheduled_days=f,a.due=p(this.review_time,f,!0)}next_state(t,e,r,a){t.state=c.Review,t.learning_steps=0,e.state=c.Review,e.learning_steps=0,r.state=c.Review,r.learning_steps=0,a.state=c.Review,a.learning_steps=0}update_next(t,e,r,a){const s={card:t,log:this.buildLog(l.Again)},n={card:e,log:super.buildLog(l.Hard)},u={card:r,log:super.buildLog(l.Good)},h={card:a,log:super.buildLog(l.Easy)};this.next.set(l.Again,s),this.next.set(l.Hard,n),this.next.set(l.Good,u),this.next.set(l.Easy,h)}};var qt=Object.defineProperty,Yt=Object.defineProperties,Wt=Object.getOwnPropertyDescriptors,ct=Object.getOwnPropertySymbols,Xt=Object.prototype.hasOwnProperty,Bt=Object.prototype.propertyIsEnumerable,G=(i,t,e)=>t in i?qt(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,Vt=(i,t)=>{for(var e in t||(t={}))Xt.call(t,e)&&G(i,e,t[e]);if(ct)for(var e of ct(t))Bt.call(t,e)&&G(i,e,t[e]);return i},Jt=(i,t)=>Yt(i,Wt(t)),Kt=(i,t,e)=>G(i,t+"",e);class Qt{constructor(t){Kt(this,"fsrs"),this.fsrs=t}replay(t,e,r){return this.fsrs.next(t,e,r)}handleManualRating(t,e,r,a,s,n,u){if(typeof e=="undefined")throw new Error("reschedule: state is required for manual rating");let h,f;if(e===c.New)h={rating:l.Manual,state:e,due:u!=null?u:r,stability:t.stability,difficulty:t.difficulty,elapsed_days:a,last_elapsed_days:t.elapsed_days,scheduled_days:t.scheduled_days,learning_steps:t.learning_steps,review:r},f=A(r),f.last_review=r;else{if(typeof u=="undefined")throw new Error("reschedule: due is required for manual rating");const _=b(u,r,"days");h={rating:l.Manual,state:t.state,due:t.last_review||t.due,stability:t.stability,difficulty:t.difficulty,elapsed_days:a,last_elapsed_days:t.elapsed_days,scheduled_days:t.scheduled_days,learning_steps:t.learning_steps,review:r},f=Jt(Vt({},t),{state:e,due:u,last_review:r,stability:s||t.stability,difficulty:n||t.difficulty,elapsed_days:a,scheduled_days:_,reps:t.reps+1})}return{card:f,log:h}}reschedule(t,e){const r=[];let a=A(t.due);for(const s of e){let n;if(s.review=d.time(s.review),s.rating===l.Manual){let u=0;a.state!==c.New&&a.last_review&&(u=b(s.review,a.last_review,"days")),n=this.handleManualRating(a,s.state,s.review,u,s.stability,s.difficulty,s.due?d.time(s.due):void 0)}else n=this.replay(a,s.review,s.rating);r.push(n),a=n.card}return r}calculateManualRecord(t,e,r,a){if(!r)return null;const{card:s,log:n}=r,u=d.card(t);return u.due.getTime()===s.due.getTime()?null:(u.scheduled_days=b(s.due,u.due,"days"),this.handleManualRating(u,s.state,d.time(e),n.elapsed_days,a?s.stability:void 0,a?s.difficulty:void 0,s.due))}}var Zt=Object.defineProperty,te=Object.defineProperties,ee=Object.getOwnPropertyDescriptors,ft=Object.getOwnPropertySymbols,ie=Object.prototype.hasOwnProperty,re=Object.prototype.propertyIsEnumerable,z=(i,t,e)=>t in i?Zt(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,_t=(i,t)=>{for(var e in t||(t={}))ie.call(t,e)&&z(i,e,t[e]);if(ft)for(var e of ft(t))re.call(t,e)&&z(i,e,t[e]);return i},gt=(i,t)=>te(i,ee(t)),yt=(i,t,e)=>z(i,typeof t!="symbol"?t+"":t,e);class mt extends nt{constructor(t){super(t),yt(this,"strategyHandler",new Map),yt(this,"Scheduler");const{enable_short_term:e}=this.parameters;this.Scheduler=e?dt:ht}params_handler_proxy(){const t=this;return{set:function(e,r,a){return r==="request_retention"&&Number.isFinite(a)?t.intervalModifier=t.calculate_interval_modifier(Number(a)):r==="enable_short_term"?t.Scheduler=a===!0?dt:ht:r==="w"&&(a=$(F(a),e.relearning_steps.length),t.forgetting_curve=I.bind(this,a),t.intervalModifier=t.calculate_interval_modifier(Number(e.request_retention))),Reflect.set(e,r,a),!0}}}useStrategy(t,e){return this.strategyHandler.set(t,e),this}clearStrategy(t){return t?this.strategyHandler.delete(t):this.strategyHandler.clear(),this}getScheduler(t,e){const r=this.strategyHandler.get(R.SCHEDULER)||this.Scheduler;return new r(t,e,this,this.strategyHandler)}repeat(t,e,r){const a=this.getScheduler(t,e).preview();return r&&typeof r=="function"?r(a):a}next(t,e,r,a){const s=this.getScheduler(t,e),n=d.rating(r);if(n===l.Manual)throw new Error("Cannot review a manual rating");const u=s.review(n);return a&&typeof a=="function"?a(u):u}get_retrievability(t,e,r=!0){const a=d.card(t);e=e?d.time(e):new Date;const s=a.state!==c.New?Math.max(b(e,a.last_review,"days"),0):0,n=a.state!==c.New?this.forgetting_curve(s,+a.stability.toFixed(8)):0;return r?`${(n*100).toFixed(2)}%`:n}rollback(t,e,r){const a=d.card(t),s=d.review_log(e);if(s.rating===l.Manual)throw new Error("Cannot rollback a manual rating");let n,u,h;switch(s.state){case c.New:n=s.due,u=void 0,h=0;break;case c.Learning:case c.Relearning:case c.Review:n=s.review,u=s.due,h=a.lapses-(s.rating===l.Again&&s.state===c.Review?1:0);break}const f=gt(_t({},a),{due:n,stability:s.stability,difficulty:s.difficulty,elapsed_days:s.last_elapsed_days,scheduled_days:s.scheduled_days,reps:Math.max(0,a.reps-1),lapses:Math.max(0,h),learning_steps:s.learning_steps,state:s.state,last_review:u});return r&&typeof r=="function"?r(f):f}forget(t,e,r=!1,a){const s=d.card(t);e=d.time(e);const n=s.state===c.New?0:b(e,s.due,"days"),u={rating:l.Manual,state:s.state,due:s.due,stability:s.stability,difficulty:s.difficulty,elapsed_days:0,last_elapsed_days:s.elapsed_days,scheduled_days:n,learning_steps:s.learning_steps,review:e},h={card:gt(_t({},s),{due:e,stability:0,difficulty:0,elapsed_days:0,scheduled_days:0,reps:r?0:s.reps,lapses:r?0:s.lapses,learning_steps:0,state:c.New,last_review:s.last_review}),log:u};return a&&typeof a=="function"?a(h):h}reschedule(t,e=[],r={}){const{recordLogHandler:a,reviewsOrderBy:s,skipManual:n=!0,now:u=new Date,update_memory_state:h=!1}=r;s&&typeof s=="function"&&e.sort(s),n&&(e=e.filter(x=>x.rating!==l.Manual));const f=new Qt(this),_=f.reschedule(r.first_card||A(),e),m=_.length,w=d.card(t),g=f.calculateManualRecord(w,u,m?_[m-1]:void 0,h);return a&&typeof a=="function"?{collections:_.map(a),reschedule_item:g?a(g):null}:{collections:_,reschedule_item:g}}}const ae=i=>new mt(i||{});o.AbstractScheduler=T,o.BasicLearningStepsStrategy=ot,o.CLAMP_PARAMETERS=st,o.ConvertStepUnitToMinutes=ut,o.DefaultInitSeedStrategy=lt,o.FSRS=mt,o.FSRS5_DEFAULT_DECAY=P,o.FSRS6_DEFAULT_DECAY=rt,o.FSRSAlgorithm=nt,o.FSRSVersion=Ft,o.GenSeedStrategyWithCardId=Ht,o.Grades=B,o.INIT_S_MAX=E,o.Rating=l,o.S_MAX=At,o.S_MIN=v,o.State=c,o.StrategyMode=R,o.TypeConvert=d,o.W17_W18_Ceiling=at,o.checkParameters=Dt,o.clamp=y,o.clipParameters=$,o.computeDecayFactor=H,o.createEmptyCard=A,o.dateDiffInDays=J,o.date_diff=b,o.date_scheduler=p,o.default_enable_fuzz=Z,o.default_enable_short_term=tt,o.default_learning_steps=et,o.default_maximum_interval=Q,o.default_relearning_steps=it,o.default_request_retention=K,o.default_w=j,o.fixDate=xt,o.fixRating=Et,o.fixState=Mt,o.forgetting_curve=I,o.formatDate=W,o.fsrs=ae,o.generatorParameters=C,o.get_fuzz_range=V,o.migrateParameters=F,o.show_diff_message=X});
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.FSRS = {}));
|
|
5
|
+
})(this, (function (exports) { 'use strict';
|
|
6
|
+
|
|
7
|
+
var State = /* @__PURE__ */ ((State2) => {
|
|
8
|
+
State2[State2["New"] = 0] = "New";
|
|
9
|
+
State2[State2["Learning"] = 1] = "Learning";
|
|
10
|
+
State2[State2["Review"] = 2] = "Review";
|
|
11
|
+
State2[State2["Relearning"] = 3] = "Relearning";
|
|
12
|
+
return State2;
|
|
13
|
+
})(State || {});
|
|
14
|
+
var Rating = /* @__PURE__ */ ((Rating2) => {
|
|
15
|
+
Rating2[Rating2["Manual"] = 0] = "Manual";
|
|
16
|
+
Rating2[Rating2["Again"] = 1] = "Again";
|
|
17
|
+
Rating2[Rating2["Hard"] = 2] = "Hard";
|
|
18
|
+
Rating2[Rating2["Good"] = 3] = "Good";
|
|
19
|
+
Rating2[Rating2["Easy"] = 4] = "Easy";
|
|
20
|
+
return Rating2;
|
|
21
|
+
})(Rating || {});
|
|
22
|
+
|
|
23
|
+
var __defProp$6 = Object.defineProperty;
|
|
24
|
+
var __defProps$2 = Object.defineProperties;
|
|
25
|
+
var __getOwnPropDescs$2 = Object.getOwnPropertyDescriptors;
|
|
26
|
+
var __getOwnPropSymbols$2 = Object.getOwnPropertySymbols;
|
|
27
|
+
var __hasOwnProp$2 = Object.prototype.hasOwnProperty;
|
|
28
|
+
var __propIsEnum$2 = Object.prototype.propertyIsEnumerable;
|
|
29
|
+
var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
30
|
+
var __spreadValues$2 = (a, b) => {
|
|
31
|
+
for (var prop in b || (b = {}))
|
|
32
|
+
if (__hasOwnProp$2.call(b, prop))
|
|
33
|
+
__defNormalProp$6(a, prop, b[prop]);
|
|
34
|
+
if (__getOwnPropSymbols$2)
|
|
35
|
+
for (var prop of __getOwnPropSymbols$2(b)) {
|
|
36
|
+
if (__propIsEnum$2.call(b, prop))
|
|
37
|
+
__defNormalProp$6(a, prop, b[prop]);
|
|
38
|
+
}
|
|
39
|
+
return a;
|
|
40
|
+
};
|
|
41
|
+
var __spreadProps$2 = (a, b) => __defProps$2(a, __getOwnPropDescs$2(b));
|
|
42
|
+
class TypeConvert {
|
|
43
|
+
static card(card) {
|
|
44
|
+
return __spreadProps$2(__spreadValues$2({}, card), {
|
|
45
|
+
state: TypeConvert.state(card.state),
|
|
46
|
+
due: TypeConvert.time(card.due),
|
|
47
|
+
last_review: card.last_review ? TypeConvert.time(card.last_review) : void 0
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
static rating(value) {
|
|
51
|
+
if (typeof value === "string") {
|
|
52
|
+
const firstLetter = value.charAt(0).toUpperCase();
|
|
53
|
+
const restOfString = value.slice(1).toLowerCase();
|
|
54
|
+
const ret = Rating[`${firstLetter}${restOfString}`];
|
|
55
|
+
if (ret === void 0) {
|
|
56
|
+
throw new Error(`Invalid rating:[${value}]`);
|
|
57
|
+
}
|
|
58
|
+
return ret;
|
|
59
|
+
} else if (typeof value === "number") {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`Invalid rating:[${value}]`);
|
|
63
|
+
}
|
|
64
|
+
static state(value) {
|
|
65
|
+
if (typeof value === "string") {
|
|
66
|
+
const firstLetter = value.charAt(0).toUpperCase();
|
|
67
|
+
const restOfString = value.slice(1).toLowerCase();
|
|
68
|
+
const ret = State[`${firstLetter}${restOfString}`];
|
|
69
|
+
if (ret === void 0) {
|
|
70
|
+
throw new Error(`Invalid state:[${value}]`);
|
|
71
|
+
}
|
|
72
|
+
return ret;
|
|
73
|
+
} else if (typeof value === "number") {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`Invalid state:[${value}]`);
|
|
77
|
+
}
|
|
78
|
+
static time(value) {
|
|
79
|
+
if (typeof value === "object" && value instanceof Date) {
|
|
80
|
+
return value;
|
|
81
|
+
} else if (typeof value === "string") {
|
|
82
|
+
const timestamp = Date.parse(value);
|
|
83
|
+
if (!Number.isNaN(timestamp)) {
|
|
84
|
+
return new Date(timestamp);
|
|
85
|
+
} else {
|
|
86
|
+
throw new Error(`Invalid date:[${value}]`);
|
|
87
|
+
}
|
|
88
|
+
} else if (typeof value === "number") {
|
|
89
|
+
return new Date(value);
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`Invalid date:[${value}]`);
|
|
92
|
+
}
|
|
93
|
+
static review_log(log) {
|
|
94
|
+
return __spreadProps$2(__spreadValues$2({}, log), {
|
|
95
|
+
due: TypeConvert.time(log.due),
|
|
96
|
+
rating: TypeConvert.rating(log.rating),
|
|
97
|
+
state: TypeConvert.state(log.state),
|
|
98
|
+
review: TypeConvert.time(log.review)
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Date.prototype.scheduler = function(t, isDay) {
|
|
104
|
+
return date_scheduler(this, t, isDay);
|
|
105
|
+
};
|
|
106
|
+
Date.prototype.diff = function(pre, unit) {
|
|
107
|
+
return date_diff(this, pre, unit);
|
|
108
|
+
};
|
|
109
|
+
Date.prototype.format = function() {
|
|
110
|
+
return formatDate(this);
|
|
111
|
+
};
|
|
112
|
+
Date.prototype.dueFormat = function(last_review, unit, timeUnit) {
|
|
113
|
+
return show_diff_message(this, last_review, unit, timeUnit);
|
|
114
|
+
};
|
|
115
|
+
function date_scheduler(now, t, isDay) {
|
|
116
|
+
return new Date(
|
|
117
|
+
isDay ? TypeConvert.time(now).getTime() + t * 24 * 60 * 60 * 1e3 : TypeConvert.time(now).getTime() + t * 60 * 1e3
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
function date_diff(now, pre, unit) {
|
|
121
|
+
if (!now || !pre) {
|
|
122
|
+
throw new Error("Invalid date");
|
|
123
|
+
}
|
|
124
|
+
const diff = TypeConvert.time(now).getTime() - TypeConvert.time(pre).getTime();
|
|
125
|
+
let r = 0;
|
|
126
|
+
switch (unit) {
|
|
127
|
+
case "days":
|
|
128
|
+
r = Math.floor(diff / (24 * 60 * 60 * 1e3));
|
|
129
|
+
break;
|
|
130
|
+
case "minutes":
|
|
131
|
+
r = Math.floor(diff / (60 * 1e3));
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
return r;
|
|
135
|
+
}
|
|
136
|
+
function formatDate(dateInput) {
|
|
137
|
+
const date = TypeConvert.time(dateInput);
|
|
138
|
+
const year = date.getFullYear();
|
|
139
|
+
const month = date.getMonth() + 1;
|
|
140
|
+
const day = date.getDate();
|
|
141
|
+
const hours = date.getHours();
|
|
142
|
+
const minutes = date.getMinutes();
|
|
143
|
+
const seconds = date.getSeconds();
|
|
144
|
+
return `${year}-${padZero(month)}-${padZero(day)} ${padZero(hours)}:${padZero(
|
|
145
|
+
minutes
|
|
146
|
+
)}:${padZero(seconds)}`;
|
|
147
|
+
}
|
|
148
|
+
function padZero(num) {
|
|
149
|
+
return num < 10 ? `0${num}` : `${num}`;
|
|
150
|
+
}
|
|
151
|
+
const TIMEUNIT = [60, 60, 24, 31, 12];
|
|
152
|
+
const TIMEUNITFORMAT = ["second", "min", "hour", "day", "month", "year"];
|
|
153
|
+
function show_diff_message(due, last_review, unit, timeUnit = TIMEUNITFORMAT) {
|
|
154
|
+
due = TypeConvert.time(due);
|
|
155
|
+
last_review = TypeConvert.time(last_review);
|
|
156
|
+
if (timeUnit.length !== TIMEUNITFORMAT.length) {
|
|
157
|
+
timeUnit = TIMEUNITFORMAT;
|
|
158
|
+
}
|
|
159
|
+
let diff = due.getTime() - last_review.getTime();
|
|
160
|
+
let i = 0;
|
|
161
|
+
diff /= 1e3;
|
|
162
|
+
for (i = 0; i < TIMEUNIT.length; i++) {
|
|
163
|
+
if (diff < TIMEUNIT[i]) {
|
|
164
|
+
break;
|
|
165
|
+
} else {
|
|
166
|
+
diff /= TIMEUNIT[i];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return `${Math.floor(diff)}${unit ? timeUnit[i] : ""}`;
|
|
170
|
+
}
|
|
171
|
+
function fixDate(value) {
|
|
172
|
+
return TypeConvert.time(value);
|
|
173
|
+
}
|
|
174
|
+
function fixState(value) {
|
|
175
|
+
return TypeConvert.state(value);
|
|
176
|
+
}
|
|
177
|
+
function fixRating(value) {
|
|
178
|
+
return TypeConvert.rating(value);
|
|
179
|
+
}
|
|
180
|
+
const Grades = Object.freeze([
|
|
181
|
+
Rating.Again,
|
|
182
|
+
Rating.Hard,
|
|
183
|
+
Rating.Good,
|
|
184
|
+
Rating.Easy
|
|
185
|
+
]);
|
|
186
|
+
const FUZZ_RANGES = [
|
|
187
|
+
{
|
|
188
|
+
start: 2.5,
|
|
189
|
+
end: 7,
|
|
190
|
+
factor: 0.15
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
start: 7,
|
|
194
|
+
end: 20,
|
|
195
|
+
factor: 0.1
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
start: 20,
|
|
199
|
+
end: Infinity,
|
|
200
|
+
factor: 0.05
|
|
201
|
+
}
|
|
202
|
+
];
|
|
203
|
+
function get_fuzz_range(interval, elapsed_days, maximum_interval) {
|
|
204
|
+
let delta = 1;
|
|
205
|
+
for (const range of FUZZ_RANGES) {
|
|
206
|
+
delta += range.factor * Math.max(Math.min(interval, range.end) - range.start, 0);
|
|
207
|
+
}
|
|
208
|
+
interval = Math.min(interval, maximum_interval);
|
|
209
|
+
let min_ivl = Math.max(2, Math.round(interval - delta));
|
|
210
|
+
const max_ivl = Math.min(Math.round(interval + delta), maximum_interval);
|
|
211
|
+
if (interval > elapsed_days) {
|
|
212
|
+
min_ivl = Math.max(min_ivl, elapsed_days + 1);
|
|
213
|
+
}
|
|
214
|
+
min_ivl = Math.min(min_ivl, max_ivl);
|
|
215
|
+
return { min_ivl, max_ivl };
|
|
216
|
+
}
|
|
217
|
+
function clamp(value, min, max) {
|
|
218
|
+
return Math.min(Math.max(value, min), max);
|
|
219
|
+
}
|
|
220
|
+
function dateDiffInDays(last, cur) {
|
|
221
|
+
const utc1 = Date.UTC(
|
|
222
|
+
last.getUTCFullYear(),
|
|
223
|
+
last.getUTCMonth(),
|
|
224
|
+
last.getUTCDate()
|
|
225
|
+
);
|
|
226
|
+
const utc2 = Date.UTC(
|
|
227
|
+
cur.getUTCFullYear(),
|
|
228
|
+
cur.getUTCMonth(),
|
|
229
|
+
cur.getUTCDate()
|
|
230
|
+
);
|
|
231
|
+
return Math.floor(
|
|
232
|
+
(utc2 - utc1) / 864e5
|
|
233
|
+
/** 1000 * 60 * 60 * 24*/
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const ConvertStepUnitToMinutes = (step) => {
|
|
238
|
+
const unit = step.slice(-1);
|
|
239
|
+
const value = parseInt(step.slice(0, -1), 10);
|
|
240
|
+
if (Number.isNaN(value) || !Number.isFinite(value) || value < 0) {
|
|
241
|
+
throw new Error(`Invalid step value: ${step}`);
|
|
242
|
+
}
|
|
243
|
+
switch (unit) {
|
|
244
|
+
case "m":
|
|
245
|
+
return value;
|
|
246
|
+
case "h":
|
|
247
|
+
return value * 60;
|
|
248
|
+
case "d":
|
|
249
|
+
return value * 1440;
|
|
250
|
+
default:
|
|
251
|
+
throw new Error(`Invalid step unit: ${step}, expected m/h/d`);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
const BasicLearningStepsStrategy = (params, state, cur_step) => {
|
|
255
|
+
const learning_steps = state === State.Relearning || state === State.Review ? params.relearning_steps : params.learning_steps;
|
|
256
|
+
const steps_length = learning_steps.length;
|
|
257
|
+
if (steps_length === 0 || cur_step >= steps_length) return {};
|
|
258
|
+
const firstStep = learning_steps[0];
|
|
259
|
+
const toMinutes = ConvertStepUnitToMinutes;
|
|
260
|
+
const getAgainInterval = () => {
|
|
261
|
+
return toMinutes(firstStep);
|
|
262
|
+
};
|
|
263
|
+
const getHardInterval = () => {
|
|
264
|
+
if (steps_length === 1) return Math.round(toMinutes(firstStep) * 1.5);
|
|
265
|
+
const nextStep = learning_steps[1];
|
|
266
|
+
return Math.round((toMinutes(firstStep) + toMinutes(nextStep)) / 2);
|
|
267
|
+
};
|
|
268
|
+
const getStepInfo = (index) => {
|
|
269
|
+
if (index < 0 || index >= steps_length) {
|
|
270
|
+
return null;
|
|
271
|
+
} else {
|
|
272
|
+
return learning_steps[index];
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
const getGoodMinutes = (step) => {
|
|
276
|
+
return toMinutes(step);
|
|
277
|
+
};
|
|
278
|
+
const result = {};
|
|
279
|
+
const step_info = getStepInfo(Math.max(0, cur_step));
|
|
280
|
+
if (state === State.Review) {
|
|
281
|
+
result[Rating.Again] = {
|
|
282
|
+
scheduled_minutes: toMinutes(step_info),
|
|
283
|
+
next_step: 0
|
|
284
|
+
};
|
|
285
|
+
return result;
|
|
286
|
+
} else {
|
|
287
|
+
result[Rating.Again] = {
|
|
288
|
+
scheduled_minutes: getAgainInterval(),
|
|
289
|
+
next_step: 0
|
|
290
|
+
};
|
|
291
|
+
result[Rating.Hard] = {
|
|
292
|
+
scheduled_minutes: getHardInterval(),
|
|
293
|
+
next_step: cur_step
|
|
294
|
+
};
|
|
295
|
+
const next_info = getStepInfo(cur_step + 1);
|
|
296
|
+
if (next_info) {
|
|
297
|
+
const nextMin = getGoodMinutes(next_info);
|
|
298
|
+
if (nextMin) {
|
|
299
|
+
result[Rating.Good] = {
|
|
300
|
+
scheduled_minutes: Math.round(nextMin),
|
|
301
|
+
next_step: cur_step + 1
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return result;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
function DefaultInitSeedStrategy() {
|
|
310
|
+
const time = this.review_time.getTime();
|
|
311
|
+
const reps = this.current.reps;
|
|
312
|
+
const mul = this.current.difficulty * this.current.stability;
|
|
313
|
+
return `${time}_${reps}_${mul}`;
|
|
314
|
+
}
|
|
315
|
+
function GenSeedStrategyWithCardId(card_id_field) {
|
|
316
|
+
return function() {
|
|
317
|
+
var _a;
|
|
318
|
+
const card_id = (_a = Reflect.get(this.current, card_id_field)) != null ? _a : 0;
|
|
319
|
+
const reps = this.current.reps;
|
|
320
|
+
return String(card_id + reps || 0);
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
var StrategyMode = /* @__PURE__ */ ((StrategyMode2) => {
|
|
325
|
+
StrategyMode2["SCHEDULER"] = "Scheduler";
|
|
326
|
+
StrategyMode2["LEARNING_STEPS"] = "LearningSteps";
|
|
327
|
+
StrategyMode2["SEED"] = "Seed";
|
|
328
|
+
return StrategyMode2;
|
|
329
|
+
})(StrategyMode || {});
|
|
330
|
+
|
|
331
|
+
var __defProp$5 = Object.defineProperty;
|
|
332
|
+
var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
333
|
+
var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
334
|
+
class AbstractScheduler {
|
|
335
|
+
// init
|
|
336
|
+
constructor(card, now, algorithm, strategies) {
|
|
337
|
+
__publicField$5(this, "last");
|
|
338
|
+
__publicField$5(this, "current");
|
|
339
|
+
__publicField$5(this, "review_time");
|
|
340
|
+
__publicField$5(this, "next", /* @__PURE__ */ new Map());
|
|
341
|
+
__publicField$5(this, "algorithm");
|
|
342
|
+
__publicField$5(this, "strategies");
|
|
343
|
+
__publicField$5(this, "elapsed_days", 0);
|
|
344
|
+
this.algorithm = algorithm;
|
|
345
|
+
this.last = TypeConvert.card(card);
|
|
346
|
+
this.current = TypeConvert.card(card);
|
|
347
|
+
this.review_time = TypeConvert.time(now);
|
|
348
|
+
this.strategies = strategies;
|
|
349
|
+
this.init();
|
|
350
|
+
}
|
|
351
|
+
checkGrade(grade) {
|
|
352
|
+
if (!Number.isFinite(grade) || grade < 0 || grade > 4) {
|
|
353
|
+
throw new Error(`Invalid grade "${grade}",expected 1-4`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
init() {
|
|
357
|
+
const { state, last_review } = this.current;
|
|
358
|
+
let interval = 0;
|
|
359
|
+
if (state !== State.New && last_review) {
|
|
360
|
+
interval = dateDiffInDays(last_review, this.review_time);
|
|
361
|
+
}
|
|
362
|
+
this.current.last_review = this.review_time;
|
|
363
|
+
this.elapsed_days = interval;
|
|
364
|
+
this.current.elapsed_days = interval;
|
|
365
|
+
this.current.reps += 1;
|
|
366
|
+
let seed_strategy = DefaultInitSeedStrategy;
|
|
367
|
+
if (this.strategies) {
|
|
368
|
+
const custom_strategy = this.strategies.get(StrategyMode.SEED);
|
|
369
|
+
if (custom_strategy) {
|
|
370
|
+
seed_strategy = custom_strategy;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
this.algorithm.seed = seed_strategy.call(this);
|
|
374
|
+
}
|
|
375
|
+
preview() {
|
|
376
|
+
return {
|
|
377
|
+
[Rating.Again]: this.review(Rating.Again),
|
|
378
|
+
[Rating.Hard]: this.review(Rating.Hard),
|
|
379
|
+
[Rating.Good]: this.review(Rating.Good),
|
|
380
|
+
[Rating.Easy]: this.review(Rating.Easy),
|
|
381
|
+
[Symbol.iterator]: this.previewIterator.bind(this)
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
*previewIterator() {
|
|
385
|
+
for (const grade of Grades) {
|
|
386
|
+
yield this.review(grade);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
review(grade) {
|
|
390
|
+
const { state } = this.last;
|
|
391
|
+
let item;
|
|
392
|
+
this.checkGrade(grade);
|
|
393
|
+
switch (state) {
|
|
394
|
+
case State.New:
|
|
395
|
+
item = this.newState(grade);
|
|
396
|
+
break;
|
|
397
|
+
case State.Learning:
|
|
398
|
+
case State.Relearning:
|
|
399
|
+
item = this.learningState(grade);
|
|
400
|
+
break;
|
|
401
|
+
case State.Review:
|
|
402
|
+
item = this.reviewState(grade);
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
return item;
|
|
406
|
+
}
|
|
407
|
+
buildLog(rating) {
|
|
408
|
+
const { last_review, due, elapsed_days } = this.last;
|
|
409
|
+
return {
|
|
410
|
+
rating,
|
|
411
|
+
state: this.current.state,
|
|
412
|
+
due: last_review || due,
|
|
413
|
+
stability: this.current.stability,
|
|
414
|
+
difficulty: this.current.difficulty,
|
|
415
|
+
elapsed_days: this.elapsed_days,
|
|
416
|
+
last_elapsed_days: elapsed_days,
|
|
417
|
+
scheduled_days: this.current.scheduled_days,
|
|
418
|
+
learning_steps: this.current.learning_steps,
|
|
419
|
+
review: this.review_time
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
var __defProp$4 = Object.defineProperty;
|
|
425
|
+
var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
426
|
+
var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
427
|
+
class Alea {
|
|
428
|
+
constructor(seed) {
|
|
429
|
+
__publicField$4(this, "c");
|
|
430
|
+
__publicField$4(this, "s0");
|
|
431
|
+
__publicField$4(this, "s1");
|
|
432
|
+
__publicField$4(this, "s2");
|
|
433
|
+
const mash = Mash();
|
|
434
|
+
this.c = 1;
|
|
435
|
+
this.s0 = mash(" ");
|
|
436
|
+
this.s1 = mash(" ");
|
|
437
|
+
this.s2 = mash(" ");
|
|
438
|
+
if (seed == null) seed = Date.now();
|
|
439
|
+
this.s0 -= mash(seed);
|
|
440
|
+
if (this.s0 < 0) this.s0 += 1;
|
|
441
|
+
this.s1 -= mash(seed);
|
|
442
|
+
if (this.s1 < 0) this.s1 += 1;
|
|
443
|
+
this.s2 -= mash(seed);
|
|
444
|
+
if (this.s2 < 0) this.s2 += 1;
|
|
445
|
+
}
|
|
446
|
+
next() {
|
|
447
|
+
const t = 2091639 * this.s0 + this.c * 23283064365386963e-26;
|
|
448
|
+
this.s0 = this.s1;
|
|
449
|
+
this.s1 = this.s2;
|
|
450
|
+
this.c = t | 0;
|
|
451
|
+
this.s2 = t - this.c;
|
|
452
|
+
return this.s2;
|
|
453
|
+
}
|
|
454
|
+
set state(state) {
|
|
455
|
+
this.c = state.c;
|
|
456
|
+
this.s0 = state.s0;
|
|
457
|
+
this.s1 = state.s1;
|
|
458
|
+
this.s2 = state.s2;
|
|
459
|
+
}
|
|
460
|
+
get state() {
|
|
461
|
+
return {
|
|
462
|
+
c: this.c,
|
|
463
|
+
s0: this.s0,
|
|
464
|
+
s1: this.s1,
|
|
465
|
+
s2: this.s2
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function Mash() {
|
|
470
|
+
let n = 4022871197;
|
|
471
|
+
return function mash(data) {
|
|
472
|
+
data = String(data);
|
|
473
|
+
for (let i = 0; i < data.length; i++) {
|
|
474
|
+
n += data.charCodeAt(i);
|
|
475
|
+
let h = 0.02519603282416938 * n;
|
|
476
|
+
n = h >>> 0;
|
|
477
|
+
h -= n;
|
|
478
|
+
h *= n;
|
|
479
|
+
n = h >>> 0;
|
|
480
|
+
h -= n;
|
|
481
|
+
n += h * 4294967296;
|
|
482
|
+
}
|
|
483
|
+
return (n >>> 0) * 23283064365386963e-26;
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function alea(seed) {
|
|
487
|
+
const xg = new Alea(seed);
|
|
488
|
+
const prng = () => xg.next();
|
|
489
|
+
prng.int32 = () => xg.next() * 4294967296 | 0;
|
|
490
|
+
prng.double = () => prng() + (prng() * 2097152 | 0) * 11102230246251565e-32;
|
|
491
|
+
prng.state = () => xg.state;
|
|
492
|
+
prng.importState = (state) => {
|
|
493
|
+
xg.state = state;
|
|
494
|
+
return prng;
|
|
495
|
+
};
|
|
496
|
+
return prng;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const version="5.2.2";
|
|
500
|
+
|
|
501
|
+
const default_request_retention = 0.9;
|
|
502
|
+
const default_maximum_interval = 36500;
|
|
503
|
+
const default_enable_fuzz = false;
|
|
504
|
+
const default_enable_short_term = true;
|
|
505
|
+
const default_learning_steps = Object.freeze([
|
|
506
|
+
"1m",
|
|
507
|
+
"10m"
|
|
508
|
+
]);
|
|
509
|
+
const default_relearning_steps = Object.freeze([
|
|
510
|
+
"10m"
|
|
511
|
+
]);
|
|
512
|
+
const FSRSVersion = `v${version} using FSRS-6.0`;
|
|
513
|
+
const S_MIN = 1e-3;
|
|
514
|
+
const S_MAX = 36500;
|
|
515
|
+
const INIT_S_MAX = 100;
|
|
516
|
+
const FSRS5_DEFAULT_DECAY = 0.5;
|
|
517
|
+
const FSRS6_DEFAULT_DECAY = 0.1542;
|
|
518
|
+
const default_w = Object.freeze([
|
|
519
|
+
0.212,
|
|
520
|
+
1.2931,
|
|
521
|
+
2.3065,
|
|
522
|
+
8.2956,
|
|
523
|
+
6.4133,
|
|
524
|
+
0.8334,
|
|
525
|
+
3.0194,
|
|
526
|
+
1e-3,
|
|
527
|
+
1.8722,
|
|
528
|
+
0.1666,
|
|
529
|
+
0.796,
|
|
530
|
+
1.4835,
|
|
531
|
+
0.0614,
|
|
532
|
+
0.2629,
|
|
533
|
+
1.6483,
|
|
534
|
+
0.6014,
|
|
535
|
+
1.8729,
|
|
536
|
+
0.5425,
|
|
537
|
+
0.0912,
|
|
538
|
+
0.0658,
|
|
539
|
+
FSRS6_DEFAULT_DECAY
|
|
540
|
+
]);
|
|
541
|
+
const W17_W18_Ceiling = 2;
|
|
542
|
+
const CLAMP_PARAMETERS = (w17_w18_ceiling, enable_short_term = default_enable_short_term) => [
|
|
543
|
+
[S_MIN, INIT_S_MAX],
|
|
544
|
+
[S_MIN, INIT_S_MAX],
|
|
545
|
+
[S_MIN, INIT_S_MAX],
|
|
546
|
+
[S_MIN, INIT_S_MAX],
|
|
547
|
+
[1, 10],
|
|
548
|
+
[1e-3, 4],
|
|
549
|
+
[1e-3, 4],
|
|
550
|
+
[1e-3, 0.75],
|
|
551
|
+
[0, 4.5],
|
|
552
|
+
[0, 0.8],
|
|
553
|
+
[1e-3, 3.5],
|
|
554
|
+
[1e-3, 5],
|
|
555
|
+
[1e-3, 0.25],
|
|
556
|
+
[1e-3, 0.9],
|
|
557
|
+
[0, 4],
|
|
558
|
+
[0, 1],
|
|
559
|
+
[1, 6],
|
|
560
|
+
[0, w17_w18_ceiling],
|
|
561
|
+
[0, w17_w18_ceiling],
|
|
562
|
+
[
|
|
563
|
+
enable_short_term ? 0.01 : 0,
|
|
564
|
+
0.8
|
|
565
|
+
],
|
|
566
|
+
[0.1, 0.8]
|
|
567
|
+
];
|
|
568
|
+
|
|
569
|
+
const clipParameters = (parameters, numRelearningSteps, enableShortTerm = default_enable_short_term) => {
|
|
570
|
+
let w17_w18_ceiling = W17_W18_Ceiling;
|
|
571
|
+
if (Math.max(0, numRelearningSteps) > 1) {
|
|
572
|
+
const value = -(Math.log(parameters[11]) + Math.log(Math.pow(2, parameters[13]) - 1) + parameters[14] * 0.3) / numRelearningSteps;
|
|
573
|
+
w17_w18_ceiling = clamp(+value.toFixed(8), 0.01, 2);
|
|
574
|
+
}
|
|
575
|
+
const clip = CLAMP_PARAMETERS(w17_w18_ceiling, enableShortTerm).slice(
|
|
576
|
+
0,
|
|
577
|
+
parameters.length
|
|
578
|
+
);
|
|
579
|
+
return clip.map(
|
|
580
|
+
([min, max], index) => clamp(parameters[index] || 0, min, max)
|
|
581
|
+
);
|
|
582
|
+
};
|
|
583
|
+
const checkParameters = (parameters) => {
|
|
584
|
+
const invalid = parameters.find(
|
|
585
|
+
(param) => !Number.isFinite(param) && !Number.isNaN(param)
|
|
586
|
+
);
|
|
587
|
+
if (invalid !== void 0) {
|
|
588
|
+
throw Error(`Non-finite or NaN value in parameters ${parameters}`);
|
|
589
|
+
} else if (![17, 19, 21].includes(parameters.length)) {
|
|
590
|
+
throw Error(
|
|
591
|
+
`Invalid parameter length: ${parameters.length}. Must be 17, 19 or 21 for FSRSv4, 5 and 6 respectively.`
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
return parameters;
|
|
595
|
+
};
|
|
596
|
+
const migrateParameters = (parameters, numRelearningSteps = 0, enableShortTerm = default_enable_short_term) => {
|
|
597
|
+
if (parameters === void 0) {
|
|
598
|
+
return [...default_w];
|
|
599
|
+
}
|
|
600
|
+
switch (parameters.length) {
|
|
601
|
+
case 21:
|
|
602
|
+
return clipParameters(
|
|
603
|
+
Array.from(parameters),
|
|
604
|
+
numRelearningSteps,
|
|
605
|
+
enableShortTerm
|
|
606
|
+
);
|
|
607
|
+
case 19:
|
|
608
|
+
console.debug("[FSRS-6]auto fill w from 19 to 21 length");
|
|
609
|
+
return clipParameters(
|
|
610
|
+
Array.from(parameters),
|
|
611
|
+
numRelearningSteps,
|
|
612
|
+
enableShortTerm
|
|
613
|
+
).concat([0, FSRS5_DEFAULT_DECAY]);
|
|
614
|
+
case 17: {
|
|
615
|
+
const w = clipParameters(
|
|
616
|
+
Array.from(parameters),
|
|
617
|
+
numRelearningSteps,
|
|
618
|
+
enableShortTerm
|
|
619
|
+
);
|
|
620
|
+
w[4] = +(w[5] * 2 + w[4]).toFixed(8);
|
|
621
|
+
w[5] = +(Math.log(w[5] * 3 + 1) / 3).toFixed(8);
|
|
622
|
+
w[6] = +(w[6] + 0.5).toFixed(8);
|
|
623
|
+
console.debug("[FSRS-6]auto fill w from 17 to 21 length");
|
|
624
|
+
return w.concat([0, 0, 0, FSRS5_DEFAULT_DECAY]);
|
|
625
|
+
}
|
|
626
|
+
default:
|
|
627
|
+
console.warn("[FSRS]Invalid parameters length, using default parameters");
|
|
628
|
+
return [...default_w];
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
const generatorParameters = (props) => {
|
|
632
|
+
var _a, _b;
|
|
633
|
+
const learning_steps = Array.isArray(props == null ? void 0 : props.learning_steps) ? props.learning_steps : default_learning_steps;
|
|
634
|
+
const relearning_steps = Array.isArray(props == null ? void 0 : props.relearning_steps) ? props.relearning_steps : default_relearning_steps;
|
|
635
|
+
const enable_short_term = (_a = props == null ? void 0 : props.enable_short_term) != null ? _a : default_enable_short_term;
|
|
636
|
+
const w = migrateParameters(
|
|
637
|
+
props == null ? void 0 : props.w,
|
|
638
|
+
relearning_steps.length,
|
|
639
|
+
enable_short_term
|
|
640
|
+
);
|
|
641
|
+
return {
|
|
642
|
+
request_retention: (props == null ? void 0 : props.request_retention) || default_request_retention,
|
|
643
|
+
maximum_interval: (props == null ? void 0 : props.maximum_interval) || default_maximum_interval,
|
|
644
|
+
w,
|
|
645
|
+
enable_fuzz: (_b = props == null ? void 0 : props.enable_fuzz) != null ? _b : default_enable_fuzz,
|
|
646
|
+
enable_short_term,
|
|
647
|
+
learning_steps,
|
|
648
|
+
relearning_steps
|
|
649
|
+
};
|
|
650
|
+
};
|
|
651
|
+
function createEmptyCard(now, afterHandler) {
|
|
652
|
+
const emptyCard = {
|
|
653
|
+
due: now ? TypeConvert.time(now) : /* @__PURE__ */ new Date(),
|
|
654
|
+
stability: 0,
|
|
655
|
+
difficulty: 0,
|
|
656
|
+
elapsed_days: 0,
|
|
657
|
+
scheduled_days: 0,
|
|
658
|
+
reps: 0,
|
|
659
|
+
lapses: 0,
|
|
660
|
+
learning_steps: 0,
|
|
661
|
+
state: State.New,
|
|
662
|
+
last_review: void 0
|
|
663
|
+
};
|
|
664
|
+
if (afterHandler && typeof afterHandler === "function") {
|
|
665
|
+
return afterHandler(emptyCard);
|
|
666
|
+
} else {
|
|
667
|
+
return emptyCard;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
var __defProp$3 = Object.defineProperty;
|
|
672
|
+
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
673
|
+
var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
674
|
+
const computeDecayFactor = (decayOrParams) => {
|
|
675
|
+
const decay = typeof decayOrParams === "number" ? -decayOrParams : -decayOrParams[20];
|
|
676
|
+
const factor = Math.exp(Math.pow(decay, -1) * Math.log(0.9)) - 1;
|
|
677
|
+
return { decay, factor: +factor.toFixed(8) };
|
|
678
|
+
};
|
|
679
|
+
function forgetting_curve(decayOrParams, elapsed_days, stability) {
|
|
680
|
+
const { decay, factor } = computeDecayFactor(decayOrParams);
|
|
681
|
+
return +Math.pow(1 + factor * elapsed_days / stability, decay).toFixed(8);
|
|
682
|
+
}
|
|
683
|
+
class FSRSAlgorithm {
|
|
684
|
+
constructor(params) {
|
|
685
|
+
__publicField$3(this, "param");
|
|
686
|
+
__publicField$3(this, "intervalModifier");
|
|
687
|
+
__publicField$3(this, "_seed");
|
|
688
|
+
/**
|
|
689
|
+
* The formula used is :
|
|
690
|
+
* $$R(t,S) = (1 + \text{FACTOR} \times \frac{t}{9 \cdot S})^{\text{DECAY}}$$
|
|
691
|
+
* @param {number} elapsed_days t days since the last review
|
|
692
|
+
* @param {number} stability Stability (interval when R=90%)
|
|
693
|
+
* @return {number} r Retrievability (probability of recall)
|
|
694
|
+
*/
|
|
695
|
+
__publicField$3(this, "forgetting_curve");
|
|
696
|
+
this.param = new Proxy(
|
|
697
|
+
generatorParameters(params),
|
|
698
|
+
this.params_handler_proxy()
|
|
699
|
+
);
|
|
700
|
+
this.intervalModifier = this.calculate_interval_modifier(
|
|
701
|
+
this.param.request_retention
|
|
702
|
+
);
|
|
703
|
+
this.forgetting_curve = forgetting_curve.bind(this, this.param.w);
|
|
704
|
+
}
|
|
705
|
+
get interval_modifier() {
|
|
706
|
+
return this.intervalModifier;
|
|
707
|
+
}
|
|
708
|
+
set seed(seed) {
|
|
709
|
+
this._seed = seed;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* @see https://github.com/open-spaced-repetition/fsrs4anki/wiki/The-Algorithm#fsrs-5
|
|
713
|
+
*
|
|
714
|
+
* The formula used is: $$I(r,s) = (r^{\frac{1}{DECAY}} - 1) / FACTOR \times s$$
|
|
715
|
+
* @param request_retention 0<request_retention<=1,Requested retention rate
|
|
716
|
+
* @throws {Error} Requested retention rate should be in the range (0,1]
|
|
717
|
+
*/
|
|
718
|
+
calculate_interval_modifier(request_retention) {
|
|
719
|
+
if (request_retention <= 0 || request_retention > 1) {
|
|
720
|
+
throw new Error("Requested retention rate should be in the range (0,1]");
|
|
721
|
+
}
|
|
722
|
+
const { decay, factor } = computeDecayFactor(this.param.w);
|
|
723
|
+
return +((Math.pow(request_retention, 1 / decay) - 1) / factor).toFixed(8);
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Get the parameters of the algorithm.
|
|
727
|
+
*/
|
|
728
|
+
get parameters() {
|
|
729
|
+
return this.param;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Set the parameters of the algorithm.
|
|
733
|
+
* @param params Partial<FSRSParameters>
|
|
734
|
+
*/
|
|
735
|
+
set parameters(params) {
|
|
736
|
+
this.update_parameters(params);
|
|
737
|
+
}
|
|
738
|
+
params_handler_proxy() {
|
|
739
|
+
const _this = this;
|
|
740
|
+
return {
|
|
741
|
+
set: function(target, prop, value) {
|
|
742
|
+
if (prop === "request_retention" && Number.isFinite(value)) {
|
|
743
|
+
_this.intervalModifier = _this.calculate_interval_modifier(
|
|
744
|
+
Number(value)
|
|
745
|
+
);
|
|
746
|
+
} else if (prop === "w") {
|
|
747
|
+
value = migrateParameters(
|
|
748
|
+
value,
|
|
749
|
+
target.relearning_steps.length,
|
|
750
|
+
target.enable_short_term
|
|
751
|
+
);
|
|
752
|
+
_this.forgetting_curve = forgetting_curve.bind(this, value);
|
|
753
|
+
_this.intervalModifier = _this.calculate_interval_modifier(
|
|
754
|
+
Number(target.request_retention)
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
Reflect.set(target, prop, value);
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
update_parameters(params) {
|
|
763
|
+
const _params = generatorParameters(params);
|
|
764
|
+
for (const key in _params) {
|
|
765
|
+
const paramKey = key;
|
|
766
|
+
this.param[paramKey] = _params[paramKey];
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* The formula used is :
|
|
771
|
+
* $$ S_0(G) = w_{G-1}$$
|
|
772
|
+
* $$S_0 = \max \lbrace S_0,0.1\rbrace $$
|
|
773
|
+
|
|
774
|
+
* @param g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
|
|
775
|
+
* @return Stability (interval when R=90%)
|
|
776
|
+
*/
|
|
777
|
+
init_stability(g) {
|
|
778
|
+
return Math.max(this.param.w[g - 1], 0.1);
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* The formula used is :
|
|
782
|
+
* $$D_0(G) = w_4 - e^{(G-1) \cdot w_5} + 1 $$
|
|
783
|
+
* $$D_0 = \min \lbrace \max \lbrace D_0(G),1 \rbrace,10 \rbrace$$
|
|
784
|
+
* where the $$D_0(1)=w_4$$ when the first rating is good.
|
|
785
|
+
*
|
|
786
|
+
* @param {Grade} g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
|
|
787
|
+
* @return {number} Difficulty $$D \in [1,10]$$
|
|
788
|
+
*/
|
|
789
|
+
init_difficulty(g) {
|
|
790
|
+
const d = this.param.w[4] - Math.exp((g - 1) * this.param.w[5]) + 1;
|
|
791
|
+
return +d.toFixed(8);
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* If fuzzing is disabled or ivl is less than 2.5, it returns the original interval.
|
|
795
|
+
* @param {number} ivl - The interval to be fuzzed.
|
|
796
|
+
* @param {number} elapsed_days t days since the last review
|
|
797
|
+
* @return {number} - The fuzzed interval.
|
|
798
|
+
**/
|
|
799
|
+
apply_fuzz(ivl, elapsed_days) {
|
|
800
|
+
if (!this.param.enable_fuzz || ivl < 2.5) return Math.round(ivl);
|
|
801
|
+
const generator = alea(this._seed);
|
|
802
|
+
const fuzz_factor = generator();
|
|
803
|
+
const { min_ivl, max_ivl } = get_fuzz_range(
|
|
804
|
+
ivl,
|
|
805
|
+
elapsed_days,
|
|
806
|
+
this.param.maximum_interval
|
|
807
|
+
);
|
|
808
|
+
return Math.floor(fuzz_factor * (max_ivl - min_ivl + 1) + min_ivl);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* @see The formula used is : {@link FSRSAlgorithm.calculate_interval_modifier}
|
|
812
|
+
* @param {number} s - Stability (interval when R=90%)
|
|
813
|
+
* @param {number} elapsed_days t days since the last review
|
|
814
|
+
*/
|
|
815
|
+
next_interval(s, elapsed_days) {
|
|
816
|
+
const newInterval = Math.min(
|
|
817
|
+
Math.max(1, Math.round(s * this.intervalModifier)),
|
|
818
|
+
this.param.maximum_interval
|
|
819
|
+
);
|
|
820
|
+
return this.apply_fuzz(newInterval, elapsed_days);
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* @see https://github.com/open-spaced-repetition/fsrs4anki/issues/697
|
|
824
|
+
*/
|
|
825
|
+
linear_damping(delta_d, old_d) {
|
|
826
|
+
return +(delta_d * (10 - old_d) / 9).toFixed(8);
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* The formula used is :
|
|
830
|
+
* $$\text{delta}_d = -w_6 \cdot (g - 3)$$
|
|
831
|
+
* $$\text{next}_d = D + \text{linear damping}(\text{delta}_d , D)$$
|
|
832
|
+
* $$D^\prime(D,R) = w_7 \cdot D_0(4) +(1 - w_7) \cdot \text{next}_d$$
|
|
833
|
+
* @param {number} d Difficulty $$D \in [1,10]$$
|
|
834
|
+
* @param {Grade} g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
|
|
835
|
+
* @return {number} $$\text{next}_D$$
|
|
836
|
+
*/
|
|
837
|
+
next_difficulty(d, g) {
|
|
838
|
+
const delta_d = -this.param.w[6] * (g - 3);
|
|
839
|
+
const next_d = d + this.linear_damping(delta_d, d);
|
|
840
|
+
return clamp(
|
|
841
|
+
this.mean_reversion(this.init_difficulty(Rating.Easy), next_d),
|
|
842
|
+
1,
|
|
843
|
+
10
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* The formula used is :
|
|
848
|
+
* $$w_7 \cdot \text{init} +(1 - w_7) \cdot \text{current}$$
|
|
849
|
+
* @param {number} init $$w_2 : D_0(3) = w_2 + (R-2) \cdot w_3= w_2$$
|
|
850
|
+
* @param {number} current $$D - w_6 \cdot (R - 2)$$
|
|
851
|
+
* @return {number} difficulty
|
|
852
|
+
*/
|
|
853
|
+
mean_reversion(init, current) {
|
|
854
|
+
return +(this.param.w[7] * init + (1 - this.param.w[7]) * current).toFixed(
|
|
855
|
+
8
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* The formula used is :
|
|
860
|
+
* $$S^\prime_r(D,S,R,G) = S\cdot(e^{w_8}\cdot (11-D)\cdot S^{-w_9}\cdot(e^{w_{10}\cdot(1-R)}-1)\cdot w_{15}(\text{if} G=2) \cdot w_{16}(\text{if} G=4)+1)$$
|
|
861
|
+
* @param {number} d Difficulty D \in [1,10]
|
|
862
|
+
* @param {number} s Stability (interval when R=90%)
|
|
863
|
+
* @param {number} r Retrievability (probability of recall)
|
|
864
|
+
* @param {Grade} g Grade (Rating[0.again,1.hard,2.good,3.easy])
|
|
865
|
+
* @return {number} S^\prime_r new stability after recall
|
|
866
|
+
*/
|
|
867
|
+
next_recall_stability(d, s, r, g) {
|
|
868
|
+
const hard_penalty = Rating.Hard === g ? this.param.w[15] : 1;
|
|
869
|
+
const easy_bound = Rating.Easy === g ? this.param.w[16] : 1;
|
|
870
|
+
return +clamp(
|
|
871
|
+
s * (1 + Math.exp(this.param.w[8]) * (11 - d) * Math.pow(s, -this.param.w[9]) * (Math.exp((1 - r) * this.param.w[10]) - 1) * hard_penalty * easy_bound),
|
|
872
|
+
S_MIN,
|
|
873
|
+
36500
|
|
874
|
+
).toFixed(8);
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* The formula used is :
|
|
878
|
+
* $$S^\prime_f(D,S,R) = w_{11}\cdot D^{-w_{12}}\cdot ((S+1)^{w_{13}}-1) \cdot e^{w_{14}\cdot(1-R)}$$
|
|
879
|
+
* enable_short_term = true : $$S^\prime_f \in \min \lbrace \max \lbrace S^\prime_f,0.01\rbrace, \frac{S}{e^{w_{17} \cdot w_{18}}} \rbrace$$
|
|
880
|
+
* enable_short_term = false : $$S^\prime_f \in \min \lbrace \max \lbrace S^\prime_f,0.01\rbrace, S \rbrace$$
|
|
881
|
+
* @param {number} d Difficulty D \in [1,10]
|
|
882
|
+
* @param {number} s Stability (interval when R=90%)
|
|
883
|
+
* @param {number} r Retrievability (probability of recall)
|
|
884
|
+
* @return {number} S^\prime_f new stability after forgetting
|
|
885
|
+
*/
|
|
886
|
+
next_forget_stability(d, s, r) {
|
|
887
|
+
return +clamp(
|
|
888
|
+
this.param.w[11] * Math.pow(d, -this.param.w[12]) * (Math.pow(s + 1, this.param.w[13]) - 1) * Math.exp((1 - r) * this.param.w[14]),
|
|
889
|
+
S_MIN,
|
|
890
|
+
36500
|
|
891
|
+
).toFixed(8);
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* The formula used is :
|
|
895
|
+
* $$S^\prime_s(S,G) = S \cdot e^{w_{17} \cdot (G-3+w_{18})}$$
|
|
896
|
+
* @param {number} s Stability (interval when R=90%)
|
|
897
|
+
* @param {Grade} g Grade (Rating[0.again,1.hard,2.good,3.easy])
|
|
898
|
+
*/
|
|
899
|
+
next_short_term_stability(s, g) {
|
|
900
|
+
const sinc = Math.pow(s, -this.param.w[19]) * Math.exp(this.param.w[17] * (g - 3 + this.param.w[18]));
|
|
901
|
+
const maskedSinc = g >= 3 ? Math.max(sinc, 1) : sinc;
|
|
902
|
+
return +clamp(s * maskedSinc, S_MIN, 36500).toFixed(8);
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Calculates the next state of memory based on the current state, time elapsed, and grade.
|
|
906
|
+
*
|
|
907
|
+
* @param memory_state - The current state of memory, which can be null.
|
|
908
|
+
* @param t - The time elapsed since the last review.
|
|
909
|
+
* @param {Rating} g Grade (Rating[0.Manual,1.Again,2.Hard,3.Good,4.Easy])
|
|
910
|
+
* @returns The next state of memory with updated difficulty and stability.
|
|
911
|
+
*/
|
|
912
|
+
next_state(memory_state, t, g) {
|
|
913
|
+
const { difficulty: d, stability: s } = memory_state != null ? memory_state : {
|
|
914
|
+
difficulty: 0,
|
|
915
|
+
stability: 0
|
|
916
|
+
};
|
|
917
|
+
if (t < 0) {
|
|
918
|
+
throw new Error(`Invalid delta_t "${t}"`);
|
|
919
|
+
}
|
|
920
|
+
if (g < 0 || g > 4) {
|
|
921
|
+
throw new Error(`Invalid grade "${g}"`);
|
|
922
|
+
}
|
|
923
|
+
if (d === 0 && s === 0) {
|
|
924
|
+
return {
|
|
925
|
+
difficulty: clamp(this.init_difficulty(g), 1, 10),
|
|
926
|
+
stability: this.init_stability(g)
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
if (g === 0) {
|
|
930
|
+
return {
|
|
931
|
+
difficulty: d,
|
|
932
|
+
stability: s
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
if (d < 1 || s < S_MIN) {
|
|
936
|
+
throw new Error(
|
|
937
|
+
`Invalid memory state { difficulty: ${d}, stability: ${s} }`
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
const r = this.forgetting_curve(t, s);
|
|
941
|
+
const s_after_success = this.next_recall_stability(d, s, r, g);
|
|
942
|
+
const s_after_fail = this.next_forget_stability(d, s, r);
|
|
943
|
+
const s_after_short_term = this.next_short_term_stability(s, g);
|
|
944
|
+
let new_s = s_after_success;
|
|
945
|
+
if (g === 1) {
|
|
946
|
+
let [w_17, w_18] = [0, 0];
|
|
947
|
+
if (this.param.enable_short_term) {
|
|
948
|
+
w_17 = this.param.w[17];
|
|
949
|
+
w_18 = this.param.w[18];
|
|
950
|
+
}
|
|
951
|
+
const next_s_min = s / Math.exp(w_17 * w_18);
|
|
952
|
+
new_s = clamp(+next_s_min.toFixed(8), S_MIN, s_after_fail);
|
|
953
|
+
}
|
|
954
|
+
if (t === 0 && this.param.enable_short_term) {
|
|
955
|
+
new_s = s_after_short_term;
|
|
956
|
+
}
|
|
957
|
+
const new_d = this.next_difficulty(d, g);
|
|
958
|
+
return { difficulty: new_d, stability: new_s };
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
var __defProp$2 = Object.defineProperty;
|
|
963
|
+
var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
964
|
+
var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, key + "" , value);
|
|
965
|
+
class BasicScheduler extends AbstractScheduler {
|
|
966
|
+
constructor(card, now, algorithm, strategies) {
|
|
967
|
+
super(card, now, algorithm, strategies);
|
|
968
|
+
__publicField$2(this, "learningStepsStrategy");
|
|
969
|
+
let learningStepStrategy = BasicLearningStepsStrategy;
|
|
970
|
+
if (this.strategies) {
|
|
971
|
+
const custom_strategy = this.strategies.get(StrategyMode.LEARNING_STEPS);
|
|
972
|
+
if (custom_strategy) {
|
|
973
|
+
learningStepStrategy = custom_strategy;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
this.learningStepsStrategy = learningStepStrategy;
|
|
977
|
+
}
|
|
978
|
+
getLearningInfo(card, grade) {
|
|
979
|
+
var _a, _b, _c, _d;
|
|
980
|
+
const parameters = this.algorithm.parameters;
|
|
981
|
+
card.learning_steps = card.learning_steps || 0;
|
|
982
|
+
const steps_strategy = this.learningStepsStrategy(
|
|
983
|
+
parameters,
|
|
984
|
+
card.state,
|
|
985
|
+
// In the original learning steps setup (Again = 5m, Hard = 10m, Good = FSRS),
|
|
986
|
+
// not adding 1 can cause slight variations in the memory state’s ds.
|
|
987
|
+
this.current.state === State.Learning && grade !== Rating.Again && grade !== Rating.Hard ? card.learning_steps + 1 : card.learning_steps
|
|
988
|
+
);
|
|
989
|
+
const scheduled_minutes = Math.max(
|
|
990
|
+
0,
|
|
991
|
+
(_b = (_a = steps_strategy[grade]) == null ? void 0 : _a.scheduled_minutes) != null ? _b : 0
|
|
992
|
+
);
|
|
993
|
+
const next_steps = Math.max(0, (_d = (_c = steps_strategy[grade]) == null ? void 0 : _c.next_step) != null ? _d : 0);
|
|
994
|
+
return {
|
|
995
|
+
scheduled_minutes,
|
|
996
|
+
next_steps
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* @description This function applies the learning steps based on the current card's state and grade.
|
|
1001
|
+
*/
|
|
1002
|
+
applyLearningSteps(nextCard, grade, to_state) {
|
|
1003
|
+
const { scheduled_minutes, next_steps } = this.getLearningInfo(
|
|
1004
|
+
this.current,
|
|
1005
|
+
grade
|
|
1006
|
+
);
|
|
1007
|
+
if (scheduled_minutes > 0 && scheduled_minutes < 1440) {
|
|
1008
|
+
nextCard.learning_steps = next_steps;
|
|
1009
|
+
nextCard.scheduled_days = 0;
|
|
1010
|
+
nextCard.state = to_state;
|
|
1011
|
+
nextCard.due = date_scheduler(
|
|
1012
|
+
this.review_time,
|
|
1013
|
+
Math.round(scheduled_minutes),
|
|
1014
|
+
false
|
|
1015
|
+
/** true:days false: minute */
|
|
1016
|
+
);
|
|
1017
|
+
} else {
|
|
1018
|
+
nextCard.state = State.Review;
|
|
1019
|
+
if (scheduled_minutes >= 1440) {
|
|
1020
|
+
nextCard.learning_steps = next_steps;
|
|
1021
|
+
nextCard.due = date_scheduler(
|
|
1022
|
+
this.review_time,
|
|
1023
|
+
Math.round(scheduled_minutes),
|
|
1024
|
+
false
|
|
1025
|
+
/** true:days false: minute */
|
|
1026
|
+
);
|
|
1027
|
+
nextCard.scheduled_days = Math.floor(scheduled_minutes / 1440);
|
|
1028
|
+
} else {
|
|
1029
|
+
nextCard.learning_steps = 0;
|
|
1030
|
+
const interval = this.algorithm.next_interval(
|
|
1031
|
+
nextCard.stability,
|
|
1032
|
+
this.elapsed_days
|
|
1033
|
+
);
|
|
1034
|
+
nextCard.scheduled_days = interval;
|
|
1035
|
+
nextCard.due = date_scheduler(this.review_time, interval, true);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
newState(grade) {
|
|
1040
|
+
const exist = this.next.get(grade);
|
|
1041
|
+
if (exist) {
|
|
1042
|
+
return exist;
|
|
1043
|
+
}
|
|
1044
|
+
const next = TypeConvert.card(this.current);
|
|
1045
|
+
next.difficulty = clamp(this.algorithm.init_difficulty(grade), 1, 10);
|
|
1046
|
+
next.stability = this.algorithm.init_stability(grade);
|
|
1047
|
+
this.applyLearningSteps(next, grade, State.Learning);
|
|
1048
|
+
const item = {
|
|
1049
|
+
card: next,
|
|
1050
|
+
log: this.buildLog(grade)
|
|
1051
|
+
};
|
|
1052
|
+
this.next.set(grade, item);
|
|
1053
|
+
return item;
|
|
1054
|
+
}
|
|
1055
|
+
learningState(grade) {
|
|
1056
|
+
const exist = this.next.get(grade);
|
|
1057
|
+
if (exist) {
|
|
1058
|
+
return exist;
|
|
1059
|
+
}
|
|
1060
|
+
const { state, difficulty, stability } = this.last;
|
|
1061
|
+
const next = TypeConvert.card(this.current);
|
|
1062
|
+
next.difficulty = this.algorithm.next_difficulty(difficulty, grade);
|
|
1063
|
+
next.stability = this.algorithm.next_short_term_stability(stability, grade);
|
|
1064
|
+
this.applyLearningSteps(
|
|
1065
|
+
next,
|
|
1066
|
+
grade,
|
|
1067
|
+
state
|
|
1068
|
+
/** Learning or Relearning */
|
|
1069
|
+
);
|
|
1070
|
+
const item = {
|
|
1071
|
+
card: next,
|
|
1072
|
+
log: this.buildLog(grade)
|
|
1073
|
+
};
|
|
1074
|
+
this.next.set(grade, item);
|
|
1075
|
+
return item;
|
|
1076
|
+
}
|
|
1077
|
+
reviewState(grade) {
|
|
1078
|
+
const exist = this.next.get(grade);
|
|
1079
|
+
if (exist) {
|
|
1080
|
+
return exist;
|
|
1081
|
+
}
|
|
1082
|
+
const interval = this.elapsed_days;
|
|
1083
|
+
const { difficulty, stability } = this.last;
|
|
1084
|
+
const retrievability = this.algorithm.forgetting_curve(interval, stability);
|
|
1085
|
+
const next_again = TypeConvert.card(this.current);
|
|
1086
|
+
const next_hard = TypeConvert.card(this.current);
|
|
1087
|
+
const next_good = TypeConvert.card(this.current);
|
|
1088
|
+
const next_easy = TypeConvert.card(this.current);
|
|
1089
|
+
this.next_ds(
|
|
1090
|
+
next_again,
|
|
1091
|
+
next_hard,
|
|
1092
|
+
next_good,
|
|
1093
|
+
next_easy,
|
|
1094
|
+
difficulty,
|
|
1095
|
+
stability,
|
|
1096
|
+
retrievability
|
|
1097
|
+
);
|
|
1098
|
+
this.next_interval(next_hard, next_good, next_easy, interval);
|
|
1099
|
+
this.next_state(next_hard, next_good, next_easy);
|
|
1100
|
+
this.applyLearningSteps(next_again, Rating.Again, State.Relearning);
|
|
1101
|
+
next_again.lapses += 1;
|
|
1102
|
+
const item_again = {
|
|
1103
|
+
card: next_again,
|
|
1104
|
+
log: this.buildLog(Rating.Again)
|
|
1105
|
+
};
|
|
1106
|
+
const item_hard = {
|
|
1107
|
+
card: next_hard,
|
|
1108
|
+
log: super.buildLog(Rating.Hard)
|
|
1109
|
+
};
|
|
1110
|
+
const item_good = {
|
|
1111
|
+
card: next_good,
|
|
1112
|
+
log: super.buildLog(Rating.Good)
|
|
1113
|
+
};
|
|
1114
|
+
const item_easy = {
|
|
1115
|
+
card: next_easy,
|
|
1116
|
+
log: super.buildLog(Rating.Easy)
|
|
1117
|
+
};
|
|
1118
|
+
this.next.set(Rating.Again, item_again);
|
|
1119
|
+
this.next.set(Rating.Hard, item_hard);
|
|
1120
|
+
this.next.set(Rating.Good, item_good);
|
|
1121
|
+
this.next.set(Rating.Easy, item_easy);
|
|
1122
|
+
return this.next.get(grade);
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Review next_ds
|
|
1126
|
+
*/
|
|
1127
|
+
next_ds(next_again, next_hard, next_good, next_easy, difficulty, stability, retrievability) {
|
|
1128
|
+
next_again.difficulty = this.algorithm.next_difficulty(
|
|
1129
|
+
difficulty,
|
|
1130
|
+
Rating.Again
|
|
1131
|
+
);
|
|
1132
|
+
const nextSMin = stability / Math.exp(
|
|
1133
|
+
this.algorithm.parameters.w[17] * this.algorithm.parameters.w[18]
|
|
1134
|
+
);
|
|
1135
|
+
const s_after_fail = this.algorithm.next_forget_stability(
|
|
1136
|
+
difficulty,
|
|
1137
|
+
stability,
|
|
1138
|
+
retrievability
|
|
1139
|
+
);
|
|
1140
|
+
next_again.stability = clamp(+nextSMin.toFixed(8), S_MIN, s_after_fail);
|
|
1141
|
+
next_hard.difficulty = this.algorithm.next_difficulty(
|
|
1142
|
+
difficulty,
|
|
1143
|
+
Rating.Hard
|
|
1144
|
+
);
|
|
1145
|
+
next_hard.stability = this.algorithm.next_recall_stability(
|
|
1146
|
+
difficulty,
|
|
1147
|
+
stability,
|
|
1148
|
+
retrievability,
|
|
1149
|
+
Rating.Hard
|
|
1150
|
+
);
|
|
1151
|
+
next_good.difficulty = this.algorithm.next_difficulty(
|
|
1152
|
+
difficulty,
|
|
1153
|
+
Rating.Good
|
|
1154
|
+
);
|
|
1155
|
+
next_good.stability = this.algorithm.next_recall_stability(
|
|
1156
|
+
difficulty,
|
|
1157
|
+
stability,
|
|
1158
|
+
retrievability,
|
|
1159
|
+
Rating.Good
|
|
1160
|
+
);
|
|
1161
|
+
next_easy.difficulty = this.algorithm.next_difficulty(
|
|
1162
|
+
difficulty,
|
|
1163
|
+
Rating.Easy
|
|
1164
|
+
);
|
|
1165
|
+
next_easy.stability = this.algorithm.next_recall_stability(
|
|
1166
|
+
difficulty,
|
|
1167
|
+
stability,
|
|
1168
|
+
retrievability,
|
|
1169
|
+
Rating.Easy
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Review next_interval
|
|
1174
|
+
*/
|
|
1175
|
+
next_interval(next_hard, next_good, next_easy, interval) {
|
|
1176
|
+
let hard_interval, good_interval;
|
|
1177
|
+
hard_interval = this.algorithm.next_interval(next_hard.stability, interval);
|
|
1178
|
+
good_interval = this.algorithm.next_interval(next_good.stability, interval);
|
|
1179
|
+
hard_interval = Math.min(hard_interval, good_interval);
|
|
1180
|
+
good_interval = Math.max(good_interval, hard_interval + 1);
|
|
1181
|
+
const easy_interval = Math.max(
|
|
1182
|
+
this.algorithm.next_interval(next_easy.stability, interval),
|
|
1183
|
+
good_interval + 1
|
|
1184
|
+
);
|
|
1185
|
+
next_hard.scheduled_days = hard_interval;
|
|
1186
|
+
next_hard.due = date_scheduler(this.review_time, hard_interval, true);
|
|
1187
|
+
next_good.scheduled_days = good_interval;
|
|
1188
|
+
next_good.due = date_scheduler(this.review_time, good_interval, true);
|
|
1189
|
+
next_easy.scheduled_days = easy_interval;
|
|
1190
|
+
next_easy.due = date_scheduler(this.review_time, easy_interval, true);
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Review next_state
|
|
1194
|
+
*/
|
|
1195
|
+
next_state(next_hard, next_good, next_easy) {
|
|
1196
|
+
next_hard.state = State.Review;
|
|
1197
|
+
next_hard.learning_steps = 0;
|
|
1198
|
+
next_good.state = State.Review;
|
|
1199
|
+
next_good.learning_steps = 0;
|
|
1200
|
+
next_easy.state = State.Review;
|
|
1201
|
+
next_easy.learning_steps = 0;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
class LongTermScheduler extends AbstractScheduler {
|
|
1206
|
+
newState(grade) {
|
|
1207
|
+
const exist = this.next.get(grade);
|
|
1208
|
+
if (exist) {
|
|
1209
|
+
return exist;
|
|
1210
|
+
}
|
|
1211
|
+
this.current.scheduled_days = 0;
|
|
1212
|
+
this.current.elapsed_days = 0;
|
|
1213
|
+
const next_again = TypeConvert.card(this.current);
|
|
1214
|
+
const next_hard = TypeConvert.card(this.current);
|
|
1215
|
+
const next_good = TypeConvert.card(this.current);
|
|
1216
|
+
const next_easy = TypeConvert.card(this.current);
|
|
1217
|
+
this.init_ds(next_again, next_hard, next_good, next_easy);
|
|
1218
|
+
const first_interval = 0;
|
|
1219
|
+
this.next_interval(
|
|
1220
|
+
next_again,
|
|
1221
|
+
next_hard,
|
|
1222
|
+
next_good,
|
|
1223
|
+
next_easy,
|
|
1224
|
+
first_interval
|
|
1225
|
+
);
|
|
1226
|
+
this.next_state(next_again, next_hard, next_good, next_easy);
|
|
1227
|
+
this.update_next(next_again, next_hard, next_good, next_easy);
|
|
1228
|
+
return this.next.get(grade);
|
|
1229
|
+
}
|
|
1230
|
+
init_ds(next_again, next_hard, next_good, next_easy) {
|
|
1231
|
+
next_again.difficulty = clamp(
|
|
1232
|
+
this.algorithm.init_difficulty(Rating.Again),
|
|
1233
|
+
1,
|
|
1234
|
+
10
|
|
1235
|
+
);
|
|
1236
|
+
next_again.stability = this.algorithm.init_stability(Rating.Again);
|
|
1237
|
+
next_hard.difficulty = clamp(
|
|
1238
|
+
this.algorithm.init_difficulty(Rating.Hard),
|
|
1239
|
+
1,
|
|
1240
|
+
10
|
|
1241
|
+
);
|
|
1242
|
+
next_hard.stability = this.algorithm.init_stability(Rating.Hard);
|
|
1243
|
+
next_good.difficulty = clamp(
|
|
1244
|
+
this.algorithm.init_difficulty(Rating.Good),
|
|
1245
|
+
1,
|
|
1246
|
+
10
|
|
1247
|
+
);
|
|
1248
|
+
next_good.stability = this.algorithm.init_stability(Rating.Good);
|
|
1249
|
+
next_easy.difficulty = clamp(
|
|
1250
|
+
this.algorithm.init_difficulty(Rating.Easy),
|
|
1251
|
+
1,
|
|
1252
|
+
10
|
|
1253
|
+
);
|
|
1254
|
+
next_easy.stability = this.algorithm.init_stability(Rating.Easy);
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* @see https://github.com/open-spaced-repetition/ts-fsrs/issues/98#issuecomment-2241923194
|
|
1258
|
+
*/
|
|
1259
|
+
learningState(grade) {
|
|
1260
|
+
return this.reviewState(grade);
|
|
1261
|
+
}
|
|
1262
|
+
reviewState(grade) {
|
|
1263
|
+
const exist = this.next.get(grade);
|
|
1264
|
+
if (exist) {
|
|
1265
|
+
return exist;
|
|
1266
|
+
}
|
|
1267
|
+
const interval = this.elapsed_days;
|
|
1268
|
+
const { difficulty, stability } = this.last;
|
|
1269
|
+
const retrievability = this.algorithm.forgetting_curve(interval, stability);
|
|
1270
|
+
const next_again = TypeConvert.card(this.current);
|
|
1271
|
+
const next_hard = TypeConvert.card(this.current);
|
|
1272
|
+
const next_good = TypeConvert.card(this.current);
|
|
1273
|
+
const next_easy = TypeConvert.card(this.current);
|
|
1274
|
+
this.next_ds(
|
|
1275
|
+
next_again,
|
|
1276
|
+
next_hard,
|
|
1277
|
+
next_good,
|
|
1278
|
+
next_easy,
|
|
1279
|
+
difficulty,
|
|
1280
|
+
stability,
|
|
1281
|
+
retrievability
|
|
1282
|
+
);
|
|
1283
|
+
this.next_interval(next_again, next_hard, next_good, next_easy, interval);
|
|
1284
|
+
this.next_state(next_again, next_hard, next_good, next_easy);
|
|
1285
|
+
next_again.lapses += 1;
|
|
1286
|
+
this.update_next(next_again, next_hard, next_good, next_easy);
|
|
1287
|
+
return this.next.get(grade);
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Review next_ds
|
|
1291
|
+
*/
|
|
1292
|
+
next_ds(next_again, next_hard, next_good, next_easy, difficulty, stability, retrievability) {
|
|
1293
|
+
next_again.difficulty = this.algorithm.next_difficulty(
|
|
1294
|
+
difficulty,
|
|
1295
|
+
Rating.Again
|
|
1296
|
+
);
|
|
1297
|
+
const s_after_fail = this.algorithm.next_forget_stability(
|
|
1298
|
+
difficulty,
|
|
1299
|
+
stability,
|
|
1300
|
+
retrievability
|
|
1301
|
+
);
|
|
1302
|
+
next_again.stability = clamp(stability, S_MIN, s_after_fail);
|
|
1303
|
+
next_hard.difficulty = this.algorithm.next_difficulty(
|
|
1304
|
+
difficulty,
|
|
1305
|
+
Rating.Hard
|
|
1306
|
+
);
|
|
1307
|
+
next_hard.stability = this.algorithm.next_recall_stability(
|
|
1308
|
+
difficulty,
|
|
1309
|
+
stability,
|
|
1310
|
+
retrievability,
|
|
1311
|
+
Rating.Hard
|
|
1312
|
+
);
|
|
1313
|
+
next_good.difficulty = this.algorithm.next_difficulty(
|
|
1314
|
+
difficulty,
|
|
1315
|
+
Rating.Good
|
|
1316
|
+
);
|
|
1317
|
+
next_good.stability = this.algorithm.next_recall_stability(
|
|
1318
|
+
difficulty,
|
|
1319
|
+
stability,
|
|
1320
|
+
retrievability,
|
|
1321
|
+
Rating.Good
|
|
1322
|
+
);
|
|
1323
|
+
next_easy.difficulty = this.algorithm.next_difficulty(
|
|
1324
|
+
difficulty,
|
|
1325
|
+
Rating.Easy
|
|
1326
|
+
);
|
|
1327
|
+
next_easy.stability = this.algorithm.next_recall_stability(
|
|
1328
|
+
difficulty,
|
|
1329
|
+
stability,
|
|
1330
|
+
retrievability,
|
|
1331
|
+
Rating.Easy
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Review/New next_interval
|
|
1336
|
+
*/
|
|
1337
|
+
next_interval(next_again, next_hard, next_good, next_easy, interval) {
|
|
1338
|
+
let again_interval, hard_interval, good_interval, easy_interval;
|
|
1339
|
+
again_interval = this.algorithm.next_interval(
|
|
1340
|
+
next_again.stability,
|
|
1341
|
+
interval
|
|
1342
|
+
);
|
|
1343
|
+
hard_interval = this.algorithm.next_interval(next_hard.stability, interval);
|
|
1344
|
+
good_interval = this.algorithm.next_interval(next_good.stability, interval);
|
|
1345
|
+
easy_interval = this.algorithm.next_interval(next_easy.stability, interval);
|
|
1346
|
+
again_interval = Math.min(again_interval, hard_interval);
|
|
1347
|
+
hard_interval = Math.max(hard_interval, again_interval + 1);
|
|
1348
|
+
good_interval = Math.max(good_interval, hard_interval + 1);
|
|
1349
|
+
easy_interval = Math.max(easy_interval, good_interval + 1);
|
|
1350
|
+
next_again.scheduled_days = again_interval;
|
|
1351
|
+
next_again.due = date_scheduler(this.review_time, again_interval, true);
|
|
1352
|
+
next_hard.scheduled_days = hard_interval;
|
|
1353
|
+
next_hard.due = date_scheduler(this.review_time, hard_interval, true);
|
|
1354
|
+
next_good.scheduled_days = good_interval;
|
|
1355
|
+
next_good.due = date_scheduler(this.review_time, good_interval, true);
|
|
1356
|
+
next_easy.scheduled_days = easy_interval;
|
|
1357
|
+
next_easy.due = date_scheduler(this.review_time, easy_interval, true);
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Review/New next_state
|
|
1361
|
+
*/
|
|
1362
|
+
next_state(next_again, next_hard, next_good, next_easy) {
|
|
1363
|
+
next_again.state = State.Review;
|
|
1364
|
+
next_again.learning_steps = 0;
|
|
1365
|
+
next_hard.state = State.Review;
|
|
1366
|
+
next_hard.learning_steps = 0;
|
|
1367
|
+
next_good.state = State.Review;
|
|
1368
|
+
next_good.learning_steps = 0;
|
|
1369
|
+
next_easy.state = State.Review;
|
|
1370
|
+
next_easy.learning_steps = 0;
|
|
1371
|
+
}
|
|
1372
|
+
update_next(next_again, next_hard, next_good, next_easy) {
|
|
1373
|
+
const item_again = {
|
|
1374
|
+
card: next_again,
|
|
1375
|
+
log: this.buildLog(Rating.Again)
|
|
1376
|
+
};
|
|
1377
|
+
const item_hard = {
|
|
1378
|
+
card: next_hard,
|
|
1379
|
+
log: super.buildLog(Rating.Hard)
|
|
1380
|
+
};
|
|
1381
|
+
const item_good = {
|
|
1382
|
+
card: next_good,
|
|
1383
|
+
log: super.buildLog(Rating.Good)
|
|
1384
|
+
};
|
|
1385
|
+
const item_easy = {
|
|
1386
|
+
card: next_easy,
|
|
1387
|
+
log: super.buildLog(Rating.Easy)
|
|
1388
|
+
};
|
|
1389
|
+
this.next.set(Rating.Again, item_again);
|
|
1390
|
+
this.next.set(Rating.Hard, item_hard);
|
|
1391
|
+
this.next.set(Rating.Good, item_good);
|
|
1392
|
+
this.next.set(Rating.Easy, item_easy);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
var __defProp$1 = Object.defineProperty;
|
|
1397
|
+
var __defProps$1 = Object.defineProperties;
|
|
1398
|
+
var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors;
|
|
1399
|
+
var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
|
|
1400
|
+
var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
|
|
1401
|
+
var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
|
|
1402
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1403
|
+
var __spreadValues$1 = (a, b) => {
|
|
1404
|
+
for (var prop in b || (b = {}))
|
|
1405
|
+
if (__hasOwnProp$1.call(b, prop))
|
|
1406
|
+
__defNormalProp$1(a, prop, b[prop]);
|
|
1407
|
+
if (__getOwnPropSymbols$1)
|
|
1408
|
+
for (var prop of __getOwnPropSymbols$1(b)) {
|
|
1409
|
+
if (__propIsEnum$1.call(b, prop))
|
|
1410
|
+
__defNormalProp$1(a, prop, b[prop]);
|
|
1411
|
+
}
|
|
1412
|
+
return a;
|
|
1413
|
+
};
|
|
1414
|
+
var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b));
|
|
1415
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, key + "" , value);
|
|
1416
|
+
class Reschedule {
|
|
1417
|
+
/**
|
|
1418
|
+
* Creates an instance of the `Reschedule` class.
|
|
1419
|
+
* @param fsrs - An instance of the FSRS class used for scheduling.
|
|
1420
|
+
*/
|
|
1421
|
+
constructor(fsrs) {
|
|
1422
|
+
__publicField$1(this, "fsrs");
|
|
1423
|
+
this.fsrs = fsrs;
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Replays a review for a card and determines the next review date based on the given rating.
|
|
1427
|
+
* @param card - The card being reviewed.
|
|
1428
|
+
* @param reviewed - The date the card was reviewed.
|
|
1429
|
+
* @param rating - The grade given to the card during the review.
|
|
1430
|
+
* @returns A `RecordLogItem` containing the updated card and review log.
|
|
1431
|
+
*/
|
|
1432
|
+
replay(card, reviewed, rating) {
|
|
1433
|
+
return this.fsrs.next(card, reviewed, rating);
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Processes a manual review for a card, allowing for custom state, stability, difficulty, and due date.
|
|
1437
|
+
* @param card - The card being reviewed.
|
|
1438
|
+
* @param state - The state of the card after the review.
|
|
1439
|
+
* @param reviewed - The date the card was reviewed.
|
|
1440
|
+
* @param elapsed_days - The number of days since the last review.
|
|
1441
|
+
* @param stability - (Optional) The stability of the card.
|
|
1442
|
+
* @param difficulty - (Optional) The difficulty of the card.
|
|
1443
|
+
* @param due - (Optional) The due date for the next review.
|
|
1444
|
+
* @returns A `RecordLogItem` containing the updated card and review log.
|
|
1445
|
+
* @throws Will throw an error if the state or due date is not provided when required.
|
|
1446
|
+
*/
|
|
1447
|
+
handleManualRating(card, state, reviewed, elapsed_days, stability, difficulty, due) {
|
|
1448
|
+
if (typeof state === "undefined") {
|
|
1449
|
+
throw new Error("reschedule: state is required for manual rating");
|
|
1450
|
+
}
|
|
1451
|
+
let log;
|
|
1452
|
+
let next_card;
|
|
1453
|
+
if (state === State.New) {
|
|
1454
|
+
log = {
|
|
1455
|
+
rating: Rating.Manual,
|
|
1456
|
+
state,
|
|
1457
|
+
due: due != null ? due : reviewed,
|
|
1458
|
+
stability: card.stability,
|
|
1459
|
+
difficulty: card.difficulty,
|
|
1460
|
+
elapsed_days,
|
|
1461
|
+
last_elapsed_days: card.elapsed_days,
|
|
1462
|
+
scheduled_days: card.scheduled_days,
|
|
1463
|
+
learning_steps: card.learning_steps,
|
|
1464
|
+
review: reviewed
|
|
1465
|
+
};
|
|
1466
|
+
next_card = createEmptyCard(reviewed);
|
|
1467
|
+
next_card.last_review = reviewed;
|
|
1468
|
+
} else {
|
|
1469
|
+
if (typeof due === "undefined") {
|
|
1470
|
+
throw new Error("reschedule: due is required for manual rating");
|
|
1471
|
+
}
|
|
1472
|
+
const scheduled_days = date_diff(due, reviewed, "days");
|
|
1473
|
+
log = {
|
|
1474
|
+
rating: Rating.Manual,
|
|
1475
|
+
state: card.state,
|
|
1476
|
+
due: card.last_review || card.due,
|
|
1477
|
+
stability: card.stability,
|
|
1478
|
+
difficulty: card.difficulty,
|
|
1479
|
+
elapsed_days,
|
|
1480
|
+
last_elapsed_days: card.elapsed_days,
|
|
1481
|
+
scheduled_days: card.scheduled_days,
|
|
1482
|
+
learning_steps: card.learning_steps,
|
|
1483
|
+
review: reviewed
|
|
1484
|
+
};
|
|
1485
|
+
next_card = __spreadProps$1(__spreadValues$1({}, card), {
|
|
1486
|
+
state,
|
|
1487
|
+
due,
|
|
1488
|
+
last_review: reviewed,
|
|
1489
|
+
stability: stability || card.stability,
|
|
1490
|
+
difficulty: difficulty || card.difficulty,
|
|
1491
|
+
elapsed_days,
|
|
1492
|
+
scheduled_days,
|
|
1493
|
+
reps: card.reps + 1
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
return { card: next_card, log };
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Reschedules a card based on its review history.
|
|
1500
|
+
*
|
|
1501
|
+
* @param current_card - The card to be rescheduled.
|
|
1502
|
+
* @param reviews - An array of review history objects.
|
|
1503
|
+
* @returns An array of record log items representing the rescheduling process.
|
|
1504
|
+
*/
|
|
1505
|
+
reschedule(current_card, reviews) {
|
|
1506
|
+
const collections = [];
|
|
1507
|
+
let cur_card = createEmptyCard(current_card.due);
|
|
1508
|
+
for (const review of reviews) {
|
|
1509
|
+
let item;
|
|
1510
|
+
review.review = TypeConvert.time(review.review);
|
|
1511
|
+
if (review.rating === Rating.Manual) {
|
|
1512
|
+
let interval = 0;
|
|
1513
|
+
if (cur_card.state !== State.New && cur_card.last_review) {
|
|
1514
|
+
interval = date_diff(review.review, cur_card.last_review, "days");
|
|
1515
|
+
}
|
|
1516
|
+
item = this.handleManualRating(
|
|
1517
|
+
cur_card,
|
|
1518
|
+
review.state,
|
|
1519
|
+
review.review,
|
|
1520
|
+
interval,
|
|
1521
|
+
review.stability,
|
|
1522
|
+
review.difficulty,
|
|
1523
|
+
review.due ? TypeConvert.time(review.due) : void 0
|
|
1524
|
+
);
|
|
1525
|
+
} else {
|
|
1526
|
+
item = this.replay(cur_card, review.review, review.rating);
|
|
1527
|
+
}
|
|
1528
|
+
collections.push(item);
|
|
1529
|
+
cur_card = item.card;
|
|
1530
|
+
}
|
|
1531
|
+
return collections;
|
|
1532
|
+
}
|
|
1533
|
+
calculateManualRecord(current_card, now, record_log_item, update_memory) {
|
|
1534
|
+
if (!record_log_item) {
|
|
1535
|
+
return null;
|
|
1536
|
+
}
|
|
1537
|
+
const { card: reschedule_card, log } = record_log_item;
|
|
1538
|
+
const cur_card = TypeConvert.card(current_card);
|
|
1539
|
+
if (cur_card.due.getTime() === reschedule_card.due.getTime()) {
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
cur_card.scheduled_days = date_diff(
|
|
1543
|
+
reschedule_card.due,
|
|
1544
|
+
cur_card.due,
|
|
1545
|
+
"days"
|
|
1546
|
+
);
|
|
1547
|
+
return this.handleManualRating(
|
|
1548
|
+
cur_card,
|
|
1549
|
+
reschedule_card.state,
|
|
1550
|
+
TypeConvert.time(now),
|
|
1551
|
+
log.elapsed_days,
|
|
1552
|
+
update_memory ? reschedule_card.stability : void 0,
|
|
1553
|
+
update_memory ? reschedule_card.difficulty : void 0,
|
|
1554
|
+
reschedule_card.due
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
var __defProp = Object.defineProperty;
|
|
1560
|
+
var __defProps = Object.defineProperties;
|
|
1561
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
1562
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
1563
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
1564
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
1565
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1566
|
+
var __spreadValues = (a, b) => {
|
|
1567
|
+
for (var prop in b || (b = {}))
|
|
1568
|
+
if (__hasOwnProp.call(b, prop))
|
|
1569
|
+
__defNormalProp(a, prop, b[prop]);
|
|
1570
|
+
if (__getOwnPropSymbols)
|
|
1571
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
1572
|
+
if (__propIsEnum.call(b, prop))
|
|
1573
|
+
__defNormalProp(a, prop, b[prop]);
|
|
1574
|
+
}
|
|
1575
|
+
return a;
|
|
1576
|
+
};
|
|
1577
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
1578
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1579
|
+
class FSRS extends FSRSAlgorithm {
|
|
1580
|
+
constructor(param) {
|
|
1581
|
+
super(param);
|
|
1582
|
+
__publicField(this, "strategyHandler", /* @__PURE__ */ new Map());
|
|
1583
|
+
__publicField(this, "Scheduler");
|
|
1584
|
+
const { enable_short_term } = this.parameters;
|
|
1585
|
+
this.Scheduler = enable_short_term ? BasicScheduler : LongTermScheduler;
|
|
1586
|
+
}
|
|
1587
|
+
params_handler_proxy() {
|
|
1588
|
+
const _this = this;
|
|
1589
|
+
return {
|
|
1590
|
+
set: function(target, prop, value) {
|
|
1591
|
+
if (prop === "request_retention" && Number.isFinite(value)) {
|
|
1592
|
+
_this.intervalModifier = _this.calculate_interval_modifier(
|
|
1593
|
+
Number(value)
|
|
1594
|
+
);
|
|
1595
|
+
} else if (prop === "enable_short_term") {
|
|
1596
|
+
_this.Scheduler = value === true ? BasicScheduler : LongTermScheduler;
|
|
1597
|
+
} else if (prop === "w") {
|
|
1598
|
+
value = migrateParameters(
|
|
1599
|
+
value,
|
|
1600
|
+
target.relearning_steps.length,
|
|
1601
|
+
target.enable_short_term
|
|
1602
|
+
);
|
|
1603
|
+
_this.forgetting_curve = forgetting_curve.bind(this, value);
|
|
1604
|
+
_this.intervalModifier = _this.calculate_interval_modifier(
|
|
1605
|
+
Number(target.request_retention)
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
Reflect.set(target, prop, value);
|
|
1609
|
+
return true;
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
useStrategy(mode, handler) {
|
|
1614
|
+
this.strategyHandler.set(mode, handler);
|
|
1615
|
+
return this;
|
|
1616
|
+
}
|
|
1617
|
+
clearStrategy(mode) {
|
|
1618
|
+
if (mode) {
|
|
1619
|
+
this.strategyHandler.delete(mode);
|
|
1620
|
+
} else {
|
|
1621
|
+
this.strategyHandler.clear();
|
|
1622
|
+
}
|
|
1623
|
+
return this;
|
|
1624
|
+
}
|
|
1625
|
+
getScheduler(card, now) {
|
|
1626
|
+
const schedulerStrategy = this.strategyHandler.get(
|
|
1627
|
+
StrategyMode.SCHEDULER
|
|
1628
|
+
);
|
|
1629
|
+
const Scheduler = schedulerStrategy || this.Scheduler;
|
|
1630
|
+
const instance = new Scheduler(card, now, this, this.strategyHandler);
|
|
1631
|
+
return instance;
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Display the collection of cards and logs for the four scenarios after scheduling the card at the current time.
|
|
1635
|
+
* @param card Card to be processed
|
|
1636
|
+
* @param now Current time or scheduled time
|
|
1637
|
+
* @param afterHandler Convert the result to another type. (Optional)
|
|
1638
|
+
* @example
|
|
1639
|
+
* ```typescript
|
|
1640
|
+
* const card: Card = createEmptyCard(new Date());
|
|
1641
|
+
* const f = fsrs();
|
|
1642
|
+
* const recordLog = f.repeat(card, new Date());
|
|
1643
|
+
* ```
|
|
1644
|
+
* @example
|
|
1645
|
+
* ```typescript
|
|
1646
|
+
* interface RevLogUnchecked
|
|
1647
|
+
* extends Omit<ReviewLog, "due" | "review" | "state" | "rating"> {
|
|
1648
|
+
* cid: string;
|
|
1649
|
+
* due: Date | number;
|
|
1650
|
+
* state: StateType;
|
|
1651
|
+
* review: Date | number;
|
|
1652
|
+
* rating: RatingType;
|
|
1653
|
+
* }
|
|
1654
|
+
*
|
|
1655
|
+
* interface RepeatRecordLog {
|
|
1656
|
+
* card: CardUnChecked; //see method: createEmptyCard
|
|
1657
|
+
* log: RevLogUnchecked;
|
|
1658
|
+
* }
|
|
1659
|
+
*
|
|
1660
|
+
* function repeatAfterHandler(recordLog: RecordLog) {
|
|
1661
|
+
* const record: { [key in Grade]: RepeatRecordLog } = {} as {
|
|
1662
|
+
* [key in Grade]: RepeatRecordLog;
|
|
1663
|
+
* };
|
|
1664
|
+
* for (const grade of Grades) {
|
|
1665
|
+
* record[grade] = {
|
|
1666
|
+
* card: {
|
|
1667
|
+
* ...(recordLog[grade].card as Card & { cid: string }),
|
|
1668
|
+
* due: recordLog[grade].card.due.getTime(),
|
|
1669
|
+
* state: State[recordLog[grade].card.state] as StateType,
|
|
1670
|
+
* last_review: recordLog[grade].card.last_review
|
|
1671
|
+
* ? recordLog[grade].card.last_review!.getTime()
|
|
1672
|
+
* : null,
|
|
1673
|
+
* },
|
|
1674
|
+
* log: {
|
|
1675
|
+
* ...recordLog[grade].log,
|
|
1676
|
+
* cid: (recordLog[grade].card as Card & { cid: string }).cid,
|
|
1677
|
+
* due: recordLog[grade].log.due.getTime(),
|
|
1678
|
+
* review: recordLog[grade].log.review.getTime(),
|
|
1679
|
+
* state: State[recordLog[grade].log.state] as StateType,
|
|
1680
|
+
* rating: Rating[recordLog[grade].log.rating] as RatingType,
|
|
1681
|
+
* },
|
|
1682
|
+
* };
|
|
1683
|
+
* }
|
|
1684
|
+
* return record;
|
|
1685
|
+
* }
|
|
1686
|
+
* const card: Card = createEmptyCard(new Date(), cardAfterHandler); //see method: createEmptyCard
|
|
1687
|
+
* const f = fsrs();
|
|
1688
|
+
* const recordLog = f.repeat(card, new Date(), repeatAfterHandler);
|
|
1689
|
+
* ```
|
|
1690
|
+
*/
|
|
1691
|
+
repeat(card, now, afterHandler) {
|
|
1692
|
+
const instance = this.getScheduler(card, now);
|
|
1693
|
+
const recordLog = instance.preview();
|
|
1694
|
+
if (afterHandler && typeof afterHandler === "function") {
|
|
1695
|
+
return afterHandler(recordLog);
|
|
1696
|
+
} else {
|
|
1697
|
+
return recordLog;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Display the collection of cards and logs for the card scheduled at the current time, after applying a specific grade rating.
|
|
1702
|
+
* @param card Card to be processed
|
|
1703
|
+
* @param now Current time or scheduled time
|
|
1704
|
+
* @param grade Rating of the review (Again, Hard, Good, Easy)
|
|
1705
|
+
* @param afterHandler Convert the result to another type. (Optional)
|
|
1706
|
+
* @example
|
|
1707
|
+
* ```typescript
|
|
1708
|
+
* const card: Card = createEmptyCard(new Date());
|
|
1709
|
+
* const f = fsrs();
|
|
1710
|
+
* const recordLogItem = f.next(card, new Date(), Rating.Again);
|
|
1711
|
+
* ```
|
|
1712
|
+
* @example
|
|
1713
|
+
* ```typescript
|
|
1714
|
+
* interface RevLogUnchecked
|
|
1715
|
+
* extends Omit<ReviewLog, "due" | "review" | "state" | "rating"> {
|
|
1716
|
+
* cid: string;
|
|
1717
|
+
* due: Date | number;
|
|
1718
|
+
* state: StateType;
|
|
1719
|
+
* review: Date | number;
|
|
1720
|
+
* rating: RatingType;
|
|
1721
|
+
* }
|
|
1722
|
+
*
|
|
1723
|
+
* interface NextRecordLog {
|
|
1724
|
+
* card: CardUnChecked; //see method: createEmptyCard
|
|
1725
|
+
* log: RevLogUnchecked;
|
|
1726
|
+
* }
|
|
1727
|
+
*
|
|
1728
|
+
function nextAfterHandler(recordLogItem: RecordLogItem) {
|
|
1729
|
+
const recordItem = {
|
|
1730
|
+
card: {
|
|
1731
|
+
...(recordLogItem.card as Card & { cid: string }),
|
|
1732
|
+
due: recordLogItem.card.due.getTime(),
|
|
1733
|
+
state: State[recordLogItem.card.state] as StateType,
|
|
1734
|
+
last_review: recordLogItem.card.last_review
|
|
1735
|
+
? recordLogItem.card.last_review!.getTime()
|
|
1736
|
+
: null,
|
|
1737
|
+
},
|
|
1738
|
+
log: {
|
|
1739
|
+
...recordLogItem.log,
|
|
1740
|
+
cid: (recordLogItem.card as Card & { cid: string }).cid,
|
|
1741
|
+
due: recordLogItem.log.due.getTime(),
|
|
1742
|
+
review: recordLogItem.log.review.getTime(),
|
|
1743
|
+
state: State[recordLogItem.log.state] as StateType,
|
|
1744
|
+
rating: Rating[recordLogItem.log.rating] as RatingType,
|
|
1745
|
+
},
|
|
1746
|
+
};
|
|
1747
|
+
return recordItem
|
|
1748
|
+
}
|
|
1749
|
+
* const card: Card = createEmptyCard(new Date(), cardAfterHandler); //see method: createEmptyCard
|
|
1750
|
+
* const f = fsrs();
|
|
1751
|
+
* const recordLogItem = f.repeat(card, new Date(), Rating.Again, nextAfterHandler);
|
|
1752
|
+
* ```
|
|
1753
|
+
*/
|
|
1754
|
+
next(card, now, grade, afterHandler) {
|
|
1755
|
+
const instance = this.getScheduler(card, now);
|
|
1756
|
+
const g = TypeConvert.rating(grade);
|
|
1757
|
+
if (g === Rating.Manual) {
|
|
1758
|
+
throw new Error("Cannot review a manual rating");
|
|
1759
|
+
}
|
|
1760
|
+
const recordLogItem = instance.review(g);
|
|
1761
|
+
if (afterHandler && typeof afterHandler === "function") {
|
|
1762
|
+
return afterHandler(recordLogItem);
|
|
1763
|
+
} else {
|
|
1764
|
+
return recordLogItem;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Get the retrievability of the card
|
|
1769
|
+
* @param card Card to be processed
|
|
1770
|
+
* @param now Current time or scheduled time
|
|
1771
|
+
* @param format default:true , Convert the result to another type. (Optional)
|
|
1772
|
+
* @returns The retrievability of the card,if format is true, the result is a string, otherwise it is a number
|
|
1773
|
+
*/
|
|
1774
|
+
get_retrievability(card, now, format = true) {
|
|
1775
|
+
const processedCard = TypeConvert.card(card);
|
|
1776
|
+
now = now ? TypeConvert.time(now) : /* @__PURE__ */ new Date();
|
|
1777
|
+
const t = processedCard.state !== State.New ? Math.max(date_diff(now, processedCard.last_review, "days"), 0) : 0;
|
|
1778
|
+
const r = processedCard.state !== State.New ? this.forgetting_curve(t, +processedCard.stability.toFixed(8)) : 0;
|
|
1779
|
+
return format ? `${(r * 100).toFixed(2)}%` : r;
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
*
|
|
1783
|
+
* @param card Card to be processed
|
|
1784
|
+
* @param log last review log
|
|
1785
|
+
* @param afterHandler Convert the result to another type. (Optional)
|
|
1786
|
+
* @example
|
|
1787
|
+
* ```typescript
|
|
1788
|
+
* const now = new Date();
|
|
1789
|
+
* const f = fsrs();
|
|
1790
|
+
* const emptyCardFormAfterHandler = createEmptyCard(now);
|
|
1791
|
+
* const repeatFormAfterHandler = f.repeat(emptyCardFormAfterHandler, now);
|
|
1792
|
+
* const { card, log } = repeatFormAfterHandler[Rating.Hard];
|
|
1793
|
+
* const rollbackFromAfterHandler = f.rollback(card, log);
|
|
1794
|
+
* ```
|
|
1795
|
+
*
|
|
1796
|
+
* @example
|
|
1797
|
+
* ```typescript
|
|
1798
|
+
* const now = new Date();
|
|
1799
|
+
* const f = fsrs();
|
|
1800
|
+
* const emptyCardFormAfterHandler = createEmptyCard(now, cardAfterHandler); //see method: createEmptyCard
|
|
1801
|
+
* const repeatFormAfterHandler = f.repeat(emptyCardFormAfterHandler, now, repeatAfterHandler); //see method: fsrs.repeat()
|
|
1802
|
+
* const { card, log } = repeatFormAfterHandler[Rating.Hard];
|
|
1803
|
+
* const rollbackFromAfterHandler = f.rollback(card, log, cardAfterHandler);
|
|
1804
|
+
* ```
|
|
1805
|
+
*/
|
|
1806
|
+
rollback(card, log, afterHandler) {
|
|
1807
|
+
const processedCard = TypeConvert.card(card);
|
|
1808
|
+
const processedLog = TypeConvert.review_log(log);
|
|
1809
|
+
if (processedLog.rating === Rating.Manual) {
|
|
1810
|
+
throw new Error("Cannot rollback a manual rating");
|
|
1811
|
+
}
|
|
1812
|
+
let last_due;
|
|
1813
|
+
let last_review;
|
|
1814
|
+
let last_lapses;
|
|
1815
|
+
switch (processedLog.state) {
|
|
1816
|
+
case State.New:
|
|
1817
|
+
last_due = processedLog.due;
|
|
1818
|
+
last_review = void 0;
|
|
1819
|
+
last_lapses = 0;
|
|
1820
|
+
break;
|
|
1821
|
+
case State.Learning:
|
|
1822
|
+
case State.Relearning:
|
|
1823
|
+
case State.Review:
|
|
1824
|
+
last_due = processedLog.review;
|
|
1825
|
+
last_review = processedLog.due;
|
|
1826
|
+
last_lapses = processedCard.lapses - (processedLog.rating === Rating.Again && processedLog.state === State.Review ? 1 : 0);
|
|
1827
|
+
break;
|
|
1828
|
+
}
|
|
1829
|
+
const prevCard = __spreadProps(__spreadValues({}, processedCard), {
|
|
1830
|
+
due: last_due,
|
|
1831
|
+
stability: processedLog.stability,
|
|
1832
|
+
difficulty: processedLog.difficulty,
|
|
1833
|
+
elapsed_days: processedLog.last_elapsed_days,
|
|
1834
|
+
scheduled_days: processedLog.scheduled_days,
|
|
1835
|
+
reps: Math.max(0, processedCard.reps - 1),
|
|
1836
|
+
lapses: Math.max(0, last_lapses),
|
|
1837
|
+
learning_steps: processedLog.learning_steps,
|
|
1838
|
+
state: processedLog.state,
|
|
1839
|
+
last_review
|
|
1840
|
+
});
|
|
1841
|
+
if (afterHandler && typeof afterHandler === "function") {
|
|
1842
|
+
return afterHandler(prevCard);
|
|
1843
|
+
} else {
|
|
1844
|
+
return prevCard;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
*
|
|
1849
|
+
* @param card Card to be processed
|
|
1850
|
+
* @param now Current time or scheduled time
|
|
1851
|
+
* @param reset_count Should the review count information(reps,lapses) be reset. (Optional)
|
|
1852
|
+
* @param afterHandler Convert the result to another type. (Optional)
|
|
1853
|
+
* @example
|
|
1854
|
+
* ```typescript
|
|
1855
|
+
* const now = new Date();
|
|
1856
|
+
* const f = fsrs();
|
|
1857
|
+
* const emptyCard = createEmptyCard(now);
|
|
1858
|
+
* const scheduling_cards = f.repeat(emptyCard, now);
|
|
1859
|
+
* const { card, log } = scheduling_cards[Rating.Hard];
|
|
1860
|
+
* const forgetCard = f.forget(card, new Date(), true);
|
|
1861
|
+
* ```
|
|
1862
|
+
*
|
|
1863
|
+
* @example
|
|
1864
|
+
* ```typescript
|
|
1865
|
+
* interface RepeatRecordLog {
|
|
1866
|
+
* card: CardUnChecked; //see method: createEmptyCard
|
|
1867
|
+
* log: RevLogUnchecked; //see method: fsrs.repeat()
|
|
1868
|
+
* }
|
|
1869
|
+
*
|
|
1870
|
+
* function forgetAfterHandler(recordLogItem: RecordLogItem): RepeatRecordLog {
|
|
1871
|
+
* return {
|
|
1872
|
+
* card: {
|
|
1873
|
+
* ...(recordLogItem.card as Card & { cid: string }),
|
|
1874
|
+
* due: recordLogItem.card.due.getTime(),
|
|
1875
|
+
* state: State[recordLogItem.card.state] as StateType,
|
|
1876
|
+
* last_review: recordLogItem.card.last_review
|
|
1877
|
+
* ? recordLogItem.card.last_review!.getTime()
|
|
1878
|
+
* : null,
|
|
1879
|
+
* },
|
|
1880
|
+
* log: {
|
|
1881
|
+
* ...recordLogItem.log,
|
|
1882
|
+
* cid: (recordLogItem.card as Card & { cid: string }).cid,
|
|
1883
|
+
* due: recordLogItem.log.due.getTime(),
|
|
1884
|
+
* review: recordLogItem.log.review.getTime(),
|
|
1885
|
+
* state: State[recordLogItem.log.state] as StateType,
|
|
1886
|
+
* rating: Rating[recordLogItem.log.rating] as RatingType,
|
|
1887
|
+
* },
|
|
1888
|
+
* };
|
|
1889
|
+
* }
|
|
1890
|
+
* const now = new Date();
|
|
1891
|
+
* const f = fsrs();
|
|
1892
|
+
* const emptyCardFormAfterHandler = createEmptyCard(now, cardAfterHandler); //see method: createEmptyCard
|
|
1893
|
+
* const repeatFormAfterHandler = f.repeat(emptyCardFormAfterHandler, now, repeatAfterHandler); //see method: fsrs.repeat()
|
|
1894
|
+
* const { card } = repeatFormAfterHandler[Rating.Hard];
|
|
1895
|
+
* const forgetFromAfterHandler = f.forget(card, date_scheduler(now, 1, true), false, forgetAfterHandler);
|
|
1896
|
+
* ```
|
|
1897
|
+
*/
|
|
1898
|
+
forget(card, now, reset_count = false, afterHandler) {
|
|
1899
|
+
const processedCard = TypeConvert.card(card);
|
|
1900
|
+
now = TypeConvert.time(now);
|
|
1901
|
+
const scheduled_days = processedCard.state === State.New ? 0 : date_diff(now, processedCard.due, "days");
|
|
1902
|
+
const forget_log = {
|
|
1903
|
+
rating: Rating.Manual,
|
|
1904
|
+
state: processedCard.state,
|
|
1905
|
+
due: processedCard.due,
|
|
1906
|
+
stability: processedCard.stability,
|
|
1907
|
+
difficulty: processedCard.difficulty,
|
|
1908
|
+
elapsed_days: 0,
|
|
1909
|
+
last_elapsed_days: processedCard.elapsed_days,
|
|
1910
|
+
scheduled_days,
|
|
1911
|
+
learning_steps: processedCard.learning_steps,
|
|
1912
|
+
review: now
|
|
1913
|
+
};
|
|
1914
|
+
const forget_card = __spreadProps(__spreadValues({}, processedCard), {
|
|
1915
|
+
due: now,
|
|
1916
|
+
stability: 0,
|
|
1917
|
+
difficulty: 0,
|
|
1918
|
+
elapsed_days: 0,
|
|
1919
|
+
scheduled_days: 0,
|
|
1920
|
+
reps: reset_count ? 0 : processedCard.reps,
|
|
1921
|
+
lapses: reset_count ? 0 : processedCard.lapses,
|
|
1922
|
+
learning_steps: 0,
|
|
1923
|
+
state: State.New,
|
|
1924
|
+
last_review: processedCard.last_review
|
|
1925
|
+
});
|
|
1926
|
+
const recordLogItem = { card: forget_card, log: forget_log };
|
|
1927
|
+
if (afterHandler && typeof afterHandler === "function") {
|
|
1928
|
+
return afterHandler(recordLogItem);
|
|
1929
|
+
} else {
|
|
1930
|
+
return recordLogItem;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Reschedules the current card and returns the rescheduled collections and reschedule item.
|
|
1935
|
+
*
|
|
1936
|
+
* @template T - The type of the record log item.
|
|
1937
|
+
* @param {CardInput | Card} current_card - The current card to be rescheduled.
|
|
1938
|
+
* @param {Array<FSRSHistory>} reviews - The array of FSRSHistory objects representing the reviews.
|
|
1939
|
+
* @param {Partial<RescheduleOptions<T>>} options - The optional reschedule options.
|
|
1940
|
+
* @returns {IReschedule<T>} - The rescheduled collections and reschedule item.
|
|
1941
|
+
*
|
|
1942
|
+
* @example
|
|
1943
|
+
* ```typescript
|
|
1944
|
+
* const f = fsrs()
|
|
1945
|
+
* const grades: Grade[] = [Rating.Good, Rating.Good, Rating.Good, Rating.Good]
|
|
1946
|
+
* const reviews_at = [
|
|
1947
|
+
* new Date(2024, 8, 13),
|
|
1948
|
+
* new Date(2024, 8, 13),
|
|
1949
|
+
* new Date(2024, 8, 17),
|
|
1950
|
+
* new Date(2024, 8, 28),
|
|
1951
|
+
* ]
|
|
1952
|
+
*
|
|
1953
|
+
* const reviews: FSRSHistory[] = []
|
|
1954
|
+
* for (let i = 0; i < grades.length; i++) {
|
|
1955
|
+
* reviews.push({
|
|
1956
|
+
* rating: grades[i],
|
|
1957
|
+
* review: reviews_at[i],
|
|
1958
|
+
* })
|
|
1959
|
+
* }
|
|
1960
|
+
*
|
|
1961
|
+
* const results_short = scheduler.reschedule(
|
|
1962
|
+
* createEmptyCard(),
|
|
1963
|
+
* reviews,
|
|
1964
|
+
* {
|
|
1965
|
+
* skipManual: false,
|
|
1966
|
+
* }
|
|
1967
|
+
* )
|
|
1968
|
+
* console.log(results_short)
|
|
1969
|
+
* ```
|
|
1970
|
+
*/
|
|
1971
|
+
reschedule(current_card, reviews = [], options = {}) {
|
|
1972
|
+
const {
|
|
1973
|
+
recordLogHandler,
|
|
1974
|
+
reviewsOrderBy,
|
|
1975
|
+
skipManual = true,
|
|
1976
|
+
now = /* @__PURE__ */ new Date(),
|
|
1977
|
+
update_memory_state: updateMemoryState = false
|
|
1978
|
+
} = options;
|
|
1979
|
+
if (reviewsOrderBy && typeof reviewsOrderBy === "function") {
|
|
1980
|
+
reviews.sort(reviewsOrderBy);
|
|
1981
|
+
}
|
|
1982
|
+
if (skipManual) {
|
|
1983
|
+
reviews = reviews.filter((review) => review.rating !== Rating.Manual);
|
|
1984
|
+
}
|
|
1985
|
+
const rescheduleSvc = new Reschedule(this);
|
|
1986
|
+
const collections = rescheduleSvc.reschedule(
|
|
1987
|
+
options.first_card || createEmptyCard(),
|
|
1988
|
+
reviews
|
|
1989
|
+
);
|
|
1990
|
+
const len = collections.length;
|
|
1991
|
+
const cur_card = TypeConvert.card(current_card);
|
|
1992
|
+
const manual_item = rescheduleSvc.calculateManualRecord(
|
|
1993
|
+
cur_card,
|
|
1994
|
+
now,
|
|
1995
|
+
len ? collections[len - 1] : void 0,
|
|
1996
|
+
updateMemoryState
|
|
1997
|
+
);
|
|
1998
|
+
if (recordLogHandler && typeof recordLogHandler === "function") {
|
|
1999
|
+
return {
|
|
2000
|
+
collections: collections.map(recordLogHandler),
|
|
2001
|
+
reschedule_item: manual_item ? recordLogHandler(manual_item) : null
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
return {
|
|
2005
|
+
collections,
|
|
2006
|
+
reschedule_item: manual_item
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
const fsrs = (params) => {
|
|
2011
|
+
return new FSRS(params || {});
|
|
2012
|
+
};
|
|
2013
|
+
|
|
2014
|
+
exports.AbstractScheduler = AbstractScheduler;
|
|
2015
|
+
exports.BasicLearningStepsStrategy = BasicLearningStepsStrategy;
|
|
2016
|
+
exports.CLAMP_PARAMETERS = CLAMP_PARAMETERS;
|
|
2017
|
+
exports.ConvertStepUnitToMinutes = ConvertStepUnitToMinutes;
|
|
2018
|
+
exports.DefaultInitSeedStrategy = DefaultInitSeedStrategy;
|
|
2019
|
+
exports.FSRS = FSRS;
|
|
2020
|
+
exports.FSRS5_DEFAULT_DECAY = FSRS5_DEFAULT_DECAY;
|
|
2021
|
+
exports.FSRS6_DEFAULT_DECAY = FSRS6_DEFAULT_DECAY;
|
|
2022
|
+
exports.FSRSAlgorithm = FSRSAlgorithm;
|
|
2023
|
+
exports.FSRSVersion = FSRSVersion;
|
|
2024
|
+
exports.GenSeedStrategyWithCardId = GenSeedStrategyWithCardId;
|
|
2025
|
+
exports.Grades = Grades;
|
|
2026
|
+
exports.INIT_S_MAX = INIT_S_MAX;
|
|
2027
|
+
exports.Rating = Rating;
|
|
2028
|
+
exports.S_MAX = S_MAX;
|
|
2029
|
+
exports.S_MIN = S_MIN;
|
|
2030
|
+
exports.State = State;
|
|
2031
|
+
exports.StrategyMode = StrategyMode;
|
|
2032
|
+
exports.TypeConvert = TypeConvert;
|
|
2033
|
+
exports.W17_W18_Ceiling = W17_W18_Ceiling;
|
|
2034
|
+
exports.checkParameters = checkParameters;
|
|
2035
|
+
exports.clamp = clamp;
|
|
2036
|
+
exports.clipParameters = clipParameters;
|
|
2037
|
+
exports.computeDecayFactor = computeDecayFactor;
|
|
2038
|
+
exports.createEmptyCard = createEmptyCard;
|
|
2039
|
+
exports.dateDiffInDays = dateDiffInDays;
|
|
2040
|
+
exports.date_diff = date_diff;
|
|
2041
|
+
exports.date_scheduler = date_scheduler;
|
|
2042
|
+
exports.default_enable_fuzz = default_enable_fuzz;
|
|
2043
|
+
exports.default_enable_short_term = default_enable_short_term;
|
|
2044
|
+
exports.default_learning_steps = default_learning_steps;
|
|
2045
|
+
exports.default_maximum_interval = default_maximum_interval;
|
|
2046
|
+
exports.default_relearning_steps = default_relearning_steps;
|
|
2047
|
+
exports.default_request_retention = default_request_retention;
|
|
2048
|
+
exports.default_w = default_w;
|
|
2049
|
+
exports.fixDate = fixDate;
|
|
2050
|
+
exports.fixRating = fixRating;
|
|
2051
|
+
exports.fixState = fixState;
|
|
2052
|
+
exports.forgetting_curve = forgetting_curve;
|
|
2053
|
+
exports.formatDate = formatDate;
|
|
2054
|
+
exports.fsrs = fsrs;
|
|
2055
|
+
exports.generatorParameters = generatorParameters;
|
|
2056
|
+
exports.get_fuzz_range = get_fuzz_range;
|
|
2057
|
+
exports.migrateParameters = migrateParameters;
|
|
2058
|
+
exports.show_diff_message = show_diff_message;
|
|
2059
|
+
|
|
2060
|
+
}));
|
|
2
2061
|
//# sourceMappingURL=index.umd.js.map
|