track all reps & time on a per-day basis

We did away with the stats table because it's impossible to merge it, so the
revlog is canonical now. But we also want a cheap way to display to the user
how much time or how many cards they've done over the day, even if their study
is split into multiple sessions. We were already storing the new cards of a
day in the top level groups, so we just expand that out to log the other info
too.

In the event of a user studying in two places on the same day without syncing,
the counts will not be accurate as they can't be merged without consulting the
revlog, which we want to avoid for performance reasons. But the graphs and
stats do not use the groups for reporting, so the inaccurate counts are only
temporary. Might need to mention this in an FAQ.

Also, since groups are cheap to fetch now, cards now automatically limit
timeTaken() to the group limit, instead of relying on the calling code to do
so.
This commit is contained in:
Damien Elmes 2011-09-07 18:48:29 +09:00
parent 28d045feef
commit 8997d8cc8b
3 changed files with 30 additions and 11 deletions

View file

@ -127,6 +127,9 @@ lapses=?, grade=?, cycles=?, edue=? where id = ?""",
def model(self, reload=False): def model(self, reload=False):
return self._reviewData()[1] return self._reviewData()[1]
def groupConf(self):
return self.deck.groups.conf(self.gid)
def template(self): def template(self):
return self._reviewData()[1]['tmpls'][self.ord] return self._reviewData()[1]['tmpls'][self.ord]
@ -139,4 +142,5 @@ lapses=?, grade=?, cycles=?, edue=? where id = ?""",
def timeTaken(self): def timeTaken(self):
"Time taken to answer card, in integer MS." "Time taken to answer card, in integer MS."
return int((time.time() - self.timerStarted)*1000) total = int((time.time() - self.timerStarted)*1000)
return min(total, self.groupConf()['maxTaken']*1000)

View file

@ -16,6 +16,9 @@ from anki.consts import *
defaultTopConf = { defaultTopConf = {
'newPerDay': 20, 'newPerDay': 20,
'newToday': [0, 0], # currentDay, count 'newToday': [0, 0], # currentDay, count
'revToday': [0, 0],
'lrnToday': [0, 0],
'timeToday': [0, 0], # currentDay, time in ms
'newTodayOrder': NEW_TODAY_ORD, 'newTodayOrder': NEW_TODAY_ORD,
'newSpread': NEW_CARDS_DISTRIBUTE, 'newSpread': NEW_CARDS_DISTRIBUTE,
'collapseTime': 1200, 'collapseTime': 1200,

View file

@ -45,17 +45,22 @@ class Scheduler(object):
self.deck.markReview(card) self.deck.markReview(card)
self.reps += 1 self.reps += 1
card.reps += 1 card.reps += 1
if card.queue == 0: wasNew = card.queue == 0
if wasNew:
# put it in the learn queue # put it in the learn queue
card.queue = 1 card.queue = 1
card.type = 1 card.type = 1
self.deck.groups.top()['newToday'][1] += 1 self._updateStats('new')
if card.queue == 1: if card.queue == 1:
self._answerLrnCard(card, ease) self._answerLrnCard(card, ease)
if not wasNew:
self._updateStats('lrn')
elif card.queue == 2: elif card.queue == 2:
self._answerRevCard(card, ease) self._answerRevCard(card, ease)
self._updateStats('rev')
else: else:
raise Exception("Invalid queue") raise Exception("Invalid queue")
self._updateStats('time', card.timeTaken())
card.mod = intTime() card.mod = intTime()
card.flushSched() card.flushSched()
@ -103,6 +108,16 @@ order by due""" % self._groupLimit(),
self._resetLrnCount() self._resetLrnCount()
self._resetRevCount() self._resetRevCount()
self._resetNewCount() self._resetNewCount()
self._updateStatsDay("time")
def _updateStatsDay(self, type):
l = self.deck.groups.top()
if l[type+'Today'][0] != self.today:
# it's a new day; reset counts
l[type+'Today'] = [self.today, 0]
def _updateStats(self, type, cnt=1):
self.deck.groups.top()[type+'Today'][1] += cnt
# Group counts # Group counts
########################################################################## ##########################################################################
@ -208,10 +223,8 @@ select 1 from cards where gid = ? and
# FIXME: need to keep track of reps for timebox and new card introduction # FIXME: need to keep track of reps for timebox and new card introduction
def _resetNewCount(self): def _resetNewCount(self):
self._updateStatsDay("new")
l = self.deck.groups.top() l = self.deck.groups.top()
if l['newToday'][0] != self.today:
# it's a new day; reset counts
l['newToday'] = [self.today, 0]
lim = min(self.reportLimit, l['newPerDay'] - l['newToday'][1]) lim = min(self.reportLimit, l['newPerDay'] - l['newToday'][1])
if lim <= 0: if lim <= 0:
self.newCount = 0 self.newCount = 0
@ -274,6 +287,7 @@ queue = 0 %s order by due limit %d""" % (self._groupLimit(),
########################################################################## ##########################################################################
def _resetLrnCount(self): def _resetLrnCount(self):
self._updateStatsDay("lrn")
self.lrnCount = self.deck.db.scalar(""" self.lrnCount = self.deck.db.scalar("""
select count() from (select id from cards where select count() from (select id from cards where
queue = 1 %s and due < ? limit %d)""" % ( queue = 1 %s and due < ? limit %d)""" % (
@ -371,8 +385,6 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
card.factor = conf['initialFactor'] card.factor = conf['initialFactor']
def _logLrn(self, card, ease, conf, leaving, type): def _logLrn(self, card, ease, conf, leaving, type):
# limit time taken to global setting
taken = min(card.timeTaken(), self._cardConf(card)['maxTaken']*1000)
lastIvl = -(self._delayForGrade(conf, max(0, card.grade-1))) lastIvl = -(self._delayForGrade(conf, max(0, card.grade-1)))
ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.grade)) ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.grade))
def log(): def log():
@ -380,7 +392,7 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
"insert into revlog values (?,?,?,?,?,?,?,?)", "insert into revlog values (?,?,?,?,?,?,?,?)",
int(time.time()*1000), card.id, ease, int(time.time()*1000), card.id, ease,
ivl, lastIvl, ivl, lastIvl,
card.factor, taken, type) card.factor, card.timeTaken(), type)
try: try:
log() log()
except: except:
@ -404,6 +416,7 @@ where queue = 1 and type = 2
########################################################################## ##########################################################################
def _resetRevCount(self): def _resetRevCount(self):
self._updateStatsDay("rev")
self.revCount = self.deck.db.scalar(""" self.revCount = self.deck.db.scalar("""
select count() from (select id from cards where select count() from (select id from cards where
queue = 2 %s and due <= :lim limit %d)""" % ( queue = 2 %s and due <= :lim limit %d)""" % (
@ -478,12 +491,11 @@ queue = 2 %s and due <= :lim order by %s limit %d""" % (
card.due = self.today + card.ivl card.due = self.today + card.ivl
def _logRev(self, card, ease): def _logRev(self, card, ease):
taken = min(card.timeTaken(), self._cardConf(card)['maxTaken']*1000)
def log(): def log():
self.deck.db.execute( self.deck.db.execute(
"insert into revlog values (?,?,?,?,?,?,?,?)", "insert into revlog values (?,?,?,?,?,?,?,?)",
int(time.time()*1000), card.id, ease, int(time.time()*1000), card.id, ease,
card.ivl, card.lastIvl, card.factor, taken, card.ivl, card.lastIvl, card.factor, card.timeTaken(),
1) 1)
try: try:
log() log()