customizable revision order, refactor failed cards handling

This commit is contained in:
Damien Elmes 2008-11-28 15:09:13 +09:00
parent 75a61a00cc
commit 0b52e2f0ec

View file

@ -34,6 +34,10 @@ PRIORITY_NONE = 0
MATURE_THRESHOLD = 21 MATURE_THRESHOLD = 21
NEW_CARDS_LAST = 1 NEW_CARDS_LAST = 1
NEW_CARDS_DISTRIBUTE = 0 NEW_CARDS_DISTRIBUTE = 0
REV_CARDS_OLD_FIRST = 0
REV_CARDS_NEW_FIRST = 1
REV_CARDS_DUE_FIRST = 2
REV_CARDS_RANDOM = 3
# parts of the code assume we only have one deck # parts of the code assume we only have one deck
decksTable = Table( decksTable = Table(
@ -42,7 +46,7 @@ decksTable = Table(
Column('created', Float, nullable=False, default=time.time), Column('created', Float, nullable=False, default=time.time),
Column('modified', Float, nullable=False, default=time.time), Column('modified', Float, nullable=False, default=time.time),
Column('description', UnicodeText, nullable=False, default=u""), Column('description', UnicodeText, nullable=False, default=u""),
Column('version', Integer, nullable=False, default=16), Column('version', Integer, nullable=False, default=17),
Column('currentModelId', Integer, ForeignKey("models.id")), Column('currentModelId', Integer, ForeignKey("models.id")),
# syncing # syncing
Column('syncName', UnicodeText), Column('syncName', UnicodeText),
@ -86,7 +90,9 @@ decksTable = Table(
Column('failedNowCount', Integer, nullable=False, default=0), Column('failedNowCount', Integer, nullable=False, default=0),
Column('failedSoonCount', Integer, nullable=False, default=0), Column('failedSoonCount', Integer, nullable=False, default=0),
Column('revCount', Integer, nullable=False, default=0), Column('revCount', Integer, nullable=False, default=0),
Column('newCount', Integer, nullable=False, default=0)) Column('newCount', Integer, nullable=False, default=0),
# rev order
Column('revCardOrder', Integer, nullable=False, default=0))
class Deck(object): class Deck(object):
"Top-level object. Manages facts, cards and scheduling information." "Top-level object. Manages facts, cards and scheduling information."
@ -124,11 +130,11 @@ class Deck(object):
"Return the next due card id, or None." "Return the next due card id, or None."
# failed card due? # failed card due?
if self.failedNowCount: if self.failedNowCount:
return self.s.scalar("select id from failedCardsNow limit 1") return self.s.scalar("select id from failedCards limit 1")
# failed card queue too big? # failed card queue too big?
if self.failedSoonCount >= self.failedCardMax: if self.failedSoonCount >= self.failedCardMax:
return self.s.scalar( return self.s.scalar(
"select id from failedCardsSoon limit 1") "select id from failedCards limit 1")
# distribute new cards? # distribute new cards?
if self.newCardSpacing == NEW_CARDS_DISTRIBUTE: if self.newCardSpacing == NEW_CARDS_DISTRIBUTE:
if self._timeForNewCard(): if self._timeForNewCard():
@ -137,7 +143,7 @@ class Deck(object):
return id return id
# card due for review? # card due for review?
if self.revCount: if self.revCount:
return self.s.scalar("select id from revCards limit 1") return self._getRevCard()
# new cards left? # new cards left?
id = self._maybeGetNewCard() id = self._maybeGetNewCard()
if id: if id:
@ -145,7 +151,7 @@ class Deck(object):
# display failed cards early # display failed cards early
if self.collapseTime: if self.collapseTime:
id = self.s.scalar( id = self.s.scalar(
"select id from failedCardsSoon limit 1") "select id from failedCards limit 1")
return id return id
# Get card: helper functions # Get card: helper functions
@ -178,6 +184,21 @@ class Deck(object):
return self.s.scalar( return self.s.scalar(
"select id from acqCardsOrdered limit 1") "select id from acqCardsOrdered limit 1")
def _getRevCard(self):
"Return the next review card id."
if self.revCardOrder == 0:
return self.s.scalar(
"select id from revCardsOld limit 1")
elif self.revCardOrder == 1:
return self.s.scalar(
"select id from revCardsNew limit 1")
elif self.revCardOrder == 2:
return self.s.scalar(
"select id from revCardsDue limit 1")
else:
return self.s.scalar(
"select id from revCardsRandom limit 1")
def cardFromId(self, id, orm=False): def cardFromId(self, id, orm=False):
"Given a card ID, return a card, and start the card timer." "Given a card ID, return a card, and start the card timer."
if orm: if orm:
@ -241,9 +262,19 @@ type, due, interval, factor, priority from """
new = "acqCardsRandom" new = "acqCardsRandom"
else: else:
new = "acqCardsOrdered" new = "acqCardsOrdered"
if self.revCardOrder == 0:
rev = "revCardsOld"
elif self.revCardOrder == 1:
rev = "revCardsNew"
elif self.revCardOrder == 2:
rev = "revCardsDue"
else:
rev = "revCardsRandom"
d = {} d = {}
d['fail'] = self.s.all(sel + "failedCardsNow limit 100") d['fail'] = self.s.all(sel + """
d['rev'] = self.s.all(sel + "revCards limit 30") cards where type = 0 and isDue = 1 and
combinedDue <= :now limit 30""", now=time.time())
d['rev'] = self.s.all(sel + rev + " limit 30")
if self.newCountToday: if self.newCountToday:
d['acq'] = self.s.all(sel + """ d['acq'] = self.s.all(sel + """
%s where factId in (select distinct factId from cards %s where factId in (select distinct factId from cards
@ -251,7 +282,7 @@ where factId in (select factId from %s limit 60))""" % (new, new))
else: else:
d['acq'] = [] d['acq'] = []
if (not d['fail'] and not d['rev'] and not d['acq']): if (not d['fail'] and not d['rev'] and not d['acq']):
d['fail'] = self.s.all(sel + "failedCardsSoon limit 100") d['fail'] = self.s.all(sel + "failedCards limit 100")
return d return d
# Answering a card # Answering a card
@ -290,7 +321,7 @@ select type, count(type) from cards
where factId = :fid and isDue = 1 where factId = :fid and isDue = 1
group by type""", fid=card.factId): group by type""", fid=card.factId):
if type == 0: if type == 0:
self.failedNowCount -= count self.failedSoonCount -= count
elif type == 1: elif type == 1:
self.revCount -= count self.revCount -= count
else: else:
@ -435,11 +466,12 @@ end)""" + where)
self.cardCount = self.s.scalar("select count(*) from cards") self.cardCount = self.s.scalar("select count(*) from cards")
self.factCount = self.s.scalar("select count(*) from facts") self.factCount = self.s.scalar("select count(*) from facts")
# due counts # due counts
self.failedNowCount = self.s.scalar(
"select count(*) from failedCardsNow")
self.failedSoonCount = cardCount = self.s.scalar( self.failedSoonCount = cardCount = self.s.scalar(
"select count(*) from failedCardsSoon") "select count(*) from failedCards")
self.revCount = self.s.scalar("select count(*) from revCards") self.failedNowCount = self.s.scalar("""
select count(*) from cards where type = 0 and isDue = 1
and combinedDue <= :t""", t=time.time())
self.revCount = self.s.scalar("select count(*) from revCardsOld")
self.newCount = self.s.scalar("select count(*) from acqCardsOrdered") self.newCount = self.s.scalar("select count(*) from acqCardsOrdered")
def checkDue(self): def checkDue(self):
@ -449,20 +481,19 @@ end)""" + where)
stmt = """ stmt = """
update cards set update cards set
isDue = 1 where type = %d and isDue = 0 and isDue = 1 where type = %d and isDue = 0 and
priority in (1,2,3,4) and combinedDue < :now""" priority in (1,2,3,4) and combinedDue <= :now"""
self.failedNowCount += self.s.statement( # failed cards
stmt % 0, now=time.time()).rowcount self.failedSoonCount += self.s.statement(
stmt % 0, now=time.time()+self.delay0).rowcount
self.failedNowCount = self.s.scalar("""
select count(*) from cards where
type = 0 and isDue = 1 and combinedDue <= :now""", now=time.time())
# review
self.revCount += self.s.statement( self.revCount += self.s.statement(
stmt % 1, now=time.time()).rowcount stmt % 1, now=time.time()).rowcount
# new
self.newCount += self.s.statement( self.newCount += self.s.statement(
stmt % 2, now=time.time()).rowcount stmt % 2, now=time.time()).rowcount
self.failedSoonCount = (self.failedNowCount +
self.s.scalar("""
select count(*) from cards where
type = 0 and isDue = 0 and priority in (1,2,3,4)
and combinedDue <= (select delay0 +
strftime("%s", "now")+1 from decks)"""))
# new card handling
self.newCountToday = max(min( self.newCountToday = max(min(
self.newCount, self.newCardsPerDay - self.newCount, self.newCardsPerDay -
self.newCardsToday()), 0) self.newCardsToday()), 0)
@ -1622,6 +1653,9 @@ alter table decks add column failedSoonCount integer not null default 0""")
alter table decks add column revCount integer not null default 0""") alter table decks add column revCount integer not null default 0""")
s.execute(""" s.execute("""
alter table decks add column newCount integer not null default 0""") alter table decks add column newCount integer not null default 0""")
if ver < 17:
s.execute("""
alter table decks add column revCardOrder integer not null default 0""")
except: except:
pass pass
deck = s.query(Deck).get(1) deck = s.query(Deck).get(1)
@ -1693,9 +1727,12 @@ alter table decks add column newCount integer not null default 0""")
create index if not exists ix_cards_combinedDue on cards create index if not exists ix_cards_combinedDue on cards
(type, isDue, combinedDue, priority)""") (type, isDue, combinedDue, priority)""")
deck.s.statement(""" deck.s.statement("""
create index if not exists ix_cards_revisionOrder on cards create index if not exists ix_cards_revOldOrder on cards
(type, isDue, priority desc, interval desc)""") (type, isDue, priority desc, interval desc)""")
deck.s.statement(""" deck.s.statement("""
create index if not exists ix_cards_revNewOrder on cards
(type, isDue, priority desc, interval)""")
deck.s.statement("""
create index if not exists ix_cards_newRandomOrder on cards create index if not exists ix_cards_newRandomOrder on cards
(type, isDue, priority desc, factId, ordinal)""") (type, isDue, priority desc, factId, ordinal)""")
deck.s.statement(""" deck.s.statement("""
@ -1733,36 +1770,42 @@ create index if not exists ix_mediaDeleted_factId on mediaDeleted (mediaId)""")
s = deck.s s = deck.s
# old views # old views
s.statement("drop view if exists failedCards") s.statement("drop view if exists failedCards")
s.statement("drop view if exists acqCards") s.statement("drop view if exists revCardsOld")
s.statement("drop view if exists futureCards") s.statement("drop view if exists revCardsNew")
s.statement("drop view if exists typedCards") s.statement("drop view if exists revCardsDue")
s.statement("drop view if exists failedCards") s.statement("drop view if exists revCardsRandom")
s.statement("drop view if exists failedCardsNow")
s.statement("drop view if exists failedCardsSoon")
s.statement("drop view if exists revCards")
s.statement("drop view if exists acqCardsRandom") s.statement("drop view if exists acqCardsRandom")
s.statement("drop view if exists acqCardsOrdered") s.statement("drop view if exists acqCardsOrdered")
# failed cards
s.statement(""" s.statement("""
create view failedCardsNow as create view failedCards as
select * from cards select * from cards
where type = 0 and isDue = 1 where type = 0 and isDue = 1
and +priority in (1,2,3,4)
order by type, isDue, combinedDue order by type, isDue, combinedDue
""") """)
# rev cards
s.statement(""" s.statement("""
create view failedCardsSoon as create view revCardsOld as
select * from cards
where type = 0 and +priority in (1,2,3,4)
and combinedDue <= (select delay0 +
strftime("%s", "now")+1 from decks where id = 1)
order by type, isDue, combinedDue
""")
s.statement("""
create view revCards as
select * from cards select * from cards
where type = 1 and isDue = 1 where type = 1 and isDue = 1
order by priority desc, interval desc""") order by priority desc, interval desc""")
s.statement(""" s.statement("""
create view revCardsNew as
select * from cards
where type = 1 and isDue = 1
order by priority desc, interval""")
s.statement("""
create view revCardsDue as
select * from cards
where type = 1 and isDue = 1
order by priority desc, combinedDue""")
s.statement("""
create view revCardsRandom as
select * from cards
where type = 1 and isDue = 1
order by priority desc, factId, ordinal""")
# new cards
s.statement("""
create view acqCardsRandom as create view acqCardsRandom as
select * from cards select * from cards
where type = 2 and isDue = 1 where type = 2 and isDue = 1
@ -1933,8 +1976,19 @@ where interval < 1""")
deck.delay2 = 0.0 deck.delay2 = 0.0
deck.version = 15 deck.version = 15
if deck.version < 16: if deck.version < 16:
DeckStorage._addViews(deck) #DeckStorage._addViews(deck)
deck.version = 16 deck.version = 16
if deck.version < 17:
deck.s.statement("drop view if exists acqCards")
deck.s.statement("drop view if exists futureCards")
deck.s.statement("drop view if exists revCards")
deck.s.statement("drop view if exists typedCards")
deck.s.statement("drop view if exists failedCardsNow")
deck.s.statement("drop view if exists failedCardsSoon")
deck.s.statement("drop index if exists ix_cards_revisionOrder")
# add new views
DeckStorage._addViews(deck)
deck.version = 17
deck.s.commit() deck.s.commit()
return deck return deck
_upgradeDeck = staticmethod(_upgradeDeck) _upgradeDeck = staticmethod(_upgradeDeck)
@ -2001,3 +2055,11 @@ def newCardSchedulingLabels():
0: _("Spread new cards out through reviews"), 0: _("Spread new cards out through reviews"),
1: _("Show new cards after all other cards"), 1: _("Show new cards after all other cards"),
} }
def revCardOrderLabels():
return {
0: _("Review oldest cards first"),
1: _("Review newest cards first"),
2: _("Review cards in order due"),
3: _("Review cards in random order"),
}