update finished msg

This commit is contained in:
Damien Elmes 2011-03-18 07:30:36 +09:00
parent 0a9d032343
commit 9e46db5b3e
3 changed files with 96 additions and 187 deletions

View file

@ -7,7 +7,7 @@ from operator import itemgetter
from heapq import * from heapq import *
#from anki.cards import Card #from anki.cards import Card
from anki.utils import parseTags, ids2str, intTime from anki.utils import parseTags, ids2str, intTime
from anki.lang import _ from anki.lang import _, ngettext
from anki.consts import * from anki.consts import *
from anki.hooks import runHook from anki.hooks import runHook
@ -58,18 +58,6 @@ class Scheduler(object):
"Does not include fetched but unanswered." "Does not include fetched but unanswered."
return (self.learnCount, self.revCount, self.newCount) 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): def cardQueue(self, card):
return card.queue return card.queue
@ -476,206 +464,96 @@ queue = 1 %s and due <= :lim order by %s limit %d""" % (
self.updateCutoff() self.updateCutoff()
self.reset() self.reset()
# Times # Deck finished state
########################################################################## ##########################################################################
def nextDueMsg(self): def finishedMsg(self):
next = self.earliestTime() return (
if next: "<h1>"+_("Congratulations!")+"</h1>"+
# all new cards except suspended self._finishedSubtitle()+
newCount = self.newCardsDueBy(self.dayCutoff + 86400) "<br><br>"+
newCardsTomorrow = min(newCount, self.newCardsPerDay) self._nextDueMsg())
cards = self.cardsDueBy(self.dayCutoff + 86400)
msg = _('''\ def _finishedSubtitle(self):
<style>b { color: #00f; }</style> if self.deck.activeGroups("rev") or self.deck.activeGroups("new"):
At this time tomorrow:<br> return _("You have finished the selected groups for now.")
%(wait)s<br>
%(new)s''') % {
'new': ngettext("There will be <b>%d new</b> card.",
"There will be <b>%d new</b> cards.",
newCardsTomorrow) % newCardsTomorrow,
'wait': ngettext("There will be <b>%s review</b>.",
"There will be <b>%s reviews</b>.", cards) % cards,
}
if next > (self.dayCutoff+86400) and not newCardsTomorrow:
msg = (_("The next review is in <b>%s</b>.") %
self.earliestTimeStr())
else: else:
msg = _("No cards are due.") return _("You have finished the deck for now.")
return msg
def earliestTime(self): def _nextDueMsg(self):
"""Return the time of the earliest card. line = []
This may be in the past if the deck is not finished. rev = self.revTomorrow() + self.lrnTomorrow()
If the deck has no (enabled) cards, return None. if rev:
Ignore new cards.""" line.append(
earliestRev = self.db.scalar(self.cardLimit("revActive", "revInactive", """ ngettext("There will be <b>%s review</b>.",
select due from cards c where queue = 1 "There will be <b>%s reviews</b>.", rev) % rev)
order by due new = self.newTomorrow()
limit 1""")) if new:
earliestFail = self.db.scalar(self.cardLimit("revActive", "revInactive", """ line.append(
select due+%d from cards c where queue = 0 ngettext("There will be <b>%d new</b> card.",
order by due "There will be <b>%d new</b> cards.", new) % new)
limit 1""" % self.delay0)) if line:
if earliestRev and earliestFail: line.insert(0, _("At this time tomorrow:"))
return min(earliestRev, earliestFail) buf = "<br>".join(line)
elif earliestRev:
return earliestRev
else: else:
return earliestFail buf = _("No cards are due tomorrow.")
buf = '<style>b { color: #00f; }</style>' + buf
return buf
def earliestTimeStr(self, next=None): def lrnTomorrow(self):
"""Return the relative time to the earliest card as a string.""" "Number of cards in the learning queue due tomorrow."
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"
return self.db.scalar( return self.db.scalar(
self.cardLimit( "select count() from cards where queue = 0 and due < ?",
"revActive", "revInactive", self.dayCutoff+86400)
"select count(*) from cards c where queue between 0 and 1 "
"and due < :lim"), lim=time)
def newCardsDueBy(self, time): def revTomorrow(self):
"Number of new cards due at TIME." "Number of reviews due tomorrow."
return self.db.scalar( return self.db.scalar(
self.cardLimit( "select count() from cards where queue = 1 and due = ?"+
"newActive", "newInactive", self._groupLimit("rev"),
"select count(*) from cards c where queue = 2 " self.today+1)
"and due < :lim"), lim=time)
def deckFinishedMsg(self): def newTomorrow(self):
spaceSusp = "" "Number of new cards tomorrow."
c= self.spacedCardCount() lim = self.deck.qconf['newPerDay']
if c: return self.db.scalar(
spaceSusp += ngettext( "select count() from (select id from cards where "
'There is <b>%d delayed</b> card.', "queue = 2 limit %d)" % lim)
'There are <b>%d delayed</b> cards.', c) % c
c2 = self.hiddenCards()
if c2:
if spaceSusp:
spaceSusp += "<br>"
spaceSusp += _(
"Some cards are inactive or suspended.")
if spaceSusp:
spaceSusp = "<br><br>" + spaceSusp
return _('''\
<div style="white-space: normal;">
<h1>Congratulations!</h1>You have finished for now.<br><br>
%(next)s
%(spaceSusp)s
</div>''') % {
"next": self.nextDueMsg(),
"spaceSusp": spaceSusp,
}
# Suspending # Suspending
########################################################################## ##########################################################################
def suspendCards(self, ids): def suspendCards(self, ids):
"Suspend cards." "Suspend cards."
self.db.execute(""" self.db.execute(
update cards "update cards set queue = -1, mod = ? where id in "+
set queue = -1, mod = :t ids2str(ids), intTime())
where id in %s""" % ids2str(ids), t=time.time())
def unsuspendCards(self, ids): def unsuspendCards(self, ids):
"Unsuspend cards." "Unsuspend cards."
self.db.execute(""" self.db.execute(
update cards set queue = type, mod=:t "update cards set queue = type, mod = ? "
where queue = -1 and id in %s""" % "where queue = -1 and id in "+ ids2str(ids),
ids2str(ids), t=time.time()) intTime())
def buryFact(self, fact): def buryFact(self, fid):
"Bury all cards for fact until next session." "Bury all cards for fact until next session."
for card in fact.cards: self.db.execute("update cards set queue = -2 where fid = ?", fid)
if card.queue in (0,1,2):
card.queue = -2
# Counts # Counts
########################################################################## ##########################################################################
def hiddenCards(self): def timeToday(self):
"Assumes queue finished. True if some due cards have not been shown." "Time spent learning today, in seconds."
return self.db.scalar(""" return self.deck.db.scalar(
select 1 from cards where due < :now "select sum(taken/1000.0) from revlog where time > ?*1000",
and queue between 0 and 1 limit 1""", now=self.dayCutoff) self.dayCutoff-86400) or 0
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 repsToday(self):
"Number of cards answered today."
return self.deck.db.scalar(
"select count() from revlog where time > ?*1000",
self.dayCutoff-86400)
# Dynamic indices # Dynamic indices
########################################################################## ##########################################################################

View file

@ -64,6 +64,21 @@ class DeckStats(object):
def __init__(self, deck): def __init__(self, deck):
self.deck = 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): def report(self):
"Return an HTML string with a report." "Return an HTML string with a report."
fmtPerc = anki.utils.fmtPercentage fmtPerc = anki.utils.fmtPercentage

View file

@ -215,3 +215,19 @@ def test_reviews():
assert c.queue == -1 assert c.queue == -1
c.load() c.load()
assert c.queue == -1 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()