daedsidog / Anki-autoLapseNewInterval

An Anki 2.1 addon which automatically adjusts the new lapse interval.
GNU General Public License v3.0
6 stars 1 forks source link

Anki 2.1.26 error on startup #3

Closed ghost closed 4 years ago

ghost commented 4 years ago

Debug info: Anki 2.1.26 (70784154) Python 3.8.0 Qt 5.14.1 PyQt 5.14.1 Platform: Windows 10 Flags: frz=True ao=True sv=2 Add-ons, last update check: 2020-09-16 14:05:46

Caught exception: Traceback (most recent call last): File "aqt\main.py", line 253, in onOpenProfile File "aqt\main.py", line 398, in loadProfile File "aqt\gui_hooks.py", line 1559, in call File "lib\site-packages\anki\hooks.py", line 583, in runHook File "C:\Users\mario\AppData\Roaming\Anki2\addons21\autoLapseNewInterval__init.py", line 78, in adjLapse_startup adjLapse_all(True) # Includes functions below File "C:\Users\mario\AppData\Roaming\Anki2\addons21\autoLapseNewInterval__init__.py", line 69, in adjLapse_all eval_lapsed_newIvl(silent) File "C:\Users\mario\AppData\Roaming\Anki2\addons21\autoLapseNewInterval\init__.py", line 203, in eval_lapsed_newIvl dconf = mw.col.decks.dconf AttributeError: 'DeckManager' object has no attribute 'dconf'

daedsidog commented 4 years ago

Looks like the fellas over at Anki moved the table out of where it was. I don't use Anki anymore so I can't test anything I do, but try this and tell me if it works, just be sure to back up your decks before you do.

# Auto-Lapse-New-Interval
# Anki 2.1 plugin
# Author: EJS, daeds1dog
# Version 0.1
# License: GNU GPL v3 <www.gnu.org/licenses/gpl.html>
from __future__ import division
import datetime, time, math, json, os
from anki.hooks import wrap, addHook
from aqt import *
from aqt.main import AnkiQt
from anki.utils import intTime

# card_sample_size
# Number of cards needed for adequate sample size
#    we won't update settings
#    unless we have at least this many cards 
#    to go off of. This prevents us from changing initial 
#    settings based on a small set of data.
card_sample_size = 100
defaultTSR = 85 #Default target success rate (as a percentage)
change_silently = False #Do the changes without asking the user

# ------------Nothing to edit below--------------------------------#
rev_lapses = {}

# CONFIG file to store last time options settings were adjusted
previous = {} # Record of previous adjustment dates
LapseConffile = os.path.join(os.path.dirname(os.path.realpath(__file__)), "autoLapseNewInterval.data")
# NB The line below may not work with Anki 2.1
# Try commenting it out if you get errors.
#LapseConffile = LapseConffile.decode(sys.getfilesystemencoding())
if os.path.exists(LapseConffile):
    previous = json.load(open(LapseConffile, 'r'))

def save_lapseStats():
    json.dump(previous, open(LapseConffile, 'w'))

# Find settings group ID
def find_settings_group_id(name):
    dconf = mw.col.deck_config
    for k in dconf:
        if dconf[k]['name'] == name:
            return k
    return False

# Find decks in settings group
def find_decks_in_settings_group(group_id):
    members = []
    decks = mw.col.deck_config
    for d in decks:
        if 'conf' in decks[d] and int(decks[d]['conf']) == int(group_id):
            members.append(d)
    return members

# NOTE:
# from anki.utils import intTime
# intTime function returns seconds since epoch UTC multipiled by an optional scaling parameter; defaults to 1.

# Main Function
def adjLapse_all(silent=True):
    eval_lapsed_newIvl(silent)

# Startup Function
def adjLapse_startup():
    global previous
    profile = aqt.mw.pm.name
    if profile not in previous:
        previous[profile] = {}
    adjLapse_all(True) # Includes functions below

#Run when profile is loaded, save stats when unloaded
addHook("profileLoaded", adjLapse_startup)
addHook("unloadProfile", save_lapseStats)

#Find the success rate for a deck
def deck_lapsed_success_rate(deck_ids, lapsed_rev_records, from_date):
    lapsed_query = """select count() from
              (select a.id, a.ease
              from revlog as a, cards as c
              where
              a.id > %s
              and a.type = 1
              and a.ease > 1
              and a.cid = c.id
              and (""" % from_date
    i = 0
    for d in deck_ids:
        if i == 0:
            lapsed_query += "c.did = %s" % d
            i = 1
        else:
            lapsed_query += " or c.did = %s" % d
    lapsed_query += """)
            group by a.id
            order by a.id)"""
    lapsed_successes = mw.col.db.scalar(lapsed_query)

    lapsed_success_rate = int(100 * lapsed_successes / (lapsed_rev_records + lapsed_successes))
    return lapsed_success_rate

#Find number of lapsed review records in deck
def lapsed_records_in_deck(deck_id, from_date):
    lapsed_query = """select count() from
            (select a.id, a.ease
            from revlog as a, cards as c
            where
            a.id > %s
            and a.type = 1
            and a.ease = 1
            and a.cid = c.id
            and c.did = %s
            group by a.id
            order by a.id)""" % (from_date, deck_id)
    lapsed_records = mw.col.db.scalar(lapsed_query)
    if lapsed_records:
        return lapsed_records
    else:
        return 0

# Calculate the Lapse success rate of an options group
def og_lapsed_success_rate(name, min_look_back):
    profile = aqt.mw.pm.name
    creation_date = (int(mw.col.crt) * 1000)
    deck_ids = []
    lapsed_rev_records = 0
    group_id = find_settings_group_id(name)
        #add profile to previous dates dictionary
    if 'lapsed' not in previous[profile][name]:
        previous[profile][name]['lapsed'] = creation_date
    from_date = previous[profile][name]['lapsed']
    #exit if it has been less than 24 hours since last adjustment
    cur_date = intTime(1000)
    #if (cur_date - from_date) < (24 * 60 * 60 * 1000):
    #    #utils.showInfo("Waiting 24 hours to adjust Lapse Next Interval for %s." % name)
    #    return False, False
    if group_id:
        # Find decks and cycle through
        decks = find_decks_in_settings_group(group_id)
        #utils.showInfo("Will now calculate total lapsed cards for %s." % name)
        for d in decks:
            deck_ids.append(d)
            lapsed_rev_records += lapsed_records_in_deck(d, from_date)
        # make sure we have enough records in review to
        if lapsed_rev_records >= min_look_back:
            lapsed_success_rate = deck_lapsed_success_rate(deck_ids, lapsed_rev_records, from_date) #look back over all records since last adjustment
        else:
            lapsed_success_rate = False
        return lapsed_success_rate, lapsed_rev_records
    else:
        return False, False

#Adjust the lapsed new interval setting for an options group
def adj_lapsed_newIvl(group_id, silent=True):
    global previous
    profile = aqt.mw.pm.name
    tsr = defaultTSR
    # Return if target success rate is false or 0.
    if not tsr: return
    #find name of group
    dconf = mw.col.decks.dconf
    name = dconf[group_id]['name']
    if name not in previous[profile]:
        previous[profile][name] = {}
    cur_LNIvl = float(mw.col.decks.dconf[group_id]['lapse']['mult'])
    lapsed_success_rate, lapsed_rev_records = og_lapsed_success_rate(name, card_sample_size)
    # Returns False, False if we don't have enough review records since last time.
    if lapsed_success_rate and lapsed_rev_records and lapsed_success_rate != tsr:
        target_rate = float(tsr) / 100
        cur_rate = float(lapsed_success_rate) / 100
        #Simplistic ratio adjustment
        new_LNIvl = round(cur_LNIvl * cur_rate/target_rate, 2)
        if new_LNIvl > 1:
            new_LNIvl = 1
        if change_silently or utils.askUser("%s"
                "\n\nLapsed Card Success Rate: %s"
                "\nTarget Success Rate: %s"
                "\nCurrent lapsed new interval: %s"
                "\nSuggested interval: %s"
                "\n\nAccept new Lapsed new interval?" % (
                name, cur_rate, target_rate, cur_LNIvl, new_LNIvl)):
            # make changes
            mw.col.decks.dconf[group_id]['lapse']['mult'] = new_LNIvl
            mw.col.decks.save(mw.col.decks.dconf[group_id])
            previous[profile][name]['lapsed'] = intTime(1000)
            #utils.showInfo("Updating lapsed new interval currently disabled")
    else:
        if not silent:
            utils.showInfo("Lapsed New Interval\n\nNot enough records for options group %s (%d)" % (name, lapsed_rev_records))

def eval_lapsed_newIvl(silent=False):
    #find all deck options groups
    dconf = mw.col.deck_config
    for og in dconf:
        adj_lapsed_newIvl(og, silent)
ghost commented 4 years ago

Caught exception: Traceback (most recent call last): File "aqt\main.py", line 253, in onOpenProfile File "aqt\main.py", line 398, in loadProfile File "aqt\gui_hooks.py", line 1559, in call File "lib\site-packages\anki\hooks.py", line 583, in runHook File "C:\Users\mario\AppData\Roaming\Anki2\addons21\autoLapseNewInterval__init.py", line 78, in adjLapse_startup adjLapse_all(True) # Includes functions below File "C:\Users\mario\AppData\Roaming\Anki2\addons21\autoLapseNewInterval__init__.py", line 69, in adjLapse_all eval_lapsed_newIvl(silent) File "C:\Users\mario\AppData\Roaming\Anki2\addons21\autoLapseNewInterval\init__.py", line 203, in eval_lapsed_newIvl dconf = mw.col.deck_config AttributeError: '_Collection' object has no attribute 'deck_config'

daedsidog commented 4 years ago

Fixed with 9e15cc1167d5b131df4f1672c340654b48f24718, let me know if that did it for you.

ghost commented 4 years ago

it works