A Spaced Repetition Practice system implemented in DT

Synopsis: Below I report the implementation of a spaced repetition system entirely in DT/DTTG. At first, the description of the setup might look complicated, but once the initial steps are done, this works without further ado:

  • Add any card/file by replicating it into the review folder, that’s it!
  • Daily, just look at all cards in the review folder and rate them wrong/right, that’s it!

Spaced repetition practice can be very helpful to learn and maintain knowledge. There are well-known standalone programs for computers and handheld devices, for example Anki and SuperMemo. However, the bulk of my information is inside Devonthink databases, and I did not want to create yet another, unrelated system. In addition, I am not interested in zipping through vocabulary flashcards. As a physicist about a decade from retirement, I want to review a mix of self-created cards with facts that I try to remember as well as documents such as scientific literature that I periodically want to remind myself of. In other words, any document could be a “card”. I also want to study and review mostly on my iPad and iPhone, while maintaining the system on the Mac.

I’ve now implemented such a system in DT/DTTG:

Here is what’s needed to get this rolling:

  • Create a group SRP and subgroups ForReview and NotDueYet.
    • I put SRP at the DB root, and if you do the same, the smart rule Apple Scripts will not need modification). UPDATE: Wrong, unless your DB is called res2, you need to edit the paths in both AppleScripts.
  • Create a DT custom meta-data field (multi-line text) called srp.
  • Set up the two smart rules srp1 and srp2 (details below) and make them act hourly.

That’s it!

Loading content

“Cards” (really any documents to review) are then loaded by replicating them from anywhere in the same DB into ForPractice, making sure that the item’s rating is set to zero. From then on, this card will remain in the review loop.

Every day, open ForReview and go through all the cards. If you answer incorrectly, set the rating to 1. If your answer was right, set the rating to > 1.

Within the hour, the smartrule srp1 will reschedule the cards using the DT reminder due date. Cards that were reviewed today are parked in NotDueYet, and smart rule srp2 will bring due cards back into ForReview according to the spaced repetition algorithm (inside the srp1 AppleScript).

Reviewing on iOS is facilitated by setting the view options for the ForReview group to “sort by rating (ascending)” and for “Summary” choose to show Property Icons so that you can see the rating. Then, during your review, the unrated (hence unreviewed) cards will stay at the top. Keep reviewing the top-most card, unless there is no unrated one left.

When reviewing on iOS, of course, sync has to be on and DT on the Mac running, executing the smart rules prior to the next morning, when the next round of reviews becomes available.

Here are the smart rules and the AppleScripts contained in them:

srp1:

(*  SRP srp1.scpt 
This smart rule runs hourly looks for items in group "ForReview" which 
have a rating > 0  and delivers them to this script.

The script extracts the review rating (1 for incorrect answer or >1 for 
correct answer) for an item. It then reads the review history of the item 
from a custom metadata field "srp" (multi-line text) and extracts the 
latest review level. From this information, the script determines the date
 of the next review and sets the DT due date accordingly. Finally the 
item is moved to the group "NotYetDue" until it is due (note: this is done 
in the script instead of in the smart rule, because if the card has replicants 
outside of the SRP group, the smart rule "move to" command moves all 
replicant instances of an item to the same place; I found this way of doing 
it on the DT Forum in a post by user Korm, I believe).

*)

on performSmartRule(theRecords)
	tell application id "DNtp"
		
		-- the due date/time is set to be at 5am on a given day to avoid time zone issues
		-- with doing things around midnight
		set currDate to current date
		set currTime to time of currDate
		set currDate5am to currDate - currTime + (5 * hours)
		
		-- this is the spaced repetition algorithm, so to speak
		-- I used a very simple, increasing, sequence here, this can be modified
		-- and expanded to arbitary complexity a la SuperMemo, if wanted/needed
		-- interval_list determines the spacings in days after successive, succesful
		-- reviews
		set interval_list to {1, 2, 5, 11, 23, 52, 113, 249} -- 2.2th power
		
		-- comment out the next line if used in a smart rule
		-- set theRecords to (selection as list)
		
		repeat with theRecord in theRecords
			-- read the rating of the record
			-- rating = 1: answer was incorrect,  rating > 1: answer was correct
			set myRating to the rating of theRecord
			
			-- read the existing SRP level for this item
			-- the custom metadata field (multi-line text) "srp" pre-pends after each
			-- review the level at which the card currently is (i.e. how many
			-- successive correct reviews)
			
			-- use "try" to avoid error when new item has no history yet
			try
				set histSRP to get custom meta data for "srp" from theRecord
				-- extract the last level (the number prior to the first comma)
				set oldDelims to AppleScript's text item delimiters
				set AppleScript's text item delimiters to ","
				set prevSRP to text item 1 of histSRP
				set AppleScript's text item delimiters to oldDelims
			on error
				set prevSRP to 0
				set histSRP to ""
			end try
			-- now we have the SRP history
			
			-- if current review yielded an incorrect answer (rating 1_
			-- the card is reset to level 1
			-- if the review was successful, the card's level is upped by 1
			-- but the mechanism saturates at level 8
			if myRating is 1 then
				set newSRP to 1
			else
				set newSRP to prevSRP + 1
				if newSRP > 8 then set newSRP to 8
			end if
			
			-- updated the review history in the custom metadata field "srp"
			set updatedSRP to (newSRP as string) & "," & histSRP
			-- write this string to the custom metadata field
			add custom meta data updatedSRP for "srp" to theRecord
			
			-- set the new due date
			set jump to item (newSRP) of interval_list
			-- randomize the jump a bit so that not all cards advance exactly the same
			set randJump to (jump * (1 + 0.2 * (random number from -50 to 50) / 100)) as integer
			set newDueDate to currDate5am + (jump * days)
			tell theRecord to make new reminder with properties {schedule:once, alarm:notification, due date:newDueDate}
			
			-- move the modified items into the "NotYetDue" group
			-- this version makes sure that only this replicant is moved over
			-- all other replicants outside SRP are untouched
			set theFrom to create location "/SRP/ForReview" in database "res2"
			set theTo to create location "/SRP/NotDueYet" in database "res2"
			set theResult to move record theRecord from theFrom to theTo
			
			
			
			
		end repeat
		
	end tell
	
end performSmartRule

srp2:

(*  Smart rule srp2 is run hourly and monitors items in 
the group "NotYetDue" for their due date. If an item 
has become due, it will be moved to the group 
"ForReview" and its rating will be reset to zero

Smart rule settings:
- search in NotYetDue
- date due within last 10000 days
- perform hourly and on demand
- change rating to zero stars
- execute script - apple script - this script

The items are moved to the group "ForReview" in the 
script instead of in the smart rule, because if the card 
has replicants outside of the SRP group, the smart 
rule "move to" command moves all replicant instances 
of an item to the same place; I found this way of doing 
it on the DT Forum in a post by user Korm, I believe.

*)

on performSmartRule(theRecords)
	tell application id "DNtp"
		repeat with theRecord in theRecords
			set theFrom to create location "/SRP/NotDueYet" in database "res2"
			set theTo to create location "/SRP/ForReview" in database "res2"
			set theResult to move record theRecord from theFrom to theTo
		end repeat
	end tell
end performSmartRule

Some tips for creating cards

Create a Keynote document with two pages, first one for question, second for answer. Choose a portrait format for the slides so that the first slide is filling the screen on the iPad, not to reveal the answer on the second page. Keep such a blank file in the SRP group, duplicate it, edit it, and move it into ForReview to create a card. Using Keynote for this, the Apple Pencil can be easily used to create content or annotate right on the iPad. Furthermore, it is trivial to add rich components such as images and LaTex-based formulae (caveat: hyperlinks will not function in the DTTG-internal view of the Keynote file).

The spacing algorithm

  • In my script, the intervals between reviews are set to 1, 2, 5, 11, 23, 52, 113, 249 days. So not really an “algorithm” at all, just a list.
  • First view of an item after it is placed in the system is not a review yet. So even if you answer correctly, the card will be offered for a first review on the next day.
  • If the card is then again successfully reviewed, it comes back after 2 days, then 5, etc.
  • The system tops out at 249 days, the spacing will not go further out.
    • I have not tested this system nearly that far out, so I have no idea yet whether this spacing is sensible.
  • Any time the card is not answered correctly, it is reset to the 1 day interval.

This method can be arbitrarily modified by editing the srp1 AppleScript. Since we use the ratings to provide feedback, a more differentiated feedback could be provided, e.g. 1= wrong, 2 = struggling, but kind of correct, …, 5 = super easy. Based on the level of correctness, the spacing could be fine tuned.

I’m not an expert, and have not used my system for a long time yet, but after reading the literature, I became a bit skeptical of all these ingenious, fine-tuned (and sometimes super-secret), spacing algorithms. I would not be surprised if applying the KISS principle is the way to go.

Things I don’t like
Unfortunately, in DTTG V3, the ratings and other flags have been relegated one menu level down, since the Info menu is now inside of the “three-dot-in-a-circle” top level menu. So it is a bit cumbersome to set the rating for each card. That might be a deal breaker for quick-studies. It would be ideal to have something at top level.

Further reading

Gwern’s review of spaced repetition practice is an excellent starting point to learn more.

2 Likes

I participated in a recent discussion at Spaced repetition on DT3 using Markdown - #4 by DTLow

Yes, I saw and read that topic. I also described something SRPish using ratings as the storage of the review history here last year. But then I felt that the 6 levels of rating where too confining. So I write the review history away in a custom metadata field with unlimited space, and use the rating only as a tool for making a review. It might be interesting to some day look at the review history, e.g. by exporting the card name and the history meta-data field for analysis in Excel. E.g. to find out whether the whole SRP thing actually works for me.