mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 09:16:38 -04:00
schedtest changes, squashed from local branch
This commit is contained in:
parent
1c390218fc
commit
4070f4eef8
8 changed files with 432 additions and 414 deletions
488
anki/sched.py
488
anki/sched.py
|
@ -17,11 +17,11 @@ from anki.hooks import runHook
|
||||||
# queue types: 0=new/cram, 1=lrn, 2=rev, 3=day lrn, -1=suspended, -2=buried
|
# queue types: 0=new/cram, 1=lrn, 2=rev, 3=day lrn, -1=suspended, -2=buried
|
||||||
# revlog types: 0=lrn, 1=rev, 2=relrn, 3=cram
|
# revlog types: 0=lrn, 1=rev, 2=relrn, 3=cram
|
||||||
# positive revlog intervals are in days (rev), negative in seconds (lrn)
|
# positive revlog intervals are in days (rev), negative in seconds (lrn)
|
||||||
|
# odue/odid store original due/did when cards moved to filtered deck
|
||||||
|
|
||||||
class Scheduler:
|
class Scheduler:
|
||||||
name = "std"
|
name = "std"
|
||||||
haveCustomStudy = True
|
haveCustomStudy = True
|
||||||
_spreadRev = True
|
|
||||||
_burySiblingsOnAnswer = True
|
_burySiblingsOnAnswer = True
|
||||||
|
|
||||||
def __init__(self, col):
|
def __init__(self, col):
|
||||||
|
@ -57,42 +57,52 @@ class Scheduler:
|
||||||
def answerCard(self, card, ease):
|
def answerCard(self, card, ease):
|
||||||
self.col.log()
|
self.col.log()
|
||||||
assert 1 <= ease <= 4
|
assert 1 <= ease <= 4
|
||||||
|
assert 0 <= card.queue <= 3
|
||||||
self.col.markReview(card)
|
self.col.markReview(card)
|
||||||
if self._burySiblingsOnAnswer:
|
if self._burySiblingsOnAnswer:
|
||||||
self._burySiblings(card)
|
self._burySiblings(card)
|
||||||
card.reps += 1
|
card.reps += 1
|
||||||
# former is for logging new cards, latter also covers filt. decks
|
|
||||||
card.wasNew = card.type == 0
|
# filtered cards handled differently
|
||||||
wasNewQ = card.queue == 0
|
if card.odid:
|
||||||
if wasNewQ:
|
self._answerFilteredCard(card, ease)
|
||||||
# came from the new queue, move to learning
|
|
||||||
card.queue = 1
|
|
||||||
# if it was a new card, it's now a learning card
|
|
||||||
if card.type == 0:
|
|
||||||
card.type = 1
|
|
||||||
# init reps to graduation
|
|
||||||
card.left = self._startingLeft(card)
|
|
||||||
# dynamic?
|
|
||||||
if card.odid and card.type == 2:
|
|
||||||
if self._resched(card):
|
|
||||||
# reviews get their ivl boosted on first sight
|
|
||||||
card.ivl = self._dynIvlBoost(card)
|
|
||||||
card.odue = self.today + card.ivl
|
|
||||||
self._updateStats(card, 'new')
|
|
||||||
if card.queue in (1, 3):
|
|
||||||
self._answerLrnCard(card, ease)
|
|
||||||
if not wasNewQ:
|
|
||||||
self._updateStats(card, 'lrn')
|
|
||||||
elif card.queue == 2:
|
|
||||||
self._answerRevCard(card, ease)
|
|
||||||
self._updateStats(card, 'rev')
|
|
||||||
else:
|
else:
|
||||||
raise Exception("Invalid queue")
|
self._answerNormalCard(card, ease)
|
||||||
|
|
||||||
self._updateStats(card, 'time', card.timeTaken())
|
self._updateStats(card, 'time', card.timeTaken())
|
||||||
card.mod = intTime()
|
card.mod = intTime()
|
||||||
card.usn = self.col.usn()
|
card.usn = self.col.usn()
|
||||||
card.flushSched()
|
card.flushSched()
|
||||||
|
|
||||||
|
def _answerNormalCard(self, card, ease):
|
||||||
|
card.wasNew = card.type == 0
|
||||||
|
|
||||||
|
if card.queue == 0:
|
||||||
|
# came from the new queue, move to learning
|
||||||
|
card.queue = 1
|
||||||
|
card.type = 1
|
||||||
|
# init reps to graduation
|
||||||
|
card.left = self._startingLeft(card)
|
||||||
|
# update daily limit
|
||||||
|
self._updateStats(card, 'new')
|
||||||
|
|
||||||
|
if card.queue in (1, 3):
|
||||||
|
self._answerLrnCard(card, ease)
|
||||||
|
#if not wasNewQ:
|
||||||
|
# self._updateStats(card, 'lrn')
|
||||||
|
elif card.queue == 2:
|
||||||
|
self._answerRevCard(card, ease)
|
||||||
|
# update daily limit
|
||||||
|
self._updateStats(card, 'rev')
|
||||||
|
|
||||||
|
def _answerFilteredCard(self, card, ease):
|
||||||
|
# a review card that wasn't due?
|
||||||
|
if card.queue == 0 and card.type == 2:
|
||||||
|
self._answerEarlyRevCard(card, ease)
|
||||||
|
return
|
||||||
|
|
||||||
|
return self._answerNormalCard(card, ease)
|
||||||
|
|
||||||
def counts(self, card=None):
|
def counts(self, card=None):
|
||||||
counts = [self.newCount, self.lrnCount, self.revCount]
|
counts = [self.newCount, self.lrnCount, self.revCount]
|
||||||
if card:
|
if card:
|
||||||
|
@ -127,18 +137,19 @@ order by due""" % self._deckLimit(),
|
||||||
return card.queue
|
return card.queue
|
||||||
|
|
||||||
def answerButtons(self, card):
|
def answerButtons(self, card):
|
||||||
if card.odue:
|
conf = self._cardConf(card)
|
||||||
# normal review in dyn deck?
|
|
||||||
if card.odid and card.queue == 2:
|
# fixme: resched=off case
|
||||||
return 4
|
# if card.odid:
|
||||||
conf = self._lrnConf(card)
|
# if not conf['resched']:
|
||||||
if card.type in (0,1) or len(conf['delays']) > 1:
|
# if card.queue == 2:
|
||||||
return 3
|
# return 4
|
||||||
return 2
|
# conf = self._lrnConf(card)
|
||||||
elif card.queue == 2:
|
# if card.type in (0,1) or len(conf['delays']) > 1:
|
||||||
return 4
|
# return 3
|
||||||
else:
|
# return 2
|
||||||
return 3
|
|
||||||
|
return 4
|
||||||
|
|
||||||
def unburyCards(self):
|
def unburyCards(self):
|
||||||
"Unbury cards."
|
"Unbury cards."
|
||||||
|
@ -516,66 +527,90 @@ did = ? and queue = 3 and due <= ? limit ?""",
|
||||||
return self.col.getCard(self._lrnDayQueue.pop())
|
return self.col.getCard(self._lrnDayQueue.pop())
|
||||||
|
|
||||||
def _answerLrnCard(self, card, ease):
|
def _answerLrnCard(self, card, ease):
|
||||||
# ease 1=no, 2=yes, 3=remove
|
|
||||||
conf = self._lrnConf(card)
|
conf = self._lrnConf(card)
|
||||||
if card.odid and not card.wasNew:
|
if card.type == 2:
|
||||||
type = 3
|
|
||||||
elif card.type == 2:
|
|
||||||
type = 2
|
type = 2
|
||||||
else:
|
else:
|
||||||
type = 0
|
type = 0
|
||||||
leaving = False
|
|
||||||
# lrnCount was decremented once when card was fetched
|
# lrnCount was decremented once when card was fetched
|
||||||
lastLeft = card.left
|
lastLeft = card.left
|
||||||
# immediate graduate?
|
# immediate graduate?
|
||||||
if ease == 3:
|
if ease == 4:
|
||||||
self._rescheduleAsRev(card, conf, True)
|
self._rescheduleAsRev(card, conf, True)
|
||||||
leaving = True
|
self._logLrn(card, ease, conf, True, type, lastLeft)
|
||||||
# graduation time?
|
# next step?
|
||||||
elif ease == 2 and (card.left%1000)-1 <= 0:
|
elif ease == 3:
|
||||||
self._rescheduleAsRev(card, conf, False)
|
# graduation time?
|
||||||
leaving = True
|
if (card.left%1000)-1 <= 0:
|
||||||
|
self._rescheduleAsRev(card, conf, False)
|
||||||
|
self._logLrn(card, ease, conf, True, type, lastLeft)
|
||||||
|
else:
|
||||||
|
self._moveToNextStep(card, conf)
|
||||||
|
self._logLrn(card, ease, conf, False, type, lastLeft)
|
||||||
|
elif ease == 2:
|
||||||
|
self._repeatStep(card, conf)
|
||||||
|
self._logLrn(card, ease, conf, False, type, lastLeft)
|
||||||
else:
|
else:
|
||||||
# one step towards graduation
|
# back to first step
|
||||||
if ease == 2:
|
self._moveToFirstStep(card, conf)
|
||||||
# decrement real left count and recalculate left today
|
self._logLrn(card, ease, conf, False, type, lastLeft)
|
||||||
left = (card.left % 1000) - 1
|
|
||||||
card.left = self._leftToday(conf['delays'], left)*1000 + left
|
def _moveToFirstStep(self, card, conf):
|
||||||
# failed
|
card.left = self._startingLeft(card)
|
||||||
else:
|
resched = self._resched(card)
|
||||||
card.left = self._startingLeft(card)
|
|
||||||
resched = self._resched(card)
|
card.lastIvl = card.ivl
|
||||||
if 'mult' in conf and resched:
|
if card.type == 2 and resched:
|
||||||
# review that's lapsed
|
# review card that will move to relearning
|
||||||
card.ivl = max(1, conf['minInt'], card.ivl*conf['mult'])
|
card.ivl = self._lapseIvl(card, self._lrnConf(card))
|
||||||
else:
|
|
||||||
# new card; no ivl adjustment
|
if card.queue not in (1,3):
|
||||||
pass
|
card.lapses += 1
|
||||||
if resched and card.odid:
|
card.factor = max(1300, card.factor-200)
|
||||||
card.odue = self.today + 1
|
|
||||||
|
# if no relearning steps, reschedule as review immediately
|
||||||
|
if not conf['delays']:
|
||||||
|
self._rescheduleAsRev(card, conf, False)
|
||||||
|
return
|
||||||
|
|
||||||
|
return self._rescheduleLrnCard(card, conf)
|
||||||
|
|
||||||
|
def _moveToNextStep(self, card, conf):
|
||||||
|
# decrement real left count and recalculate left today
|
||||||
|
left = (card.left % 1000) - 1
|
||||||
|
card.left = self._leftToday(conf['delays'], left)*1000 + left
|
||||||
|
|
||||||
|
self._rescheduleLrnCard(card, conf)
|
||||||
|
|
||||||
|
def _repeatStep(self, card, conf):
|
||||||
|
delay = self._delayForRepeatingGrade(conf, card.left)
|
||||||
|
self._rescheduleLrnCard(card, conf, delay=delay)
|
||||||
|
|
||||||
|
def _rescheduleLrnCard(self, card, conf, delay=None):
|
||||||
|
if delay is None:
|
||||||
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)
|
||||||
# due today?
|
# due today?
|
||||||
if card.due < self.dayCutoff:
|
if card.due < self.dayCutoff:
|
||||||
self.lrnCount += card.left // 1000
|
self.lrnCount += card.left // 1000
|
||||||
# if the queue is not empty and there's nothing else to do, make
|
# if the queue is not empty and there's nothing else to do, make
|
||||||
# sure we don't put it at the head of the queue and end up showing
|
# sure we don't put it at the head of the queue and end up showing
|
||||||
# it twice in a row
|
# it twice in a row
|
||||||
card.queue = 1
|
card.queue = 1
|
||||||
if self._lrnQueue and not self.revCount and not self.newCount:
|
if self._lrnQueue and not self.revCount and not self.newCount:
|
||||||
smallestDue = self._lrnQueue[0][0]
|
smallestDue = self._lrnQueue[0][0]
|
||||||
card.due = max(card.due, smallestDue+1)
|
card.due = max(card.due, smallestDue+1)
|
||||||
heappush(self._lrnQueue, (card.due, card.id))
|
heappush(self._lrnQueue, (card.due, card.id))
|
||||||
else:
|
else:
|
||||||
# the card is due in one or more days, so we need to use the
|
# the card is due in one or more days, so we need to use the
|
||||||
# day learn queue
|
# day learn queue
|
||||||
ahead = ((card.due - self.dayCutoff) // 86400) + 1
|
ahead = ((card.due - self.dayCutoff) // 86400) + 1
|
||||||
card.due = self.today + ahead
|
card.due = self.today + ahead
|
||||||
card.queue = 3
|
card.queue = 3
|
||||||
self._logLrn(card, ease, conf, leaving, type, lastLeft)
|
return delay
|
||||||
|
|
||||||
def _delayForGrade(self, conf, left):
|
def _delayForGrade(self, conf, left):
|
||||||
left = left % 1000
|
left = left % 1000
|
||||||
|
@ -589,6 +624,13 @@ did = ? and queue = 3 and due <= ? limit ?""",
|
||||||
delay = 1
|
delay = 1
|
||||||
return delay*60
|
return delay*60
|
||||||
|
|
||||||
|
def _delayForRepeatingGrade(self, conf, left):
|
||||||
|
# halfway between last and next
|
||||||
|
delay1 = self._delayForGrade(conf, left)
|
||||||
|
delay2 = self._delayForGrade(conf, left-1)
|
||||||
|
avg = (delay1+max(delay1, delay2))//2
|
||||||
|
return avg
|
||||||
|
|
||||||
def _lrnConf(self, card):
|
def _lrnConf(self, card):
|
||||||
if card.type == 2:
|
if card.type == 2:
|
||||||
return self._lapseConf(card)
|
return self._lapseConf(card)
|
||||||
|
@ -597,26 +639,31 @@ did = ? and queue = 3 and due <= ? limit ?""",
|
||||||
|
|
||||||
def _rescheduleAsRev(self, card, conf, early):
|
def _rescheduleAsRev(self, card, conf, early):
|
||||||
lapse = card.type == 2
|
lapse = card.type == 2
|
||||||
if lapse:
|
|
||||||
if self._resched(card):
|
|
||||||
card.due = max(self.today+1, card.odue)
|
|
||||||
else:
|
|
||||||
card.due = card.odue
|
|
||||||
card.odue = 0
|
|
||||||
else:
|
|
||||||
self._rescheduleNew(card, conf, early)
|
|
||||||
card.queue = 2
|
|
||||||
card.type = 2
|
|
||||||
# if we were dynamic, graduating means moving back to the old deck
|
|
||||||
resched = self._resched(card)
|
resched = self._resched(card)
|
||||||
|
|
||||||
|
if resched:
|
||||||
|
if lapse:
|
||||||
|
self._rescheduleGraduatingLapse(card)
|
||||||
|
else:
|
||||||
|
self._rescheduleNew(card, conf, early)
|
||||||
|
|
||||||
|
# if we were dynamic, graduating means moving back to the old deck
|
||||||
if card.odid:
|
if card.odid:
|
||||||
|
# and leaving learning if scheduling off
|
||||||
|
if not resched:
|
||||||
|
card.due = card.odue
|
||||||
|
if card.type == 2:
|
||||||
|
card.queue = 2
|
||||||
|
else:
|
||||||
|
card.queue = card.type = 0
|
||||||
|
|
||||||
card.did = card.odid
|
card.did = card.odid
|
||||||
card.odue = 0
|
card.odue = 0
|
||||||
card.odid = 0
|
card.odid = 0
|
||||||
# if rescheduling is off, it needs to be set back to a new card
|
|
||||||
if not resched and not lapse:
|
def _rescheduleGraduatingLapse(self, card):
|
||||||
card.queue = card.type = 0
|
card.due = self.today+card.ivl
|
||||||
card.due = self.col.nextID("pos")
|
card.queue = 2
|
||||||
|
|
||||||
def _startingLeft(self, card):
|
def _startingLeft(self, card):
|
||||||
if card.type == 2:
|
if card.type == 2:
|
||||||
|
@ -640,12 +687,8 @@ did = ? and queue = 3 and due <= ? limit ?""",
|
||||||
ok = i
|
ok = i
|
||||||
return ok+1
|
return ok+1
|
||||||
|
|
||||||
def _graduatingIvl(self, card, conf, early, adj=True):
|
def _graduatingIvl(self, card, conf, early, fuzz=True):
|
||||||
if card.type == 2:
|
if card.type == 2:
|
||||||
# lapsed card being relearnt
|
|
||||||
if card.odid:
|
|
||||||
if conf['resched']:
|
|
||||||
return self._dynIvlBoost(card)
|
|
||||||
return card.ivl
|
return card.ivl
|
||||||
if not early:
|
if not early:
|
||||||
# graduate
|
# graduate
|
||||||
|
@ -653,16 +696,16 @@ did = ? and queue = 3 and due <= ? limit ?""",
|
||||||
else:
|
else:
|
||||||
# early remove
|
# early remove
|
||||||
ideal = conf['ints'][1]
|
ideal = conf['ints'][1]
|
||||||
if adj:
|
if fuzz:
|
||||||
return self._adjRevIvl(card, ideal)
|
ideal = self._fuzzedIvl(ideal)
|
||||||
else:
|
return ideal
|
||||||
return ideal
|
|
||||||
|
|
||||||
def _rescheduleNew(self, card, conf, early):
|
def _rescheduleNew(self, card, conf, early):
|
||||||
"Reschedule a new card that's graduated for the first time."
|
"Reschedule a new card that's graduated for the first time."
|
||||||
card.ivl = self._graduatingIvl(card, conf, early)
|
card.ivl = self._graduatingIvl(card, conf, early)
|
||||||
card.due = self.today+card.ivl
|
card.due = self.today+card.ivl
|
||||||
card.factor = conf['initialFactor']
|
card.factor = conf['initialFactor']
|
||||||
|
card.type = card.queue = 2
|
||||||
|
|
||||||
def _logLrn(self, card, ease, conf, leaving, type, lastLeft):
|
def _logLrn(self, card, ease, conf, leaving, type, lastLeft):
|
||||||
lastIvl = -(self._delayForGrade(conf, lastLeft))
|
lastIvl = -(self._delayForGrade(conf, lastLeft))
|
||||||
|
@ -690,10 +733,11 @@ did = ? and queue = 3 and due <= ? limit ?""",
|
||||||
# review cards in relearning
|
# review cards in relearning
|
||||||
self.col.db.execute("""
|
self.col.db.execute("""
|
||||||
update cards set
|
update cards set
|
||||||
due = odue, queue = 2, mod = %d, usn = %d, odue = 0
|
due = ivl+?, queue = 2, mod = %d, usn = %d,
|
||||||
|
odue = 0 -- older anki versions set odue when cards in relearning
|
||||||
where queue in (1,3) and type = 2
|
where queue in (1,3) and type = 2
|
||||||
%s
|
%s
|
||||||
""" % (intTime(), self.col.usn(), extra))
|
""" % (intTime(), self.col.usn(), extra), self.today)
|
||||||
# new cards in learning
|
# new cards in learning
|
||||||
self.forgetCards(self.col.db.list(
|
self.forgetCards(self.col.db.list(
|
||||||
"select id from cards where queue in (1,3) %s" % extra))
|
"select id from cards where queue in (1,3) %s" % extra))
|
||||||
|
@ -806,48 +850,36 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
|
||||||
self._rescheduleRev(card, ease)
|
self._rescheduleRev(card, ease)
|
||||||
self._logRev(card, ease, delay)
|
self._logRev(card, ease, delay)
|
||||||
|
|
||||||
|
def _answerEarlyRevCard(self, card, ease):
|
||||||
|
delay = 0
|
||||||
|
if ease == 1:
|
||||||
|
delay = self._rescheduleLapse(card)
|
||||||
|
else:
|
||||||
|
self._rescheduleEarlyRev(card, ease)
|
||||||
|
|
||||||
|
self._logRev(card, ease, delay, type=3)
|
||||||
|
|
||||||
def _rescheduleLapse(self, card):
|
def _rescheduleLapse(self, card):
|
||||||
conf = self._lapseConf(card)
|
conf = self._lapseConf(card)
|
||||||
card.lastIvl = card.ivl
|
delay = self._moveToFirstStep(card, conf)
|
||||||
if self._resched(card):
|
|
||||||
card.lapses += 1
|
|
||||||
card.ivl = self._nextLapseIvl(card, conf)
|
|
||||||
card.factor = max(1300, card.factor-200)
|
|
||||||
card.due = self.today + card.ivl
|
|
||||||
# if it's a filtered deck, update odue as well
|
|
||||||
if card.odid:
|
|
||||||
card.odue = card.due
|
|
||||||
# if suspended as a leech, nothing to do
|
# if suspended as a leech, nothing to do
|
||||||
delay = 0
|
|
||||||
if self._checkLeech(card, conf) and card.queue == -1:
|
if self._checkLeech(card, conf) and card.queue == -1:
|
||||||
return delay
|
return 0
|
||||||
# if no relearning steps, nothing to do
|
|
||||||
if not conf['delays']:
|
|
||||||
return delay
|
|
||||||
# record rev due date for later
|
|
||||||
if not card.odue:
|
|
||||||
card.odue = card.due
|
|
||||||
delay = self._delayForGrade(conf, 0)
|
|
||||||
card.due = int(delay + time.time())
|
|
||||||
card.left = self._startingLeft(card)
|
|
||||||
# queue 1
|
|
||||||
if card.due < self.dayCutoff:
|
|
||||||
self.lrnCount += card.left // 1000
|
|
||||||
card.queue = 1
|
|
||||||
heappush(self._lrnQueue, (card.due, card.id))
|
|
||||||
else:
|
|
||||||
# day learn queue
|
|
||||||
ahead = ((card.due - self.dayCutoff) // 86400) + 1
|
|
||||||
card.due = self.today + ahead
|
|
||||||
card.queue = 3
|
|
||||||
return delay
|
return delay
|
||||||
|
|
||||||
def _nextLapseIvl(self, card, conf):
|
def _lapseIvl(self, card, conf):
|
||||||
return max(conf['minInt'], int(card.ivl*conf['mult']))
|
due = card.odue or card.due
|
||||||
|
elapsed = card.ivl - (due - self.today)
|
||||||
|
ivl = min(elapsed, card.ivl)
|
||||||
|
ivl = max(1, conf['minInt'], ivl*conf['mult'])
|
||||||
|
return ivl
|
||||||
|
|
||||||
def _rescheduleRev(self, card, ease):
|
def _rescheduleRev(self, card, ease):
|
||||||
# update interval
|
# update interval
|
||||||
card.lastIvl = card.ivl
|
card.lastIvl = card.ivl
|
||||||
|
|
||||||
if self._resched(card):
|
if self._resched(card):
|
||||||
self._updateRevIvl(card, ease)
|
self._updateRevIvl(card, ease)
|
||||||
# then the rest
|
# then the rest
|
||||||
|
@ -855,18 +887,41 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
|
||||||
card.due = self.today + card.ivl
|
card.due = self.today + card.ivl
|
||||||
else:
|
else:
|
||||||
card.due = card.odue
|
card.due = card.odue
|
||||||
|
|
||||||
|
# card leaves filtered deck
|
||||||
if card.odid:
|
if card.odid:
|
||||||
card.did = card.odid
|
card.did = card.odid
|
||||||
card.odid = 0
|
card.odid = 0
|
||||||
card.odue = 0
|
card.odue = 0
|
||||||
|
|
||||||
def _logRev(self, card, ease, delay):
|
def _rescheduleEarlyRev(self, card, ease):
|
||||||
|
# update interval
|
||||||
|
card.lastIvl = card.ivl
|
||||||
|
|
||||||
|
if self._resched(card):
|
||||||
|
self._updateEarlyRevIvl(card, ease)
|
||||||
|
# then the rest
|
||||||
|
card.factor = max(1300, card.factor+[-150, 0, 150][ease-2])
|
||||||
|
card.due = self.today + card.ivl
|
||||||
|
else:
|
||||||
|
card.due = card.odue
|
||||||
|
|
||||||
|
# move from 0->2
|
||||||
|
card.queue = 2
|
||||||
|
|
||||||
|
# card leaves filtered deck
|
||||||
|
if card.odid:
|
||||||
|
card.did = card.odid
|
||||||
|
card.odid = 0
|
||||||
|
card.odue = 0
|
||||||
|
|
||||||
|
def _logRev(self, card, ease, delay, type=1):
|
||||||
def log():
|
def log():
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
"insert into revlog values (?,?,?,?,?,?,?,?,?)",
|
"insert into revlog values (?,?,?,?,?,?,?,?,?)",
|
||||||
int(time.time()*1000), card.id, self.col.usn(), ease,
|
int(time.time()*1000), card.id, self.col.usn(), ease,
|
||||||
-delay or card.ivl, card.lastIvl, card.factor, card.timeTaken(),
|
-delay or card.ivl, card.lastIvl, card.factor, card.timeTaken(),
|
||||||
1)
|
type)
|
||||||
try:
|
try:
|
||||||
log()
|
log()
|
||||||
except:
|
except:
|
||||||
|
@ -926,13 +981,49 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
|
||||||
|
|
||||||
def _updateRevIvl(self, card, ease):
|
def _updateRevIvl(self, card, ease):
|
||||||
idealIvl = self._nextRevIvl(card, ease)
|
idealIvl = self._nextRevIvl(card, ease)
|
||||||
card.ivl = min(max(self._adjRevIvl(card, idealIvl), card.ivl+1),
|
card.ivl = min(max(self._fuzzedIvl(idealIvl), card.ivl+1),
|
||||||
self._revConf(card)['maxIvl'])
|
self._revConf(card)['maxIvl'])
|
||||||
|
|
||||||
def _adjRevIvl(self, card, idealIvl):
|
def _updateEarlyRevIvl(self, card, ease):
|
||||||
if self._spreadRev:
|
idealIvl = self._earlyReviewIvl(card, ease)
|
||||||
idealIvl = self._fuzzedIvl(idealIvl)
|
card.ivl = min(max(self._fuzzedIvl(idealIvl), card.ivl+1),
|
||||||
return idealIvl
|
self._revConf(card)['maxIvl'])
|
||||||
|
|
||||||
|
# next interval for card when answered early+correctly
|
||||||
|
def _earlyReviewIvl(self, card, ease):
|
||||||
|
assert card.odid and card.type == 2
|
||||||
|
assert card.factor
|
||||||
|
assert ease > 1
|
||||||
|
|
||||||
|
elapsed = card.ivl - (card.odue - self.today)
|
||||||
|
|
||||||
|
conf = self._revConf(card)
|
||||||
|
|
||||||
|
easyBonus = 0
|
||||||
|
# early 3/4 reviews shouldn't decrease previous interval
|
||||||
|
minNewIvl = 1
|
||||||
|
|
||||||
|
if ease == 2:
|
||||||
|
factor = 1.2
|
||||||
|
# hard cards shouldn't have their interval decreased by more than 50%
|
||||||
|
minNewIvl = 0.5
|
||||||
|
elif ease == 3:
|
||||||
|
factor = card.factor / 1000
|
||||||
|
else: # ease == 4:
|
||||||
|
factor = card.factor / 1000 * conf['ease4']
|
||||||
|
# add an extra day, so early reviews have an easy interval nominally
|
||||||
|
# different from the good answer
|
||||||
|
easyBonus = 1
|
||||||
|
|
||||||
|
ivl = max(elapsed * factor, 1)
|
||||||
|
|
||||||
|
# cap interval decreases
|
||||||
|
ivl = max(card.ivl*minNewIvl+easyBonus, ivl)
|
||||||
|
|
||||||
|
# don't require prev+1 for early
|
||||||
|
ivl = self._constrainedIvl(ivl, conf, 0)
|
||||||
|
|
||||||
|
return min(conf['maxIvl'], ivl)
|
||||||
|
|
||||||
# Dynamic deck handling
|
# Dynamic deck handling
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -971,15 +1062,37 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
|
||||||
if not lim:
|
if not lim:
|
||||||
lim = "did = %s" % did
|
lim = "did = %s" % did
|
||||||
self.col.log(self.col.db.list("select id from cards where %s" % lim))
|
self.col.log(self.col.db.list("select id from cards where %s" % lim))
|
||||||
# move out of cram queue
|
|
||||||
self.col.db.execute("""
|
self.col.db.execute("""
|
||||||
update cards set did = odid, queue = (case when type = 1 then 0
|
update cards set did = odid, queue = (case when queue = 0 then type else queue end),
|
||||||
else type end), type = (case when type = 1 then 0 else type end),
|
|
||||||
due = odue, odue = 0, odid = 0, usn = ? where %s""" % lim,
|
due = odue, odue = 0, odid = 0, usn = ? where %s""" % lim,
|
||||||
self.col.usn())
|
self.col.usn())
|
||||||
|
|
||||||
def remFromDyn(self, cids):
|
def remFromDyn(self, cids):
|
||||||
self.emptyDyn(None, "id in %s and odid" % ids2str(cids))
|
for did, grpcids in self._cidsByDid(cids):
|
||||||
|
self.emptyDyn(did, "id in %s" % ids2str(grpcids))
|
||||||
|
|
||||||
|
def _cidsByDid(self, cids):
|
||||||
|
groups = []
|
||||||
|
currentCids = []
|
||||||
|
currentDid = None
|
||||||
|
for id, did in self.col.db.execute("""
|
||||||
|
select id, did from cards where id in %s and odid
|
||||||
|
group by did
|
||||||
|
""" % ids2str(cids)):
|
||||||
|
# next group?
|
||||||
|
if did != currentDid:
|
||||||
|
if currentCids:
|
||||||
|
groups.append((currentDid, currentCids))
|
||||||
|
currentCids = []
|
||||||
|
currentDid = did
|
||||||
|
|
||||||
|
currentCids.append(id)
|
||||||
|
|
||||||
|
if currentCids:
|
||||||
|
groups.append((currentDid, currentCids))
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
def _dynOrder(self, o, l):
|
def _dynOrder(self, o, l):
|
||||||
if o == DYN_OLDEST:
|
if o == DYN_OLDEST:
|
||||||
|
@ -1013,26 +1126,22 @@ due = odue, odue = 0, odid = 0, usn = ? where %s""" % lim,
|
||||||
for c, id in enumerate(ids):
|
for c, id in enumerate(ids):
|
||||||
# start at -100000 so that reviews are all due
|
# start at -100000 so that reviews are all due
|
||||||
data.append((did, -100000+c, u, id))
|
data.append((did, -100000+c, u, id))
|
||||||
# due reviews stay in the review queue. careful: can't use
|
|
||||||
# "odid or did", as sqlite converts to boolean
|
|
||||||
queue = """
|
|
||||||
(case when type=2 and (case when odue then odue <= %d else due <= %d end)
|
|
||||||
then 2 else 0 end)"""
|
|
||||||
queue %= (self.today, self.today)
|
|
||||||
self.col.db.executemany("""
|
|
||||||
update cards set
|
|
||||||
odid = (case when odid then odid else did end),
|
|
||||||
odue = (case when odue then odue else due end),
|
|
||||||
did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
|
|
||||||
|
|
||||||
def _dynIvlBoost(self, card):
|
# fixme: put due reviews in new queue in the future
|
||||||
assert card.odid and card.type == 2
|
queue = """
|
||||||
assert card.factor
|
(case
|
||||||
elapsed = card.ivl - (card.odue - self.today)
|
when queue=2 and due<=%d then queue
|
||||||
factor = ((card.factor/1000)+1.2)/2
|
when queue in (1,3) then queue
|
||||||
ivl = int(max(card.ivl, elapsed * factor, 1))
|
else 0 end)"""
|
||||||
conf = self._revConf(card)
|
queue %= self.today
|
||||||
return min(conf['maxIvl'], ivl)
|
|
||||||
|
query = """
|
||||||
|
update cards set
|
||||||
|
odid = did, odue = due,
|
||||||
|
did = ?, queue = %s, due = ?, usn = ? where id = ?
|
||||||
|
""" % queue
|
||||||
|
|
||||||
|
self.col.db.executemany(query, data)
|
||||||
|
|
||||||
# Leeches
|
# Leeches
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -1076,14 +1185,13 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
|
||||||
return conf['new']
|
return conf['new']
|
||||||
# dynamic deck; override some attributes, use original deck for others
|
# dynamic deck; override some attributes, use original deck for others
|
||||||
oconf = self.col.decks.confForDid(card.odid)
|
oconf = self.col.decks.confForDid(card.odid)
|
||||||
delays = conf['delays'] or oconf['new']['delays']
|
|
||||||
return dict(
|
return dict(
|
||||||
# original deck
|
# original deck
|
||||||
ints=oconf['new']['ints'],
|
ints=oconf['new']['ints'],
|
||||||
initialFactor=oconf['new']['initialFactor'],
|
initialFactor=oconf['new']['initialFactor'],
|
||||||
bury=oconf['new'].get("bury", True),
|
bury=oconf['new'].get("bury", True),
|
||||||
|
delays=oconf['new']['delays'],
|
||||||
# overrides
|
# overrides
|
||||||
delays=delays,
|
|
||||||
separate=conf['separate'],
|
separate=conf['separate'],
|
||||||
order=NEW_CARDS_DUE,
|
order=NEW_CARDS_DUE,
|
||||||
perDay=self.reportLimit
|
perDay=self.reportLimit
|
||||||
|
@ -1096,15 +1204,14 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
|
||||||
return conf['lapse']
|
return conf['lapse']
|
||||||
# dynamic deck; override some attributes, use original deck for others
|
# dynamic deck; override some attributes, use original deck for others
|
||||||
oconf = self.col.decks.confForDid(card.odid)
|
oconf = self.col.decks.confForDid(card.odid)
|
||||||
delays = conf['delays'] or oconf['lapse']['delays']
|
|
||||||
return dict(
|
return dict(
|
||||||
# original deck
|
# original deck
|
||||||
minInt=oconf['lapse']['minInt'],
|
minInt=oconf['lapse']['minInt'],
|
||||||
leechFails=oconf['lapse']['leechFails'],
|
leechFails=oconf['lapse']['leechFails'],
|
||||||
leechAction=oconf['lapse']['leechAction'],
|
leechAction=oconf['lapse']['leechAction'],
|
||||||
mult=oconf['lapse']['mult'],
|
mult=oconf['lapse']['mult'],
|
||||||
|
delays=oconf['lapse']['delays'],
|
||||||
# overrides
|
# overrides
|
||||||
delays=delays,
|
|
||||||
resched=conf['resched'],
|
resched=conf['resched'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1224,14 +1331,19 @@ To study outside of the normal schedule, click the Custom Study button below."""
|
||||||
|
|
||||||
def nextIvl(self, card, ease):
|
def nextIvl(self, card, ease):
|
||||||
"Return the next interval for CARD, in seconds."
|
"Return the next interval for CARD, in seconds."
|
||||||
if card.queue in (0,1,3):
|
if card.queue == 0 and card.type == 2 and ease > 1:
|
||||||
|
if self._resched(card):
|
||||||
|
return self._earlyReviewIvl(card, ease)*86400
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
elif card.queue in (0,1,3):
|
||||||
return self._nextLrnIvl(card, ease)
|
return self._nextLrnIvl(card, ease)
|
||||||
elif ease == 1:
|
elif ease == 1:
|
||||||
# lapsed
|
# lapsed
|
||||||
conf = self._lapseConf(card)
|
conf = self._lapseConf(card)
|
||||||
if conf['delays']:
|
if conf['delays']:
|
||||||
return conf['delays'][0]*60
|
return conf['delays'][0]*60
|
||||||
return self._nextLapseIvl(card, conf)*86400
|
return self._lapseIvl(card, conf)*86400
|
||||||
else:
|
else:
|
||||||
# review
|
# review
|
||||||
return self._nextRevIvl(card, ease)*86400
|
return self._nextRevIvl(card, ease)*86400
|
||||||
|
@ -1244,18 +1356,20 @@ To study outside of the normal schedule, click the Custom Study button below."""
|
||||||
if ease == 1:
|
if ease == 1:
|
||||||
# fail
|
# fail
|
||||||
return self._delayForGrade(conf, len(conf['delays']))
|
return self._delayForGrade(conf, len(conf['delays']))
|
||||||
elif ease == 3:
|
elif ease == 2:
|
||||||
|
return self._delayForRepeatingGrade(conf, card.left)
|
||||||
|
elif ease == 4:
|
||||||
# early removal
|
# early removal
|
||||||
if not self._resched(card):
|
if not self._resched(card):
|
||||||
return 0
|
return 0
|
||||||
return self._graduatingIvl(card, conf, True, adj=False) * 86400
|
return self._graduatingIvl(card, conf, True, fuzz=False) * 86400
|
||||||
else:
|
else: # ease == 3
|
||||||
left = card.left%1000 - 1
|
left = card.left%1000 - 1
|
||||||
if left <= 0:
|
if left <= 0:
|
||||||
# graduate
|
# graduate
|
||||||
if not self._resched(card):
|
if not self._resched(card):
|
||||||
return 0
|
return 0
|
||||||
return self._graduatingIvl(card, conf, False, adj=False) * 86400
|
return self._graduatingIvl(card, conf, False, fuzz=False) * 86400
|
||||||
else:
|
else:
|
||||||
return self._delayForGrade(conf, left)
|
return self._delayForGrade(conf, left)
|
||||||
|
|
||||||
|
|
|
@ -133,19 +133,15 @@ class CustomStudy(QDialog):
|
||||||
dyn = self.mw.col.decks.get(did)
|
dyn = self.mw.col.decks.get(did)
|
||||||
# and then set various options
|
# and then set various options
|
||||||
if i == RADIO_FORGOT:
|
if i == RADIO_FORGOT:
|
||||||
dyn['delays'] = [1]
|
|
||||||
dyn['terms'][0] = ['rated:%d:1' % spin, DYN_MAX_SIZE, DYN_RANDOM]
|
dyn['terms'][0] = ['rated:%d:1' % spin, DYN_MAX_SIZE, DYN_RANDOM]
|
||||||
dyn['resched'] = False
|
dyn['resched'] = False
|
||||||
elif i == RADIO_AHEAD:
|
elif i == RADIO_AHEAD:
|
||||||
dyn['delays'] = None
|
|
||||||
dyn['terms'][0] = ['prop:due<=%d' % spin, DYN_MAX_SIZE, DYN_DUE]
|
dyn['terms'][0] = ['prop:due<=%d' % spin, DYN_MAX_SIZE, DYN_DUE]
|
||||||
dyn['resched'] = True
|
dyn['resched'] = True
|
||||||
elif i == RADIO_PREVIEW:
|
elif i == RADIO_PREVIEW:
|
||||||
dyn['delays'] = None
|
|
||||||
dyn['terms'][0] = ['is:new added:%s'%spin, DYN_MAX_SIZE, DYN_OLDEST]
|
dyn['terms'][0] = ['is:new added:%s'%spin, DYN_MAX_SIZE, DYN_OLDEST]
|
||||||
dyn['resched'] = False
|
dyn['resched'] = False
|
||||||
elif i == RADIO_CRAM:
|
elif i == RADIO_CRAM:
|
||||||
dyn['delays'] = None
|
|
||||||
type = f.cardType.currentRow()
|
type = f.cardType.currentRow()
|
||||||
if type == TYPE_NEW:
|
if type == TYPE_NEW:
|
||||||
terms = "is:new "
|
terms = "is:new "
|
||||||
|
|
|
@ -43,12 +43,6 @@ class DeckConf(QDialog):
|
||||||
d = self.deck
|
d = self.deck
|
||||||
search, limit, order = d['terms'][0]
|
search, limit, order = d['terms'][0]
|
||||||
f.search.setText(search)
|
f.search.setText(search)
|
||||||
if d['delays']:
|
|
||||||
f.steps.setText(self.listToUser(d['delays']))
|
|
||||||
f.stepsOn.setChecked(True)
|
|
||||||
else:
|
|
||||||
f.steps.setText("1 10")
|
|
||||||
f.stepsOn.setChecked(False)
|
|
||||||
f.resched.setChecked(d['resched'])
|
f.resched.setChecked(d['resched'])
|
||||||
f.order.setCurrentIndex(order)
|
f.order.setCurrentIndex(order)
|
||||||
f.limit.setValue(limit)
|
f.limit.setValue(limit)
|
||||||
|
@ -57,12 +51,6 @@ class DeckConf(QDialog):
|
||||||
f = self.form
|
f = self.form
|
||||||
d = self.deck
|
d = self.deck
|
||||||
d['delays'] = None
|
d['delays'] = None
|
||||||
if f.stepsOn.isChecked():
|
|
||||||
steps = self.userToList(f.steps)
|
|
||||||
if steps:
|
|
||||||
d['delays'] = steps
|
|
||||||
else:
|
|
||||||
d['delays'] = None
|
|
||||||
d['terms'][0] = [f.search.text(),
|
d['terms'][0] = [f.search.text(),
|
||||||
f.limit.value(),
|
f.limit.value(),
|
||||||
f.order.currentIndex()]
|
f.order.currentIndex()]
|
||||||
|
|
|
@ -82,23 +82,6 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QCheckBox" name="stepsOn">
|
|
||||||
<property name="text">
|
|
||||||
<string>Custom steps (in minutes)</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QLineEdit" name="steps">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>1 10</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -132,8 +115,6 @@
|
||||||
<tabstop>limit</tabstop>
|
<tabstop>limit</tabstop>
|
||||||
<tabstop>order</tabstop>
|
<tabstop>order</tabstop>
|
||||||
<tabstop>resched</tabstop>
|
<tabstop>resched</tabstop>
|
||||||
<tabstop>stepsOn</tabstop>
|
|
||||||
<tabstop>steps</tabstop>
|
|
||||||
<tabstop>buttonBox</tabstop>
|
<tabstop>buttonBox</tabstop>
|
||||||
</tabstops>
|
</tabstops>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
@ -170,21 +151,5 @@
|
||||||
</hint>
|
</hint>
|
||||||
</hints>
|
</hints>
|
||||||
</connection>
|
</connection>
|
||||||
<connection>
|
|
||||||
<sender>stepsOn</sender>
|
|
||||||
<signal>toggled(bool)</signal>
|
|
||||||
<receiver>steps</receiver>
|
|
||||||
<slot>setEnabled(bool)</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>126</x>
|
|
||||||
<y>207</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>272</x>
|
|
||||||
<y>205</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
</connections>
|
||||||
</ui>
|
</ui>
|
||||||
|
|
|
@ -89,8 +89,8 @@ def test_export_anki_due():
|
||||||
deck.crt -= 86400*10
|
deck.crt -= 86400*10
|
||||||
deck.sched.reset()
|
deck.sched.reset()
|
||||||
c = deck.sched.getCard()
|
c = deck.sched.getCard()
|
||||||
deck.sched.answerCard(c, 2)
|
deck.sched.answerCard(c, 3)
|
||||||
deck.sched.answerCard(c, 2)
|
deck.sched.answerCard(c, 3)
|
||||||
# should have ivl of 1, due on day 11
|
# should have ivl of 1, due on day 11
|
||||||
assert c.ivl == 1
|
assert c.ivl == 1
|
||||||
assert c.due == 11
|
assert c.due == 11
|
||||||
|
|
|
@ -8,7 +8,6 @@ from tests.shared import getEmptyCol
|
||||||
from anki.utils import intTime
|
from anki.utils import intTime
|
||||||
from anki.hooks import addHook
|
from anki.hooks import addHook
|
||||||
|
|
||||||
|
|
||||||
def test_clock():
|
def test_clock():
|
||||||
d = getEmptyCol()
|
d = getEmptyCol()
|
||||||
if (d.sched.dayCutoff - intTime()) < 10*60:
|
if (d.sched.dayCutoff - intTime()) < 10*60:
|
||||||
|
@ -132,18 +131,18 @@ def test_learn():
|
||||||
t = round(c.due - time.time())
|
t = round(c.due - time.time())
|
||||||
assert t >= 25 and t <= 40
|
assert t >= 25 and t <= 40
|
||||||
# pass it once
|
# pass it once
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 3)
|
||||||
# it should by due in 3 minutes
|
# it should by due in 3 minutes
|
||||||
assert round(c.due - time.time()) in (179, 180)
|
assert round(c.due - time.time()) in (179, 180)
|
||||||
assert c.left%1000 == 2
|
assert c.left%1000 == 2
|
||||||
assert c.left//1000 == 2
|
assert c.left//1000 == 2
|
||||||
# check log is accurate
|
# check log is accurate
|
||||||
log = d.db.first("select * from revlog order by id desc")
|
log = d.db.first("select * from revlog order by id desc")
|
||||||
assert log[3] == 2
|
assert log[3] == 3
|
||||||
assert log[4] == -180
|
assert log[4] == -180
|
||||||
assert log[5] == -30
|
assert log[5] == -30
|
||||||
# pass again
|
# pass again
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 3)
|
||||||
# it should by due in 10 minutes
|
# it should by due in 10 minutes
|
||||||
assert round(c.due - time.time()) in (599, 600)
|
assert round(c.due - time.time()) in (599, 600)
|
||||||
assert c.left%1000 == 1
|
assert c.left%1000 == 1
|
||||||
|
@ -151,7 +150,7 @@ def test_learn():
|
||||||
# the next pass should graduate the card
|
# the next pass should graduate the card
|
||||||
assert c.queue == 1
|
assert c.queue == 1
|
||||||
assert c.type == 1
|
assert c.type == 1
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 3)
|
||||||
assert c.queue == 2
|
assert c.queue == 2
|
||||||
assert c.type == 2
|
assert c.type == 2
|
||||||
# should be due tomorrow, with an interval of 1
|
# should be due tomorrow, with an interval of 1
|
||||||
|
@ -160,29 +159,48 @@ def test_learn():
|
||||||
# or normal removal
|
# or normal removal
|
||||||
c.type = 0
|
c.type = 0
|
||||||
c.queue = 1
|
c.queue = 1
|
||||||
d.sched.answerCard(c, 3)
|
d.sched.answerCard(c, 4)
|
||||||
assert c.type == 2
|
assert c.type == 2
|
||||||
assert c.queue == 2
|
assert c.queue == 2
|
||||||
assert checkRevIvl(d, c, 4)
|
assert checkRevIvl(d, c, 4)
|
||||||
# revlog should have been updated each time
|
# revlog should have been updated each time
|
||||||
assert d.db.scalar("select count() from revlog where type = 0") == 5
|
assert d.db.scalar("select count() from revlog where type = 0") == 5
|
||||||
# now failed card handling
|
|
||||||
c.type = 2
|
def test_relearn():
|
||||||
c.queue = 1
|
d = getEmptyCol()
|
||||||
c.odue = 123
|
f = d.newNote()
|
||||||
d.sched.answerCard(c, 3)
|
f['Front'] = "one"
|
||||||
assert c.due == 123
|
d.addNote(f)
|
||||||
assert c.type == 2
|
c = f.cards()[0]
|
||||||
assert c.queue == 2
|
c.ivl = 100
|
||||||
# we should be able to remove manually, too
|
c.due = d.sched.today
|
||||||
c.type = 2
|
c.type = c.queue = 2
|
||||||
c.queue = 1
|
|
||||||
c.odue = 321
|
|
||||||
c.flush()
|
c.flush()
|
||||||
d.sched.removeLrn()
|
|
||||||
|
# fail the card
|
||||||
|
d.reset()
|
||||||
|
c = d.sched.getCard()
|
||||||
|
d.sched.answerCard(c, 1)
|
||||||
|
assert c.queue == 1
|
||||||
|
assert c.type == 2
|
||||||
|
assert c.ivl == 1
|
||||||
|
|
||||||
|
# immediately graduate it
|
||||||
|
d.sched.answerCard(c, 4)
|
||||||
|
assert c.queue == c.type == 2
|
||||||
|
assert c.ivl == 1
|
||||||
|
assert c.due == d.sched.today + c.ivl
|
||||||
|
|
||||||
|
# if forced out of learning, it should have the correct due date
|
||||||
|
c.ivl = 100
|
||||||
|
c.due = d.sched.today
|
||||||
|
c.flush()
|
||||||
|
c = d.sched.getCard()
|
||||||
|
d.sched.answerCard(c, 1)
|
||||||
|
assert c.due > intTime()
|
||||||
|
d.sched.removeLrn([c.id])
|
||||||
c.load()
|
c.load()
|
||||||
assert c.queue == 2
|
assert c.due == d.sched.today + c.ivl
|
||||||
assert c.due == 321
|
|
||||||
|
|
||||||
def test_learn_collapsed():
|
def test_learn_collapsed():
|
||||||
d = getEmptyCol()
|
d = getEmptyCol()
|
||||||
|
@ -200,7 +218,7 @@ def test_learn_collapsed():
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
assert c.q().endswith("1")
|
assert c.q().endswith("1")
|
||||||
# pass it so it's due in 10 minutes
|
# pass it so it's due in 10 minutes
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 3)
|
||||||
# get the other card
|
# get the other card
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
assert c.q().endswith("2")
|
assert c.q().endswith("2")
|
||||||
|
@ -220,16 +238,16 @@ def test_learn_day():
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
d.sched._cardConf(c)['new']['delays'] = [1, 10, 1440, 2880]
|
d.sched._cardConf(c)['new']['delays'] = [1, 10, 1440, 2880]
|
||||||
# pass it
|
# pass it
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 3)
|
||||||
# two reps to graduate, 1 more today
|
# two reps to graduate, 1 more today
|
||||||
assert c.left%1000 == 3
|
assert c.left%1000 == 3
|
||||||
assert c.left//1000 == 1
|
assert c.left//1000 == 1
|
||||||
assert d.sched.counts() == (0, 1, 0)
|
assert d.sched.counts() == (0, 1, 0)
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
ni = d.sched.nextIvl
|
ni = d.sched.nextIvl
|
||||||
assert ni(c, 2) == 86400
|
assert ni(c, 3) == 86400
|
||||||
# answering it will place it in queue 3
|
# answering it will place it in queue 3
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 3)
|
||||||
assert c.due == d.sched.today+1
|
assert c.due == d.sched.today+1
|
||||||
assert c.queue == 3
|
assert c.queue == 3
|
||||||
assert not d.sched.getCard()
|
assert not d.sched.getCard()
|
||||||
|
@ -240,21 +258,21 @@ def test_learn_day():
|
||||||
assert d.sched.counts() == (0, 1, 0)
|
assert d.sched.counts() == (0, 1, 0)
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
# nextIvl should work
|
# nextIvl should work
|
||||||
assert ni(c, 2) == 86400*2
|
assert ni(c, 3) == 86400*2
|
||||||
# if we fail it, it should be back in the correct queue
|
# if we fail it, it should be back in the correct queue
|
||||||
d.sched.answerCard(c, 1)
|
d.sched.answerCard(c, 1)
|
||||||
assert c.queue == 1
|
assert c.queue == 1
|
||||||
d.undo()
|
d.undo()
|
||||||
d.reset()
|
d.reset()
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 3)
|
||||||
# simulate the passing of another two days
|
# simulate the passing of another two days
|
||||||
c.due -= 2
|
c.due -= 2
|
||||||
c.flush()
|
c.flush()
|
||||||
d.reset()
|
d.reset()
|
||||||
# the last pass should graduate it into a review card
|
# the last pass should graduate it into a review card
|
||||||
assert ni(c, 2) == 86400
|
assert ni(c, 3) == 86400
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 3)
|
||||||
assert c.queue == c.type == 2
|
assert c.queue == c.type == 2
|
||||||
# if the lapse step is tomorrow, failing it should handle the counts
|
# if the lapse step is tomorrow, failing it should handle the counts
|
||||||
# correctly
|
# correctly
|
||||||
|
@ -287,33 +305,11 @@ def test_reviews():
|
||||||
c.flush()
|
c.flush()
|
||||||
# save it for later use as well
|
# save it for later use as well
|
||||||
cardcopy = copy.copy(c)
|
cardcopy = copy.copy(c)
|
||||||
# failing it should put it in the learn queue with the default options
|
# try with an ease of 2
|
||||||
##################################################
|
|
||||||
# different delay to new
|
|
||||||
d.reset()
|
|
||||||
d.sched._cardConf(c)['lapse']['delays'] = [2, 20]
|
|
||||||
d.sched.answerCard(c, 1)
|
|
||||||
assert c.queue == 1
|
|
||||||
# it should be due tomorrow, with an interval of 1
|
|
||||||
assert c.odue == d.sched.today + 1
|
|
||||||
assert c.ivl == 1
|
|
||||||
# but because it's in the learn queue, its current due time should be in
|
|
||||||
# the future
|
|
||||||
assert c.due >= time.time()
|
|
||||||
assert (c.due - time.time()) > 119
|
|
||||||
# factor should have been decremented
|
|
||||||
assert c.factor == 2300
|
|
||||||
# check counters
|
|
||||||
assert c.lapses == 2
|
|
||||||
assert c.reps == 4
|
|
||||||
# check ests.
|
|
||||||
ni = d.sched.nextIvl
|
|
||||||
assert ni(c, 1) == 120
|
|
||||||
assert ni(c, 2) == 20*60
|
|
||||||
# try again with an ease of 2 instead
|
|
||||||
##################################################
|
##################################################
|
||||||
c = copy.copy(cardcopy)
|
c = copy.copy(cardcopy)
|
||||||
c.flush()
|
c.flush()
|
||||||
|
d.reset()
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 2)
|
||||||
assert c.queue == 2
|
assert c.queue == 2
|
||||||
# the new interval should be (100 + 8/4) * 1.2 = 122
|
# the new interval should be (100 + 8/4) * 1.2 = 122
|
||||||
|
@ -448,30 +444,33 @@ def test_nextIvl():
|
||||||
##################################################
|
##################################################
|
||||||
ni = d.sched.nextIvl
|
ni = d.sched.nextIvl
|
||||||
assert ni(c, 1) == 30
|
assert ni(c, 1) == 30
|
||||||
assert ni(c, 2) == 180
|
assert ni(c, 2) == (30+180)//2
|
||||||
assert ni(c, 3) == 4*86400
|
assert ni(c, 3) == 180
|
||||||
|
assert ni(c, 4) == 4*86400
|
||||||
d.sched.answerCard(c, 1)
|
d.sched.answerCard(c, 1)
|
||||||
# cards in learning
|
# cards in learning
|
||||||
##################################################
|
##################################################
|
||||||
assert ni(c, 1) == 30
|
assert ni(c, 1) == 30
|
||||||
assert ni(c, 2) == 180
|
assert ni(c, 2) == (30+180)//2
|
||||||
assert ni(c, 3) == 4*86400
|
assert ni(c, 3) == 180
|
||||||
d.sched.answerCard(c, 2)
|
assert ni(c, 4) == 4*86400
|
||||||
|
d.sched.answerCard(c, 3)
|
||||||
assert ni(c, 1) == 30
|
assert ni(c, 1) == 30
|
||||||
assert ni(c, 2) == 600
|
assert ni(c, 2) == (180+600)//2
|
||||||
assert ni(c, 3) == 4*86400
|
assert ni(c, 3) == 600
|
||||||
d.sched.answerCard(c, 2)
|
assert ni(c, 4) == 4*86400
|
||||||
|
d.sched.answerCard(c, 3)
|
||||||
# normal graduation is tomorrow
|
# normal graduation is tomorrow
|
||||||
assert ni(c, 2) == 1*86400
|
assert ni(c, 3) == 1*86400
|
||||||
assert ni(c, 3) == 4*86400
|
assert ni(c, 4) == 4*86400
|
||||||
# lapsed cards
|
# lapsed cards
|
||||||
##################################################
|
##################################################
|
||||||
c.type = 2
|
c.type = 2
|
||||||
c.ivl = 100
|
c.ivl = 100
|
||||||
c.factor = STARTING_FACTOR
|
c.factor = STARTING_FACTOR
|
||||||
assert ni(c, 1) == 60
|
assert ni(c, 1) == 60
|
||||||
assert ni(c, 2) == 100*86400
|
|
||||||
assert ni(c, 3) == 100*86400
|
assert ni(c, 3) == 100*86400
|
||||||
|
assert ni(c, 4) == 100*86400
|
||||||
# review cards
|
# review cards
|
||||||
##################################################
|
##################################################
|
||||||
c.queue = 2
|
c.queue = 2
|
||||||
|
@ -547,7 +546,7 @@ def test_suspend():
|
||||||
assert c.due == 1
|
assert c.due == 1
|
||||||
assert c.did == 1
|
assert c.did == 1
|
||||||
|
|
||||||
def test_cram():
|
def test_filt_reviewing_early_normal():
|
||||||
d = getEmptyCol()
|
d = getEmptyCol()
|
||||||
f = d.newNote()
|
f = d.newNote()
|
||||||
f['Front'] = "one"
|
f['Front'] = "one"
|
||||||
|
@ -563,7 +562,6 @@ def test_cram():
|
||||||
c.flush()
|
c.flush()
|
||||||
d.reset()
|
d.reset()
|
||||||
assert d.sched.counts() == (0,0,0)
|
assert d.sched.counts() == (0,0,0)
|
||||||
cardcopy = copy.copy(c)
|
|
||||||
# create a dynamic deck and refresh it
|
# create a dynamic deck and refresh it
|
||||||
did = d.decks.newDyn("Cram")
|
did = d.decks.newDyn("Cram")
|
||||||
d.sched.rebuildDyn(did)
|
d.sched.rebuildDyn(did)
|
||||||
|
@ -574,110 +572,62 @@ def test_cram():
|
||||||
assert d.sched.counts() == (1,0,0)
|
assert d.sched.counts() == (1,0,0)
|
||||||
# grab it and check estimates
|
# grab it and check estimates
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
assert d.sched.answerButtons(c) == 2
|
assert d.sched.answerButtons(c) == 4
|
||||||
assert d.sched.nextIvl(c, 1) == 600
|
assert d.sched.nextIvl(c, 1) == 600
|
||||||
assert d.sched.nextIvl(c, 2) == 138*60*60*24
|
assert d.sched.nextIvl(c, 2) == int(75*1.2)*86400
|
||||||
cram = d.decks.get(did)
|
assert d.sched.nextIvl(c, 3) == int(75*2.5)*86400
|
||||||
cram['delays'] = [1, 10]
|
assert d.sched.nextIvl(c, 4) == int(75*2.5*1.3)*86400
|
||||||
assert d.sched.answerButtons(c) == 3
|
|
||||||
assert d.sched.nextIvl(c, 1) == 60
|
# answer 'good'
|
||||||
assert d.sched.nextIvl(c, 2) == 600
|
d.sched.answerCard(c, 3)
|
||||||
assert d.sched.nextIvl(c, 3) == 138*60*60*24
|
checkRevIvl(d, c, 90)
|
||||||
d.sched.answerCard(c, 2)
|
assert c.due == d.sched.today + c.ivl
|
||||||
# elapsed time was 75 days
|
assert not c.odue
|
||||||
# factor = 2.5+1.2/2 = 1.85
|
# should not be in learning
|
||||||
# int(75*1.85) = 138
|
assert c.queue == 2
|
||||||
assert c.ivl == 138
|
|
||||||
assert c.odue == 138
|
|
||||||
assert c.queue == 1
|
|
||||||
# should be logged as a cram rep
|
# should be logged as a cram rep
|
||||||
assert d.db.scalar(
|
assert d.db.scalar(
|
||||||
"select type from revlog order by id desc limit 1") == 3
|
"select type from revlog order by id desc limit 1") == 3
|
||||||
# check ivls again
|
|
||||||
assert d.sched.nextIvl(c, 1) == 60
|
|
||||||
assert d.sched.nextIvl(c, 2) == 138*60*60*24
|
|
||||||
assert d.sched.nextIvl(c, 3) == 138*60*60*24
|
|
||||||
# when it graduates, due is updated
|
|
||||||
c = d.sched.getCard()
|
|
||||||
d.sched.answerCard(c, 2)
|
|
||||||
assert c.ivl == 138
|
|
||||||
assert c.due == 138
|
|
||||||
assert c.queue == 2
|
|
||||||
# and it will have moved back to the previous deck
|
|
||||||
assert c.did == 1
|
|
||||||
# cram the deck again
|
|
||||||
d.sched.rebuildDyn(did)
|
|
||||||
d.reset()
|
|
||||||
c = d.sched.getCard()
|
|
||||||
# check ivls again - passing should be idempotent
|
|
||||||
assert d.sched.nextIvl(c, 1) == 60
|
|
||||||
assert d.sched.nextIvl(c, 2) == 600
|
|
||||||
assert d.sched.nextIvl(c, 3) == 138*60*60*24
|
|
||||||
d.sched.answerCard(c, 2)
|
|
||||||
assert c.ivl == 138
|
|
||||||
assert c.odue == 138
|
|
||||||
# fail
|
|
||||||
d.sched.answerCard(c, 1)
|
|
||||||
assert d.sched.nextIvl(c, 1) == 60
|
|
||||||
assert d.sched.nextIvl(c, 2) == 600
|
|
||||||
assert d.sched.nextIvl(c, 3) == 86400
|
|
||||||
# delete the deck, returning the card mid-study
|
|
||||||
d.decks.rem(d.decks.selected())
|
|
||||||
assert len(d.sched.deckDueList()) == 1
|
|
||||||
c.load()
|
|
||||||
assert c.ivl == 1
|
|
||||||
assert c.due == d.sched.today+1
|
|
||||||
# make it due
|
|
||||||
d.reset()
|
|
||||||
assert d.sched.counts() == (0,0,0)
|
|
||||||
c.due = -5
|
|
||||||
c.ivl = 100
|
|
||||||
c.flush()
|
|
||||||
d.reset()
|
|
||||||
assert d.sched.counts() == (0,0,1)
|
|
||||||
# cram again
|
|
||||||
did = d.decks.newDyn("Cram")
|
|
||||||
d.sched.rebuildDyn(did)
|
|
||||||
d.reset()
|
|
||||||
assert d.sched.counts() == (0,0,1)
|
|
||||||
c.load()
|
|
||||||
assert d.sched.answerButtons(c) == 4
|
|
||||||
# add a sibling so we can test minSpace, etc
|
|
||||||
c.col = None
|
|
||||||
c2 = copy.deepcopy(c)
|
|
||||||
c2.col = c.col = d
|
|
||||||
c2.id = 123
|
|
||||||
c2.ord = 1
|
|
||||||
c2.due = 325
|
|
||||||
c2.col = c.col
|
|
||||||
c2.flush()
|
|
||||||
# should be able to answer it
|
|
||||||
c = d.sched.getCard()
|
|
||||||
d.sched.answerCard(c, 4)
|
|
||||||
# it should have been moved back to the original deck
|
|
||||||
assert c.did == 1
|
|
||||||
|
|
||||||
def test_cram_rem():
|
# due in 75 days, so it's been waiting 25 days
|
||||||
|
c.ivl = 100
|
||||||
|
c.due = d.sched.today + 75
|
||||||
|
c.flush()
|
||||||
|
d.sched.rebuildDyn(did)
|
||||||
|
d.reset()
|
||||||
|
c = d.sched.getCard()
|
||||||
|
|
||||||
|
assert d.sched.nextIvl(c, 2) == 50*86400
|
||||||
|
assert d.sched.nextIvl(c, 3) == 100*86400
|
||||||
|
assert d.sched.nextIvl(c, 4) == 101*86400
|
||||||
|
|
||||||
|
def test_filt_keep_lrn_state():
|
||||||
d = getEmptyCol()
|
d = getEmptyCol()
|
||||||
f = d.newNote()
|
f = d.newNote()
|
||||||
f['Front'] = "one"
|
f['Front'] = "one"
|
||||||
d.addNote(f)
|
d.addNote(f)
|
||||||
oldDue = f.cards()[0].due
|
|
||||||
|
# fail the card outside filtered deck
|
||||||
|
c = d.sched.getCard()
|
||||||
|
d.sched.answerCard(c, 1)
|
||||||
|
|
||||||
|
assert c.type == c.queue == 1
|
||||||
|
|
||||||
|
# create a dynamic deck and refresh it
|
||||||
did = d.decks.newDyn("Cram")
|
did = d.decks.newDyn("Cram")
|
||||||
d.sched.rebuildDyn(did)
|
d.sched.rebuildDyn(did)
|
||||||
d.reset()
|
d.reset()
|
||||||
c = d.sched.getCard()
|
|
||||||
d.sched.answerCard(c, 2)
|
# card should still be in learning state
|
||||||
# answering the card will put it in the learning queue
|
c.load()
|
||||||
assert c.type == c.queue == 1
|
assert c.type == c.queue == 1
|
||||||
assert c.due != oldDue
|
|
||||||
# if we terminate cramming prematurely it should be set back to new
|
# emptying the deck preserves learning state
|
||||||
d.sched.emptyDyn(did)
|
d.sched.emptyDyn(did)
|
||||||
c.load()
|
c.load()
|
||||||
assert c.type == c.queue == 0
|
assert c.type == c.queue == 1
|
||||||
assert c.due == oldDue
|
|
||||||
|
|
||||||
def test_cram_resched():
|
def test_filt_reschedoff():
|
||||||
# add card
|
# add card
|
||||||
d = getEmptyCol()
|
d = getEmptyCol()
|
||||||
f = d.newNote()
|
f = d.newNote()
|
||||||
|
@ -693,14 +643,16 @@ def test_cram_resched():
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
ni = d.sched.nextIvl
|
ni = d.sched.nextIvl
|
||||||
assert ni(c, 1) == 60
|
assert ni(c, 1) == 60
|
||||||
assert ni(c, 2) == 600
|
assert ni(c, 2) == (60+600)//2
|
||||||
assert ni(c, 3) == 0
|
assert ni(c, 3) == 600
|
||||||
assert d.sched.nextIvlStr(c, 3) == "(end)"
|
assert ni(c, 4) == 0
|
||||||
d.sched.answerCard(c, 3)
|
assert d.sched.nextIvlStr(c, 4) == "(end)"
|
||||||
|
d.sched.answerCard(c, 4)
|
||||||
assert c.queue == c.type == 0
|
assert c.queue == c.type == 0
|
||||||
# undue reviews should also be unaffected
|
# undue reviews should also be unaffected
|
||||||
c.ivl = 100
|
c.ivl = 100
|
||||||
c.type = c.queue = 2
|
c.type = 2
|
||||||
|
c.queue = 0
|
||||||
c.due = d.sched.today + 25
|
c.due = d.sched.today + 25
|
||||||
c.factor = STARTING_FACTOR
|
c.factor = STARTING_FACTOR
|
||||||
c.flush()
|
c.flush()
|
||||||
|
@ -732,7 +684,7 @@ def test_cram_resched():
|
||||||
d.reset()
|
d.reset()
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
d.sched.answerCard(c, 1)
|
d.sched.answerCard(c, 1)
|
||||||
d.sched.answerCard(c, 3)
|
d.sched.answerCard(c, 4)
|
||||||
d.sched.emptyDyn(did)
|
d.sched.emptyDyn(did)
|
||||||
c.load()
|
c.load()
|
||||||
assert c.ivl == 100
|
assert c.ivl == 100
|
||||||
|
@ -769,7 +721,7 @@ def test_cram_resched():
|
||||||
d.reset()
|
d.reset()
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
d.sched.answerCard(c, 1)
|
d.sched.answerCard(c, 1)
|
||||||
d.sched.answerCard(c, 3)
|
d.sched.answerCard(c, 4)
|
||||||
c.load()
|
c.load()
|
||||||
assert c.ivl == 100
|
assert c.ivl == 100
|
||||||
assert c.due == -25
|
assert c.due == -25
|
||||||
|
@ -840,31 +792,31 @@ def test_repCounts():
|
||||||
assert d.sched.counts() == (0, 2, 0)
|
assert d.sched.counts() == (0, 2, 0)
|
||||||
d.sched.answerCard(d.sched.getCard(), 1)
|
d.sched.answerCard(d.sched.getCard(), 1)
|
||||||
assert d.sched.counts() == (0, 2, 0)
|
assert d.sched.counts() == (0, 2, 0)
|
||||||
d.sched.answerCard(d.sched.getCard(), 2)
|
d.sched.answerCard(d.sched.getCard(), 3)
|
||||||
assert d.sched.counts() == (0, 1, 0)
|
assert d.sched.counts() == (0, 1, 0)
|
||||||
d.sched.answerCard(d.sched.getCard(), 1)
|
d.sched.answerCard(d.sched.getCard(), 1)
|
||||||
assert d.sched.counts() == (0, 2, 0)
|
assert d.sched.counts() == (0, 2, 0)
|
||||||
d.sched.answerCard(d.sched.getCard(), 2)
|
d.sched.answerCard(d.sched.getCard(), 3)
|
||||||
assert d.sched.counts() == (0, 1, 0)
|
assert d.sched.counts() == (0, 1, 0)
|
||||||
d.sched.answerCard(d.sched.getCard(), 2)
|
d.sched.answerCard(d.sched.getCard(), 3)
|
||||||
assert d.sched.counts() == (0, 0, 0)
|
assert d.sched.counts() == (0, 0, 0)
|
||||||
f = d.newNote()
|
f = d.newNote()
|
||||||
f['Front'] = "two"
|
f['Front'] = "two"
|
||||||
d.addNote(f)
|
d.addNote(f)
|
||||||
d.reset()
|
d.reset()
|
||||||
# initial pass should be correct too
|
# initial pass should be correct too
|
||||||
d.sched.answerCard(d.sched.getCard(), 2)
|
d.sched.answerCard(d.sched.getCard(), 3)
|
||||||
assert d.sched.counts() == (0, 1, 0)
|
assert d.sched.counts() == (0, 1, 0)
|
||||||
d.sched.answerCard(d.sched.getCard(), 1)
|
d.sched.answerCard(d.sched.getCard(), 1)
|
||||||
assert d.sched.counts() == (0, 2, 0)
|
assert d.sched.counts() == (0, 2, 0)
|
||||||
d.sched.answerCard(d.sched.getCard(), 3)
|
d.sched.answerCard(d.sched.getCard(), 4)
|
||||||
assert d.sched.counts() == (0, 0, 0)
|
assert d.sched.counts() == (0, 0, 0)
|
||||||
# immediate graduate should work
|
# immediate graduate should work
|
||||||
f = d.newNote()
|
f = d.newNote()
|
||||||
f['Front'] = "three"
|
f['Front'] = "three"
|
||||||
d.addNote(f)
|
d.addNote(f)
|
||||||
d.reset()
|
d.reset()
|
||||||
d.sched.answerCard(d.sched.getCard(), 3)
|
d.sched.answerCard(d.sched.getCard(), 4)
|
||||||
assert d.sched.counts() == (0, 0, 0)
|
assert d.sched.counts() == (0, 0, 0)
|
||||||
# and failing a review should too
|
# and failing a review should too
|
||||||
f = d.newNote()
|
f = d.newNote()
|
||||||
|
@ -917,7 +869,7 @@ def test_collapse():
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
d.sched.answerCard(c, 1)
|
d.sched.answerCard(c, 1)
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
d.sched.answerCard(c, 3)
|
d.sched.answerCard(c, 4)
|
||||||
assert not d.sched.getCard()
|
assert not d.sched.getCard()
|
||||||
|
|
||||||
def test_deckDue():
|
def test_deckDue():
|
||||||
|
@ -1001,7 +953,7 @@ def test_deckFlow():
|
||||||
for i in "one", "three", "two":
|
for i in "one", "three", "two":
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
assert c.note()['Front'] == i
|
assert c.note()['Front'] == i
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 3)
|
||||||
|
|
||||||
def test_reorder():
|
def test_reorder():
|
||||||
d = getEmptyCol()
|
d = getEmptyCol()
|
||||||
|
@ -1111,5 +1063,7 @@ def test_failmult():
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
d.sched.answerCard(c, 1)
|
d.sched.answerCard(c, 1)
|
||||||
assert c.ivl == 50
|
assert c.ivl == 50
|
||||||
|
# failing again, the actual elapsed interval is 0,
|
||||||
|
# so the card is reset to new
|
||||||
d.sched.answerCard(c, 1)
|
d.sched.answerCard(c, 1)
|
||||||
assert c.ivl == 25
|
assert c.ivl == 1
|
||||||
|
|
|
@ -343,6 +343,7 @@ def test_filtered_delete():
|
||||||
nid = deck1.db.scalar("select id from notes")
|
nid = deck1.db.scalar("select id from notes")
|
||||||
note = deck1.getNote(nid)
|
note = deck1.getNote(nid)
|
||||||
card = note.cards()[0]
|
card = note.cards()[0]
|
||||||
|
card.queue = 2
|
||||||
card.type = 2
|
card.type = 2
|
||||||
card.ivl = 10
|
card.ivl = 10
|
||||||
card.factor = STARTING_FACTOR
|
card.factor = STARTING_FACTOR
|
||||||
|
|
|
@ -47,7 +47,7 @@ def test_review():
|
||||||
assert d.sched.counts() == (1, 0, 0)
|
assert d.sched.counts() == (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, 3)
|
||||||
assert c.left == 1001
|
assert c.left == 1001
|
||||||
assert d.sched.counts() == (0, 1, 0)
|
assert d.sched.counts() == (0, 1, 0)
|
||||||
assert c.queue == 1
|
assert c.queue == 1
|
||||||
|
@ -67,9 +67,9 @@ def test_review():
|
||||||
d.reset()
|
d.reset()
|
||||||
assert d.sched.counts() == (2, 0, 0)
|
assert d.sched.counts() == (2, 0, 0)
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 3)
|
||||||
c = d.sched.getCard()
|
c = d.sched.getCard()
|
||||||
d.sched.answerCard(c, 2)
|
d.sched.answerCard(c, 3)
|
||||||
assert d.sched.counts() == (0, 2, 0)
|
assert d.sched.counts() == (0, 2, 0)
|
||||||
d.undo()
|
d.undo()
|
||||||
d.reset()
|
d.reset()
|
||||||
|
@ -79,7 +79,7 @@ def test_review():
|
||||||
assert d.sched.counts() == (2, 0, 0)
|
assert d.sched.counts() == (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, 3)
|
||||||
assert d.undoName() == "Review"
|
assert d.undoName() == "Review"
|
||||||
d.save("foo")
|
d.save("foo")
|
||||||
assert d.undoName() == "foo"
|
assert d.undoName() == "foo"
|
||||||
|
|
Loading…
Reference in a new issue