From 8997d8cc8ba6a66ab4b08364c447ef3e0482eb0a Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 7 Sep 2011 18:48:29 +0900 Subject: [PATCH] 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. --- anki/cards.py | 6 +++++- anki/groups.py | 3 +++ anki/sched.py | 32 ++++++++++++++++++++++---------- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/anki/cards.py b/anki/cards.py index 5a7249e75..2f558aade 100644 --- a/anki/cards.py +++ b/anki/cards.py @@ -127,6 +127,9 @@ lapses=?, grade=?, cycles=?, edue=? where id = ?""", def model(self, reload=False): return self._reviewData()[1] + def groupConf(self): + return self.deck.groups.conf(self.gid) + def template(self): return self._reviewData()[1]['tmpls'][self.ord] @@ -139,4 +142,5 @@ lapses=?, grade=?, cycles=?, edue=? where id = ?""", def timeTaken(self): "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) diff --git a/anki/groups.py b/anki/groups.py index aec4f8c8e..f3dcca4af 100644 --- a/anki/groups.py +++ b/anki/groups.py @@ -16,6 +16,9 @@ from anki.consts import * defaultTopConf = { 'newPerDay': 20, 'newToday': [0, 0], # currentDay, count + 'revToday': [0, 0], + 'lrnToday': [0, 0], + 'timeToday': [0, 0], # currentDay, time in ms 'newTodayOrder': NEW_TODAY_ORD, 'newSpread': NEW_CARDS_DISTRIBUTE, 'collapseTime': 1200, diff --git a/anki/sched.py b/anki/sched.py index 6925e2be7..84a531a2c 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -45,17 +45,22 @@ class Scheduler(object): self.deck.markReview(card) self.reps += 1 card.reps += 1 - if card.queue == 0: + wasNew = card.queue == 0 + if wasNew: # put it in the learn queue card.queue = 1 card.type = 1 - self.deck.groups.top()['newToday'][1] += 1 + self._updateStats('new') if card.queue == 1: self._answerLrnCard(card, ease) + if not wasNew: + self._updateStats('lrn') elif card.queue == 2: self._answerRevCard(card, ease) + self._updateStats('rev') else: raise Exception("Invalid queue") + self._updateStats('time', card.timeTaken()) card.mod = intTime() card.flushSched() @@ -103,6 +108,16 @@ order by due""" % self._groupLimit(), self._resetLrnCount() self._resetRevCount() 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 ########################################################################## @@ -208,10 +223,8 @@ select 1 from cards where gid = ? and # FIXME: need to keep track of reps for timebox and new card introduction def _resetNewCount(self): + self._updateStatsDay("new") 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]) if lim <= 0: self.newCount = 0 @@ -274,6 +287,7 @@ queue = 0 %s order by due limit %d""" % (self._groupLimit(), ########################################################################## def _resetLrnCount(self): + self._updateStatsDay("lrn") self.lrnCount = self.deck.db.scalar(""" select count() from (select id from cards where queue = 1 %s and due < ? limit %d)""" % ( @@ -371,8 +385,6 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) card.factor = conf['initialFactor'] 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))) ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.grade)) def log(): @@ -380,7 +392,7 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) "insert into revlog values (?,?,?,?,?,?,?,?)", int(time.time()*1000), card.id, ease, ivl, lastIvl, - card.factor, taken, type) + card.factor, card.timeTaken(), type) try: log() except: @@ -404,6 +416,7 @@ where queue = 1 and type = 2 ########################################################################## def _resetRevCount(self): + self._updateStatsDay("rev") self.revCount = self.deck.db.scalar(""" select count() from (select id from cards where 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 def _logRev(self, card, ease): - taken = min(card.timeTaken(), self._cardConf(card)['maxTaken']*1000) def log(): self.deck.db.execute( "insert into revlog values (?,?,?,?,?,?,?,?)", int(time.time()*1000), card.id, ease, - card.ivl, card.lastIvl, card.factor, taken, + card.ivl, card.lastIvl, card.factor, card.timeTaken(), 1) try: log()