mirror of
https://github.com/ankitects/anki.git
synced 2025-09-23 16:26:40 -04:00
start of cram refactor
This commit is contained in:
parent
a2312f9a1f
commit
01404fafaa
6 changed files with 125 additions and 242 deletions
|
@ -98,10 +98,10 @@ insert or replace into cards values
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
"""update cards set
|
"""update cards set
|
||||||
mod=?, usn=?, type=?, queue=?, due=?, ivl=?, factor=?, reps=?,
|
mod=?, usn=?, type=?, queue=?, due=?, ivl=?, factor=?, reps=?,
|
||||||
lapses=?, left=?, odue=? where id = ?""",
|
lapses=?, left=?, odue=?, did=? where id = ?""",
|
||||||
self.mod, self.usn, self.type, self.queue, self.due, self.ivl,
|
self.mod, self.usn, self.type, self.queue, self.due, self.ivl,
|
||||||
self.factor, self.reps, self.lapses,
|
self.factor, self.reps, self.lapses,
|
||||||
self.left, self.odue, self.id)
|
self.left, self.odue, self.did, self.id)
|
||||||
|
|
||||||
def q(self, reload=False):
|
def q(self, reload=False):
|
||||||
return self.css() + self._getQA(reload)['q']
|
return self.css() + self._getQA(reload)['q']
|
||||||
|
|
|
@ -58,8 +58,7 @@ class _Collection(object):
|
||||||
self.sessionStartReps = 0
|
self.sessionStartReps = 0
|
||||||
self.sessionStartTime = 0
|
self.sessionStartTime = 0
|
||||||
self.lastSessionStart = 0
|
self.lastSessionStart = 0
|
||||||
self._stdSched = Scheduler(self)
|
self.sched = Scheduler(self)
|
||||||
self.sched = self._stdSched
|
|
||||||
# check for improper shutdown
|
# check for improper shutdown
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
|
|
||||||
|
@ -461,8 +460,8 @@ where c.nid == f.id
|
||||||
# Finding cards
|
# Finding cards
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def findCards(self, query, full=False):
|
def findCards(self, query, full=False, order=None):
|
||||||
return anki.find.Finder(self).findCards(query, full)
|
return anki.find.Finder(self).findCards(query, full, order)
|
||||||
|
|
||||||
def findReplace(self, nids, src, dst, regex=None, field=None, fold=True):
|
def findReplace(self, nids, src, dst, regex=None, field=None, fold=True):
|
||||||
return anki.find.findReplace(self, nids, src, dst, regex, field, fold)
|
return anki.find.findReplace(self, nids, src, dst, regex, field, fold)
|
||||||
|
@ -507,20 +506,6 @@ where c.nid == f.id
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Schedulers and cramming
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def stdSched(self):
|
|
||||||
"True if scheduler changed."
|
|
||||||
if self.sched.name != "std":
|
|
||||||
self.cleanup()
|
|
||||||
self.sched = self._stdSched
|
|
||||||
return True
|
|
||||||
|
|
||||||
def cramDecks(self, order="mod desc", min=0, max=None):
|
|
||||||
self.stdSched()
|
|
||||||
self.sched = anki.cram.CramScheduler(self, order, min, max)
|
|
||||||
|
|
||||||
# Undo
|
# Undo
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
116
anki/cram.py
116
anki/cram.py
|
@ -1,116 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
||||||
|
|
||||||
from anki.utils import ids2str, intTime
|
|
||||||
from anki.sched import Scheduler
|
|
||||||
|
|
||||||
# fixme: set log type for cram
|
|
||||||
|
|
||||||
class CramScheduler(Scheduler):
|
|
||||||
name = "cram"
|
|
||||||
|
|
||||||
def __init__(self, col, order, min=0, max=None):
|
|
||||||
Scheduler.__init__(self, col)
|
|
||||||
# should be the opposite order of what you want
|
|
||||||
self.order = order
|
|
||||||
# days to limit cram to, where tomorrow=0. Max is inclusive.
|
|
||||||
self.min = min
|
|
||||||
self.max = max
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def counts(self):
|
|
||||||
return (self.newCount, self.lrnCount, 0)
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self._updateCutoff()
|
|
||||||
self._resetLrnCount()
|
|
||||||
self._resetLrn()
|
|
||||||
self._resetNew()
|
|
||||||
self._resetRev()
|
|
||||||
|
|
||||||
def answerCard(self, card, ease):
|
|
||||||
if card.queue == 2:
|
|
||||||
card.queue = 1
|
|
||||||
card.edue = card.due
|
|
||||||
if card.queue == 1:
|
|
||||||
self._answerLrnCard(card, ease)
|
|
||||||
else:
|
|
||||||
raise Exception("Invalid queue")
|
|
||||||
if ease == 1:
|
|
||||||
conf = self._lrnConf(card)
|
|
||||||
if conf['reset']:
|
|
||||||
# reset interval
|
|
||||||
card.ivl = max(1, int(card.ivl * conf['mult']))
|
|
||||||
# mark card as due today so that it doesn't get rescheduled
|
|
||||||
card.due = card.edue = self.today
|
|
||||||
card.mod = intTime()
|
|
||||||
card.flushSched()
|
|
||||||
|
|
||||||
def countIdx(self, card):
|
|
||||||
if card.queue == 2:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def answerButtons(self, card):
|
|
||||||
return 3
|
|
||||||
|
|
||||||
# Fetching
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def _resetNew(self):
|
|
||||||
"All review cards that are not due yet."
|
|
||||||
if self.max is not None:
|
|
||||||
maxlim = "and due <= %d" % (self.today+1+self.max)
|
|
||||||
else:
|
|
||||||
maxlim = ""
|
|
||||||
self.newQueue = self.col.db.list("""
|
|
||||||
select id from cards where did in %s and queue = 2 and due >= %d
|
|
||||||
%s order by %s limit %d""" % (self._deckLimit(),
|
|
||||||
self.today+1+self.min,
|
|
||||||
maxlim,
|
|
||||||
self.order,
|
|
||||||
self.reportLimit))
|
|
||||||
self.newCount = len(self.newQueue)
|
|
||||||
|
|
||||||
def _resetRev(self):
|
|
||||||
self.revQueue = []
|
|
||||||
self.revCount = 0
|
|
||||||
|
|
||||||
def _timeForNewCard(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _getNewCard(self):
|
|
||||||
if self.newQueue:
|
|
||||||
id = self.newQueue.pop()
|
|
||||||
self.newCount -= 1
|
|
||||||
return id
|
|
||||||
|
|
||||||
# Answering
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def _rescheduleAsRev(self, card, conf, early):
|
|
||||||
Scheduler._rescheduleAsRev(self, card, conf, early)
|
|
||||||
ivl = self._graduatingIvl(card, conf, early)
|
|
||||||
card.due = self.today + ivl
|
|
||||||
# temporarily suspend it
|
|
||||||
self.col.setDirty()
|
|
||||||
card.queue = -3
|
|
||||||
|
|
||||||
def _graduatingIvl(self, card, conf, early):
|
|
||||||
if conf['resched']:
|
|
||||||
# shift card by the time it was delayed
|
|
||||||
return card.ivl - card.edue - self.today
|
|
||||||
else:
|
|
||||||
return card.ivl
|
|
||||||
|
|
||||||
def _lrnConf(self, card):
|
|
||||||
return self._cardConf(card)['cram']
|
|
||||||
|
|
||||||
# Next time reports
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def nextIvl(self, card, ease):
|
|
||||||
"Return the next interval for CARD, in seconds."
|
|
||||||
return self._nextLrnIvl(card, ease)
|
|
|
@ -37,8 +37,9 @@ class Finder(object):
|
||||||
def __init__(self, col):
|
def __init__(self, col):
|
||||||
self.col = col
|
self.col = col
|
||||||
|
|
||||||
def findCards(self, query, full=False):
|
def findCards(self, query, full=False, order=None):
|
||||||
"Return a list of card ids for QUERY."
|
"Return a list of card ids for QUERY."
|
||||||
|
self.order = order
|
||||||
self.query = query
|
self.query = query
|
||||||
self.full = full
|
self.full = full
|
||||||
self._findLimits()
|
self._findLimits()
|
||||||
|
@ -57,7 +58,7 @@ and c.nid=n.id
|
||||||
select c.id from cards c, notes n where %s
|
select c.id from cards c, notes n where %s
|
||||||
and c.nid=n.id %s""" % (q, order)
|
and c.nid=n.id %s""" % (q, order)
|
||||||
res = self.col.db.list(query, **args)
|
res = self.col.db.list(query, **args)
|
||||||
if self.col.conf['sortBackwards']:
|
if not self.order and self.col.conf['sortBackwards']:
|
||||||
res.reverse()
|
res.reverse()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -68,6 +69,9 @@ and c.nid=n.id %s""" % (q, order)
|
||||||
return q, self.lims['args']
|
return q, self.lims['args']
|
||||||
|
|
||||||
def _order(self):
|
def _order(self):
|
||||||
|
# user provided override?
|
||||||
|
if self.order:
|
||||||
|
return self.order
|
||||||
type = self.col.conf['sortType']
|
type = self.col.conf['sortType']
|
||||||
if not type:
|
if not type:
|
||||||
return
|
return
|
||||||
|
|
|
@ -15,6 +15,11 @@ from anki.hooks import runHook
|
||||||
# other queue types: -1=suspended, -2=buried
|
# other queue types: -1=suspended, -2=buried
|
||||||
# positive intervals are in days (rev), negative intervals in seconds (lrn)
|
# positive intervals are in days (rev), negative intervals in seconds (lrn)
|
||||||
|
|
||||||
|
# fixme:
|
||||||
|
# - should log cram reps as cramming
|
||||||
|
# - later we should set conf=None for the cram deck to catch where we're
|
||||||
|
# pulling from the original conf instead of the cram conf
|
||||||
|
|
||||||
class Scheduler(object):
|
class Scheduler(object):
|
||||||
name = "std"
|
name = "std"
|
||||||
def __init__(self, col):
|
def __init__(self, col):
|
||||||
|
@ -23,6 +28,7 @@ class Scheduler(object):
|
||||||
self.reportLimit = 1000
|
self.reportLimit = 1000
|
||||||
# fixme: replace reps with deck based counts
|
# fixme: replace reps with deck based counts
|
||||||
self.reps = 0
|
self.reps = 0
|
||||||
|
self._cramming = False
|
||||||
self._updateCutoff()
|
self._updateCutoff()
|
||||||
|
|
||||||
def getCard(self):
|
def getCard(self):
|
||||||
|
@ -34,6 +40,8 @@ class Scheduler(object):
|
||||||
return card
|
return card
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
|
deck = self.col.decks.current()
|
||||||
|
self._cramming = deck.get('cram')
|
||||||
self._updateCutoff()
|
self._updateCutoff()
|
||||||
self._resetLrn()
|
self._resetLrn()
|
||||||
self._resetRev()
|
self._resetRev()
|
||||||
|
@ -44,12 +52,23 @@ class Scheduler(object):
|
||||||
self.col.markReview(card)
|
self.col.markReview(card)
|
||||||
self.reps += 1
|
self.reps += 1
|
||||||
card.reps += 1
|
card.reps += 1
|
||||||
wasNew = (card.queue == 0) and card.type != 2
|
wasNew = card.queue == 0
|
||||||
if wasNew:
|
if wasNew:
|
||||||
# put it in the learn queue
|
# came from the new queue, move to learning
|
||||||
card.queue = 1
|
card.queue = 1
|
||||||
|
# if it was a new card, it's now a learning card
|
||||||
|
if card.type == 0:
|
||||||
card.type = 1
|
card.type = 1
|
||||||
|
# init reps to graduation
|
||||||
card.left = self._startingLeft(card)
|
card.left = self._startingLeft(card)
|
||||||
|
# cramming?
|
||||||
|
if self._cramming and card.type == 2:
|
||||||
|
# reviews get their ivl boosted on first sight
|
||||||
|
elapsed = card.ivl - card.odue - self.today
|
||||||
|
assert card.factor
|
||||||
|
factor = ((card.factor/1000.0)+1.2)/2.0
|
||||||
|
card.ivl = int(max(card.ivl, elapsed * factor, 1))+1
|
||||||
|
card.odue = self.today + card.ivl
|
||||||
self._updateStats(card, 'new')
|
self._updateStats(card, 'new')
|
||||||
if card.queue == 1:
|
if card.queue == 1:
|
||||||
self._answerLrnCard(card, ease)
|
self._answerLrnCard(card, ease)
|
||||||
|
@ -316,6 +335,8 @@ select id, due from cards where did = ? and queue = 0 limit ?""", did, lim)
|
||||||
"True if it's time to display a new card when distributing."
|
"True if it's time to display a new card when distributing."
|
||||||
if not self.newCount:
|
if not self.newCount:
|
||||||
return False
|
return False
|
||||||
|
if self._cramming:
|
||||||
|
return True
|
||||||
if self.col.conf['newSpread'] == NEW_CARDS_LAST:
|
if self.col.conf['newSpread'] == NEW_CARDS_LAST:
|
||||||
return False
|
return False
|
||||||
elif self.col.conf['newSpread'] == NEW_CARDS_FIRST:
|
elif self.col.conf['newSpread'] == NEW_CARDS_FIRST:
|
||||||
|
@ -348,6 +369,8 @@ select count() from
|
||||||
|
|
||||||
def _deckNewLimitSingle(self, g):
|
def _deckNewLimitSingle(self, g):
|
||||||
"Limit for deck without parent limits."
|
"Limit for deck without parent limits."
|
||||||
|
if self._cramming:
|
||||||
|
return self.reportLimit
|
||||||
c = self.col.decks.confForDid(g['id'])
|
c = self.col.decks.confForDid(g['id'])
|
||||||
return max(0, c['new']['perDay'] - g['newToday'][1])
|
return max(0, c['new']['perDay'] - g['newToday'][1])
|
||||||
|
|
||||||
|
@ -445,12 +468,16 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
|
||||||
|
|
||||||
def _rescheduleAsRev(self, card, conf, early):
|
def _rescheduleAsRev(self, card, conf, early):
|
||||||
if card.type == 2:
|
if card.type == 2:
|
||||||
# failed; put back entry due
|
|
||||||
card.due = card.odue
|
card.due = card.odue
|
||||||
else:
|
else:
|
||||||
self._rescheduleNew(card, conf, early)
|
self._rescheduleNew(card, conf, early)
|
||||||
card.queue = 2
|
card.queue = 2
|
||||||
card.type = 2
|
card.type = 2
|
||||||
|
# if we were cramming, graduating means moving back to the old deck
|
||||||
|
if self._cramming:
|
||||||
|
card.did = card.odid
|
||||||
|
card.odue = 0
|
||||||
|
card.odid = 0
|
||||||
|
|
||||||
def _startingLeft(self, card):
|
def _startingLeft(self, card):
|
||||||
return len(self._cardConf(card)['new']['delays'])
|
return len(self._cardConf(card)['new']['delays'])
|
||||||
|
@ -471,6 +498,7 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
|
||||||
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."
|
||||||
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']
|
||||||
|
@ -691,6 +719,57 @@ did = ? and queue = 2 and due <= ? %s limit ?""" % order,
|
||||||
break
|
break
|
||||||
return idealIvl + fudge
|
return idealIvl + fudge
|
||||||
|
|
||||||
|
# Creation of cramming decks
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
# type is one of all, due, rev
|
||||||
|
# order is one of due, added, random, relative, lapses
|
||||||
|
def cram(self, search, type="all", order="due",
|
||||||
|
limit=100, sched=[1, 10]):
|
||||||
|
# gather card ids and sort
|
||||||
|
order = self._cramOrder(order)
|
||||||
|
limit = " limit %d" % limit
|
||||||
|
ids = self.col.findCards(search, order=order+limit)
|
||||||
|
if not order:
|
||||||
|
random.shuffle(ids)
|
||||||
|
# get a cram deck
|
||||||
|
did = self._cramDeck()
|
||||||
|
# move the cards over
|
||||||
|
self._moveToCram(did, ids)
|
||||||
|
# and change to our new deck
|
||||||
|
self.col.decks.select(did)
|
||||||
|
|
||||||
|
def _cramOrder(self, order):
|
||||||
|
if order == "due":
|
||||||
|
return "order by c.due"
|
||||||
|
elif order == "added":
|
||||||
|
return "order by n.id"
|
||||||
|
elif order == "random":
|
||||||
|
return ""
|
||||||
|
elif order == "relative":
|
||||||
|
pass
|
||||||
|
elif order == "lapses":
|
||||||
|
return "order by lapses desc"
|
||||||
|
elif order == "failed":
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _cramDeck(self):
|
||||||
|
did = self.col.decks.id(_("Cram"))
|
||||||
|
deck = self.col.decks.get(did)
|
||||||
|
# mark it as a cram deck
|
||||||
|
deck['cram'] = True
|
||||||
|
self.col.decks.save(deck)
|
||||||
|
return did
|
||||||
|
|
||||||
|
def _moveToCram(self, did, ids):
|
||||||
|
data = []
|
||||||
|
t = intTime(); u = self.col.usn()
|
||||||
|
for c, id in enumerate(ids):
|
||||||
|
data.append((did, c, t, u, id))
|
||||||
|
self.col.db.executemany("""
|
||||||
|
update cards set odid = did, odue = due, did = ?, queue = 0, due = ?,
|
||||||
|
mod = ?, usn = ? where id = ?""", data)
|
||||||
|
|
||||||
# Leeches
|
# Leeches
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
|
@ -431,8 +431,6 @@ def test_suspend():
|
||||||
assert c.due == 1
|
assert c.due == 1
|
||||||
|
|
||||||
def test_cram():
|
def test_cram():
|
||||||
print "disabled for now"
|
|
||||||
return
|
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
f = d.newNote()
|
f = d.newNote()
|
||||||
f['Front'] = u"one"
|
f['Front'] = u"one"
|
||||||
|
@ -443,112 +441,45 @@ def test_cram():
|
||||||
# due in 25 days, so it's been waiting 75 days
|
# due in 25 days, so it's been waiting 75 days
|
||||||
c.due = d.sched.today + 25
|
c.due = d.sched.today + 25
|
||||||
c.mod = 1
|
c.mod = 1
|
||||||
|
c.factor = 2500
|
||||||
c.startTimer()
|
c.startTimer()
|
||||||
c.flush()
|
c.flush()
|
||||||
cardcopy = copy.copy(c)
|
d.reset()
|
||||||
d.conf['decks'] = [1]
|
|
||||||
d.cramDecks()
|
|
||||||
# first, test with initial intervals preserved
|
|
||||||
conf = d.sched._lrnConf(c)
|
|
||||||
conf['reset'] = False
|
|
||||||
conf['resched'] = False
|
|
||||||
assert d.sched.counts() == (1, 0, 0)
|
|
||||||
c = d.sched.getCard()
|
|
||||||
d.sched._cardConf(c)['cram']['delays'] = [0.5, 3, 10]
|
|
||||||
assert d.sched.counts() == (0,0,0)
|
assert d.sched.counts() == (0,0,0)
|
||||||
|
cardcopy = copy.copy(c)
|
||||||
|
d.sched.cram("")
|
||||||
|
d.reset()
|
||||||
|
# should appear as new in the deck list
|
||||||
|
assert sorted(d.sched.deckDueList())[0][3] == 1
|
||||||
|
# and should appear in the counts
|
||||||
|
assert d.sched.counts() == (1,0,0)
|
||||||
|
# grab it and make one step
|
||||||
|
c = d.sched.getCard()
|
||||||
|
d.sched.answerCard(c, 2)
|
||||||
|
# elapsed time was 75 days
|
||||||
|
# factor = 2.5+1.2/2 = 1.85
|
||||||
|
# int(75*1.85)+1 = 139
|
||||||
|
assert c.ivl == 139
|
||||||
|
assert c.odue == 139
|
||||||
|
assert c.queue == 1
|
||||||
|
# when it graduates, due is updated
|
||||||
|
c = d.sched.getCard()
|
||||||
|
d.sched.answerCard(c, 2)
|
||||||
|
assert c.ivl == 139
|
||||||
|
assert c.due == 139
|
||||||
|
assert c.queue == 2
|
||||||
|
# and it will have moved back to the previous deck
|
||||||
|
assert c.did == 1
|
||||||
|
|
||||||
|
|
||||||
|
# card will have moved b
|
||||||
|
#assert sorted(d.sched.deckDueList())[0][3] == 1
|
||||||
|
|
||||||
|
return
|
||||||
# check that estimates work
|
# check that estimates work
|
||||||
assert d.sched.nextIvl(c, 1) == 30
|
assert d.sched.nextIvl(c, 1) == 30
|
||||||
assert d.sched.nextIvl(c, 2) == 180
|
assert d.sched.nextIvl(c, 2) == 180
|
||||||
assert d.sched.nextIvl(c, 3) == 86400*100
|
assert d.sched.nextIvl(c, 3) == 86400*100
|
||||||
# failing it should not reset ivl
|
|
||||||
assert c.ivl == 100
|
|
||||||
d.sched.answerCard(c, 1)
|
|
||||||
assert c.ivl == 100
|
|
||||||
# and should have incremented lrn count
|
|
||||||
assert d.sched.counts()[1] == 1
|
|
||||||
# reset ivl for exit test, and pass card
|
|
||||||
d.sched.answerCard(c, 2)
|
|
||||||
delta = c.due - time.time()
|
|
||||||
assert delta > 175 and delta <= 180
|
|
||||||
# another two answers should reschedule it
|
|
||||||
assert c.queue == 1
|
|
||||||
d.sched.answerCard(c, 2)
|
|
||||||
d.sched.answerCard(c, 2)
|
|
||||||
assert c.queue == -3
|
|
||||||
assert c.ivl == 100
|
|
||||||
assert c.due == d.sched.today + c.ivl
|
|
||||||
# and if the queue is reset, it shouldn't appear in the new queue again
|
|
||||||
d.reset()
|
|
||||||
assert d.sched.counts() == (0, 0, 0)
|
|
||||||
# now try again with ivl rescheduling
|
|
||||||
c = copy.copy(cardcopy)
|
|
||||||
c.flush()
|
|
||||||
d.cramDecks()
|
|
||||||
conf = d.sched._lrnConf(c)
|
|
||||||
conf['reset'] = False
|
|
||||||
conf['resched'] = True
|
|
||||||
# failures shouldn't matter
|
|
||||||
d.sched.answerCard(c, 1)
|
|
||||||
# graduating the card will keep the same interval, but shift the card
|
|
||||||
# forward the number of days it had been waiting (75)
|
|
||||||
assert d.sched.nextIvl(c, 3) == 75*86400
|
|
||||||
d.sched.answerCard(c, 3)
|
|
||||||
assert c.ivl == 100
|
|
||||||
assert c.due == d.sched.today + 75
|
|
||||||
# try with ivl reset
|
|
||||||
c = copy.copy(cardcopy)
|
|
||||||
c.flush()
|
|
||||||
d.cramDecks()
|
|
||||||
conf = d.sched._lrnConf(c)
|
|
||||||
conf['reset'] = True
|
|
||||||
conf['resched'] = True
|
|
||||||
d.sched.answerCard(c, 1)
|
|
||||||
assert d.sched.nextIvl(c, 3) == 1*86400
|
|
||||||
d.sched.answerCard(c, 3)
|
|
||||||
assert c.ivl == 1
|
|
||||||
assert c.due == d.sched.today + 1
|
|
||||||
# users should be able to cram entire deck too
|
|
||||||
d.conf['decks'] = []
|
|
||||||
d.cramDecks()
|
|
||||||
assert d.sched.counts()[0] > 0
|
|
||||||
|
|
||||||
def test_cramLimits():
|
|
||||||
d = getEmptyDeck()
|
|
||||||
# create three cards, due tomorrow, the next, etc
|
|
||||||
for i in range(3):
|
|
||||||
f = d.newNote()
|
|
||||||
f['Front'] = str(i)
|
|
||||||
d.addNote(f)
|
|
||||||
c = f.cards()[0]
|
|
||||||
c.type = c.queue = 2
|
|
||||||
c.due = d.sched.today + 1 + i
|
|
||||||
c.flush()
|
|
||||||
# the default cram should return all three
|
|
||||||
d.conf['decks'] = [1]
|
|
||||||
d.cramDecks()
|
|
||||||
assert d.sched.counts()[0] == 3
|
|
||||||
# if we start from the day after tomorrow, it should be 2
|
|
||||||
d.cramDecks(min=1)
|
|
||||||
assert d.sched.counts()[0] == 2
|
|
||||||
# or after 2 days
|
|
||||||
d.cramDecks(min=2)
|
|
||||||
assert d.sched.counts()[0] == 1
|
|
||||||
# we may get nothing
|
|
||||||
d.cramDecks(min=3)
|
|
||||||
assert d.sched.counts()[0] == 0
|
|
||||||
# tomorrow(0) + dayAfter(1) = 2
|
|
||||||
d.cramDecks(max=1)
|
|
||||||
assert d.sched.counts()[0] == 2
|
|
||||||
# if max is tomorrow, we get only one
|
|
||||||
d.cramDecks(max=0)
|
|
||||||
assert d.sched.counts()[0] == 1
|
|
||||||
# both should work
|
|
||||||
d.cramDecks(min=0, max=0)
|
|
||||||
assert d.sched.counts()[0] == 1
|
|
||||||
d.cramDecks(min=1, max=1)
|
|
||||||
assert d.sched.counts()[0] == 1
|
|
||||||
d.cramDecks(min=0, max=1)
|
|
||||||
assert d.sched.counts()[0] == 2
|
|
||||||
|
|
||||||
def test_adjIvl():
|
def test_adjIvl():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
|
|
Loading…
Reference in a new issue