mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
update finished msg
This commit is contained in:
parent
0a9d032343
commit
9e46db5b3e
3 changed files with 96 additions and 187 deletions
252
anki/sched.py
252
anki/sched.py
|
@ -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
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue