schedtest changes, squashed from local branch

This commit is contained in:
Damien Elmes 2017-12-23 10:45:56 +10:00
parent 1c390218fc
commit 4070f4eef8
8 changed files with 432 additions and 414 deletions

View file

@ -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
# if card.odid:
# if not conf['resched']:
# if card.queue == 2:
# return 4
# conf = self._lrnConf(card)
# if card.type in (0,1) or len(conf['delays']) > 1:
# return 3
# return 2
return 4 return 4
conf = self._lrnConf(card)
if card.type in (0,1) or len(conf['delays']) > 1:
return 3
return 2
elif card.queue == 2:
return 4
else:
return 3
def unburyCards(self): def unburyCards(self):
"Unbury cards." "Unbury cards."
@ -516,43 +527,67 @@ 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)
# next step?
elif ease == 3:
# graduation time? # graduation time?
elif ease == 2 and (card.left%1000)-1 <= 0: if (card.left%1000)-1 <= 0:
self._rescheduleAsRev(card, conf, False) self._rescheduleAsRev(card, conf, False)
leaving = True self._logLrn(card, ease, conf, True, type, lastLeft)
else: else:
# one step towards graduation self._moveToNextStep(card, conf)
if ease == 2: self._logLrn(card, ease, conf, False, type, lastLeft)
elif ease == 2:
self._repeatStep(card, conf)
self._logLrn(card, ease, conf, False, type, lastLeft)
else:
# back to first step
self._moveToFirstStep(card, conf)
self._logLrn(card, ease, conf, False, type, lastLeft)
def _moveToFirstStep(self, card, conf):
card.left = self._startingLeft(card)
resched = self._resched(card)
card.lastIvl = card.ivl
if card.type == 2 and resched:
# review card that will move to relearning
card.ivl = self._lapseIvl(card, self._lrnConf(card))
if card.queue not in (1,3):
card.lapses += 1
card.factor = max(1300, card.factor-200)
# 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 # decrement real left count and recalculate left today
left = (card.left % 1000) - 1 left = (card.left % 1000) - 1
card.left = self._leftToday(conf['delays'], left)*1000 + left card.left = self._leftToday(conf['delays'], left)*1000 + left
# failed
else: self._rescheduleLrnCard(card, conf)
card.left = self._startingLeft(card)
resched = self._resched(card) def _repeatStep(self, card, conf):
if 'mult' in conf and resched: delay = self._delayForRepeatingGrade(conf, card.left)
# review that's lapsed self._rescheduleLrnCard(card, conf, delay=delay)
card.ivl = max(1, conf['minInt'], card.ivl*conf['mult'])
else: def _rescheduleLrnCard(self, card, conf, delay=None):
# new card; no ivl adjustment if delay is None:
pass
if resched and card.odid:
card.odue = self.today + 1
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
@ -575,7 +610,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
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
resched = self._resched(card)
if resched:
if lapse: if lapse:
if self._resched(card): self._rescheduleGraduatingLapse(card)
card.due = max(self.today+1, card.odue)
else:
card.due = card.odue
card.odue = 0
else: else:
self._rescheduleNew(card, conf, early) self._rescheduleNew(card, conf, early)
card.queue = 2
card.type = 2
# if we were dynamic, graduating means moving back to the old deck # if we were dynamic, graduating means moving back to the old deck
resched = self._resched(card)
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,9 +696,8 @@ 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):
@ -663,6 +705,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
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)

View file

@ -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 "

View file

@ -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()]

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"