Altering Anki's revlog table, or how to recover your streak

Anki users are protective of their streak - the number of consecutive days they’ve done their reviews. Right now, for example, my streak is 621 days. So if you miss a day for whatever reason, not only do you have to deal with double the number of reviews, but you also deal with the emotional toll of having lost your streak.

You can lose your streak for one of several reasons. You could have simply been lazy. You may have forgotten that you didn’t do your Anki. Or travel across timezones put you in a situation where Anki’s clock and your clock differ. Others have described a procedure for resetting the computer’s clock as a way of recovering a lost streak. It apparently works though I haven’t tried it. Instead I’ll focus on a technique that involves working directly with the Anki database.

First, I must warn you: if you don’t know anything about sqlite, SQL or the like don’t attempt this. It’s very easy to do something that will wreck your collection’s database. You’ve been warned.

Locating the database

First of all, make any modifications to the Anki db with care; make backups, etc. Make sure Anki is closed.

On macOS the sqlite database file is at ~/Library/Application Support/Anki2/your_collection_name/collection.anki2. On other platforms, the path is something else, I’m sure the manual says something about this. Or you can just search for collection.anki2 and navigate there. That file is the Anki database.

Moving a review

Let’s say the goal is to move the latest review back to a different day. This query will find the latest review (here I’ve restricted to a particular deck named Словарный запас…)

SELECT * FROM revlog r 
INNER JOIN cards c ON c.id = r.cid 
INNER JOIN decks d ON c.did = d.id
WHERE d.name LIKE '%Словарный запас%'
   AND c.queue = 2
ORDER BY r.id DESC
LIMIT 1

This query returns a single row. In that row, focusing on the id column, that will give us the timestamp in Unix epoch milliseconds. In my case it is 1656057796342. That translates to Fri Jun 24 2022 08:03:16 GMT+0000, which checks out.

Now we are going to need to move that row to an id with a different timestamp. But what should that timestamp be? Well, if we want to move it to yesterday, then we can subtract 86400 seconds (the number of seconds in one day) from the id above. Remember that the id field is in epoch milliseconds so we have to divide by 1000 first → 1656057796. Now subtract 86400 → 1655971396, and multiply by 1000 → 1655971396000 which translates to Thu Jun 23 2022 08:03:16 GMT+0000. Bingo! One day earlier that our existing row.

Now we just need to move the row to our new computed id:

UPDATE revlog 
SET id = 1655971396000
WHERE id = 1656057796342

As long as our new id is unique, this query should succeed.

Again, if you’re not comfortable working with sqlite and SQL queries - this is not a good idea. At the very minimum backup your database in case of error.

References

A deep dive into my Anki language learning: Part III (Sentences)

Welcome to Part III of a deep dive into my Anki language learning decks. In Part I I covered the principles that guide how I setup my decks and the overall deck structure. In the lengthy Part II I delved into my vocabulary deck. In this installment, Part III, we’ll cover my sentence decks.

Principles

First, sentences (and still larger units of language) should eventually take precedence in language study. What help is it to know the word for “tomato” in your L2, if you don’t know how to slice a tomato, how to eat a tomato, how to grow a tomato plant? Focus on larger units of language increases your success rate in integrating vocabulary into daily use.

Second, I don’t want sentence learning to require a lot of extra effort. If I’m learning a new word, I don’t want to have to create a separate sentence card while I’m making a vocabulary card. (Fortunately I have a solution for that!)

Types of sentence cards

Cloze deletion cards

My sentence cards are almost all have a cloze deletion format.

Above is the front side of a typical cloze deletion card. The card is asking to recall the bracketed word(s) and the back will reveal them.

On the back, we reveal the clozed text and also expose any notes, such as usage notes, alternative translations and so forth. And that’s the essence of the cloze deletion card.

Two methods of generating cloze deletion cards

There are two ways that cloze deletion cards come about. The standard method relies on Anki’s built in cloze mechanism. The other way uses a script Anki Cloze Anything that simulates a cloze card. The outcome looks the same but the generation mechanism differs.

Straight cloze deletion cards

My straight cloze deletion cards use a version of the built-in cloze deletion note type. I’ve added several fields that are specific to my purposes, but it’s basically a cloze deletion note. To designate a block of text to be clozed out, the format looks like this:

Anki Cloze Anything cards

I hinted at this idea when I wrote about vocabulary cards in Part II. The problem that this solves is the need to use a cloze deletion note type in order to access cloze deletion functionality. Since Anki notes are capable of generating multiple different card types, this seems an unnecessary distinction. Anki Cloze Anything (ACA) is a JavaScript that allows you to bypass this limitation by simulating a cloze deletion card in any standard note type.

In this way, I can add a sentence cloze deletion inside my standard vocabulary cards.

Note that the format that ACA uses is slightly different from the built-in cloze. Instead of curly braces, it uses parentheses. But the outcome is the same; the resulting cards look exactly like a built-in cloze deletion card!

There are two important issues with the ACA mechanism: the appearance of the sentence on non-ACA cloze, and the pronunciation of the sentence when using AwesomeTTS. Both can be solved, but they require some work.

ACA sentences and display on non-cloze cards

If you try to display an example sentence that has ACA markup on a non-cloze card, it shows the markup still in place:

Fortunately, we can solve this easily by applying some text manipulation using a regular expression in JavaScript:

 /*

_fix_cloze_anything_example_sentence.js

2022-06-04

On any card that shows an example sentence that has
Cloze Anything markup and is in a span
that has rusentence class, strip that markup from it.

*/

function fix_cloze_anything_example_sentence() {
   document.querySelectorAll('span.rusentence').forEach((el, idx) => {
      let text = el.textContent;
      let re = /\(\(c\d::([^\)\(:]+)(?:::[^\):]+)?\)\)/g;
      text = text.replace(re, "$1");
      text = text.replace(new RegExp('`', 'g'),"");
      el.textContent = text;
   });
}

When this is wrapped in a <script></script> block in the card template, the marked-up sentence will appear normal.

ACA sentences and AwesomeTTS

The harder problem to solve is with TTS. The pronunciation file seems to be generated before the Anki Cloze Anything script has a chance to process and strip the markup. As a result, AwesomeTTS pronounces the sentence with the markup in place. Needless to say, that won’t work.

The only solution I’ve found is to create a separate copy of the sentence without the ACA markup and use that for the pronunciation. Not ideal, but I’ve written a Keyboard Maestro macro that ingests the original marked up sentence, removes the formatting and pastes in a dedicated pronounceable field on my template. It could be worse…

Parting words on sentences

A few miscellaneous thoughts that have informed my use of sentence cards:

  1. You don’t have to cloze single words. Prepositional phrases, whole clauses, even entire sentences are excellent candidates for cloze deletion and pushes you in the direction of larger and larger units of language.
  2. Sentences can come from anywhere. The best source is real life or whatever you’re reading in the target language; that’s where a majority of mine come from. But Tatoeba is a great source of sentences that are verified by native speakers.
  3. Text-to-speech (TTS) is excellent. There are several ways to go about this. I use AwesomeTTS. The developers now are saying that it is being phased out in favour of HyperTTS. As of this writing, I haven’t made the transition.
  4. I’ve begun adding images to my sentence cards. Anything to reinforce the learning using different sensory modalities.

That’s what I have on sentence cards. In the next article in the series, I’ll describe my grammar cards. In the meanwhile, if you would like to contact me about something in this article or any of my Anki-related posts, you can use this contact form.

A deep dive into my Anki language learning: Part II (Vocabulary)

In Part I of my series on my Anki language-learning setup, I described the philosophy that informs my Anki setup and touched on the deck overview. Now I’ll tackle the largest and most complex deck(s), my vocabulary decks. First some FAQ’s about my vocabulary deck: Do you organize it as L1 → L2 or as L2 → L1, or both? Actually, it’s both and more. Keep reading. Do you have separate subdecks by language level, or source, or some other characteristic?

A deep dive into my Anki language learning: Part I (Overview and philosophy)

Although I’ve been writing about Anki for years, it’s been in bits and pieces. Solving little problems. Creating efficiencies. But I realized that I’ve never taken a top-down approach to my Anki language learning system. So consider the post the launch of that overdue effort. Caveats A few caveats at the outset: I’m not a professional language tutor or pedagogue of any sort really. Much of what I’ve developed, I’ve done through trial-and-error, some intuition, and a some reading on relevant topics.

A tool for scraping definitions of Russian words from Wikitionary

In my perpetual attempt to make my language learning process using Anki more efficient, I’ve written a tool to extract English-language definitions from Russian words from Wiktionary. I wrote about the idea previously in Scraping Russian word definitions from Wikitionary: utility for Anki but it relied on the WiktionaryParser module which is good but misses some important edge cases. So I rolled up my sleeves and crafted my own solution. As with WiktionaryParser the heavy-lifting is done by the Beautiful Soup parser.

Getting plaintext into Anki fields on macOS: An update

A few years ago, I wrote about my problems with HTML in Anki fields. If you check out that previous post you’ll get the backstory about my objection. The gist is this: If you copy something from the web, Anki tries to maintain the formatting. Basically it just pastes the HTML off the clipboard. Supposedly, Anki offers to strip the formatting with Shift-paste, but I’ve point out to the developer specific examples where this fails.

Thursday, May 26 2022

I would like to propose a constitutional amendment that prohibits Sen. Ted Cruz (F-TX)1 from speaking or tweeting for seven days after a national tragedy. I’d also be fine with an amendment that prohibits him from speaking ever. The “F” designation stands for Fascist. The party to which Cruz nominally belongs is more aligned with WW2-era Axis dictatorships than those of a legitimate free civil democracy. ↩︎

Extracting title title of a web page from the command line

I was using a REST API at https://textance.herokuapp.com/title but it seems awfully fragile. Sure enough this morning, the entire application is down. It’s also not open-source and I have no idea who actually runs this thing. Here’s the solution: #!/bin/bash url=$(pbpaste) curl $url -so - | pup 'meta[property=og:title] attr{content}' It does require pup. On macOS, you can install via brew install pup. There are other ways using regular expressions but no dependency on pup but parsing HTML with regex is not such a good idea.

Friday, May 20, 2022

“Enlightenment is the absolute cooperation with the inevitable." - Anthony De Mello. Although he writes like a Buddhist, apparently he’s a Jesuit.