From 5e0cc2ff5db3a30c99bf282bd25279017cb48879 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 18 Mar 2011 08:18:01 +0900 Subject: [PATCH] next time reports and associated unit tests --- anki/sched.py | 73 ++++++++++++++++++++++++++++++++++----------- tests/test_sched.py | 38 +++++++++++++++++++++++ 2 files changed, 94 insertions(+), 17 deletions(-) diff --git a/anki/sched.py b/anki/sched.py index ad0981075..aaecd6175 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -6,7 +6,7 @@ import time, datetime, simplejson, random from operator import itemgetter from heapq import * #from anki.cards import Card -from anki.utils import parseTags, ids2str, intTime +from anki.utils import parseTags, ids2str, intTime, fmtTimeSpan from anki.lang import _, ngettext from anki.consts import * from anki.hooks import runHook @@ -211,18 +211,20 @@ limit %d""" % self.reportLimit, lim=self.dayCutoff) card.queue = 1 card.type = 1 - def _rescheduleNew(self, card, conf, early): + def _graduatingIvl(self, card, conf, early): if not early: # graduate - int_ = conf['ints'][0] + return conf['ints'][0] elif card.cycles: # remove - int_ = conf['ints'][2] + return conf['ints'][2] else: # first time bonus - int_ = conf['ints'][1] - card.ivl = int_ - card.due = self.today+int_ + return conf['ints'][1] + + def _rescheduleNew(self, card, conf, early): + card.ivl = self._graduatingIvl(card, conf, early) + card.due = self.today+card.ivl card.factor = conf['initialFactor'] def _logLearn(self, card, ease, conf, leaving): @@ -304,7 +306,7 @@ queue = 1 %s and due <= :lim order by %s limit %d""" % ( card.streak = 0 card.lapses += 1 card.lastIvl = card.ivl - card.ivl = int(card.ivl*conf['mult']) + 1 + card.ivl = self._nextLapseIvl(card, conf) card.factor = max(1300, card.factor-200) card.due = card.edue = self.today + card.ivl # put back in the learn queue? @@ -314,11 +316,14 @@ queue = 1 %s and due <= :lim order by %s limit %d""" % ( # leech? self._checkLeech(card, conf) + def _nextLapseIvl(self, card, conf): + return int(card.ivl*conf['mult']) + 1 + def _rescheduleReview(self, card, ease): card.streak += 1 # update interval card.lastIvl = card.ivl - self._updateInterval(card, ease) + self._updateRevIvl(card, ease) # then the rest card.factor = max(1300, card.factor+[-150, 0, 150][ease-2]) card.due = self.today + card.ivl @@ -342,7 +347,7 @@ queue = 1 %s and due <= :lim order by %s limit %d""" % ( # Interval management ########################################################################## - def nextInterval(self, card, ease): + def _nextRevIvl(self, card, ease): "Ideal next interval for CARD, given EASE." delay = self._daysLate(card) conf = self._cardConf(card) @@ -356,18 +361,13 @@ queue = 1 %s and due <= :lim order by %s limit %d""" % ( # must be at least one day greater than previous interval return max(card.ivl+1, int(interval)) - def nextIntervalStr(self, card, ease, short=False): - "Return the next interval for CARD given EASE as a string." - int = self.nextInterval(card, ease) - return anki.utils.fmtTimeSpan(int*86400, short=short) - def _daysLate(self, card): "Number of days later than scheduled." return max(0, self.today - card.due) - def _updateInterval(self, card, ease): + def _updateRevIvl(self, card, ease): "Update CARD's interval, trying to avoid siblings." - idealIvl = self.nextInterval(card, ease) + idealIvl = self._nextRevIvl(card, ease) idealDue = self.today + idealIvl conf = self._cardConf(card)['rev'] # find sibling positions @@ -520,6 +520,45 @@ queue = 1 %s and due <= :lim order by %s limit %d""" % ( "select count() from (select id from cards where " "queue = 2 limit %d)" % lim) + # Next time reports + ########################################################################## + + def nextIvlStr(self, card, ease, short=False): + "Return the next interval for CARD as a string." + return fmtTimeSpan( + self.nextIvl(card, ease), short=short) + + def nextIvl(self, card, ease): + "Return the next interval for CARD, in seconds." + if card.queue in (0,2): + # in learning + return self._nextLrnIvl(card, ease) + elif ease == 1: + # lapsed + conf = self._cardConf(card)['lapse'] + return self._nextLapseIvl(card, conf)*86400 + else: + # review + return self._nextRevIvl(card, ease)*86400 + + # this isn't easily extracted from the learn code + def _nextLrnIvl(self, card, ease): + conf = self._learnConf(card) + if ease == 1: + # grade 0 + return self._delayForGrade(conf, 0) + elif ease == 3: + # early removal + return self._graduatingIvl(card, conf, True) * 86400 + else: + grade = card.grade + 1 + if grade >= len(conf['delays']): + # graduate + return self._graduatingIvl(card, conf, False) * 86400 + else: + # next level + return self._delayForGrade(conf, grade) + # Suspending ########################################################################## diff --git a/tests/test_sched.py b/tests/test_sched.py index f6c818683..8055ddc3a 100644 --- a/tests/test_sched.py +++ b/tests/test_sched.py @@ -231,3 +231,41 @@ def test_finished(): d.sched.answerCard(c, 3) # nothing should be due tomorrow, as it's due in a week assert "No cards are due" in d.sched.finishedMsg() + +def test_nextIvl(): + d = getEmptyDeck() + f = d.newFact() + f['Front'] = u"one"; f['Back'] = u"two" + d.addFact(f) + c = f.cards()[0] + # cards in learning + ################################################## + ni = d.sched.nextIvl + assert ni(c, 1) == 30 + assert ni(c, 2) == 180 + # immediate removal is 7 days + assert ni(c, 3) == 7*86400 + c.cycles = 1 + c.grade = 1 + assert ni(c, 1) == 30 + assert ni(c, 2) == 600 + # no first time bonus + assert ni(c, 3) == 4*86400 + c.grade = 2 + # normal graduation is tomorrow + assert ni(c, 2) == 1*86400 + assert ni(c, 3) == 4*86400 + # review cards + ################################################## + c.queue = 1 + c.ivl = 100 + c.factor = 2500 + # failing it puts it at tomorrow + assert ni(c, 1) == 1*86400 + # (* 100 1.2 86400)10368000.0 + assert ni(c, 2) == 10368000 + # (* 100 2.5 86400)21600000.0 + assert ni(c, 3) == 21600000 + # (* 100 2.5 1.3 86400)28080000.0 + assert ni(c, 4) == 28080000 + print d.sched.nextIvlStr(c, 4) == "10.8 months"