active_recall 2.3.0 → 2.3.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63a48f6351a76013bbf2922ca75b507b6dccb1ab23867896217b53bc6a77e26f
4
- data.tar.gz: 9d8e3189bb8f5b1f15b284168e5973e87070bf223c0427b5a574cacd38c8e4b0
3
+ metadata.gz: 25a05ad2ad88bc270c9e58cb8a2760b280677fb6d1d3c35ee76df8b021e1c6fd
4
+ data.tar.gz: 1503c4d5cc0863a42d51a084c7651d79fe574f9d98fb60254fd2084b6cf4ec13
5
5
  SHA512:
6
- metadata.gz: efe496383029f35391f1b54533f525451c5ee8a02e440316b990b812923b2061fc94eeff2ed1d691b17a1838b2d266b51ec9dde7d3305d511b484edb53b606b7
7
- data.tar.gz: ff9f67e94b77d93175af60926ad889655862989c18a9b2fb07b8e75503526b8281f32cd3a982ab1d70933363454e40461a10dd2be72784bc5097243173ff5b1e
6
+ metadata.gz: aab226bca430aac827eb11c7c46b1765f6b367181e233a614748f509640589306d86cd3fc4ad32655b431f4b49d5beaa163f056b7dfb09ccd51a17e5d727cc9b
7
+ data.tar.gz: c630d5a2fe83551b8929ef1a9f22b9408c908f3cb90a376b56dd056cdfa90e0c530f0c6aac02cae4d3a6d6891b457db6022258cf549687241783954a61ceb65f
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_recall (2.3.0)
4
+ active_recall (2.3.1)
5
5
  activerecord (>= 7.0, < 9.0)
6
6
  activesupport (>= 7.0, < 9.0)
7
+ fsrs (>= 0.9.2, < 1.0)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
@@ -98,6 +99,8 @@ GEM
98
99
  drb (2.2.0)
99
100
  ruby2_keywords
100
101
  erubi (1.12.0)
102
+ fsrs (0.9.2)
103
+ activesupport (>= 7.0, < 9.0)
101
104
  globalid (1.2.1)
102
105
  activesupport (>= 6.1)
103
106
  i18n (1.14.1)
@@ -41,5 +41,9 @@ Gem::Specification.new do |spec|
41
41
  spec.add_development_dependency "standard"
42
42
  spec.add_runtime_dependency "activerecord", ">= 7.0", "< 9.0"
43
43
  spec.add_runtime_dependency "activesupport", ">= 7.0", "< 9.0"
44
+ # fsrs 0.9.2 is the first release that widens activesupport to allow Rails 8
45
+ # and ships the new-card scheduling fix (minute-vs-day in schedule_new_state).
46
+ # Do not relax this floor without verifying both still hold.
47
+ spec.add_runtime_dependency "fsrs", ">= 0.9.2", "< 1.0"
44
48
  spec.required_ruby_version = ">= 3.2"
45
49
  end
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- active_recall (2.3.0)
4
+ active_recall (2.3.1)
5
5
  activerecord (>= 7.0, < 9.0)
6
6
  activesupport (>= 7.0, < 9.0)
7
+ fsrs (>= 0.9.2, < 1.0)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
@@ -98,6 +99,8 @@ GEM
98
99
  drb (2.2.0)
99
100
  ruby2_keywords
100
101
  erubi (1.12.0)
102
+ fsrs (0.9.2)
103
+ activesupport (>= 7.0, < 9.0)
101
104
  globalid (1.2.1)
102
105
  activesupport (>= 6.1)
103
106
  i18n (1.14.1)
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- active_recall (2.3.0)
4
+ active_recall (2.3.1)
5
5
  activerecord (>= 7.0, < 9.0)
6
6
  activesupport (>= 7.0, < 9.0)
7
+ fsrs (>= 0.9.2, < 1.0)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
@@ -98,6 +99,8 @@ GEM
98
99
  drb (2.2.0)
99
100
  ruby2_keywords
100
101
  erubi (1.12.0)
102
+ fsrs (0.9.2)
103
+ activesupport (>= 7.0, < 9.0)
101
104
  globalid (1.2.1)
102
105
  activesupport (>= 6.1)
103
106
  i18n (1.14.1)
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- active_recall (2.3.0)
4
+ active_recall (2.3.1)
5
5
  activerecord (>= 7.0, < 9.0)
6
6
  activesupport (>= 7.0, < 9.0)
7
+ fsrs (>= 0.9.2, < 1.0)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
@@ -95,6 +96,8 @@ GEM
95
96
  diff-lcs (1.5.1)
96
97
  drb (2.2.1)
97
98
  erubi (1.13.1)
99
+ fsrs (0.9.2)
100
+ activesupport (>= 7.0, < 9.0)
98
101
  globalid (1.2.1)
99
102
  activesupport (>= 6.1)
100
103
  i18n (1.14.6)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_recall/algorithms/fsrs/internal"
3
+ require "fsrs"
4
4
 
5
5
  module ActiveRecall
6
6
  class FSRS
@@ -19,10 +19,10 @@ module ActiveRecall
19
19
  ].freeze
20
20
 
21
21
  GRADE_TO_RATING = {
22
- 1 => Internal::Rating::AGAIN,
23
- 2 => Internal::Rating::HARD,
24
- 3 => Internal::Rating::GOOD,
25
- 4 => Internal::Rating::EASY
22
+ 1 => Fsrs::Rating::AGAIN,
23
+ 2 => Fsrs::Rating::HARD,
24
+ 3 => Fsrs::Rating::GOOD,
25
+ 4 => Fsrs::Rating::EASY
26
26
  }.freeze
27
27
 
28
28
  def self.required_attributes
@@ -33,8 +33,8 @@ module ActiveRecall
33
33
  :gradable
34
34
  end
35
35
 
36
- def self.score(**kwargs)
37
- new(**kwargs).score
36
+ def self.score(**)
37
+ new(**).score
38
38
  end
39
39
 
40
40
  def initialize(box:, stability:, difficulty:, state:, lapses:,
@@ -43,7 +43,7 @@ module ActiveRecall
43
43
  @box = box || 0
44
44
  @stability = stability
45
45
  @difficulty = difficulty
46
- @state = state || Internal::State::NEW
46
+ @state = state || Fsrs::State::NEW
47
47
  @lapses = lapses || 0
48
48
  @elapsed_days = elapsed_days || 0
49
49
  @scheduled_days = scheduled_days || 0
@@ -79,7 +79,7 @@ module ActiveRecall
79
79
  private
80
80
 
81
81
  def build_card
82
- card = Internal::Card.new
82
+ card = Fsrs::Card.new
83
83
  card.stability = @stability if @stability
84
84
  card.difficulty = @difficulty if @difficulty
85
85
  card.state = @state
@@ -92,7 +92,7 @@ module ActiveRecall
92
92
  end
93
93
 
94
94
  def scheduler
95
- scheduler = Internal::Scheduler.new
95
+ scheduler = Fsrs::Scheduler.new
96
96
  config = ActiveRecall.configuration
97
97
  scheduler.p.request_retention = config.fsrs_request_retention if config.fsrs_request_retention
98
98
  scheduler.p.maximum_interval = config.fsrs_maximum_interval if config.fsrs_maximum_interval
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecall
4
- VERSION = "2.3.0"
4
+ VERSION = "2.3.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_recall
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Gravina
@@ -154,6 +154,26 @@ dependencies:
154
154
  - - "<"
155
155
  - !ruby/object:Gem::Version
156
156
  version: '9.0'
157
+ - !ruby/object:Gem::Dependency
158
+ name: fsrs
159
+ requirement: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: 0.9.2
164
+ - - "<"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.0'
167
+ type: :runtime
168
+ prerelease: false
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: 0.9.2
174
+ - - "<"
175
+ - !ruby/object:Gem::Version
176
+ version: '1.0'
157
177
  description: A spaced-repetition system to be used with ActiveRecord models
158
178
  email:
159
179
  - robert.gravina@gmail.com
@@ -173,7 +193,6 @@ files:
173
193
  - LICENSE
174
194
  - README.md
175
195
  - Rakefile
176
- - VENDORED_LICENSES.md
177
196
  - active_recall.gemspec
178
197
  - bin/console
179
198
  - bin/flatten
@@ -189,7 +208,6 @@ files:
189
208
  - lib/active_recall.rb
190
209
  - lib/active_recall/algorithms/fibonacci_sequence.rb
191
210
  - lib/active_recall/algorithms/fsrs.rb
192
- - lib/active_recall/algorithms/fsrs/internal.rb
193
211
  - lib/active_recall/algorithms/leitner_system.rb
194
212
  - lib/active_recall/algorithms/sm2.rb
195
213
  - lib/active_recall/algorithms/soft_leitner_system.rb
data/VENDORED_LICENSES.md DELETED
@@ -1,54 +0,0 @@
1
- # Vendored Licenses
2
-
3
- This file records third-party source code that has been vendored into this
4
- repository, along with the upstream license terms. ActiveRecall's own license
5
- is in [LICENSE](LICENSE).
6
-
7
- ## rb-fsrs
8
-
9
- - **Vendored at:** [`lib/active_recall/algorithms/fsrs/internal.rb`](lib/active_recall/algorithms/fsrs/internal.rb)
10
- - **Upstream:** https://github.com/open-spaced-repetition/rb-fsrs
11
- - **Version:** 0.9.0 (commit pulled from the published gem)
12
- - **License:** MIT
13
- - **Reason for vendoring:** The published `fsrs` 0.9.0 gem pins
14
- `activesupport ~> 7.0`, which excludes Rails 8. ActiveRecall supports
15
- Rails 8, so the code was vendored under `ActiveRecall::FSRS::Internal`.
16
- The constraint has been widened on rb-fsrs `master`; revisit the
17
- dependency-vs-vendoring decision once a release with the wider
18
- constraint ships.
19
-
20
- ### Local divergences from upstream
21
-
22
- - `Scheduler#schedule_new_state` uses `1.minute` / `5.minutes` /
23
- `10.minutes` in place of upstream's bare integer arithmetic
24
- (`now + 60`, `now + (5 * 60)`, `now + (10 * 60)`). With `now` as a
25
- `DateTime`, the upstream form adds days, not seconds, scheduling new
26
- cards rated Again/Hard/Good 60 / 300 / 600 days out instead of
27
- 1 / 5 / 10 minutes. Tracks upstream PR
28
- https://github.com/open-spaced-repetition/rb-fsrs/pull/9.
29
-
30
- ### Upstream license text
31
-
32
- ```
33
- The MIT License (MIT)
34
-
35
- Copyright (c) 2024 clayton
36
-
37
- Permission is hereby granted, free of charge, to any person obtaining a copy
38
- of this software and associated documentation files (the "Software"), to deal
39
- in the Software without restriction, including without limitation the rights
40
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
41
- copies of the Software, and to permit persons to whom the Software is
42
- furnished to do so, subject to the following conditions:
43
-
44
- The above copyright notice and this permission notice shall be included in
45
- all copies or substantial portions of the Software.
46
-
47
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
48
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
49
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
50
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
51
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
52
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
53
- THE SOFTWARE.
54
- ```
@@ -1,335 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Vendored from rb-fsrs 0.9.0 (https://github.com/open-spaced-repetition/rb-fsrs).
4
- # MIT License, Copyright (c) clayton. See VENDORED_LICENSES.md for the full notice.
5
- #
6
- # Pulled into ActiveRecall::FSRS::Internal because the published rb-fsrs gem
7
- # pins activesupport to ~> 7.0, which excludes Rails 8 — incompatible with
8
- # this gem's Rails 8 support. Re-evaluate when an upstream release widens
9
- # the constraint (master already does).
10
- #
11
- # Local divergence from upstream:
12
- # - schedule_new_state uses 1.minute / 5.minutes / 10.minutes instead of
13
- # bare integers (which DateTime treats as days). Tracks upstream PR
14
- # https://github.com/open-spaced-repetition/rb-fsrs/pull/9; remove this
15
- # patch once a release with that fix is available.
16
-
17
- require "date"
18
-
19
- module ActiveRecall
20
- class FSRS
21
- module Internal
22
- class InvalidDateError < StandardError
23
- def initialize(msg = "Date must be UTC and timezone-aware")
24
- super
25
- end
26
- end
27
-
28
- class SchedulingInfo
29
- attr_accessor :card, :review_log
30
-
31
- def initialize(card, review_log)
32
- @card = card
33
- @review_log = review_log
34
- end
35
- end
36
-
37
- class ReviewLog
38
- attr_accessor :rating, :scheduled_days, :elapsed_days, :review, :state
39
-
40
- def initialize(rating, scheduled_days, elapsed_days, review, state)
41
- @rating = rating
42
- @scheduled_days = scheduled_days
43
- @elapsed_days = elapsed_days
44
- @review = review
45
- @state = state
46
- end
47
- end
48
-
49
- class Rating
50
- AGAIN = 1
51
- HARD = 2
52
- GOOD = 3
53
- EASY = 4
54
- end
55
-
56
- class State
57
- NEW = 0
58
- LEARNING = 1
59
- REVIEW = 2
60
- RELEARNING = 3
61
- end
62
-
63
- class Card
64
- attr_accessor :due, :stability, :difficulty, :elapsed_days,
65
- :scheduled_days, :reps, :lapses, :state, :last_review
66
-
67
- def initialize
68
- @due = DateTime.new
69
- @stability = 0.0
70
- @difficulty = 0.0
71
- @elapsed_days = 0
72
- @scheduled_days = 0
73
- @reps = 0
74
- @lapses = 0
75
- @state = State::NEW
76
- end
77
-
78
- def get_retrievability(now)
79
- decay = -0.5
80
- factor = (0.9**(1 / decay)) - 1
81
-
82
- return nil unless @state == State::REVIEW
83
-
84
- elapsed_days = [0, (now - @last_review).to_i].max
85
- (1 + (factor * elapsed_days / @stability))**decay
86
- end
87
-
88
- def deep_clone
89
- Marshal.load(Marshal.dump(self))
90
- end
91
- end
92
-
93
- class CardScheduler
94
- attr_accessor :again, :hard, :good, :easy
95
-
96
- def initialize(card)
97
- @again = card.clone
98
- @hard = card.clone
99
- @good = card.clone
100
- @easy = card.clone
101
- end
102
-
103
- def update_state(state)
104
- case state
105
- when State::NEW
106
- update_new_state
107
- when State::LEARNING, State::RELEARNING
108
- update_learning_relearning_state(state)
109
- when State::REVIEW
110
- update_review_state
111
- end
112
- end
113
-
114
- def schedule(now, hard_interval, good_interval, easy_interval)
115
- update_schedule_days(hard_interval, good_interval, easy_interval)
116
- update_due_dates(now, hard_interval, good_interval, easy_interval)
117
- end
118
-
119
- def record_log(card, now)
120
- {
121
- Rating::AGAIN => record_again_log(card, now),
122
- Rating::HARD => record_hard_log(card, now),
123
- Rating::GOOD => record_good_log(card, now),
124
- Rating::EASY => record_easy_log(card, now)
125
- }
126
- end
127
-
128
- private
129
-
130
- def update_due_dates(now, hard_interval, good_interval, easy_interval)
131
- @again.due = now + 5.minutes
132
- @hard.due = hard_interval.positive? ? now + hard_interval.days : now + 10.minutes
133
- @good.due = now + good_interval.days
134
- @easy.due = now + easy_interval.days
135
- end
136
-
137
- def update_schedule_days(hard_interval, good_interval, easy_interval)
138
- @again.scheduled_days = 0
139
- @hard.scheduled_days = hard_interval
140
- @good.scheduled_days = good_interval
141
- @easy.scheduled_days = easy_interval
142
- end
143
-
144
- def update_new_state
145
- @again.state = State::LEARNING
146
- @hard.state = State::LEARNING
147
- @good.state = State::LEARNING
148
- @easy.state = State::REVIEW
149
- end
150
-
151
- def update_learning_relearning_state(state)
152
- @again.state = state
153
- @hard.state = state
154
- @good.state = State::REVIEW
155
- @easy.state = State::REVIEW
156
- end
157
-
158
- def update_review_state
159
- @again.state = State::RELEARNING
160
- @hard.state = State::REVIEW
161
- @good.state = State::REVIEW
162
- @easy.state = State::REVIEW
163
- @again.lapses += 1
164
- end
165
-
166
- def record_again_log(card, now)
167
- SchedulingInfo.new(
168
- @again,
169
- ReviewLog.new(Rating::AGAIN, @again.scheduled_days, card.elapsed_days, now, card.state)
170
- )
171
- end
172
-
173
- def record_hard_log(card, now)
174
- SchedulingInfo.new(
175
- @hard,
176
- ReviewLog.new(Rating::HARD, @hard.scheduled_days, card.elapsed_days, now, card.state)
177
- )
178
- end
179
-
180
- def record_good_log(card, now)
181
- SchedulingInfo.new(
182
- @good,
183
- ReviewLog.new(Rating::GOOD, @good.scheduled_days, card.elapsed_days, now, card.state)
184
- )
185
- end
186
-
187
- def record_easy_log(card, now)
188
- SchedulingInfo.new(
189
- @easy,
190
- ReviewLog.new(Rating::EASY, @easy.scheduled_days, card.elapsed_days, now, card.state)
191
- )
192
- end
193
- end
194
-
195
- class Parameters
196
- attr_accessor :request_retention, :maximum_interval, :w
197
-
198
- def initialize
199
- @request_retention = 0.9
200
- @maximum_interval = 36_500
201
- @w = [
202
- 0.4, 0.6, 2.4, 5.8, 4.93, 0.94, 0.86, 0.01, 1.49, 0.14,
203
- 0.94, 2.18, 0.05, 0.34, 1.26, 0.29, 2.61
204
- ]
205
- end
206
- end
207
-
208
- class Scheduler
209
- attr_accessor :p, :decay, :factor
210
-
211
- def initialize
212
- @p = Parameters.new
213
- @decay = -0.5
214
- @factor = (0.9**(1 / @decay)) - 1
215
- end
216
-
217
- def repeat(card, now)
218
- raise InvalidDateError unless now.utc?
219
-
220
- card = card.clone
221
- card.elapsed_days = if card.state == State::NEW
222
- 0
223
- else
224
- (now - card.last_review).to_i
225
- end
226
- card.last_review = now
227
- card.reps += 1
228
- card_scheduler = CardScheduler.new(card)
229
- card_scheduler.update_state(card.state)
230
-
231
- case card.state
232
- when State::NEW
233
- schedule_new_state(card_scheduler, now)
234
- when State::LEARNING, State::RELEARNING
235
- schedule_learning_relearning_state(card_scheduler, now)
236
- when State::REVIEW
237
- schedule_review_state(card_scheduler, card, now)
238
- end
239
- card_scheduler.record_log(card, now)
240
- end
241
-
242
- def schedule_new_state(s, now)
243
- init_ds(s)
244
- s.again.due = now + 1.minute
245
- s.hard.due = now + 5.minutes
246
- s.good.due = now + 10.minutes
247
- easy_interval = next_interval(s.easy.stability)
248
- s.easy.scheduled_days = easy_interval
249
- s.easy.due = now + easy_interval.days
250
- end
251
-
252
- def schedule_learning_relearning_state(s, now)
253
- hard_interval = 0
254
- good_interval = next_interval(s.good.stability)
255
- easy_interval = [next_interval(s.easy.stability), good_interval + 1].max
256
- s.schedule(now, hard_interval, good_interval, easy_interval)
257
- end
258
-
259
- def schedule_review_state(s, card, now)
260
- interval = card.elapsed_days
261
- last_d = card.difficulty
262
- last_s = card.stability
263
- retrievability = forgetting_curve(interval, last_s)
264
- next_ds(s, last_d, last_s, retrievability)
265
- hard_interval = next_interval(s.hard.stability)
266
- good_interval = next_interval(s.good.stability)
267
- hard_interval = [hard_interval, good_interval].min
268
- good_interval = [good_interval, hard_interval + 1].max
269
- easy_interval = [next_interval(s.easy.stability), good_interval + 1].max
270
- s.schedule(now, hard_interval, good_interval, easy_interval)
271
- end
272
-
273
- def init_ds(s)
274
- s.again.difficulty = init_difficulty(Rating::AGAIN)
275
- s.again.stability = init_stability(Rating::AGAIN)
276
- s.hard.difficulty = init_difficulty(Rating::HARD)
277
- s.hard.stability = init_stability(Rating::HARD)
278
- s.good.difficulty = init_difficulty(Rating::GOOD)
279
- s.good.stability = init_stability(Rating::GOOD)
280
- s.easy.difficulty = init_difficulty(Rating::EASY)
281
- s.easy.stability = init_stability(Rating::EASY)
282
- end
283
-
284
- def next_ds(s, last_d, last_s, retrievability)
285
- s.again.difficulty = next_difficulty(last_d, Rating::AGAIN)
286
- s.again.stability = next_forget_stability(last_d, last_s, retrievability)
287
- s.hard.difficulty = next_difficulty(last_d, Rating::HARD)
288
- s.hard.stability = next_recall_stability(last_d, last_s, retrievability, Rating::HARD)
289
- s.good.difficulty = next_difficulty(last_d, Rating::GOOD)
290
- s.good.stability = next_recall_stability(last_d, last_s, retrievability, Rating::GOOD)
291
- s.easy.difficulty = next_difficulty(last_d, Rating::EASY)
292
- s.easy.stability = next_recall_stability(last_d, last_s, retrievability, Rating::EASY)
293
- end
294
-
295
- def init_stability(r)
296
- [p.w[r - 1], 0.1].max
297
- end
298
-
299
- def init_difficulty(r)
300
- (p.w[4] - (p.w[5] * (r - 3))).clamp(1, 10)
301
- end
302
-
303
- def forgetting_curve(elapsed_days, stability)
304
- (1 + (factor * elapsed_days / stability))**decay
305
- end
306
-
307
- def next_interval(s)
308
- new_interval = s / factor * ((p.request_retention**(1 / decay)) - 1)
309
- new_interval.round.clamp(1, p.maximum_interval)
310
- end
311
-
312
- def next_difficulty(d, r)
313
- next_d = d - (p.w[6] * (r - 3))
314
- mean_reversion(p.w[4], next_d).clamp(1, 10)
315
- end
316
-
317
- def mean_reversion(init, current)
318
- (p.w[7] * init) + ((1 - p.w[7]) * current)
319
- end
320
-
321
- def next_recall_stability(d, s, r, rating)
322
- hard_penalty = (rating == Rating::HARD) ? p.w[15] : 1
323
- easy_bonus = (rating == Rating::EASY) ? p.w[16] : 1
324
- s * (1 + (Math.exp(p.w[8]) * (11 - d) * (s**-p.w[9]) *
325
- (Math.exp((1 - r) * p.w[10]) - 1) * hard_penalty * easy_bonus))
326
- end
327
-
328
- def next_forget_stability(d, s, r)
329
- p.w[11] * (d**-p.w[12]) * (((s + 1)**p.w[13]) - 1) *
330
- Math.exp((1 - r) * p.w[14])
331
- end
332
- end
333
- end
334
- end
335
- end