My Journey of 368 days (+ The Ultimate Guide for WK 📖 )

These are the default settings for Double-Check:

var defaults = {
    allow_retyping: true,
    allow_change_correct: true,
    allow_change_incorrect: true,
    typo_action: 'ignore',
    delay_wrong: true,
    delay_multi_meaning: false,
    delay_slightly_off: false,
    delay_period: 1.5,
    show_lightning_button: true,
    lightning_enabled: false,
    srs_msg_period: 1.2,
    autoinfo_correct: false,
    autoinfo_incorrect: false,
    autoinfo_multi_meaning: false,
    autoinfo_slightly_off: false
}

Essentially, it’s the normal WK experience except:

  • You can press backspace or click a ‘retype’ button to retype your answer (intended for typos, still requires knowing the correct answer)
  • Button to change answer to correct or incorrect is available. (like override, but can also mark an item wrong in case WK is too forgiving) (Both the ‘correct’ and ‘incorrect’ buttons can be disabled independently)
  • When you get an answer wrong, it locks the ‘next’ button for 1.5sec so you don’t accidentally proceed before taking a moment to review the item info (for people with a habit of double-tapping enter)
2 Likes

Sounds solid! Thanks for letting me know :slight_smile: I’ll be sure to add it to the list then :v:

2 Likes

For beginners, I’d recommend disabling the ‘mark correct’ button. Frankly, I’d delete it from the script if there weren’t other people who like it.

1 Like

Yeah, I think it makes sense? The user can just override the answer and type it again correctly. Somehow I don’t want people hitting it for a spam of correct answers, even if that sounds weird and extremely niche :thinking: I mean, people skip vocab lessons with the reorder, after all :sweat_smile:

3 Likes

I’ll go ahead and change that default and post an update in a few min.

I think I’ll also disable the ‘mark wrong’ button by default, so someone doesn’t accidentally click ‘mark wrong’ and not know how to get it back to ‘correct’. Once they understand the script better, they can change the settings at any time.

edit: done

3 Likes

I wonder if the SRS reorder script writer could change the defaults to something less damaging either - like only priorities apprentice items etc. (and maybe even a settings interface…)

1 Like

Yeah, I’ll contact them tomorrow about that. Now I’m dying and I’m completely avoiding communicating in order not to say nonsense :rofl:

3 Likes

Zombie!jpr!

2 Likes

I use it quite often. I type too dang fast and misspell/mistype too often. (Actually, I just hit +)

Inspirational

1 Like

What do you think about something like what iKnow does? Basically, it gives you the reviews by staleness (for lack of a better term). For example, having a Master review a day overdue isn’t a big deal compared to being an hour overdue for an Apprentice 1 review, so you’d get the Apprentice 1 item first. But if you waited long enough, a Master review could eventually be prioritized over an Apprentice 1 item. You could even throw in a customizable randomness factor (maybe 10% by default) so you’re not guaranteed to get everything in exactly SRS order.

2 Likes

Do you mean that each item would get assigned a numerical value according to its staleness? So like, master is X hours, then divided by how many houra have gone by since the last time it was reviewed, and the smaller the number the earlier it will show up in reviews, regardless of item type?

(And, let’s say, a minimum of 10% of the reviews would be randomised? Preferably with the ability to add more randomness?)

I really like this idea! Since it would still be randomised somewhat, it could even be set to run automatically when you start reviewing rather than manually.

1 Like

More or less, yeah. I think the formula would basically be time since last review / SRS interval length. The higher the number the more stale it is. I would then multiply that number by the randomness factor. For a 10% randomness factor, this would be a random number between 0.9 and 1.1.

If this is something people are interested in, I’ll take a look. I already posted a question in the Open Framework thread to find out the best way to grab available review data. After that I should be able to experiment a bit.

2 Likes

I’m interested!

2 Likes

I avoid scripts like that, ultimate timeline, or basically anything that informs me explicitly or otherwise about an item’s SRS level. My brain builds associations among the items on the same level automatically. It enables you to use contextual clues to inform your answers. Sometimes visually similar kanji from a level far lower than the average item in my reviews will pop up and for the ones I don’t know I’ll allow myself to consider the likely candidate from among the last few levels. But if I instead knew ahead of time that I had burns coming up it’s an extra hint that I don’t want, especially because items tend to age together in batches. It’s too much intel on my items to allow for a pure test. Similarly if I knew a batch of review items had let’s say one master item it might tip me off that there’s a leech among my items. When I think about unintended side effects of using scripts, that’s on the short list for me.

When it comes to the reorder script, just because we see some people misuse it doesn’t mean that most people misuse. Plus, most users are rational enough to at least recognize that by manipulating the order – let’s say pushing their vocabulary aside – they risk damaging their own learning. If not, they’ll be able to recover. In any case, the guide isn’t responsible for how they use/misuse it.

Side rant

I dunno, I think that many people that choose to neglect their vocabulary are missing the point. I think the recurring emphasis throughout the community to race to level 60 contributes to more users that get caught up in pursuing the wrong goals, even if it’s for the right reasons. But let folks learn from their mistakes, don’t try to keep them from making them.

I learned about the reorder script from this guide when I was low level, and it’s been a valuable resource for managing lessons.

2 Likes

I completely agree with you, but i don’t mind the contextual clues at all. With time, and vocabularies you should be able to recognise it by itself. I also intend to complete Wanikani twice, the second time without the following

  • any reorder script
  • “oh i typed A but i meant B” excuse
  • second guesses

And i would care more about vocabularies “currently, if i typed the right reading but the wrong meaning i would ignore the mistake untill the vocabulary reaches guru2”

I believe this the best for me

1 Like

How’s my math look?

let availableAtMs = new Date(item.assignments.available_at).getTime();
let msSinceAvailable = now - availableAtMs;

let msForSrsStage = srsStages[item.assignments.srs_stage].interval * 1000;
let msSinceLastReview = msSinceAvailable + msForSrsStage;
let staleness = msSinceLastReview / msForSrsStage;

Without any randomness, here’s how it sorts for my currently available reviews. It seems right on the surface because the lower SRS levels tend to show up sooner, but you can see (for example) the long overdue Apprentice 3 item showing up before a bunch of Apprentice 2 items.

0: {id: 8503, srs_stage: 1, available_at_time: "2019-03-23T20:00:00.000000Z", item: {…}, staleness: 5.920093125}
1: {id: 8506, srs_stage: 1, available_at_time: "2019-03-23T20:00:00.000000Z", item: {…}, staleness: 5.920093125}
2: {id: 8442, srs_stage: 3, available_at_time: "2019-03-22T10:00:00.000000Z", item: {…}, staleness: 3.333929239130435}
3: {id: 436, srs_stage: 2, available_at_time: "2019-03-24T04:00:00.000000Z", item: {…}, staleness: 2.4600465625}
4: {id: 8481, srs_stage: 2, available_at_time: "2019-03-24T04:00:00.000000Z", item: {…}, staleness: 2.4600465625}
5: {id: 8504, srs_stage: 2, available_at_time: "2019-03-24T04:00:00.000000Z", item: {…}, staleness: 2.4600465625}
6: {id: 8505, srs_stage: 2, available_at_time: "2019-03-24T04:00:00.000000Z", item: {…}, staleness: 2.4600465625}
7: {id: 8507, srs_stage: 2, available_at_time: "2019-03-24T04:00:00.000000Z", item: {…}, staleness: 2.4600465625}
8: {id: 8438, srs_stage: 3, available_at_time: "2019-03-23T10:00:00.000000Z", item: {…}, staleness: 2.2904509782608695}
9: {id: 2371, srs_stage: 2, available_at_time: "2019-03-24T08:00:00.000000Z", item: {…}, staleness: 1.9600465625}
10: {id: 7218, srs_stage: 4, available_at_time: "2019-03-22T21:00:00.000000Z", item: {…}, staleness: 1.9080930319148937}
11: {id: 2400, srs_stage: 2, available_at_time: "2019-03-24T09:00:00.000000Z", item: {…}, staleness: 1.8350465625}
12: {id: 2401, srs_stage: 2, available_at_time: "2019-03-24T09:00:00.000000Z", item: {…}, staleness: 1.8350465625}
13: {id: 2403, srs_stage: 2, available_at_time: "2019-03-24T09:00:00.000000Z", item: {…}, staleness: 1.8350465625}
14: {id: 7793, srs_stage: 4, available_at_time: "2019-03-23T01:00:00.000000Z", item: {…}, staleness: 1.8229866489361701}
15: {id: 8495, srs_stage: 2, available_at_time: "2019-03-24T10:00:00.000000Z", item: {…}, staleness: 1.7100465625}
16: {id: 6118, srs_stage: 4, available_at_time: "2019-03-23T19:00:00.000000Z", item: {…}, staleness: 1.440007925531915}
17: {id: 8310, srs_stage: 5, available_at_time: "2019-03-22T21:00:00.000000Z", item: {…}, staleness: 1.2555710928143713}
18: {id: 8208, srs_stage: 5, available_at_time: "2019-03-22T22:00:00.000000Z", item: {…}, staleness: 1.2495830688622755}
19: {id: 2377, srs_stage: 5, available_at_time: "2019-03-23T17:00:00.000000Z", item: {…}, staleness: 1.135810613772455}
20: {id: 7867, srs_stage: 6, available_at_time: "2019-03-22T23:00:00.000000Z", item: {…}, staleness: 1.121433947761194}
21: {id: 1778, srs_stage: 5, available_at_time: "2019-03-24T00:00:00.000000Z", item: {…}, staleness: 1.0938944461077844}
22: {id: 2221, srs_stage: 5, available_at_time: "2019-03-24T00:00:00.000000Z", item: {…}, staleness: 1.0938944461077844}
23: {id: 8236, srs_stage: 5, available_at_time: "2019-03-24T00:00:00.000000Z", item: {…}, staleness: 1.0938944461077844}
24: {id: 7043, srs_stage: 5, available_at_time: "2019-03-24T01:00:00.000000Z", item: {…}, staleness: 1.0879064221556887}
25: {id: 8082, srs_stage: 5, available_at_time: "2019-03-24T01:00:00.000000Z", item: {…}, staleness: 1.0879064221556887}
26: {id: 8198, srs_stage: 5, available_at_time: "2019-03-24T01:00:00.000000Z", item: {…}, staleness: 1.0879064221556887}
27: {id: 8810, srs_stage: 5, available_at_time: "2019-03-24T01:00:00.000000Z", item: {…}, staleness: 1.0879064221556887}
28: {id: 1245, srs_stage: 6, available_at_time: "2019-03-23T14:00:00.000000Z", item: {…}, staleness: 1.076657828358209}
29: {id: 2149, srs_stage: 6, available_at_time: "2019-03-23T18:00:00.000000Z", item: {…}, staleness: 1.0647175298507463}
30: {id: 6452, srs_stage: 6, available_at_time: "2019-03-23T19:00:00.000000Z", item: {…}, staleness: 1.0617324552238805}
31: {id: 1711, srs_stage: 7, available_at_time: "2019-03-23T01:00:00.000000Z", item: {…}, staleness: 1.0537974582753824}
32: {id: 7219, srs_stage: 6, available_at_time: "2019-03-24T02:00:00.000000Z", item: {…}, staleness: 1.040836932835821}
33: {id: 7557, srs_stage: 6, available_at_time: "2019-03-24T02:00:00.000000Z", item: {…}, staleness: 1.040836932835821}
34: {id: 8398, srs_stage: 6, available_at_time: "2019-03-24T02:00:00.000000Z", item: {…}, staleness: 1.040836932835821}
35: {id: 1419, srs_stage: 6, available_at_time: "2019-03-24T03:00:00.000000Z", item: {…}, staleness: 1.0378518582089553}
36: {id: 1593, srs_stage: 6, available_at_time: "2019-03-24T03:00:00.000000Z", item: {…}, staleness: 1.0378518582089553}
37: {id: 7638, srs_stage: 6, available_at_time: "2019-03-24T03:00:00.000000Z", item: {…}, staleness: 1.0378518582089553}
38: {id: 2143, srs_stage: 6, available_at_time: "2019-03-24T04:00:00.000000Z", item: {…}, staleness: 1.0348667835820895}
39: {id: 2354, srs_stage: 6, available_at_time: "2019-03-24T04:00:00.000000Z", item: {…}, staleness: 1.0348667835820895}
40: {id: 6069, srs_stage: 6, available_at_time: "2019-03-24T04:00:00.000000Z", item: {…}, staleness: 1.0348667835820895}
41: {id: 7282, srs_stage: 6, available_at_time: "2019-03-24T04:00:00.000000Z", item: {…}, staleness: 1.0348667835820895}
42: {id: 8238, srs_stage: 7, available_at_time: "2019-03-23T21:00:00.000000Z", item: {…}, staleness: 1.0259810465924897}
43: {id: 8224, srs_stage: 7, available_at_time: "2019-03-24T01:00:00.000000Z", item: {…}, staleness: 1.020417764255911}
44: {id: 8234, srs_stage: 7, available_at_time: "2019-03-24T01:00:00.000000Z", item: {…}, staleness: 1.020417764255911}
45: {id: 6217, srs_stage: 4, available_at_time: "2019-03-24T15:00:00.000000Z", item: {…}, staleness: 1.014476010638298}
46: {id: 2137, srs_stage: 8, available_at_time: "2019-03-23T08:00:00.000000Z", item: {…}, staleness: 1.0110039501563042}
47: {id: 4703, srs_stage: 8, available_at_time: "2019-03-23T08:00:00.000000Z", item: {…}, staleness: 1.0110039501563042}
48: {id: 7869, srs_stage: 8, available_at_time: "2019-03-23T16:00:00.000000Z", item: {…}, staleness: 1.0082252075373395}
49: {id: 1314, srs_stage: 6, available_at_time: "2019-03-24T14:00:00.000000Z", item: {…}, staleness: 1.005016037313433}
50: {id: 1864, srs_stage: 6, available_at_time: "2019-03-24T14:00:00.000000Z", item: {…}, staleness: 1.005016037313433}
51: {id: 2055, srs_stage: 6, available_at_time: "2019-03-24T14:00:00.000000Z", item: {…}, staleness: 1.005016037313433}
52: {id: 6772, srs_stage: 6, available_at_time: "2019-03-24T14:00:00.000000Z", item: {…}, staleness: 1.005016037313433}
53: {id: 7064, srs_stage: 6, available_at_time: "2019-03-24T14:00:00.000000Z", item: {…}, staleness: 1.005016037313433}
54: {id: 7428, srs_stage: 6, available_at_time: "2019-03-24T14:00:00.000000Z", item: {…}, staleness: 1.005016037313433}
55: {id: 7807, srs_stage: 6, available_at_time: "2019-03-24T14:00:00.000000Z", item: {…}, staleness: 1.005016037313433}
56: {id: 6272, srs_stage: 8, available_at_time: "2019-03-24T09:00:00.000000Z", item: {…}, staleness: 1.002320379472039}
57: {id: 7905, srs_stage: 8, available_at_time: "2019-03-24T09:00:00.000000Z", item: {…}, staleness: 1.002320379472039}

P.S. It’s a shame I won’t have this script done in time to use with the current set of reviews.

2 Likes

I’m not familiar with OF or the API, but what I’m assuming is written here is this:

get current date
calculate the difference between the current date and when the item was due (=‘time difference’)

multiply the interval of an item by 1000, (=‘interval’) (not sure how interval is counted, though, so I can’t understand this)
add ‘interval’ to ‘time difference’ (=‘weight’?) (for importance?)
staleness = weight/interval?

Sorry, I think the problem is that I have no idea what .interval does …

The resulting order looks good, though!

1 Like

Interval is apparently in seconds, so I’m putting it into milliseconds (ms).

It’s not really weight. What I’m trying to do is compare the time since last review and the SRS length. Basically how long you’ve actually gone since your last review compared to how long the SRS says you should have gone since your last review.


I have a beta version if anyone wants to give me feedback. Specifically if the order looks accurate. I added in some global variables for debugging, so you can type them into the console to get more details. The variables are stalenessList (basically what I showed in my last post) and queue (the sorted review items as they should be ordered in reviews). Those two lists should match it order, they just contain different data.

If you want to check out the review order without affecting the actual review order, just comment out the line that says updateQueueState(queue);. You can also turn the randomization off temporarily by setting randomOffset to 0.0.

Beta version
// ==UserScript==
// @name          WaniKani Prioritize Overdue Reviews
// @namespace     https://www.wanikani.com
// @description   Prioritize review items that are more overdue based on their SRS level and when the review became available.
// @author        seanblue
// @version       0.9.2
// @include       https://www.wanikani.com/review/session
// @grant         none
// ==/UserScript==

(function($, wkof) {
	const randomOffset = 0.25;

	wkof.include('ItemData');
	wkof.ready('ItemData').then(fetchData);

	function fetchData() {
		let promises = [];
		promises.push(wkof.Apiv2.get_endpoint('srs_stages'));
		promises.push(wkof.ItemData.get_items('assignments'));

		return Promise.all(promises).then(processData).then(updateReviewQueue);
	}

	function processData(results) {
		let srsStages = results[0];
		let items = results[1];

		let now = new Date().getTime();
		let overduePercentList = items.filter(item => isReviewAvailable(item, now)).map(item => mapToOverduePercentData(item, now, srsStages)).sort(sortByOverduePercent);

		window.overduePercentList = overduePercentList;

		return toOverduePercentDictionary(overduePercentList);
	}

	function isReviewAvailable(item, now) {
		return (item.assignments && (item.assignments.available_at != null) && (new Date(item.assignments.available_at).getTime() < now));
	}

	function mapToOverduePercentData(item, now, srsStages) {
		let availableAtMs = new Date(item.assignments.available_at).getTime();
		let msSinceAvailable = now - availableAtMs;

		let msForSrsStage = srsStages[item.assignments.srs_stage].interval * 1000;
		let msSinceLastReview = msSinceAvailable + msForSrsStage;
		let overduePercent = (msSinceLastReview / msForSrsStage) - 1;

		let adjustedOverduePercent = overduePercent * getRandomnessFactor();
		return {
			id: item.id,
			item: item.data.slug,
			srs_stage: item.assignments.srs_stage,
			//available_at_time: item.assignments.available_at,
			original_overdue_percent: overduePercent,
			overdue_percent: adjustedOverduePercent
		};
	}

	function getRandomnessFactor() {
		let min = 1 - randomOffset;
		let max = 1 + randomOffset;
		return Math.random() * (max - min) + min;
	}

	// TODO: Delete this.
	function sortByOverduePercent(item1, item2) {
		let overduePercentCompare = item1.overdue_percent - item2.overdue_percent;
		if (overduePercentCompare > 0) {
			return -1;
		}

		if (overduePercentCompare < 0) {
			return 1;
		}

		return item1.id - item2.id;
	}

	function toOverduePercentDictionary(items) {
		var dict = {};

		for (let i = 0; i < items.length; i++) {
			let item = items[i];
			dict[item.id] = item.overdue_percent;
		}

		return dict;
	}

	function updateReviewQueue(overduePercentDictionary) {
		window.overduePercentDictionary = overduePercentDictionary;

		let unsortedQueue = $.jStorage.get('activeQueue').concat($.jStorage.get('reviewQueue'));
		let queue = unsortedQueue.sort((item1, item2) => sortQueueByOverduePercent(item1, item2, overduePercentDictionary));

		window.queue = queue;

		updateQueueState(queue);
	}


	function sortQueueByOverduePercent(item1, item2, overduePercentDictionary) {
		let overduePercentCompare = overduePercentDictionary[item1.id] - overduePercentDictionary[item2.id];
		if (overduePercentCompare > 0) {
			return -1;
		}

		if (overduePercentCompare < 0) {
			return 1;
		}

		return item1.id - item2.id;
	}

	function updateQueueState(queue) {
		let batchSize = 10;

		let activeQueue = queue.slice(0, batchSize);
		let inactiveQueue = queue.slice(batchSize).reverse(); // Reverse the queue since subsequent items are grabbed from the end of the queue.

		$.jStorage.set('activeQueue', activeQueue);
		$.jStorage.set('reviewQueue', inactiveQueue);
		$.jStorage.set('currentItem', activeQueue[0])
	}

})(window.jQuery, window.wkof);
2 Likes

Hm, where does the data show? I can’t see it in the console log or anywhere else I looked :anguished: