Class: JLDrill::Schedule

Inherits:
Object
  • Object
show all
Defined in:
lib/jldrill/model/Quiz/Schedule.rb

Overview

Calculates and stores the Schedule information for an item in the Spaced Repetition Drill.

  • score is the number of times the item has been successfully drilled in the current bin.

  • level is 0 if meaning has not been introduced

    1 if kanji has not been introduced, 
    2 otherwise
    

Constant Summary collapse

SCORE_RE =
/^Score: (.*)/
LEVEL_RE =
/^Level: (.*)/
LASTREVIEWED_RE =
/^LastReviewed: (.*)/
SCHEDULEDTIME_RE =

Note: ScheduledTime is deprecated

/^ScheduledTime: (.*)/
DIFFICULTY_RE =
/^Difficulty: (.*)/
DURATION_RE =
/^Duration: (.*)/
SECONDS_PER_DAY =
60 * 60 * 24
MAX_ADDITIONAL_TIME =
4 * SECONDS_PER_DAY

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(item) ⇒ Schedule

Returns a new instance of Schedule.



35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 35

def initialize(item)
    @name = "Schedule"
    @score = 0
    @level = 0
    @lastReviewed = nil
    # scheduledTime is deprecated
    @scheduledTime = nil
    @seen = false
    @numIncorrect = 0
    @item = item
    @duration = Duration.new
end

Instance Attribute Details

#itemObject

Note: ScheduledTime is deprecated



27
28
29
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 27

def item
  @item
end

#lastReviewedObject

Note: ScheduledTime is deprecated



27
28
29
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 27

def lastReviewed
  @lastReviewed
end

#levelObject

Note: ScheduledTime is deprecated



27
28
29
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 27

def level
  @level
end

#nameObject (readonly)

Note: ScheduledTime is deprecated



27
28
29
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 27

def name
  @name
end

#numIncorrectObject

Note: ScheduledTime is deprecated



27
28
29
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 27

def numIncorrect
  @numIncorrect
end

#scheduledTimeObject

Note: ScheduledTime is deprecated



27
28
29
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 27

def scheduledTime
  @scheduledTime
end

#scoreObject

Note: ScheduledTime is deprecated



27
28
29
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 27

def score
  @score
end

#seenObject

Note: ScheduledTime is deprecated



27
28
29
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 27

def seen
  @seen
end

Class Method Details

.backoff(interval) ⇒ Object

Return the the new interval after backing off



184
185
186
187
188
189
190
191
192
193
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 184

def Schedule.backoff(interval)
    sixMonths = Duration.new
    sixMonths.days = 180
    if(interval < sixMonths.seconds)
        factor = 2.0 - (interval.to_f / sixMonths.seconds.to_f)
        return (factor * interval).to_i
    else
        return (interval).to_i
    end
end

Instance Method Details

#assign(schedule) ⇒ Object

copy the data from the Schedule passed in to this one Note: Doesn’t assign the item



82
83
84
85
86
87
88
89
90
91
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 82

def assign(schedule)
    @score = schedule.score
    @level = schedule.level
    @lastReviewed = schedule.lastReviewed
    # scheduledTime is deprecated
    @scheduledTime = schedule.scheduledTime
    @seen = schedule.seen
    @numIncorrect = schedule.numIncorrect
    @duration.seconds = schedule.duration
end

#calculateIntervalObject

To avoid increasing the gap too much, a maximum of twice the previous duration plus 25% is used.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 213

def calculateInterval
    interval = intervalFromDifficulty(difficulty)
    # If it is scheduled, then that means it isn't 
    # a newly promoted item
    if scheduled?
        elapsed = elapsedTime
        if Schedule.backoff(elapsed) > @duration.seconds
            interval = Schedule.backoff(elapsed) 
            max = maxInterval
            if (interval > max) && (max > 0)
                interval = max
            end
        else
            interval = @duration.seconds
        end
    end
    interval
end

#cloneObject

Create a clone of this schedule



74
75
76
77
78
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 74

def clone
    retVal = Schedule.new(@item)
    retVal.assign(self)
    return retVal
end

#correctObject

Mark the item as correct.



283
284
285
286
287
288
289
290
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 283

def correct
    if @item.bin == 4
        recalculateDifficulty
        schedule
    end
    markReviewed
    @score += 1
end

#demoteObject

Unschedule and reset the level and score of the item



262
263
264
265
266
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 262

def demote
    unschedule
    @score = 0
    @level = 0
end

#difficultyObject

Return the difficulty of the item. Right now that is the number of times it was incorrect.



270
271
272
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 270

def difficulty
    @numIncorrect
end

#difficultyFromInterval(interval) ⇒ Object

Calculate the difficulty from the interval. This is used to reset the difficulty of an item based on past performance.



170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 170

def difficultyFromInterval(interval)
    # to deal with corrupt files where the review times are screwed up
    if interval <= 0
        return 50
    end
    i = 0
    while interval < intervalFromDifficulty(i)
        i += 1
    end

    return i
end

#durationObject



97
98
99
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 97

def duration
    return @duration.seconds
end

#duration=(seconds) ⇒ Object



93
94
95
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 93

def duration=(seconds)
    @duration.seconds = seconds
end

#durationWithin?(range) ⇒ Boolean

Returns true if the scheduled duration is in the range of times supplied. The range is of the form of number of seconds from the epoch

Returns:

  • (Boolean)


349
350
351
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 349

def durationWithin?(range)
    range.include?(scheduleDuration)
end

#elapsedTimeObject

Returns the number of seconds since the item was last reviewed



114
115
116
117
118
119
120
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 114

def elapsedTime
    retVal = 0
    if reviewed?
        retVal = Time::now().to_i - @lastReviewed.to_i
    end
    return retVal
end

#incorrectObject

Mark the item as incorrect.



275
276
277
278
279
280
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 275

def incorrect
    @numIncorrect += 1
    unschedule
    markReviewed
    @score = 0
end

#intervalFromDifficulty(diff) ⇒ Object

This is the interval the item will have when it it first promoted into the Review Set.

It is a sliding scale based on difficulty. If the user has never gotten the item incorrect, then the interval will be 5.0. For each time the get it wrong, it moves closer to 0.



154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 154

def intervalFromDifficulty(diff)
    if diff <= 5
        SECONDS_PER_DAY +
            (MAX_ADDITIONAL_TIME * (1.0 - (diff.to_f / 5.0))).to_i
    else
        scale = diff - 5
        current = 0.0
        1.upto(scale) do |x|
            current = current + (1 - current).to_f / 10.0
        end
        (SECONDS_PER_DAY * (1.0 - current)).to_i
    end
end

#markReviewedObject

Updates the time that the item was last reviewed to be the real current time. Returns the time that it set.



104
105
106
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 104

def markReviewed
    @lastReviewed = Time::now()
end

#maxIntervalObject

Return the maximum interval that this item can have. It is calculated as twice the previous duration plus 25% It will return -1 if there is no maximum



198
199
200
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 198

def maxInterval
   return Schedule.backoff(@duration.seconds.to_f * 1.25)
end

#onDay?(current, date, day) ⇒ Boolean

Returns true if the date is on the specified day. 0 is today, -1 is yesterday, 1 is tomorrow. Uses the date, not 24 hour time period.

Returns:

  • (Boolean)


327
328
329
330
331
332
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 327

def onDay?(current, date, day)
    target = current + (SECONDS_PER_DAY * day)
    return date.day == target.day && 
        date.month == target.month &&
        date.year == target.year
end

#parse(string) ⇒ Object

Parses a single part of the Schedule information



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 49

def parse(string)
    parsed = true
    case string
        when SCORE_RE 
            @score = $1.to_i
        when LEVEL_RE
            @level = $1.to_i
        when LASTREVIEWED_RE
            @lastReviewed = Time.at($1.to_i)
        when SCHEDULEDTIME_RE
            # scheduledTime is deprecated
            if @item.bin == 4
                @scheduledTime = Time.at($1.to_i)
            end
        when DIFFICULTY_RE
            @numIncorrect = $1.to_i
        when DURATION_RE
            @duration = Duration.parse($1)
    else # Not something we understand
        parsed = false
    end
    parsed
end

#potentialScheduleInDaysObject

Returns the total number of days the item was last scheduled for. Returns a float.



304
305
306
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 304

def potentialScheduleInDays
    return secondsToDays(calculateInterval.to_i)
end

#randomVariation(interval) ⇒ Object

Returns a +-10% random variation in the interval. This smooths out the distribution of items and makes it so that similar items aren’t always together.



142
143
144
145
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 142

def randomVariation(interval)
    # 10% - rand(20%) = +- 10%
    ((interval.to_f / 10) - rand(interval.to_f / 5)).to_i 
end

#recalculateDifficultyObject



232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 232

def recalculateDifficulty
    # If it's scheduled, then it isn't a newly promoted item
    # Set the difficulty based on how long the person was
    # able to go since the last review.
    if scheduled?
        elapsed = elapsedTime
        diff = difficultyFromInterval(elapsed)
        if diff < @numIncorrect
            @numIncorrect = diff
        end
    end
end

#resetObject

Resets the schedule



123
124
125
126
127
128
129
130
131
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 123

def reset
    @lastReviewed = nil
    # scheduledTime is deprecated
    @scheduledTime = nil
    @score = 0
    @seen = false
    @numIncorrect = 0
    @duration = Duration.new
end

#reviewed?Boolean

Returns true if the item has been marked reviewed at least once

Returns:

  • (Boolean)


109
110
111
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 109

def reviewed?
    !@lastReviewed.nil?
end

#reviewedDateObject

Returns a human readable string showing when the item was last reviewed.



355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 355

def reviewedDate
    retVal = ""
    if reviewed?
        if reviewedOn?(0)
            retVal = "Today"
        elsif reviewedOn?(-1)
            retVal = "Yesterday"
        else
            retVal = @lastReviewed.strftime("%x")
        end
    end
    retVal
end

#reviewedOn?(day) ⇒ Boolean

Returns true if the item was reviewed on the specified day. 0 is today, -1 is yesterday, -2 is the day before, etc. Uses the date, not 24 hour time period (i.e., if it’s 1am, then an item reviewed 2 hours ago is yesterday).

Returns:

  • (Boolean)


338
339
340
341
342
343
344
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 338

def reviewedOn?(day)
    if !reviewed?
        return false
    else
        return onDay?(Time::now(), @lastReviewed, day)
    end
end

#reviewLoadObject

This is simply 1/reviewRate. It is used to sort the schedules since we want the smalled reviewRates at the end of the list.



385
386
387
388
389
390
391
392
393
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 385

def reviewLoad
    retVal = 1.0
    sched = scheduleDuration.to_f
    time = elapsedTime.to_f
    if time != 0.0
        retVal = sched / time
    end
    retVal
end

#reviewRateObject

Returns the “velocity” for reviewing. It shows the ratio of the actual time between review vs. scheduled time. Values greater than 1 mean it is taking longer than expected, values less than 1 mean it is taking less time than expected.



373
374
375
376
377
378
379
380
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 373

def reviewRate
    retVal = 1.0
    dur = scheduleDuration
    if dur != 0
        retVal = elapsedTime.to_f / dur.to_f
    end
    retVal
end

#schedule(int = -1)) ⇒ Object

Schedule the item for review



246
247
248
249
250
251
252
253
254
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 246

def schedule(int = -1)
    if int < 0
        interval = calculateInterval
        @duration.seconds = interval + randomVariation(interval)
    else
        @duration.seconds = int
    end
    return @duration.seconds
end

#scheduled?Boolean

Returns true if the item has been scheduled for review

Returns:

  • (Boolean)


134
135
136
137
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 134

def scheduled?
    # scheduledTime is deprecated
    (@duration.valid?) || (!@scheduledTime.nil?)
end

#scheduleDurationObject

Return the duration of the schedule Old quizes might not have the duration stored, so it is calculated from the scheduledTime. scheduledTime is now deprecated, though.



312
313
314
315
316
317
318
319
320
321
322
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 312

def scheduleDuration
    retVal = -1
    if scheduled?
        if (!@duration.valid?) && !@scheduledTime.nil?
            retVal = @scheduledTime.to_i - @lastReviewed.to_i
        else
            retVal = @duration.seconds
        end
    end
    return retVal
end

#secondsToDays(seconds) ⇒ Object

Converts seconds to days rounded to the nearest 10th of a day



298
299
300
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 298

def secondsToDays(seconds)
    return (seconds * 10 / SECONDS_PER_DAY).to_f / 10
end

#seen?Boolean

Returns true if the item has been seen before.

Returns:

  • (Boolean)


293
294
295
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 293

def seen?
    @seen
end

#to_sObject

Outputs the item schedule in save format.



396
397
398
399
400
401
402
403
404
405
406
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 396

def to_s
    retVal = "/Score: #{@score}" + "/Level: #{@level}"
    if reviewed?
        retVal += "/LastReviewed: #{@lastReviewed.to_i}"
    end
    if scheduled?
        retVal += "/Duration: #{scheduleDuration.to_i}"
    end
    retVal += "/Difficulty: #{difficulty}"
    retVal
end

#unscheduleObject

Remove review schedule for the item



257
258
259
# File 'lib/jldrill/model/Quiz/Schedule.rb', line 257

def unschedule
    @duration = Duration.new
end