make sure we update the rep count on pass/fail, and add unit test

This commit is contained in:
Damien Elmes 2011-09-23 12:52:38 +09:00
parent a98126dab4
commit 001a69db43
4 changed files with 98 additions and 65 deletions

View file

@ -52,6 +52,7 @@ class Scheduler(object):
card.queue = 1 card.queue = 1
card.type = 1 card.type = 1
card.left = self._startingLeft(card) card.left = self._startingLeft(card)
self.lrnRepCount += card.left
self._updateStats(card, 'new') self._updateStats(card, 'new')
if card.queue == 1: if card.queue == 1:
self._answerLrnCard(card, ease) self._answerLrnCard(card, ease)
@ -66,12 +67,11 @@ class Scheduler(object):
card.mod = intTime() card.mod = intTime()
card.usn = self.deck.usn() card.usn = self.deck.usn()
card.flushSched() card.flushSched()
# if nothing more to study, rebuild queue
if self.counts() == (0,0,0):
self.reset()
def counts(self): def repCounts(self):
"Does not include fetched but unanswered." return (self.newCount, self.lrnRepCount, self.revCount)
def cardCounts(self):
return (self.newCount, self.lrnCount, self.revCount) return (self.newCount, self.lrnCount, self.revCount)
def dueForecast(self, days=7): def dueForecast(self, days=7):
@ -209,7 +209,6 @@ order by due""" % self._groupLimit(),
def _resetNewCount(self): def _resetNewCount(self):
self.newCount = 0 self.newCount = 0
self.newRepCount = 0
pcounts = {} pcounts = {}
# for each of the active groups # for each of the active groups
for gid in self.deck.groups.active(): for gid in self.deck.groups.active():
@ -236,8 +235,6 @@ gid = ? and queue = 0 limit ?)""", gid, lim)
pcounts[gid] = lim - cnt pcounts[gid] = lim - cnt
# and add to running total # and add to running total
self.newCount += cnt self.newCount += cnt
conf = self.deck.groups.conf(gid)
self.newRepCount += cnt * len(conf['new']['delays'])
def _resetNew(self): def _resetNew(self):
self._resetNewCount() self._resetNewCount()
@ -335,7 +332,7 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
select count(), sum(left) from (select left from cards where select count(), sum(left) from (select left from cards where
gid in %s and queue = 1 and due < ? limit %d)""" % ( gid in %s and queue = 1 and due < ? limit %d)""" % (
self._groupLimit(), self.reportLimit), self._groupLimit(), self.reportLimit),
intTime() + self.deck.groups.top()['collapseTime']) self.dayCutoff)
self.lrnRepCount = self.lrnRepCount or 0 self.lrnRepCount = self.lrnRepCount or 0
def _resetLrn(self): def _resetLrn(self):
@ -376,24 +373,26 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
lastLeft = card.left lastLeft = card.left
if ease == 3: if ease == 3:
self._rescheduleAsRev(card, conf, True) self._rescheduleAsRev(card, conf, True)
self.lrnRepCount -= lastLeft
leaving = True leaving = True
elif ease == 2 and card.left-1 <= 0: elif ease == 2 and card.left-1 <= 0:
self._rescheduleAsRev(card, conf, False) self._rescheduleAsRev(card, conf, False)
self.lrnRepCount -= 1
leaving = True leaving = True
else: else:
if ease == 2: if ease == 2:
card.left -= 1 card.left -= 1
self.lrnRepCount -= 1
else: else:
card.left = self._startingLeft(card) card.left = self._startingLeft(card)
self.lrnRepCount += card.left - lastLeft
delay = self._delayForGrade(conf, card.left) delay = self._delayForGrade(conf, card.left)
if card.due < time.time(): if card.due < time.time():
# not collapsed; add some randomness # not collapsed; add some randomness
delay *= random.uniform(1, 1.25) delay *= random.uniform(1, 1.25)
card.due = int(time.time() + delay) card.due = int(time.time() + delay)
heappush(self._lrnQueue, (card.due, card.id)) heappush(self._lrnQueue, (card.due, card.id))
# if it's due within the cutoff, increment count self.lrnCount += 1
if delay <= self.deck.groups.top()['collapseTime']:
self.lrnCount += 1
self._logLrn(card, ease, conf, leaving, type, lastLeft) self._logLrn(card, ease, conf, leaving, type, lastLeft)
def _delayForGrade(self, conf, left): def _delayForGrade(self, conf, left):
@ -542,6 +541,7 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
card.left = len(conf['delays']) card.left = len(conf['delays'])
card.queue = 1 card.queue = 1
self.lrnCount += 1 self.lrnCount += 1
self.lrnRepCount += card.left
# leech? # leech?
if not self._checkLeech(card, conf) and conf['relearn']: if not self._checkLeech(card, conf) and conf['relearn']:
heappush(self._lrnQueue, (card.due, card.id)) heappush(self._lrnQueue, (card.due, card.id))
@ -785,18 +785,6 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
self.deck.db.list("select id from cards where fid = ?", fid)) self.deck.db.list("select id from cards where fid = ?", fid))
self.deck.db.execute("update cards set queue = -2 where fid = ?", fid) self.deck.db.execute("update cards set queue = -2 where fid = ?", fid)
# Counts
##########################################################################
def timeToday(self):
"Time spent learning today, in seconds."
t = self.deck.groups.top()
return t['timeToday'][1] / 1000
def repsToday(self):
"Number of cards answered today."
return sum(self.counts())
# Resetting # Resetting
########################################################################## ##########################################################################

View file

@ -127,7 +127,7 @@ def test_upgrade():
assert d.hour == 4 and d.minute == 0 assert d.hour == 4 and d.minute == 0
# 3 new, 2 failed, 1 due # 3 new, 2 failed, 1 due
deck.conf['counts'] = COUNT_REMAINING deck.conf['counts'] = COUNT_REMAINING
assert deck.sched.counts() == (3,2,1) assert deck.sched.cardCounts() == (3,2,1)
# now's a good time to test the integrity check too # now's a good time to test the integrity check too
deck.fixIntegrity() deck.fixIntegrity()

View file

@ -192,7 +192,7 @@ def test_reviews():
f = d.newFact() f = d.newFact()
f['Front'] = u"one"; f['Back'] = u"two" f['Front'] = u"one"; f['Back'] = u"two"
d.addFact(f) d.addFact(f)
# set the card up as a review card, due yesterday # set the card up as a review card, due 8 days ago
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = 2
c.queue = 2 c.queue = 2
@ -358,13 +358,6 @@ def test_misc():
d.sched.onClose() d.sched.onClose()
d.reset() d.reset()
assert d.sched.getCard() assert d.sched.getCard()
# counts
assert d.sched.timeToday() == 0
assert d.sched.repsToday() == 0
c.timerStarted = time.time() - 10
d.sched.answerCard(c, 2)
assert d.sched.timeToday() > 0
assert d.sched.repsToday() == 1
def test_suspend(): def test_suspend():
d = getEmptyDeck() d = getEmptyDeck()
@ -419,10 +412,10 @@ def test_cram():
conf = d.sched._lrnConf(c) conf = d.sched._lrnConf(c)
conf['reset'] = False conf['reset'] = False
conf['resched'] = False conf['resched'] = False
assert d.sched.counts() == (1, 0, 0) assert d.sched.cardCounts() == (1, 0, 0)
c = d.sched.getCard() c = d.sched.getCard()
d.sched._cardConf(c)['cram']['delays'] = [0.5, 3, 10] d.sched._cardConf(c)['cram']['delays'] = [0.5, 3, 10]
assert d.sched.counts() == (0, 0, 0) assert d.sched.cardCounts() == (0, 0, 0)
# check that estimates work # check that estimates work
assert d.sched.nextIvl(c, 1) == 30 assert d.sched.nextIvl(c, 1) == 30
assert d.sched.nextIvl(c, 2) == 180 assert d.sched.nextIvl(c, 2) == 180
@ -432,7 +425,7 @@ def test_cram():
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.ivl == 100 assert c.ivl == 100
# and should have incremented lrn count # and should have incremented lrn count
assert d.sched.counts()[1] == 1 assert d.sched.cardCounts()[1] == 1
# reset ivl for exit test, and pass card # reset ivl for exit test, and pass card
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)
delta = c.due - time.time() delta = c.due - time.time()
@ -446,7 +439,7 @@ def test_cram():
assert c.due == d.sched.today + c.ivl assert c.due == d.sched.today + c.ivl
# and if the queue is reset, it shouldn't appear in the new queue again # and if the queue is reset, it shouldn't appear in the new queue again
d.reset() d.reset()
assert d.sched.counts() == (0, 0, 0) assert d.sched.cardCounts() == (0, 0, 0)
# now try again with ivl rescheduling # now try again with ivl rescheduling
c = copy.copy(cardcopy) c = copy.copy(cardcopy)
c.flush() c.flush()
@ -477,7 +470,7 @@ def test_cram():
# users should be able to cram entire deck too # users should be able to cram entire deck too
d.conf['groups'] = [] d.conf['groups'] = []
d.cramGroups() d.cramGroups()
assert d.sched.counts()[0] > 0 assert d.sched.cardCounts()[0] > 0
def test_cramLimits(): def test_cramLimits():
d = getEmptyDeck() d = getEmptyDeck()
@ -493,29 +486,29 @@ def test_cramLimits():
# the default cram should return all three # the default cram should return all three
d.conf['groups'] = [1] d.conf['groups'] = [1]
d.cramGroups() d.cramGroups()
assert d.sched.counts()[0] == 3 assert d.sched.cardCounts()[0] == 3
# if we start from the day after tomorrow, it should be 2 # if we start from the day after tomorrow, it should be 2
d.cramGroups(min=1) d.cramGroups(min=1)
assert d.sched.counts()[0] == 2 assert d.sched.cardCounts()[0] == 2
# or after 2 days # or after 2 days
d.cramGroups(min=2) d.cramGroups(min=2)
assert d.sched.counts()[0] == 1 assert d.sched.cardCounts()[0] == 1
# we may get nothing # we may get nothing
d.cramGroups(min=3) d.cramGroups(min=3)
assert d.sched.counts()[0] == 0 assert d.sched.cardCounts()[0] == 0
# tomorrow(0) + dayAfter(1) = 2 # tomorrow(0) + dayAfter(1) = 2
d.cramGroups(max=1) d.cramGroups(max=1)
assert d.sched.counts()[0] == 2 assert d.sched.cardCounts()[0] == 2
# if max is tomorrow, we get only one # if max is tomorrow, we get only one
d.cramGroups(max=0) d.cramGroups(max=0)
assert d.sched.counts()[0] == 1 assert d.sched.cardCounts()[0] == 1
# both should work # both should work
d.cramGroups(min=0, max=0) d.cramGroups(min=0, max=0)
assert d.sched.counts()[0] == 1 assert d.sched.cardCounts()[0] == 1
d.cramGroups(min=1, max=1) d.cramGroups(min=1, max=1)
assert d.sched.counts()[0] == 1 assert d.sched.cardCounts()[0] == 1
d.cramGroups(min=0, max=1) d.cramGroups(min=0, max=1)
assert d.sched.counts()[0] == 2 assert d.sched.cardCounts()[0] == 2
def test_adjIvl(): def test_adjIvl():
d = getEmptyDeck() d = getEmptyDeck()
@ -604,7 +597,7 @@ def test_ordcycle():
assert d.sched.getCard().ord == 1 assert d.sched.getCard().ord == 1
assert d.sched.getCard().ord == 2 assert d.sched.getCard().ord == 2
def test_counts_down(): def test_cardcounts():
d = getEmptyDeck() d = getEmptyDeck()
# add a second group # add a second group
grp = d.groups.id("Default::new group") grp = d.groups.id("Default::new group")
@ -625,11 +618,11 @@ def test_counts_down():
c.flush() c.flush()
d.reset() d.reset()
# with the default settings, there's no count limit # with the default settings, there's no count limit
assert d.sched.counts() == (2,2,2) assert d.sched.cardCounts() == (2,2,2)
# check limit to one group # check limit to one group
d.groups.select(grp) d.groups.select(grp)
d.reset() d.reset()
assert d.sched.counts() == (1,1,1) assert d.sched.cardCounts() == (1,1,1)
def test_counts_idx(): def test_counts_idx():
d = getEmptyDeck() d = getEmptyDeck()
@ -637,21 +630,73 @@ def test_counts_idx():
f['Front'] = u"one"; f['Back'] = u"two" f['Front'] = u"one"; f['Back'] = u"two"
d.addFact(f) d.addFact(f)
d.reset() d.reset()
assert d.sched.counts() == (1, 0, 0) assert d.sched.cardCounts() == (1, 0, 0)
c = d.sched.getCard() c = d.sched.getCard()
# counter's been decremented but idx indicates 1 # counter's been decremented but idx indicates 1
assert d.sched.counts() == (0, 0, 0) assert d.sched.cardCounts() == (0, 0, 0)
assert d.sched.countIdx(c) == 0 assert d.sched.countIdx(c) == 0
# answer to move to learn queue # answer to move to learn queue
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert d.sched.counts() == (0, 1, 0) assert d.sched.cardCounts() == (0, 1, 0)
# fetching again will decrement the count # fetching again will decrement the count
c = d.sched.getCard() c = d.sched.getCard()
assert d.sched.counts() == (0, 0, 0) assert d.sched.cardCounts() == (0, 0, 0)
assert d.sched.countIdx(c) == 1 assert d.sched.countIdx(c) == 1
# answering should add it back again # answering should add it back again
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert d.sched.counts() == (0, 1, 0) assert d.sched.cardCounts() == (0, 1, 0)
def test_repCounts():
d = getEmptyDeck()
f = d.newFact()
f['Front'] = u"one"
d.addFact(f)
d.reset()
# lrnReps should be accurate on pass/fail
assert d.sched.repCounts() == (1, 0, 0)
d.sched.answerCard(d.sched.getCard(), 1)
assert d.sched.repCounts() == (0, 2, 0)
d.sched.answerCard(d.sched.getCard(), 1)
assert d.sched.repCounts() == (0, 2, 0)
d.sched.answerCard(d.sched.getCard(), 2)
assert d.sched.repCounts() == (0, 1, 0)
d.sched.answerCard(d.sched.getCard(), 1)
assert d.sched.repCounts() == (0, 2, 0)
d.sched.answerCard(d.sched.getCard(), 2)
assert d.sched.repCounts() == (0, 1, 0)
d.sched.answerCard(d.sched.getCard(), 2)
assert d.sched.repCounts() == (0, 0, 0)
f = d.newFact()
f['Front'] = u"two"
d.addFact(f)
d.reset()
# initial pass should be correct too
d.sched.answerCard(d.sched.getCard(), 2)
assert d.sched.repCounts() == (0, 1, 0)
d.sched.answerCard(d.sched.getCard(), 1)
assert d.sched.repCounts() == (0, 2, 0)
d.sched.answerCard(d.sched.getCard(), 3)
assert d.sched.repCounts() == (0, 0, 0)
# immediate graduate should work
f = d.newFact()
f['Front'] = u"three"
d.addFact(f)
d.reset()
d.sched.answerCard(d.sched.getCard(), 3)
assert d.sched.repCounts() == (0, 0, 0)
# and failing a review should too
f = d.newFact()
f['Front'] = u"three"
d.addFact(f)
c = f.cards()[0]
c.type = 2
c.queue = 2
c.due = d.sched.today
c.flush()
d.reset()
assert d.sched.repCounts() == (0, 0, 1)
d.sched.answerCard(d.sched.getCard(), 1)
assert d.sched.repCounts() == (0, 2, 0)
def test_timing(): def test_timing():
d = getEmptyDeck() d = getEmptyDeck()
@ -761,7 +806,7 @@ def test_groupFlow():
d.addFact(f) d.addFact(f)
# should get top level one first, then ::1, then ::2 # should get top level one first, then ::1, then ::2
d.reset() d.reset()
assert d.sched.counts() == (3,0,0) assert d.sched.cardCounts() == (3,0,0)
for i in "one", "three", "two": for i in "one", "three", "two":
c = d.sched.getCard() c = d.sched.getCard()
assert c.fact()['Front'] == i assert c.fact()['Front'] == i
@ -814,10 +859,10 @@ def test_forget():
c.queue = 2; c.type = 2; c.ivl = 100; c.due = 0 c.queue = 2; c.type = 2; c.ivl = 100; c.due = 0
c.flush() c.flush()
d.reset() d.reset()
assert d.sched.counts() == (0, 0, 1) assert d.sched.cardCounts() == (0, 0, 1)
d.sched.forgetCards([c.id]) d.sched.forgetCards([c.id])
d.reset() d.reset()
assert d.sched.counts() == (1, 0, 0) assert d.sched.cardCounts() == (1, 0, 0)
def test_resched(): def test_resched():
d = getEmptyDeck() d = getEmptyDeck()

View file

@ -44,18 +44,18 @@ def test_review():
d.reset() d.reset()
assert not d.undoName() assert not d.undoName()
# answer # answer
assert d.sched.counts() == (1, 0, 0) assert d.sched.cardCounts() == (1, 0, 0)
c = d.sched.getCard() c = d.sched.getCard()
assert c.queue == 0 assert c.queue == 0
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)
assert c.left == 1 assert c.left == 1
assert d.sched.counts() == (0, 1, 0) assert d.sched.cardCounts() == (0, 1, 0)
assert c.queue == 1 assert c.queue == 1
# undo # undo
assert d.undoName() assert d.undoName()
d.undo() d.undo()
d.reset() d.reset()
assert d.sched.counts() == (1, 0, 0) assert d.sched.cardCounts() == (1, 0, 0)
c.load() c.load()
assert c.queue == 0 assert c.queue == 0
assert c.left != 1 assert c.left != 1
@ -64,18 +64,18 @@ def test_review():
f['Front'] = u"two" f['Front'] = u"two"
d.addFact(f) d.addFact(f)
d.reset() d.reset()
assert d.sched.counts() == (2, 0, 0) assert d.sched.cardCounts() == (2, 0, 0)
c = d.sched.getCard() c = d.sched.getCard()
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)
c = d.sched.getCard() c = d.sched.getCard()
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)
assert d.sched.counts() == (0, 2, 0) assert d.sched.cardCounts() == (0, 2, 0)
d.undo() d.undo()
d.reset() d.reset()
assert d.sched.counts() == (1, 1, 0) assert d.sched.cardCounts() == (1, 1, 0)
d.undo() d.undo()
d.reset() d.reset()
assert d.sched.counts() == (2, 0, 0) assert d.sched.cardCounts() == (2, 0, 0)
# performing a normal op will clear the review queue # performing a normal op will clear the review queue
c = d.sched.getCard() c = d.sched.getCard()
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)