mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
plug new answering code in
This is not the way the code is intended to be used, but making it conform to the existing API allows us to exercise the existing unit tests and provides partial backwards compatibility. - Leech handling is currently broken - Fix answered_at in wrong units, and not being used
This commit is contained in:
parent
f811beae5e
commit
3af5221895
5 changed files with 81 additions and 84 deletions
|
@ -5,6 +5,7 @@ persistent = no
|
|||
[TYPECHECK]
|
||||
ignored-classes=
|
||||
FormatTimespanIn,
|
||||
AnswerCardIn,
|
||||
UnburyCardsInCurrentDeckIn,
|
||||
BuryOrSuspendCardsIn
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@ from anki import hooks
|
|||
from anki.cards import Card
|
||||
from anki.consts import *
|
||||
from anki.decks import Deck, DeckConfig, DeckManager, DeckTreeNode, QueueConfig
|
||||
from anki.lang import FormatTimeSpan
|
||||
from anki.notes import Note
|
||||
from anki.types import assert_exhaustive
|
||||
from anki.utils import from_json_bytes, ids2str, intTime
|
||||
|
||||
CongratsInfo = _pb.CongratsInfoOut
|
||||
|
@ -476,42 +476,33 @@ limit ?"""
|
|||
card.flush()
|
||||
|
||||
def _answerCard(self, card: Card, ease: int) -> None:
|
||||
if self._previewingCard(card):
|
||||
self._answerCardPreview(card, ease)
|
||||
return
|
||||
|
||||
card.reps += 1
|
||||
|
||||
new_delta = 0
|
||||
review_delta = 0
|
||||
|
||||
if card.queue == QUEUE_TYPE_NEW:
|
||||
# came from the new queue, move to learning
|
||||
card.queue = QUEUE_TYPE_LRN
|
||||
card.type = CARD_TYPE_LRN
|
||||
# init reps to graduation
|
||||
card.left = self._startingLeft(card)
|
||||
new_delta = +1
|
||||
|
||||
if card.queue in (QUEUE_TYPE_LRN, QUEUE_TYPE_DAY_LEARN_RELEARN):
|
||||
self._answerLrnCard(card, ease)
|
||||
elif card.queue == QUEUE_TYPE_REV:
|
||||
self._answerRevCard(card, ease)
|
||||
review_delta = +1
|
||||
states = self.col._backend.get_next_card_states(card.id)
|
||||
if ease == BUTTON_ONE:
|
||||
new_state = states.again
|
||||
rating = _pb.AnswerCardIn.AGAIN
|
||||
elif ease == BUTTON_TWO:
|
||||
new_state = states.hard
|
||||
rating = _pb.AnswerCardIn.HARD
|
||||
elif ease == BUTTON_THREE:
|
||||
new_state = states.good
|
||||
rating = _pb.AnswerCardIn.GOOD
|
||||
elif ease == BUTTON_FOUR:
|
||||
new_state = states.easy
|
||||
rating = _pb.AnswerCardIn.EASY
|
||||
else:
|
||||
raise Exception(f"Invalid queue '{card}'")
|
||||
assert False, "invalid ease"
|
||||
|
||||
self.update_stats(
|
||||
card.did,
|
||||
new_delta=new_delta,
|
||||
review_delta=review_delta,
|
||||
milliseconds_delta=+card.timeTaken(),
|
||||
self.col._backend.answer_card(
|
||||
card_id=card.id,
|
||||
current_state=states.current,
|
||||
new_state=new_state,
|
||||
rating=rating,
|
||||
answered_at_millis=intTime(1000),
|
||||
milliseconds_taken=card.timeTaken(),
|
||||
)
|
||||
|
||||
# once a card has been answered once, the original due date
|
||||
# no longer applies
|
||||
if card.odue:
|
||||
card.odue = 0
|
||||
# fixme: tests assume card will be mutated, so we need to reload it
|
||||
card.load()
|
||||
|
||||
def _maybe_requeue_card(self, card: Card) -> None:
|
||||
# preview cards
|
||||
|
@ -1053,51 +1044,59 @@ limit ?"""
|
|||
|
||||
# Next times
|
||||
##########################################################################
|
||||
# fixme: move these into tests_schedv2 in the future
|
||||
|
||||
def _interval_for_state(self, state: _pb.SchedulingState) -> int:
|
||||
kind = state.WhichOneof("value")
|
||||
if kind == "normal":
|
||||
return self._interval_for_normal_state(state.normal)
|
||||
elif kind == "filtered":
|
||||
return self._interval_for_filtered_state(state.filtered)
|
||||
else:
|
||||
assert_exhaustive(kind)
|
||||
return 0 # unreachable
|
||||
|
||||
def _interval_for_normal_state(self, normal: _pb.SchedulingState.Normal) -> int:
|
||||
kind = normal.WhichOneof("value")
|
||||
if kind == "new":
|
||||
return 0
|
||||
elif kind == "review":
|
||||
return normal.review.scheduled_days * 86400
|
||||
elif kind == "learning":
|
||||
return normal.learning.scheduled_secs
|
||||
elif kind == "relearning":
|
||||
return normal.relearning.learning.scheduled_secs
|
||||
else:
|
||||
assert_exhaustive(kind)
|
||||
return 0 # unreachable
|
||||
|
||||
def _interval_for_filtered_state(
|
||||
self, filtered: _pb.SchedulingState.Filtered
|
||||
) -> int:
|
||||
kind = filtered.WhichOneof("value")
|
||||
if kind == "preview":
|
||||
return filtered.preview.scheduled_secs
|
||||
elif kind == "rescheduling":
|
||||
return self._interval_for_normal_state(filtered.rescheduling.original_state)
|
||||
else:
|
||||
assert_exhaustive(kind)
|
||||
return 0 # unreachable
|
||||
|
||||
def nextIvl(self, card: Card, ease: int) -> Any:
|
||||
"Return the next interval for CARD, in seconds."
|
||||
# preview mode?
|
||||
if self._previewingCard(card):
|
||||
"Don't use this - it is only required by tests, and will be moved in the future."
|
||||
states = self.col._backend.get_next_card_states(card.id)
|
||||
if ease == BUTTON_ONE:
|
||||
return self._previewDelay(card)
|
||||
return 0
|
||||
|
||||
# (re)learning?
|
||||
if card.queue in (QUEUE_TYPE_NEW, QUEUE_TYPE_LRN, QUEUE_TYPE_DAY_LEARN_RELEARN):
|
||||
return self._nextLrnIvl(card, ease)
|
||||
elif ease == BUTTON_ONE:
|
||||
# lapse
|
||||
conf = self._lapseConf(card)
|
||||
if conf["delays"]:
|
||||
return conf["delays"][0] * 60
|
||||
return self._lapseIvl(card, conf) * 86400
|
||||
else:
|
||||
# review
|
||||
early = card.odid and (card.odue > self.today)
|
||||
if early:
|
||||
return self._earlyReviewIvl(card, ease) * 86400
|
||||
else:
|
||||
return self._nextRevIvl(card, ease, fuzz=False) * 86400
|
||||
|
||||
# this isn't easily extracted from the learn code
|
||||
def _nextLrnIvl(self, card: Card, ease: int) -> Any:
|
||||
if card.queue == QUEUE_TYPE_NEW:
|
||||
card.left = self._startingLeft(card)
|
||||
conf = self._lrnConf(card)
|
||||
if ease == BUTTON_ONE:
|
||||
# fail
|
||||
return self._delayForGrade(conf, len(conf["delays"]))
|
||||
new_state = states.again
|
||||
elif ease == BUTTON_TWO:
|
||||
return self._delayForRepeatingGrade(conf, card.left)
|
||||
new_state = states.hard
|
||||
elif ease == BUTTON_THREE:
|
||||
new_state = states.good
|
||||
elif ease == BUTTON_FOUR:
|
||||
return self._graduatingIvl(card, conf, True, fuzz=False) * 86400
|
||||
else: # ease == BUTTON_THREE
|
||||
left = card.left % 1000 - 1
|
||||
if left <= 0:
|
||||
# graduate
|
||||
return self._graduatingIvl(card, conf, False, fuzz=False) * 86400
|
||||
new_state = states.easy
|
||||
else:
|
||||
return self._delayForGrade(conf, left)
|
||||
assert False, "invalid ease"
|
||||
|
||||
return self._interval_for_state(new_state)
|
||||
|
||||
# Leeches
|
||||
##########################################################################
|
||||
|
@ -1181,13 +1180,8 @@ and (queue={QUEUE_TYPE_NEW} or (queue={QUEUE_TYPE_REV} and due<=?))""",
|
|||
|
||||
def nextIvlStr(self, card: Card, ease: int, short: bool = False) -> str:
|
||||
"Return the next interval for CARD as a string."
|
||||
ivl_secs = self.nextIvl(card, ease)
|
||||
if not ivl_secs:
|
||||
return self.col.tr(TR.SCHEDULING_END)
|
||||
s = self.col.format_timespan(ivl_secs, FormatTimeSpan.ANSWER_BUTTONS)
|
||||
if ivl_secs < self.col.conf["collapseTime"]:
|
||||
s = "<" + s
|
||||
return s
|
||||
states = self.col._backend.get_next_card_states(card.id)
|
||||
return self.col._backend.describe_next_states(states)[ease - 1]
|
||||
|
||||
# Deck list
|
||||
##########################################################################
|
||||
|
|
|
@ -1336,6 +1336,6 @@ message AnswerCardIn {
|
|||
SchedulingState current_state = 2;
|
||||
SchedulingState new_state = 3;
|
||||
Rating rating = 4;
|
||||
int64 answered_at = 5;
|
||||
int64 answered_at_millis = 5;
|
||||
uint32 milliseconds_taken = 6;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ impl From<pb::AnswerCardIn> for CardAnswer {
|
|||
rating: answer.rating().into(),
|
||||
current_state: answer.current_state.unwrap_or_default().into(),
|
||||
new_state: answer.new_state.unwrap_or_default().into(),
|
||||
answered_at: TimestampSecs(answer.answered_at),
|
||||
answered_at: TimestampMillis(answer.answered_at_millis),
|
||||
milliseconds_taken: answer.milliseconds_taken,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ pub struct CardAnswer {
|
|||
pub current_state: CardState,
|
||||
pub new_state: CardState,
|
||||
pub rating: Rating,
|
||||
pub answered_at: TimestampSecs,
|
||||
pub answered_at: TimestampMillis,
|
||||
pub milliseconds_taken: u32,
|
||||
}
|
||||
|
||||
|
@ -404,10 +404,11 @@ impl RevlogEntryPartial {
|
|||
usn: Usn,
|
||||
cid: CardID,
|
||||
button_chosen: u8,
|
||||
answered_at: TimestampMillis,
|
||||
taken_millis: u32,
|
||||
) -> RevlogEntry {
|
||||
RevlogEntry {
|
||||
id: TimestampMillis::now(),
|
||||
id: answered_at,
|
||||
cid,
|
||||
usn,
|
||||
button_chosen,
|
||||
|
@ -450,6 +451,7 @@ impl Collection {
|
|||
usn,
|
||||
answer.card_id,
|
||||
button_chosen,
|
||||
answer.answered_at,
|
||||
answer.milliseconds_taken,
|
||||
);
|
||||
self.storage.add_revlog_entry(&revlog)?;
|
||||
|
|
Loading…
Reference in a new issue