From 9e46db5b3eddfdf448ae92ff6ce221147b4817ca Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 18 Mar 2011 07:30:36 +0900 Subject: [PATCH] update finished msg --- anki/sched.py | 252 ++++++++++++-------------------------------- anki/stats.py | 15 +++ tests/test_sched.py | 16 +++ 3 files changed, 96 insertions(+), 187 deletions(-) diff --git a/anki/sched.py b/anki/sched.py index a0ea9c1da..ad0981075 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -7,7 +7,7 @@ from operator import itemgetter from heapq import * #from anki.cards import Card from anki.utils import parseTags, ids2str, intTime -from anki.lang import _ +from anki.lang import _, ngettext from anki.consts import * from anki.hooks import runHook @@ -58,18 +58,6 @@ class Scheduler(object): "Does not include fetched but unanswered." return (self.learnCount, self.revCount, self.newCount) - def timeToday(self): - "Time spent learning today, in seconds." - return self.deck.db.scalar( - "select sum(taken/1000.0) from revlog where time > ?*1000", - self.dayCutoff-86400) or 0 - - def repsToday(self): - "Number of cards answered today." - return self.deck.db.scalar( - "select count() from revlog where time > ?*1000", - self.dayCutoff-86400) - def cardQueue(self, card): return card.queue @@ -476,206 +464,96 @@ queue = 1 %s and due <= :lim order by %s limit %d""" % ( self.updateCutoff() self.reset() - # Times + # Deck finished state ########################################################################## - def nextDueMsg(self): - next = self.earliestTime() - if next: - # all new cards except suspended - newCount = self.newCardsDueBy(self.dayCutoff + 86400) - newCardsTomorrow = min(newCount, self.newCardsPerDay) - cards = self.cardsDueBy(self.dayCutoff + 86400) - msg = _('''\ - -At this time tomorrow:
-%(wait)s
-%(new)s''') % { - 'new': ngettext("There will be %d new card.", - "There will be %d new cards.", - newCardsTomorrow) % newCardsTomorrow, - 'wait': ngettext("There will be %s review.", - "There will be %s reviews.", cards) % cards, - } - if next > (self.dayCutoff+86400) and not newCardsTomorrow: - msg = (_("The next review is in %s.") % - self.earliestTimeStr()) + def finishedMsg(self): + return ( + "

"+_("Congratulations!")+"

"+ + self._finishedSubtitle()+ + "

"+ + self._nextDueMsg()) + + def _finishedSubtitle(self): + if self.deck.activeGroups("rev") or self.deck.activeGroups("new"): + return _("You have finished the selected groups for now.") else: - msg = _("No cards are due.") - return msg + return _("You have finished the deck for now.") - def earliestTime(self): - """Return the time of the earliest card. -This may be in the past if the deck is not finished. -If the deck has no (enabled) cards, return None. -Ignore new cards.""" - earliestRev = self.db.scalar(self.cardLimit("revActive", "revInactive", """ -select due from cards c where queue = 1 -order by due -limit 1""")) - earliestFail = self.db.scalar(self.cardLimit("revActive", "revInactive", """ -select due+%d from cards c where queue = 0 -order by due -limit 1""" % self.delay0)) - if earliestRev and earliestFail: - return min(earliestRev, earliestFail) - elif earliestRev: - return earliestRev + def _nextDueMsg(self): + line = [] + rev = self.revTomorrow() + self.lrnTomorrow() + if rev: + line.append( + ngettext("There will be %s review.", + "There will be %s reviews.", rev) % rev) + new = self.newTomorrow() + if new: + line.append( + ngettext("There will be %d new card.", + "There will be %d new cards.", new) % new) + if line: + line.insert(0, _("At this time tomorrow:")) + buf = "
".join(line) else: - return earliestFail + buf = _("No cards are due tomorrow.") + buf = '' + buf + return buf - def earliestTimeStr(self, next=None): - """Return the relative time to the earliest card as a string.""" - if next == None: - next = self.earliestTime() - if not next: - return _("unknown") - diff = next - time.time() - return anki.utils.fmtTimeSpan(diff) - - def cardsDueBy(self, time): - "Number of cards due at TIME. Ignore new cards" + def lrnTomorrow(self): + "Number of cards in the learning queue due tomorrow." return self.db.scalar( - self.cardLimit( - "revActive", "revInactive", - "select count(*) from cards c where queue between 0 and 1 " - "and due < :lim"), lim=time) + "select count() from cards where queue = 0 and due < ?", + self.dayCutoff+86400) - def newCardsDueBy(self, time): - "Number of new cards due at TIME." + def revTomorrow(self): + "Number of reviews due tomorrow." return self.db.scalar( - self.cardLimit( - "newActive", "newInactive", - "select count(*) from cards c where queue = 2 " - "and due < :lim"), lim=time) + "select count() from cards where queue = 1 and due = ?"+ + self._groupLimit("rev"), + self.today+1) - def deckFinishedMsg(self): - spaceSusp = "" - c= self.spacedCardCount() - if c: - spaceSusp += ngettext( - 'There is %d delayed card.', - 'There are %d delayed cards.', c) % c - c2 = self.hiddenCards() - if c2: - if spaceSusp: - spaceSusp += "
" - spaceSusp += _( - "Some cards are inactive or suspended.") - if spaceSusp: - spaceSusp = "

" + spaceSusp - return _('''\ -
-

Congratulations!

You have finished for now.

-%(next)s -%(spaceSusp)s -
''') % { - "next": self.nextDueMsg(), - "spaceSusp": spaceSusp, - } + def newTomorrow(self): + "Number of new cards tomorrow." + lim = self.deck.qconf['newPerDay'] + return self.db.scalar( + "select count() from (select id from cards where " + "queue = 2 limit %d)" % lim) # Suspending ########################################################################## def suspendCards(self, ids): "Suspend cards." - self.db.execute(""" -update cards -set queue = -1, mod = :t -where id in %s""" % ids2str(ids), t=time.time()) + self.db.execute( + "update cards set queue = -1, mod = ? where id in "+ + ids2str(ids), intTime()) def unsuspendCards(self, ids): "Unsuspend cards." - self.db.execute(""" -update cards set queue = type, mod=:t -where queue = -1 and id in %s""" % - ids2str(ids), t=time.time()) + self.db.execute( + "update cards set queue = type, mod = ? " + "where queue = -1 and id in "+ ids2str(ids), + intTime()) - def buryFact(self, fact): + def buryFact(self, fid): "Bury all cards for fact until next session." - for card in fact.cards: - if card.queue in (0,1,2): - card.queue = -2 + self.db.execute("update cards set queue = -2 where fid = ?", fid) # Counts ########################################################################## - def hiddenCards(self): - "Assumes queue finished. True if some due cards have not been shown." - return self.db.scalar(""" -select 1 from cards where due < :now -and queue between 0 and 1 limit 1""", now=self.dayCutoff) - - def spacedCardCount(self): - "Number of spaced cards." - print "spacedCardCount" - return 0 - return self.db.scalar(""" -select count(cards.id) from cards where -due > :now and due < :now""", now=time.time()) - - def isEmpty(self): - return not self.cardCount - - def matureCardCount(self): - return self.db.scalar( - "select count(id) from cards where interval >= :t ", - t=MATURE_THRESHOLD) - - def youngCardCount(self): - return self.db.scalar( - "select count(id) from cards where interval < :t " - "and reps != 0", t=MATURE_THRESHOLD) - - def newCountAll(self): - "All new cards, including spaced." - return self.db.scalar( - "select count(id) from cards where type = 2") - - def seenCardCount(self): - return self.db.scalar( - "select count(id) from cards where type between 0 and 1") - - # Card predicates - ########################################################################## - - def cardState(self, card): - if self.cardIsNew(card): - return "new" - elif card.interval > MATURE_THRESHOLD: - return "mature" - return "young" - - def cardIsNew(self, card): - "True if a card has never been seen before." - return card.reps == 0 - - def cardIsYoung(self, card): - "True if card is not new and not mature." - return (not self.cardIsNew(card) and - not self.cardIsMature(card)) - - def cardIsMature(self, card): - return card.interval >= MATURE_THRESHOLD - - # Stats - ########################################################################## - - def getETA(self, stats): - # rev + new cards first, account for failures - import traceback; traceback.print_stack() - count = stats['rev'] + stats['new'] - count *= 1 + stats['gYoungNo%'] / 100.0 - left = count * stats['dAverageTime'] - # failed - higher time per card for higher amount of cards - failedBaseMulti = 1.5 - failedMod = 0.07 - failedBaseCount = 20 - factor = (failedBaseMulti + - (failedMod * (stats['failed'] - failedBaseCount))) - left += stats['failed'] * stats['dAverageTime'] * factor - return left + def timeToday(self): + "Time spent learning today, in seconds." + return self.deck.db.scalar( + "select sum(taken/1000.0) from revlog where time > ?*1000", + self.dayCutoff-86400) or 0 + def repsToday(self): + "Number of cards answered today." + return self.deck.db.scalar( + "select count() from revlog where time > ?*1000", + self.dayCutoff-86400) # Dynamic indices ########################################################################## diff --git a/anki/stats.py b/anki/stats.py index 887fb7428..26dd18c0c 100644 --- a/anki/stats.py +++ b/anki/stats.py @@ -64,6 +64,21 @@ class DeckStats(object): def __init__(self, deck): self.deck = deck + def matureCardCount(self): + return self.deck.db.scalar( + "select count(id) from cards where interval >= :t ", + t=MATURE_THRESHOLD) + + def youngCardCount(self): + return self.deck.db.scalar( + "select count(id) from cards where interval < :t " + "and reps != 0", t=MATURE_THRESHOLD) + + def newCountAll(self): + "All new cards, including spaced." + return self.deck.db.scalar( + "select count(id) from cards where type = 2") + def report(self): "Return an HTML string with a report." fmtPerc = anki.utils.fmtPercentage diff --git a/tests/test_sched.py b/tests/test_sched.py index c2cd4cb4f..f6c818683 100644 --- a/tests/test_sched.py +++ b/tests/test_sched.py @@ -215,3 +215,19 @@ def test_reviews(): assert c.queue == -1 c.load() assert c.queue == -1 + +def test_finished(): + d = getEmptyDeck() + # nothing due + assert "No cards are due" in d.sched.finishedMsg() + f = d.newFact() + f['Front'] = u"one"; f['Back'] = u"two" + d.addFact(f) + # have a new card + assert "1 new" in d.sched.finishedMsg() + # turn it into a review + c = f.cards()[0] + c.startTimer() + d.sched.answerCard(c, 3) + # nothing should be due tomorrow, as it's due in a week + assert "No cards are due" in d.sched.finishedMsg()