anki integration

I use beeminder to track my anki reviews. for a long time I manually inputted a +1 when I finished, but I’d always wanted an autodata solution because I find it tedious to keep up with manual data entry for more than a couple goals at a time.

however, none of the existing solutions quite did it for me - the thing I most care about is whether I did all of my reviews, not exactly how many reviews I did (which fluctuates by day) or some other incidental statistic.

I’ve come up with an autodata extension which basically works for me. it’s not at all plug and play, but for anyone else looking to work up an autodata solution for this or some other goal, it might be helpful as simple guidelines.

# ~/.local/share/Anki2/addons21/ankibee/__init__.py

from string import Template
import requests
import os
# from anki.collection import Collection
from aqt import mw
from aqt import gui_hooks

try:
    from dotenv import load_dotenv
    load_dotenv() # loads from .env by default
except ImportError:
    pass

bee_user = os.getenv('BEEMINDER_USER')
bee_auth_token = os.getenv('BEEMINDER_AUTH_TOKEN')


bee_goal = "anki"

# is this a good way of doing this? probably not
# but it sends 0 when I haven't done any reviews, 1 when I've done all of them,
# and something in between when I've done something in between. "good enough"
def get_review_ratio():
    try:
        reviews_due = mw.col.sched.counts()[2]
    except AttributeError:
        return # something isn't set if we haven't done any reviews this session, dirty way out
    review_cards_rated = len(mw.col.find_cards("rated:1 is:review"))
    review_ratio = review_cards_rated / (reviews_due + review_cards_rated)

    return review_ratio


from aqt.utils import showInfo


def send_datapoint(goal, value, comment = 'via ankibee'):
    return requests.post(f'https://www.beeminder.com/api/v1/users/{bee_user}/goals/{goal}/datapoints.json', data = {
        'auth_token': bee_auth_token,
        'value': value,
        'requestid': mw.col.sched.today, # if we submit multiple datapoints on the same day it replaces the earlier datapoint
        'comment': comment
        })


def beemind_review_ratio():
    send_datapoint('anki', get_review_ratio())


def beemind_anki_journal():
    journal_note_count = len(mw.col.find_notes("tag:anki-journal"))

    send_datapoint('anki-journal', journal_note_count)


# TODO we also do things that are *not* beeminding # we actually don't yet ignore this
# repeatedly sending datapoints should be idempotent otherwise 	
# TODO these requests are synchronous, if we make a lot that could get annoying
def beemind_everything():
    beemind_review_ratio()
    beemind_anki_journal()


gui_hooks.profile_will_close.append(beemind_everything)
6 Likes

Thanks for sharing!

If this sends a datapoint every time the desktop anki app syncs, then you’ll also want to change the goal’s aggregation method to be max or nonzero, which you can do on a ‘custom’ goal toward the bottom of the settings tab.

1 Like

@philip sending a requestid causes beeminder to replace the existing datapoint with that requestid. I set requestid to mw.col.sched.today, so I just get the more recent version of a datapoint recorded in anki (see the comment in send_datapoint).

you could set a goal to work differently, but since I submit datapoints every time anki closes (triggers reliably for me and only a little spammy) I like this approach, rather than than filling beeminder with junk datapoints.

if I add more specific goals (which I intend to in the future) I’ll probably want start making the requests asynchronously to avoid slowing down shutdown too much. it’s also possible to have datapoints send on other triggers but most of those would probably feel bad without async sending.

2 Likes