{"id":15017297,"url":"https://github.com/ig3/srf","last_synced_at":"2025-04-12T11:43:41.975Z","repository":{"id":59172999,"uuid":"368999829","full_name":"ig3/srf","owner":"ig3","description":"Spaced Repetition Flashcards, as a web server running on NodeJS.","archived":false,"fork":false,"pushed_at":"2025-04-06T21:46:56.000Z","size":6547,"stargazers_count":13,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-06T21:51:21.541Z","etag":null,"topics":["anki","flashcards","spaced-repetition-flashcards","srf"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ig3.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-19T20:59:28.000Z","updated_at":"2025-04-06T21:46:59.000Z","dependencies_parsed_at":"2023-02-08T14:01:55.855Z","dependency_job_id":"977f18db-34fe-4a37-b374-961c0b4e82a4","html_url":"https://github.com/ig3/srf","commit_stats":{"total_commits":573,"total_committers":3,"mean_commits":191.0,"dds":"0.47643979057591623","last_synced_commit":"282ed680b94c53ec3c05dade6316fef44c065d20"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ig3%2Fsrf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ig3%2Fsrf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ig3%2Fsrf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ig3%2Fsrf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ig3","download_url":"https://codeload.github.com/ig3/srf/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248564056,"owners_count":21125405,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anki","flashcards","spaced-repetition-flashcards","srf"],"created_at":"2024-09-24T19:50:16.510Z","updated_at":"2025-04-12T11:43:41.954Z","avatar_url":"https://github.com/ig3.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# srf\n`srf` is a web server providing\n[Spaced Repetition](https://en.wikipedia.org/wiki/Spaced_repetition)\n[Flashcards](https://en.wikipedia.org/wiki/Flashcard),\nbased loosely on [Anki](https://github.com/ankitects/anki).\n\nIt runs on\n[node](https://duckduckgo.com/?t=ftsa\u0026q=nodejs\u0026ia=web)\nwith a local\n[sqlite](https://www.sqlite.org/index.html)\ndatabase.\nAll resources being local, it can run while offline.\n\n\n## Installation\n```\n$ npm install -g @ig3/srf\n```\n\nThis provides the command `srf`.\n\n## Operation\n\n 1. Get an Anki deck (e.g. from [AnkiWeb](https://ankiweb.net/shared/decks/), or wherever)\n 2. Import the Anki deck into srf: `srf import anki_deck.apkg`\n 3. Run the srf server: `srf`\n 4. Browse srf: http://localhost:8000\n 5. Click the Study button (shortcut: space bar)\n 6. Review the card then click Flip (shortcut: space bar)\n 7. Rate the card: Click Fail, Hard, Good or Easy (shortcuts: j, k, l, ;)\n 8. Repeat: review, flip then rate each card\n\nYou study cards. Each card has a front and a back. First you view the front\nand try to remember what is on the back. Then you flip the card over to see\nthe back: to see if you remembered correctly. Finally, you rate how well\nyou remembered the card: 'Fail', 'Hard', 'Good' or 'Easy'.\n\nThe scheduler presents a few new cards each day. The number of new cards is\nadjusted automatically, to keep total study time between 30 and 60 minutes.\n\nWhen you have studied all the cards due for review and reached the limit on\nnew cards, you will return to the Home page, until there are more cards to\nstudy.\n\nThe scheduler is configurable: all the limits and parameters can be\nadjusted to your preferences and ability. Do you want to study more or less\neach day? Change targetStudytime. Want a different limit on new cards?\nChange maxNewCardsPerDay. And many more. There are very few hard coded\nparameters, and even those are easy to change: just checkout the repository\nand change them. It's all JavaScript, HTML, CSS and a few handlebars\ntemplates. And if you want something more fundamentally different, you can\nreplace the scheduler. It was ease of tinkering with the scheduler that\nmotivated me to write this.\n\n### Get an Anki Deck\nThere are many places to download shared Anki decks and many tools for generating them. [AnkiWeb](https://ankiweb.net/shared/decks/) is one source. Search for 'shared Anki decks' or 'generate Anki decks'\n\n### Home page\n\nThe home page shows statistics of your study:\n\n * cards reviewed and minutes studied in the past 24 hours\n * cards due and predicted minutes to study in the next 24 hours\n * study time per day: a forward looking average used to determine new card\n   mode and a short term (7 days) historic average\n * new cards per day: new cards in the past 24 hours and a short term\n   historic average\n * percent of cards that you got correct (i.e. not rated Fail)\n * number of cards due and overdue for review\n * chart of study time per hour: past 24 hours and next 24 hours\n\nThe status at the top right includes:\n * Traffic lights for new cards:\n   * Green (go): new cards at intervals and if there are no due cards\n   * Yellow (slow): new cards at intervals\n   * Red (stop): no new cards\n * Cards due\n * New cards in the past 24 hours\n * Minutes studied in the past 24 hours\n * Predicted minutes to study in the next 24 hours\n\nTo study a card, click the Study button or press the space bar.\n\nThe study button will always present a card for study, ignoring the limits\nof the scheduler.\n\nThe space bar will present a card for study or reload the home page,\naccording to the determination of the scheduler. \n\nThe buttons at the bottom of the page are:\n\n * Admin: to access the admin page\n * Stats: to review various statistics of your study history\n * Help: to review the help file - mostly a link to this README\n\n### Card Front\n\nThe content of the card front is variable.\n\nStatus at top right is the same as the home page.\n\nAt the bottom of the page are a set of buttons:\n\n * Flip: to flip the card over to the back (shortcut: space bar)\n * Help: to review the help file\n * Edit: to edit the card content\n * Play: to replay audio, if the card has audio\n * Stop: to stop studying and return to the home page\n\n### Card Back\n\nThe content of the card back is also variable.\n\nStatus at top right is the same as the home page.\n\nAt the bottom of the page are a set of buttons:\n\n * Fail: if you couldn't remember the card (shorcut: j)\n * Hard: if you could remember the card but it was hard (shortcut: k)\n * Good: if you could remember the card reasonably well (shortcut: l)\n * Easy: if you could remember the card very easily (shortcut: ;)\n * Help: to view the help file\n * Edit: to edit the card content\n * Play: to replay audio, if the card has audio\n * Stop: to stop studying and return to the home page\n\nAfter you rate the card (Fail, Hard, Good or Easy) the front of the next\ncard will be presented, you you will be returned to the home page if there\nare no more cards to study.\n\n### Creating Cards\n\nThere are several ways to create cards:\n * Import Anki .apkg or .colpkg file\n * Import CSV files\n * Add them one at a time via the browser\n * Direct database update\n\nCards are the basic elements of study. They are produced from fieldsets,\ntransformed by templates.\n\nA fieldset is a set of name/value pairs that you want to remember in\nrelationship to each other, stored as JSON.\n\nFor example:\n\n```\n{\n  \"Country\": \"Canada',\n  \"Capital\": \"Ottawa\",\n  \"Continent\": \"North America\"\n  \"Area': \"over 9.98 million square kilometres\",\n  \"Population\": \"38 million\"\n}\n```\n\nA template is a pair of Mustache templates for the front and back of a card\nthat include some of the fields in the fieldset, and some css for styling\nthe card.\n\nFor example:\n\n```\nFront: The \u003cspan class=\"keyword\"\u003ecapital\u003c/span\u003e of {{Country}} is?\nBack: {{Capital}}\ncss: .keyword { font-style: italic; }\n```\n\nA single template can be used to produce cards from many fieldsets.\n\nTemplates are grouped into sets identified by templateset name.\n\nEach fieldset is related to a templateset and one card is produced for each\ntemplate in the templateset.\n\nYou study the cards. The cards are produced automatically when you add or\nedit fieldsets or templates.\n\nThe order that cards are presented as new cards is determined by their\nordinal (ord), which is copied from the fieldset.\n\n#### Create Cards by importing an Anki .apkg or .colpkg file\n\nThe easiest way to create cards is to import an Anki\n[Packaged Deck](https://docs.ankiweb.net/exporting.html). This will import\nthe Anki cards, notes and note types into srf. However, srf does not\nsupport all the features of Anki so not all Anki decks will work in srf.\nFor example, srf does not support cloze deletion so any deck that includes\ncloze deletion notes will not work.\n\nIf you have been using Anki, you can export your own decks. Be sure ti\ninclude media when you export your decks. If you include scheduling\ninformation, it will be preserved.\n\nAnki collection packages and shared decks can include review logs. These\nwill be imported and merged with existing logs. Daily stats will be cleared\nand recalculated to including the new revlog entries.\n\nYou can download a shared Anki deck from\n[AnkiWeb](https://ankiweb.net/shared/decks/) or use various tools to\nproduce Anki Packaged Deck files from other sources.\n\nSrf doesn't distinguish decks. If you import multiple decks or a deck\ncollection, all the cards will appear in a single pool of cards in srf. If\nyou want to keep decks separate, you can import each deck into a separate\nsrf database.\n\nNote that srf only supports simple cards. In particular, Cloze notes are not\nsupported. See below for what is/isn't supported. Start with something\nbasic.\n\nTo import an Anki packaged deck:\n\n```\n$ srf import \u003cshared_deck.apkg\u003e\n```\n\nTo import an Anki deck collection:\n\n```\n$ srf import \u003cdeck_collection.colpkg\u003e\n```\n\n#### Create Cards by importing CSV files\n\nCreate two CSV files:\n * templates.csv\n * fieldsets.csv\n\nImport each file with:\n\n```\n$ srf import templates.csv\n$ srf import fieldsets.csv\n```\n\n##### templates.csv\n\nThe templates determine which cards are produced and how the are presented.\nOne card is produced for each template with matching templateset value.\n\nThe fields for a template are:\n * templateset - the name of the templateset, must match same on fieldset\n * name - the name of the template\n * front - the mustache template code for the front of the card\n * back - the mustache template code for the back of the card\n * css - the CSS for rendering the card, common to front and back\n\nMake a CSV file with these headings and appropriate data.\n\nFor example:\n\n```\ntemplateset,name,front,back,css\nBasic,Card 1,{{Front}},{{Back}},\".card { Background-color: red; }\"\nBasic,Card 2,{{Back}},{{Front}},\".card { Background-color: red; }\"\n```\n\nYou can create many templatesets and each templateset can have as many\ntemplates as you like. One card will be produced for each template with\ntemplateset matching that of a fieldset.\n\n##### fieldsets.csv\n\nThe fields for a fieldset are:\n * guid - optional guid that uniquely identifies the fieldset\n * templateset - the templateset for rendering the fieldset\n * fields - the fields data as a JSON string\n * ord - an ordinal number for sorting the fieldsets\n\nguid will default to md5 checksum of the concatenation of the templateset\nand fields values.\n\nord will default to the row number * 10.\n\nFor example:\n\n```\ntemplateset,fields\nTest1,\"{\"\"Front\"\": \"\"Who you gonna call?\"\", \"\"Back\"\": \"\"Ghost Busters!\"\"}\"\nTest1,\"{\"\"Front\"\": \"\"Where's Waldo?\"\", \"\"Back\"\": \"\"Hold on a sec... I'm still looking.\"\"}\"\n```\n\nYou can have as many fields as you want in your templates and fieldsets.\n\nRead below for details of all the options for the templates. They are\n[Mustache](https://github.com/janl/mustache.js) templates with all the\nfield values available.\n\n##### Add media files\n\nIf the fieldsets refer to media files (images, audio, video, etc.), you\nwill have to copy these to the media directory: ~/.local/share/srf/media by\ndefault.\n\nFor example, a field might include an image with something like:\n`\u003cimg src=\\\"my-image.png\\\" /\u003e`, or an audio file with something like:\n`[sound:my-audio.mp3]`.\n\n#### Create Cards Manually\n\nYou can create templates and fieldsets manually in the browser. The\ninterface is crude, but it's an option.\n\nOn the home page, click Admin to view the admin page.\n\nOn the admin page, click Templates, Template Sets or Field sets to see a\nlist of the corresponding items.\n\nClick on an item in the list to edit it, or click the New button to add a\nnew item to the list.\n\nWhen you edit a fieldset, the fields you can set are determined from the\ntemplates in the selected templateset: every field included in any template\nin the templateset. You can't set fields that are not included in any\ntemplate. If you want to set another field, include it in at least one of\nthe templates.\n\n#### Create Cards by direct database update\n\nThe database structure is simple. You can write your own application to add\ncards to the srf database.\n\nThe database is [sqlite3](https://www.sqlite.org/index.html).\n\nBy default, the database is at ~/.local/share/srf/srf.db and media files\nare in ~/.local/share/srf/media.\n\nAdd templates by adding records to the template table.\n\nAdd fieldsets by adding records to the fieldset table.\n\nSee [the database description](#srf-database) for a description of the database\nand details of these tables.\n\nAfter modifying template or fieldset tables, generate missing cards and\ndelete orphaned cards by running fix:\n\n```\n$ srf fix\n```\n\n### Run the server\n\n```\n$ srf\nListening on http://:::8000\n```\n\nAlternatively, create a systemd service file\n(e.g. ~/.config/systemd/user/srf.service) similar to:\n\n```\n[Unit]\nDescription=Spaced Repetition Flashcards\n\n[Service]\nType=simple\nRestart=on-failure\nWorkingDirectory=/tmp\nStandardOutput=syslog\nStandardError=syslog\nSyslogIdentifier=srf\nExecStart=bash -l -c srf\n\n[Install]\nWantedBy=default.target\n```\n\nThen enable and run the service:\n\n```\n$ systemctl --user enable srf\n$ ssytemctl --user start srf\n```\n\nWith this done, your srf server will run whenever you login.\n\n\n### Study!\n\nBrowse to [http://localhost:8000/](http://localhost:8000/)\n\nClick the Study button or press the space bar to study a card.\n\n#### Front\n\nWhen you study a card, the front of the card is displayed. The content of\nthe front of the card depends on the template and the fieldset.\n\nWhen you are ready, you flip the card to see the back of the card. See\nbelow for details of the back of the card.\n\n##### Flip\n\nKeyboard shortcut: space-bar\n\nFlip the card over, to show the back of the card. This is the usual action\nwhen reviewing a card: view the front, then flip it to review the back and\nrate the card.\n\n##### Help\n\nShow the help file.\n\n##### edit\n\nEdit the card. This opens a new window with fields to edit the fieldset\nfrom which the card is derived. It is a bit crude but allows simple edits.\n\n##### Play\n\nkeyboard shortcut: p\n\nIf the card has audio, play it again.\n\n##### Stop\n\nReturn to the home page without completing review of the card.\n\n#### Back\n\nAfter you view the front of the card and flip it over, the back is\ndisplayed. The content of the back is determined by the template and the\nfieldset.\n\nThe back of the card has buttons for rating how well you remember the card:\nFail, Hard, Good and Easy.\n\nThe button you click determines when the card will be scheduled for review.\nEach button displays the new interval: the time until the card will be due\nfor review.\n\n\n##### Fail\n\nkeyboard shortcut: j\n\nIf you didn't remember the card and want to review it again soon, click\nFail. This reduces the interval for reviewing the card. The new interval\n(the time until the card is due for review) is half the previous interval\nwith a maximum interval of 1 day.\n\n##### Hard\n\nkeyboard shortcut: k\n\nIf you did remember the card but it was difficult to remember, click Hard.\nThis reduces the interval for rewviewing the card but not as much as Fail.\nThe new interval is 80% of the previous inerval with a maximum interval of\n1 week.\n\n##### Good\n\nkeyboard shortcut: l\n\nIf you remembered the card well, click Good. This will, hopefully, be the\nbutton you click most often. This increases the interval for reviewing the\ncard. \n\nCalculation of the new interval is more complicated for Good. The minimum\nnew interval is 5 minutes and the maximum new interval is 1 year. Between\nthese limits, the new interval depends on the ease factor of the card. The\nease factor is the exponentially weighted moving average of your previous\nratings. The weights are 0, 1, 1.5 and 2 for Fail, Hard, Good and Easy\nrespectively. Thus the factor ranges from 0 to 2 and it will change a bit\neach time you review the card.\n\nIf you rate the card Good several times, the ease factor will tend towards\n1.5 and the new interval will tend towards 150% of the previous interval.\n\nOn the other hand, if you have rated the card Fail many times, the ease\nfactor might be quite low and even if you rate the card Good, the new\ninterval might be less than the previous interval. However, even in this\ncase, the ease factor will increase so that after a few Good reviews the\ninterval will be increasing.\n\n##### Easy\n\nkeyboard shortcut: ;\n\nIf it was too easy to remember the card and you don't want to waste your\ntime reviewing again too soon, click Easy. The new interval will be 150% of\nwhat it would be for Good. \n\n##### Help\n\nShow the help file.\n\n##### Edit\n\nEdit the templateset the card is generated from.\n\n##### Play\n\nReplay any audio for the back of the card.\n\n##### Stop\n\nStop studying without completing review of the card: return to the home\npage.\n\n\n## Background\nI used [Anki](https://github.com/ankitects/anki) for a couple of years but\nwanted to change the scheduler. An Anki addon to add a new scheduler\nwas impossible because Anki addons are Python code but the scheduler\nimplementation is partially in Python and partially in Rust back-end code.\nEven limited modifications to the existing schedulers\n(e.g. [Anki - limit new](https://github.com/ig3/anki-limitnew))\nwere difficult and time consuming to maintain due to frequent changes\nto the Anki internals. It was easier to write this and get the scheduler I\nwanted than to maintain the Anki addon that provided only part of what I\nwanted.\n\nThe srf scheduler:\n * prioritizes cards with shorter intervals over those with longer intervals\n * regulates the introduction of new cards automatically\n * is written entirely in JavaScript and is easy to modify\n\nsrf is able to import Anki decks that use only basic features of Anki. Not\nall field and media types are supported, but enough for many decks / cards\nto work.\n\n[Spaced Repetition for Efficient Learning](https://gwern.net/spaced-repetition)\nprovides an overview and links to research on spaced repetition.\n\n### Comparison to Anki\n\nsrf provides only a small subset of the features of Anki: enough for my\npurposes: studying language on a single device, but without support for\nsynchronizing multiple devices or distinguishing decks / multiple topics of\nstudy. \n\nThe srf scheduler does not suffer from\n[Ease Hell](https://readbroca.com/anki/ease-hell/).\n\nThe srf scheduler has an equivalent to Anki ease factor for each card\nbut it is simpler: it is the\n[exponentially weighted moving average](https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average)\nof the weighted answers. The default answer weights are 0, 1, 1.5 and 2 for\nFail, Hard, Good and Easy and the default decay factor is 0.95, but these\nare all configurable. By default, the range of the ease factor is 0.0\nthrough 4.0: somewhat equivalent to Anki ease factors 0% to 200%. With srf\nthe ease factor never gets 'stuck' at an unreasonably low setting. It\nalways adapts to the recent ease of the card.\n\nThe srf scheduler does not suffer from\n[Backlog Hell](https://iansworld-nz.blogspot.com/2022/04/anki-backlog-hell.html).\n\nThe srf scheduler has two ways of sorting cards:\n\n * by ascending interval\n * by ascending due time\n\nThe sort is determined randomly each time a due card is selected with the\nprobability of sorting by due time being config.probabilityOldestDue, which\ndefaults to 0.2.\n\nThis strategy balances studying cards in they order they become due with\nfocusing on the cards with minimum interval. It makes a difference only\nwhen clearing a backlog. It allows the cards with the shortest intervals to\nbe studied on time, despite the backlog. At the same time, it allows the\nmost overdue cards to be studied, gradually clearing the backlog.\n\nNew cards are presented before due cards if total study time the past 24\nhours is less than config.minStudyTime.\n\nNew cards are presented at intervals, interleaved with due cards, if total\nstudy time the past 24 hours is less than config.targetStudytime.\n\n## Getting Started\n\n```\n$ git clone https://github.com/ig3/srf.git\n$ cd srf\n$ npm install\n$ node bin/cmd.js import \u003cexport file\u003e\n$ node bin/cmd.js\n```\nThe import supports Anki exports and shared decks (.apkg files). When I\nmigrated from Anki, I exported all decks as an Anki deck package (\\*.apkg),\nincluding media, then imported this into srf.\n\nBrowse to http://localhost:8000/.\n\n### Home Page\n\nThe home page presents some basic study statistics:\n\n * The number of cards reviewed and minutes studied in the past 24 hours\n * The number of cards due and estimated minutes to study in the next 24\n   hours\n * Study time per day: short term used to determine new card mode /\n   historic 7 days average\n * New cards per day: past 24 hours / historic average\n * New cards during the past 24 hours\n * The percentage of correct (not 'Fail') responses to mature cards in the\n   past 14 days\n * The number of cards due and overdue (due more than 24 hours ago)\n * The time until the next card is due\n * A histogram of study time per hour through past and next 24 hours\n\nIf there is a card available for study, the 'Study' button will appear.\nClick it to study a card. After studying a card, the next card will be\npresented, until there are no more cards to be studied and the home page is\ndisplayed again. Alternatively, click the space bar: shortcut for Study.\n\nThe scheduler determines when a card is due to be studied. Each time a card\nis reviewed, the scheduler sets a new time when it is due to be studied\nagain. The cards to be studied are all those which are past their due \ntime. If it is more than 24 hours past their due date and time, they\nare considered overdue. \n\nIt's like an [assembly line](https://www.youtube.com/watch?v=59BIB-2FVmM):\nyou will sometimes be waiting for the next card to study, and you will\nsometimes have a backlog of cards to catch up. While you don't want to get\ntoo far behind, it is OK to accumulate cards to review. Studying to clear\nthe backlog once per day will work well. While there is nothing wrong with\nstudying in multiple sessions each day, don't obsess about reviewing every\ncard that comes due each day. It is OK to leave them until the next day, or\neven longer.\n\nThe data is in ~/.local/share/srf by default, including database (srf.db)\nand media files. The database is ~/.local/share/srf/srf.db. Media files\nare in ~/.local/share/srf/media.\n\nAt the top right of the home page and front and back pages, there is a\nstatus indicator. Green, yellow or red depending on new card mode: Green if\nnew cards will be presented interleaved with due cards and if there are no\ncards due; Yellow if new cards will be presented interleaved with due\ncards; and Red if no new cards will be presented. Below this are counts of\ncards due, minutes of study the past 24 hours and minutes of study the next\n24 hours (estimated). \n\n#### Buttons\n\n##### Study\n\nStudy a card. This will select a card according to the scheduling algorithm\nif there is a card due to be studied. If no card is due to be selected, a\nnew card will be presented. If there are no more unseen cards, then the\ncard with the earliest due date will be presented, even if that is in the\nfuture. This button will always yield a card to study.\n\n##### Reload\n\nReload the home page.\n\n##### Admin\n\nView the admin page, with links to administer templates, template sets and\nfield sets, and view configuration.\n\n##### Stats\n\nView the statistics page.\n\n##### Help\n\nThis will be documentation, if I ever write it.\n\n### Admin\nLinks to the administration pages:\n\n#### Templates\n\nView a list of all templates in the system. Click on a template to edit it.\n\nEach template is linked to a template set and should have a unique name\namong the templates linked to that template set. There are three\nattributes:\n\nFront: handlebars template code for the front of the card.\n\nBack: handlebars template code for the back of the card.\n\nCSS: CSS that will be loaded to the front and back.\n\nAll the fields of the template/fieldset are available. For example, if the\nfieldset includes a field `English` then include this in the template with\n`{{English}}`.\n\nThe set of fields are defined in the template set definition.\n\n#### Template Sets\n\nView a list of all template sets in the system.\n\nThe view is read-only. To change them, add or edit the templates.\n\nThe fields of a templateset are all the fields of all the templates in the\ntemplateset. If you add a field to one of the templates, it will be added\nto the templateset.\n\n#### Field Sets\n\nView a list of all field sets in the system. Click on a field set to edit\nit. Click the New button to add a field set.\n\nA field set is a collection of field values that populate cards. The set of\nfields is defined in the linked template set.\n\nEach fieldset must be linked to a Template set. The set of fields is\ndefined in the template set. If you change the template set after setting\nfield data, the field data will be lost.\n\nTo attach media files, put the cursor into the field value input where the\nmedia file is to be attached, then click the Attach button and select the\nfile containing the media data.\n\nAt the moment, the only supported media types are image/jpeg and\naudio/mpeg. These are inserted as img tags and `audio` respectively.\n\n#### Config\n\nA read-only view of the loaded configuration.\n\n### Stats Page\n\nThe Stats Page is available from the Home Page by clicking the Stats\nbutton. It shows some basic statistics about study performance.\n\n#### New cards per day\n\nThis is the number of new cards viewed in the current day,\nshort term (past 7 days) average of new cards per day and\nlong term (since the beginning of study) average of new cards per day.\n\n#### Cards\n\nThis is various counts of cards:\n * Total cards, broken down into:\n     * Unseen cards,\n     * Seen cards, broken down into:\n         * New cards,\n         * Learning cards,\n         * Mature cards,\n         * Mastered cards\n\n#### Percent Correct\n\nThis is the percentage of 'correct' responses (a.k.a. not Fail) for cards\nwith intervals between config.matureThreshold and config.maxInterval,\nreviewed in the window config.percentCorrectWindow.\n\nCards with maximum interval are excluded on the premise that they are\nmastered and it is not performance on these cards that should be regulated,\nbut rather performance on mature cards (Unconcious Competence). \n\nMaybe this should also include the learning cards, but these are excluded\non the premise that while learning the error rate will be relativley high\nand the regulation effectively regulates how quickly cards progress through\nlearning to mastered, even though the learning cards are not included in\nthe calculation of percent correct.\n\nCard intervals are adjusted according to the difference between the average\npercent correct and config.percentCorrectTarget. If average percent correct\nis less than the target then intervals are shortened, making cards due\nsooner. If it is more than the target then intervals are lengthened, making\ncards due later.\n\n#### Average time per review\n\nThis is the average time for each review of a card. It includes small gaps\nbetween reviews. It is the total study time for the day divided by the\nnumber of reviews.\n\n#### Cards today\n\nThis is the total number of distinct cards viewed today and the number of\ncards due later in the same day. The day is the calendar day in local time.\n\nThe total number of cards viewed today is a count of cards not reviews. A\ncard might be reviewed multiple times in a day but this counts the cards,\nnot the reviews.\n\nThe cards due later in the same day are those cards scheduled for review\nbefore the end of the day in local time. This includes cards that are\ncurrently due or overdue. This does not included cards that will be\nrescheduled to be viewed again later in the same day.\n\n#### Reviews per new card\n\nThis is the number of reviews to be completed before the next new card will\nbe presented, counting down at each review, and the current minimum number\nof cards to be reviewed between each new card.\n\n#### Study time today\n\nThis is the total time spent studying today.\n\n#### Average study time per day\n\nThis is average daily study time, averaged over the past week (actual study\ntime) and coming week (estimated based on cards due and recent\nperformance).\n\n#### Time to earliest due card\n\nThe time until the earliest due card is due. This will be negative if there\nis a card due in the past: if there is currently one or more cards due for\nstudy.\n\n#### Charts\n\n##### New Cards per day\n\nThis is the number of new cards presented each day since the start of study\n\n##### Card Views per day\n\nThis is the total number of card views each day since the start of study.\n\n##### Minutes Studied per day\n\nThis is the total study time per day since the start of study.\n\n##### Cards per Stage\n\nThis is a stacked chart of cards per stage: New, Learning, Matured and\nMastered. The stages are distinguished by interval.\n\n * New: 0 \u003c interval \u003c config.learningThreshold\n * Learning: config.learningThreshold \u003c= interval \u003c config.matureThreshold\n * Mature: config.matureThreshold \u003c= interval \u003c config.maxInterval\n * Mastered: config.maxInterval \u003c= interval\n\n##### Cards Due per day\n\nThis is the number of cards due for review each day, looking forward from\ntoday.\n\n##### Cards per interval\n\nThis is the number of cards with the given interval in days. There are two\nlines: Adjusted Interval and Unadjusted Interval. The Adjusted Interval is\nthe interval after adjustment for percent correct. The Unadjusted Interval\nis the interval as set when the card was last viewed. The adjusted interval\nis the interval that will be the basis for calculation of the interval\nafter the next review.\n\n## Command Synopsis\n\n```\nusage:\n  srf --help\n  srf [options...] [run]\n  srf [options...] import \u003cfilename\u003e\n  srf [options...] backup\n  srf [options...] fix\n```\n\n### run\n\nRun the server. This is the default.\n\n### import\n\nImport an Anki apkg or colpkg file, or a csv file containing template or\nfieldset data.\n\n### backup\n\nCreate a backup of the srf database.\n\nThe server makes a backup of the database each time it starts and then once\nevery 24 hours if it runs that long.\n\nThe backups are in the same directory as the database, with timestamp\nappended to their name.\n\n### fix\n\nThis performs several 'fixes':\n\n * create any missing cards from fieldsets and templates and delete any\n   cards for which there is no longer a fieldset and template.\n * fix revlog entries:\n   * make revlog IDs unique and monotonic increasing\n   * set 'lastinterval' to 'interval' of the previous revlog entry for the\n     card\n   * set 'interval' of the last revlog entry for a card to the 'lastinterval'\n     of the card\n   * set revdate according to the id\n   * add missing counts of cards by stage to dailystats\n\n### options\n\n#### --help|-h\nDisplay usage and exit.\n\n#### --port|-p\nSet the port that the srf server listens on.\n\nDefault is 8000.\n#### --directory|--dir\nSet the root directory, in which the database and media files are located.\n\nDefault is ~/.local/share/srf\n\nIf the given directory is absolute (i.e. begins with '/') it is used as\ngiven. If it is relative (i.e. does not begin with '/') it is relative to\n`~/.local/share`.\n\nFor example: `/tmp/testing` would use that directory but `testing` would\nuse `~/.local/share/testing` and `testing/a` would use\n`~/.local/share/testing/a`.\n\nMedia files are located in the `media` subdirectory of this directory.\n\nThe srf database is, by default, `srf.db` in this directory, but see option\n`--database` below.\n\n#### --config|-c\nSet the configuration filename.\n\nDefault is `config.json`\n\nIf the given path is absolute (i.e. begins with '/') it is used as given.\nIf it is relative (i.e. does not begin with '/') it is relative to the path\nof the --directory option.\n\n#### --database|--db\nSet the sqlite3 database name.\n\nDefault is ~/.local/share/srf/srf.db or, if --directory is specified then\nsrf.db in that directory.\n\nIf the given path is absolute (i.e. begins with '/') it is used as given -\nthe database file can be outside the data directory. If it is relative,\nthen it is relative to the path of the --directory option.\n\nThe directory to contain the database is created if it doesn't exist.\n\n#### --media\nSet the path of the media directory.\n\nDefault is `media`\n\nIf the given path is absolute (i.e. begins with '/') it is used as given.\nIf it is relative (i.e. does not begin with '/') it is relative to the path\nof the --directory option.\n\nThe directory is created if it doesn't exist.\n\n#### --htdocs\nSet the path of the htdocs directory.\n\nDefault is `htdocs`\n\nIf the given path is absolute (i.e. it begins with '/') it is used as\ngiven. If it is relative (i.e. does not begin with '/') it is relative to\nthe path of the --directory option.\n\nThis directory contains overrides for the static content of the srf server.\n\n * css/dark.css\n * img/dark-go.png\n * img/dark-slow.png\n * img/dark-stop.png\n * img/favicon.png\n * img/logo.png\n * js/plotly-latest.min.js\n * js/sorttable.js\n\n#### --scheduler\n\nSpecify the schedulre plugin to load. The argument will be passed to\nrequire() to load the plugin, so it must be something that can be required.\nDetails will depend on how srf and the plugin are installed but the name of\na globally installed package or the full path to the scheduler module\nshould both work.\n\n#### --views\nSet the path of the views directory.\n\nDefault is `views`\n\nIf the given path is absolute (i.e. it begins with '/') it is used as\ngiven. If it is relative (i.e. does not begin with '/') it is relative to\nthe path of the --directory option.\n\nThis directory contains overrides for the handlebars templates of the srf\nserver. Templates in this folder will override the default templates.\n\n * back.handlebars\n * fieldset.handlebars\n * front.handlebars\n * help.handlebars\n * home.handlebars\n * stats.handlebars\n * template.handlebars\n * templates.handlebars\n\n### Config\n\nConfiguration files may be put in several places:\n\n * /etc/srf\n * /etc/srf.ini\n * /etc/srf.json\n * /etc/srf/config\n * /etc/srf/config.ini\n * /etc/srf/config.json\n * ~/.config/srf\n * ~/.config/srf.ini\n * ~/.config/srf.json\n * ~/.config/srf/config\n * ~/.config/srf/config.ini\n * ~/.config/srf/config.json\n * ~/.srf\n * ~/.srf.ini\n * ~/.srf.json\n * ~/.srf/config\n * ~/.srf/config.ini\n * ~/.srf/config.json\n * .srf\n * .srf.ini\n * .srf.json\n\nAnd, finally, `config.json` in the directory containing the data\n(~/.local/share/srf by default but may be set by option --directory).\n\nFiles with extension `.json` and files without extension are parsed as JSON\nafter stripping comments from them.\n\nFiles with extension `.ini` are parsed as `ini` files.\n\nParameters which are durations may be specified as integer seconds or a\nstring with units: `seconds`, `minutes`, `hours`, `days`, `weeks` or\n`years`, or any prefix of one of these. The units may be separated from the\nnumber by spaces.\n\nFor example:\n * 10 // 10 seconds\n * \"10 seconds\"\n * \"5 days\"\n * \"1 day\"\n * \"1 d\"\n * \"1d\"\n\nOther parameters are integers.\n\nFor example, a json file might be:\n\n```\n{\n  // Display theme\n  \"theme\": \"dark\",\n\n  // Minimum time between related cards (seconds)\n  \"minTimeBetweenRelatedCards\": \"1 hour\"\n\n  // Backup retention time (milliseconds)\n  \"backupRetention\": \"30 days\",\n\n  // Minimum number of backups to keep\n  \"minBackups\": 2,\n\n  // Maximum number of backups to keep\n  \"maxBackups\": 10,\n\n  // The maximum time for viewing a card (seconds).\n  // Beyond this, any answer is converted to 'again'\n  \"maxViewTime\": \"2 minutes\",\n\n  // The maximum interval to when a card is due.\n  \"maxInterval\": \"1 year\",\n  \"maxGoodInterval\": \"1 year',\n  \"maxEasyInterval\": \"1 year\",\n\n  // The interval (seconds) beyond which a card is considered 'learning'\n  \"learningThreshold\": '1 week',\n\n  // The interval (seconds) beyond which a card is considered 'mature'\n  \"matureThreshold\": \"21 days\",\n\n  // The window (seconds) in which to average Percent Correct reviews\n  \"percentCorrectWindow\": \"1 month\",\n\n  // The window (seconds) in which to average new cards per day\n  \"newCardsWindow\": \"2 weeks\",\n\n  // The minimum number of mature cards in the percent correct window\n  // at which percent correct is calculated.\n  \"minPercentCorrectCount\": 10,\n\n  // The target for percent correct reviews\n  \"percentCorrectTarget\": 90,\n  \"percentCorrectSensitivity\": 0.0001,\n\n  // The interval (seconds) between correct factor adjustments\n  \"correctFactorAdjustmentInterval\": \"1 day\",\n\n  // The maximum number of new cards in 24 hours.\n  \"maxNewCardsPerDay\": 20,\n\n  // Minimum study time (seconds) per day\n  \"minStudyTime\": \"20 minutes\",\n\n  // Target study time (seconds) per day\n  \"targetStudyTime\": \"30 minutes\",\n\n  // The probability of sorting due cards by due instead of inteval\n  \"probabilityOldestDue\": 0.2,\n\n  // minimum intervals according to responses to reviews\n  \"againMaxInterval\": \"1 day\",\n  \"hardMinInterval\": \"1 week\",\n  \"goodMinInterval\": \"5 minutes\",\n  \"goodMinFactor\": 1.1,\n  \"easyMinInterval\": \"1 days\",\n\n  // Static interval factors\n  \"againFactor\": 0.5,\n  \"hardFactor\": 0.8,\n  \"goodFactor\": 1.0,\n  \"easyFactor\": 1.5,\n\n  // Answer weights\n  \"weightFail\": 0,\n  \"weightHard\": 1,\n  \"weightGood\": 1.5,\n  \"weightEasy\": 2,\n  \"decayFactor\": 0.95\n}\n```\n\n#### theme\n\ndefault: dark\n\nThere is only one theme: dark. But this is only a CSS file. This setting is\njust the name of the CSS file, less the `.css` extension.\n\n#### minTimeBetweenRelatedCards (seconds)\n\ndefault: 1 hour\n\nA template set typically contains several templates. For each field set, a\ncard will be produced for each template in the template set. Thus there\nwill be several cards for each field set.\n\nThe set of cards from a single field set are considered related. If one\ncard from the set is reviewed, this is the minimum time before any other\ncard from the set will be presented for review.\n\n####  backupRetention\n\ndefault: 30 days\n\nThe time to retain database backups. Backups older than this will be\ndeleted.\n\n#### minBackups\n\ndefault: 2\n\nThe minimum number of backups to retain, regardless of their age.\n\n#### maxBackups\n\ndefault: 10\n\nThe maximum number of backups to retain, regardless of their age.\n\n#### maxViewTime (seconds)\n\ndefault: 2 minutes\n\nThe maximum time for viewing a card. If a card is viewed for longer than\nthis ease will be forced to 'again'.\n\n#### maxInterval (seconds)\n\ndefault: 1 year\n\nThe maximum interval (time until next review) for a card.\n\n#### maxGoodInterval (seconds)\n\ndefault: 1 year\n\nThe maximum interval (time until next review) for a card that is Good.\n\n#### maxEasyInterval (seconds)\n\ndefault: 1 year\n\nThe maximum interval (time until next review) for a card that is Easy.\n\n#### learningThreshold (seconds)\n\ndefault: 1 week\n\nThe interval beyond which cards are considered to be 'learning' cards\nrather than 'new' cards. Beyond this threshold, they are scheduled\naccording to the actual interval since they were last reviewed, rather than\nthe scheduled interval.\n\n#### matureThreshold (seconds)\n\ndefault: 21 days\n\nThis affects the calculation of Percent Correct, which is compared against\npercentCorrectTarget. Only review of cards with an interval greater than\nmatureThreshold are considered in calculating Percent Correct.\n\nA card is counted as mature (Unconcious Competence) or mastered if its\ninterval is greater than matureThreshold.\n\n#### percentCorrectWindow (seconds)\n\ndefault: 1 month\n\nThe percentage of 'correct' responses (not 'Fail') is a factor in\ndetermining the intervals of cards. All responses within this window are\nconsidered in determining the percentage. Results of reviews longer ago\nthan percentCorrectWindow are ignored.\n\n#### newCardsWindow (seconds)\n\ndefault: 2 weeks\n\nThe window in which new cards per day are averaged. The average new cards\nper day is used to calculate the minimum interval between new cards.\n\n#### minPercentCorrectCount\n\ndefault: 10\n\nThe minimum number of mature cards in the percent correct window at which\npercent correct is calculated.\n\n#### percentCorrectTarget (percent)\n\ndefault: 90\n\nThe percentage of 'correct' responses (not 'Fail') is a factor in\ndetermining the intervals of cards. The percentCorrectTarget is the target\npercentage of 'correct' responses.\n\nThe interval and due date of cards are adjusted according to the difference\nbetween the Percent Correct and this target, multiplied by\npercentCorrectSensitivity.\n\nCards actually have two interval values: interval and lastinterval. The\ninterval value is the interval, including any adjustments. The lastinterval\nvalue is what the interval was when the card was last reviewed, without any\nadjustments.\n\nRevlog also has two interval values: interval and lastinterval. But here\ntheir values are different. The value of interval is the new interval of\nthe card, after review, based on the ease. The value of lastinterval is the\nunadjusted interval of the card the last time it was reviewed. It is\nredundant with the interval value of the previous revlog record for the\ncard but saves a lot of lookup producing the statistics.\n\n#### percentCorrectSensitivity\n\ndefault: 0.0001\n\nThis determines the sensitivity to the difference between Percent Correct\nand percentCorrectTarget, when adjusting the interval and due date of\nlearning and mature cards.\n\n#### maxNewCardsPerDay\n\ndefault: 20\n\nThe maximum number of new cards to be presented within 24 hours.\n\n#### minStudyTime\n\ndefault: 20 minutes\n\nMinimum daily study time. New cards will be presented until study time in\nthe past 24 hours is more than this.\n\n#### targetStudyTime\n\ndefault: 30 minutes\n\nIf total study time for the past 24 hours and predicted study time to study\nall cards due in the next 24 hours are both less than this, then new cards\nwill be presented, interleaved with due cards. If either exceeds this, then\nonly due cards will be presented.\n\n#### probabilityOldestDue\n\ndefault: 0.2\n\nThe probability that the next due card presented will be the first one due\nrather than the one with the shortest interval.\n\n#### againMaxInterval (seconds)\n\ndefault: 1 day\n\nThe maximum interval for cards after response 'Fail'.\n\n#### hardMaxInterval (seconds)\n\ndefault: 1 week\n\nThis is the maximum interval after responding 'Hard' to a review.\n\n#### goodMinInterval (seconds)\n\ndefault: 5 minutes\n\nThis is the minimum interval after responding 'Good' to a review.\n\n#### goodMinFactor \n\ndefault: 1.1\n\nThis is the minimum interval multiplier after responding 'Good' to a review.\n\n#### easyMinInterval (seconds)\n\ndefault: 1 day\n\nThis is the minimum interval after responding 'Easy' to a review.\n\n#### againFactor\n\ndefault: 0.5\n\nAfter responding 'Fail' to a review, the new interval is the previous\ninterval multiplied by this factor, but with a maximum of againMaxInterval\nwhich, by default, is 1 day.\n\n#### hardFactor\n\ndefault: 0.8\n\nAfter responding 'Hard' to a review, the new interval is the previous\ninterval multiplied by this factor, but with a maximum of hardMaxInterval\nwhich, by default, is 1 week.\n\n#### goodFactor\n\ndefault: 1.0\n\nAfter responding 'Good' to a review, the new interval is the previous\ninterval multiplied by this factor, the card factor and the 'correct'\nfactor. See 'scheduler' below for details.\n\n#### easyFactor\n\ndefault: 1.5\n\nAfter responding 'Easy' to a review, the new interval is the actual\ninterval since last review (i.e. time since last review, which may be more\nthan the scheduled interval) multiplied by this factor and the card factor.\n\n#### weightFail\n\ndefault: 0\n\nThe weight of an answer of Fail when calculating the exponentially\nweighted moving average of review replies: the card ease factor.\n\n#### weightHard\n\ndefault: 1\n\nThe weight of an answer of Hard when calculating the exponentially\nweighted moving average of review replies: the card ease factor.\n\n#### weightGood\n\ndefault: 1.5\n\nThe weight of an answer of Good when calculating the exponentially\nweighted moving average of review replies: the card ease factor.\n\n#### weightEasy\n\ndefault: 2\n\nThe weight of an answer of Easy when calculating the exponentially\nweighted moving average of review replies: the card ease factor.\n\n#### decayFactor\n\ndefault: 0.95\n\nThe decay factor when calculating the exponentially weighted moving average\nof review replies: the card ease factor.\n\newma(n) = w(n-1) * decayFactor + w(n) * (1 - decayFactor)\n\n### Commands\n\n#### run\n\nRun the web server. This is the default command (i.e. if srf is run without\nspecifying a command on the command line, it runs the webserver).\n\n#### import \\\u003cfilename\u003e\n\nImport an Anki export (i.e. a .apkg file). This is the primary way to get\ncards into srf. To migrate from Anki to srf, export decks from Anki,\nincluding media, and then import them to srf. Exporting all decks works.\n\n#### fix\n\nFix a few inconsistencies in revlog:\n\n * duplicate IDs\n * inconsistency between interval and lastinterval\n * inconsistency between interval and card interval\n\nThis isn't necessary but it ensures greater consistency between the various\nsrf metrics, particularly related to matured cards.\n\n## Study\n\nWhen you study a card, its front side will be presented. \n\nReview the front side and try to recall the corresponding back side.\n\nWhen you are ready, click the Flip button or click the space bar (shortcut\nfor Flip).\n\nThe back side of the card will be displayed, with buttons to indicate how\nwell you remembered the card:\n\n * Fail: you didn't remember the card - you need to see it again soon\n * Hard: you remembered the card but it was a bit hard to recall\n * Good: you remembered the card \n * Easy: you remembered the card but it was too easy\n\nWhat you should remember, when you see the front side, and what constitutes\nan adequate recollection is up to you. Some cards may be very specific and\nfact based while others might be more vague or conceptual. It's up to your\nown judgement.\n\nKeyboard shortcuts for these are 'j', 'k', 'l' and ';' respectively. These\nare hard coded, but it is easy to edit the templates in the views directory\nif you want different shortcuts.\n\nThe card is then scheduled for review according to which button you\nclicked.\n\nIf there is another card due for study, the front of it is displayed and\nyou continue your study.\n\nIf there are no more cards to be studied at this time the home page is\ndisplayed. You can review your progress and return to study when additional\ncards come due for study.\n\nEvery card that is not 'new' has a time when it is due. If this is before\nthe current time, you may study the card. If it is in the future, you may\nnot study the card. You must wait until the card is due for review to study\nit.\n\nNew cards are due if total study time over the past 24 hours is less than\nconfig.minStudyTime.\n\n## Card Lifecycle\n\nCards progress through the following stages:\n * unseen / unconcious incompetence / UI\n * new / concious incompetence / CI\n * learning / concious competence / CC\n * mature / unconcious competence / UC\n * mastered / mastery / M\n\n### Unseen / unconcious incompetence / UI\n\nWhen you add cards to srf, initially they are unseen. If you add a thousand\ncards, you probably don't want to study them all on the day you add them.\nMost of them remain unseen for some time before they are presented as new\ncards.\n\nIf you look at the database. These unseen cards will have an interval of 0.\nCards that have been seen have an interval greater than 0. Even if these\ncards have a due time set, the due time is ignored until they are selected\nto be presented as new cards.\n\nIf the scheduler determines that a new card should be presented, cards with\ninterval = 0 are sorted by ord then id and the first card is presented.\n\n### New / concious incompetence / CI\n\nEventually an unseen card is presented for study for the first time,\nbecoming a 'new' card. It is deemed to be a new card until its interval\nreaches the learning threshold (config.learningThreshold).\n\nAt this stage, your ability to remember the card might be quite volatile.\nOn the one hand, the novelty of it might make it easier to remember. On the\nother hand, the unfamiliarity of it might make it harder to rememger.\n\nNew cards are scheduled according to their scheduled interval, not the\nactual interval since they were last studied. If a card has a scheduled\ninterval of 30 minutes but you don't study overnight so you don't see it\nuntil 10 hours later, it is scheduled on the basis of the scheduled 30\nminute interval, not the 10 hour interval.\n\n### Learning / concious competence / CC\n\nAfter familiarizing with a new card, the real work of committing it to long\nterm memory begins. Intervals are still fairly short and your ability to\nrecall the card might be quite sensitive to the actual interval. Reviewing\nthe card a few days late might make the card much more difficult to recall.\n\nDuring this phase, the card is scheduled according to the actual interval\nsince last review. If you don't review it on time (maybe because of exams\nor vaction) but when you do you recall it well, it is the actual interval\nthrough which you were able to recall it that matters.\n\n### Mature / unconcious competence / UC\n\nEventually the card is well committed to long term memory. When you see the\ncard, you recognize it immediately, without having to think about it. The\nthe exact interval doesn't make much difference.\n\nBy default, cards are considered mature when their interval reaches 21\ndays. The scheduling algorithm is still applied and the interval will still\ngradually increase, up to the maximum interval (config.maxInterval) which,\nby default, is one year.\n\n### Mastered / mastery / M\n\nFinally, cards reach the interval limit. They are mastered. \n\nBy default, the interval limit is 1 year (config.maxInterval). \n\n## Scheduler\n\nThe scheduler is provided by a separate package.\n\nThe default scheduler is\n[@ig3/srf-scheduler](https://www.npmjs.com/package/@ig3/srf-scheduler).\n\nThe scheduler can be specified by command line argument `--scheduler`, the\nvalue of which is the ID or full path of the scheduler module to be loaded.\n\n### @ig3/srf-scheduler\n\nThere are two aspects to the scheduler:\n\n 1. Determining when a card is due for review\n 2. Determining which card is to be studied next\n\n### Determining when a card is due to be studied\n\nCards that have been viewed at least once have a time that they are due for\nreview. Technically, this is stored in the database as a date and time in\nUTC timezone.\n\nCards that have never been viewed are called 'new' cards. They do not have\na time that they are due for review. They have not been viewed and\ntherefore cannot be reviewed, until after they have been viewed for the\nfirst time. For details of when new cards are first presented, see the next\nsection. \n\nThe timing, from one review to the next, is the spacing of spaced\nrepetition. The theory is that there is an optimum interval that minimizes\nthe total number of reviews required until a card can be remembered\nindefinitely: until it has been learned. If the interval is too long, the\ncard is forgotten before it is reviewed: it is not learned. If the interval\nis too short, the card is easily remembered but reviewing the card too\noften wastes time that could be used to learn other cards.\n\nEach time a card is studied, whether it is its initial viewing or a later\nreview, the ease with which it was remembered is rated by clicking one of\nfour buttons: Fail, Hard, Good or Easy. The card is then scheduled for\nreview, sooner or later, according to the ease with which it was\nremembered.\n\nThe interval to the next review is determined based on the ease of the\ncurrent and past reviews.\n\n(Almost) All the scheduling parameters are configurable. The following sections\ndescribe the default configuration.\n\n#### Fail\n\nThis is for cards that you could not remember: that you want to see again\nsooner rather than later. The card is scheduled to be reviewed in 30% of\nthe time since it was last reviewed, with a maximum of 1 day.\n\nFor example, if you last reviewed a card 1 hour ago but couldn't remember\nit, it will be scheduled for review in 18 minutes. If you can't remember it\nagain after 18 minutes then it will be scheduled for review in about 5\nminutes.\n\nBut there is a maximum interval. If it is more than 3 weeks since you last\nreviewed the card, the new interval will be 1 week.\n\nCards that were easily remembered at a long interval might become\ndifficult. For example, other similar cards may have been introduced,\nleading to confusion. This might make the old card difficult until it can\nbe reliably distinguished from the new one.\n\n#### Hard\n\nThis is for cards that you could remember but they were too hard: it was\ntoo long since the last review and you want to see the card again sooner.\nThe card is scheduled to be reviewed in 50% of the time since it was last\nreviewed, with a maximum of 1 week.\n\nFor example, if you last reviewed a card 10 days ago but found it hard,\nthen it will be scheduled for review in 5 days.\n\n#### Good\n\nThis is for cards that you could remember well: the timing since the last\nreview was good - it was not too hard and not too easy. The card will be\nscheduled for review after a longer interval.\n\nFor new cards (scheduled interval is less than config.learningThreshold),\nthe previous schduled interval is used to calculate the new interval. For\nlearning and mature cards, the actual interval since last review is used.\nThis only makes a difference if there is a delay from scheduled review to\nactual review. The principle is, if you still remembered it well after a\nlonger interval than scheduled, then use that longer interval to calculate\nthe next interval.\n\nThe new interval is the greater of:\n * config.goodMinInterval\n * the previous interval multiplied by config.goodMinFactor\n * the previous interval multiplied by the product of\nconfig.goodFactor and the card ease factor.\n\nThe card ease factor depends on how easy the card is. It accommodates the\nfact that some cards are easier than others and that the ease of a card\nvaries with time. It is an exponentially weighted moving average of\nresponse values.\n\n##### Card Ease\n\nThe card ease factor is an exponentially weighted moving average of the\nweighted response values for the card. The weights are 0, 1, 2 and 4 for\nFail, Hard, Good and Easy respectivley. The decay factor is 0.9.\n\nThe card ease factor is dominated by performance on recent reviews. Older\nreviews quickly become insignificant. But there is some 'history': the\nfactor is larger for cards that have consistently been Good or Easy and\nsmaller for cards that have consistently been Fail or Hard. A mix of\nreplies will result in an intermediate ease factor.\n\nThe range of the card ease factor is 0 to 4. It is a simple multiplier for the\ninterval. If you keep choosing Fail, the factor will drop to 0. If you\nconsistently click Good, the factor will rise to 2: the interval will\ndouble after each review. A mix of Good and Hard will result in a factor\nbetween 1 and 2, resulting in a slower increase in the interval.\n\nThe card ease factor begins at 0 for a new card but will quickly climb with\na few Good answers. It is expected that it will typically be between 1 and\n2 for a card past the initial learning phase.\n\nThe card ease factor is specific to the card. Each card has its own history\nof answers and its own ease factor. This addresses the issue that some\ncards are harder or easier than others. This will be reflected in\ntheir individual histories of answer and resulting ease factors. On the\nother hand, cards that were hard will generally become easy with practice\nand familiarity: they don't stay hard forever. On the other hand, a card\nthat was easy may become hard if other, similar new cards are introduced:\nit may take time to learn the subtle differences and avoid confusing them.\nThus the card ease factor is specific to each card and changes as your\nsuccess recalling the card changes. And the ease factor changes how quickly\nthe interval grows: from quite slowly (card ease factor around 1) to quite\nquickly (ease factors closer to 2 or more).\n\n#### Easy\n\nFor cards that you remember very well: the time since the last review was\ntoo short and you don't want to see the card again so soon.\n\nThe new interval is the time since last review multiplied by\nconfig.easyFactor (default: 1.5)  and the card factor (variable depending\non review history), with a minimum interval of 1 day and maximum of 1\nyear..\n\nFor example, if the time since the last review was 1 day and the card\nfactor was 1 then the new interval would be 1.5 days. This would be true\neven if the previously scheduled interval was less than 1 day.\n\n### Determining which card is to be studied next\n\nWhen you study cards, the scheduler determines which card is to be studied\nnext. There are two possiblities:\n\n 1. a new card: a card that has not been studied previously\n 2. a card that has been studied previously and is now due for review\n\n### New Cards\n\nCards that have never been studied are 'unseen' cards. But eventually they\nare selected to be presented for study: they become a 'new' card.\n\nThe scheduler presents new cards to keep daily total study time\napproximately at config.targetStudyTime.\n\nThe new cards are presented at intervals, mixed in with review cards.\n\nNew cards are not presented if:\n * total study time in the past 24 hours is more than the target\n * estimated study time to review all cards due in the next 24 hours is\n   more than the target\n * there are overdue cards\n * config.maxNewCardsPerDay new cards have been seen in the past 24 hours\n\nNew cards start with a minimal interval. If they remain hard, the interval\nwill continue minimal, until you have reviewed the card enough times to\nbegin to recall it. Once you start clicking Good or Easy, the card's ease\nfactor will increase and the rate of increase of the interval will increase\naccordingly. If the interval doubles at each review, with a series of Good\nresponses, it will soon be many days between reviews. If the card is easy\nand the reviews become tedious, an answer of Easy will increase the\ninterval more quickly, with a minimum of at least 1 day, and faster\nincreases going forward.\n\n### Review Backlog\n\nIt is normal to have a small backlog: a set of cards past due to be\nstudied. The only way to avoid it is to be studying all day: studying each\ncard as soon as it is due. But this is impractical.\n\nIf you have one study period each day (a perfectly reasonably schedule)\nthen you will start each day with a small backlog: cards that came due\nsince you finished studying the previous day.\n\nIf you take a vaction (lucky you!), are busy with exams, work or other\npriorities, you might not study for a few days or a few weeks. When you\nreturn to study, you might have a large backlog: many cards due to study.\n\nThe scheduling algorithm makes it easy to work through the backlog, whether\nit is small or large.\n\nWhen selecting a due card, the scheduler selects either cards that were due\nfirst or cards with the shortest interval. The probability of getting a\ncard that was due first is config.probabilityOldestDue which defaults to\n0.2.\n\nThe selection is slightly randomized: one of the next 5 cards due is\nreturned.\n\nIf your backlog is too large, you may not be able to review all due cards\nin a day. It may take you several days to clear the backlog. The scheduler\nallows you to cope with this, providing a balance between studying the most\noverdue cards and studying cards with the shortest intervals on time.\n\nWhile you have a backlog (cards that were due more than 24 hours ago) no\nnew cards will be shown. There is no value in making the backlog larger.\nYou can return to learning new cards after you clear your backlog.\n\nYou can override the scheduler and study a new card at any time by clicking\nthe New Card button on the home page.\n\n#### Overdue cards\nIf a card is more than 24 hours past its due time, it is deemed to be\n`overdue`. When there are overdue cards, no new cards will be presented.\n\n### Scheduler API\n\nThe scheduler must provide the following methods:\n\n * getCountCardsDueToday\n * getIntervals\n * getNewCardMode\n * getNextCard\n * getNextDue\n * getNextNew\n * getStatsNext24Hours\n * getTimeNextDue\n * review\n\n#### getCountCardsDueToday\n\nReturns the number of cards that are due before the end of the day in local\ntimezone.\n\n#### getIntervals(card)\n\nTakes a card object as argument.\n\nReturns an object with attributes for each ease: fail, hard, good and easy,\nthe values being the new interval for the given card if that ease is\nselected.\n\n#### getNewCardMode\n\nReturns the mode: 'go', 'slow' or 'stop', for presenting new cards. Mode\n`go` is the most aggressive: new cards are presented if there is no due\ncard, or at intervals if there are due cards. Mode 'slow' presents new\ncards only at intervals between due cards. Mode 'stop' indicates that new\ncards will not be presented. See the scheduler documentation for details of\nwhen these modes pertain.\n\n#### getNextCard\n\nReturns a card object or undefined if there is no card that should be\npresented. The card may be a due card or a new card. See the scheduler\ndocumentation for details of when new or due cards are presented.\n\n#### getNextDue(override)\n\nTakes a boolean which can be set to true to override limits on presenting\ndue cards.\n\nReturns a card object or undefined if there is no due card that should be\npresented.\n\nSee the scheduler documentation of details of when cards are due and how\nthe card to be presented is selected, if there are multiple cards due.\n\n#### getNextNew()\n\nReturns a card object of undefined if there are no more new cards.\n\nSee the scheduler documentation for details of how the unseen cards are\nsorted to select the next new card to be presented.\n\n#### getStatsNext24Hours\n\nReturns an objects with properties:\n \n * count: the number of cards due in the next 24 hours\n * time: extimated time to review these cards, based on recent performance\n * minReviews: minimum reviews between new cards in 'slow' mode\n * reviews: number of reviews since the last new card\n\n#### getTimeNextDue()\n\nReturns the due time (seconds since the epoch) of the card with the\nearliest due time.\n\n#### review(card, viewTime, studyTime, ease)\n\nUpdates the given card and produces a revlog record according to viewTime\n(the time the card was presented to the user), studyTime (the time to be\ncounted towards total study time) and the ease of the card.\n\nThis is the functional bit of the scheduler: this is where cards are\nscheduled for review.\n\n## Templates\n\nCard templates are rendered with\n[Mustache.js](https://github.com/janl/mustache.js), with a custom `escape`\nfunction to:\n\n * render `[sound:\u003cmedia filename\u003e]` as an audio tag\n * pass all other text unaltered (i.e. HTML is NOT escaped)\n\nThe templates are rendered with the note fields as context.\n\nThis is compatible with\n[Anki templates](https://docs.ankiweb.net/templates/intro.html)\nfor simple templates but only a small subset of Anki template features are\nsupported.\n\nNote: because field values are rendered without escaping, malicious decks\nmay inject arbitrary content into the browser. Review the content of decks\ncarefully before importing them.\n\nPage templates are rendered with [handlebars](https://handlebarsjs.com/). \nI might have used handlebars throughout but I have some templates with\nspaces in the key names (e.g. `{{Some Key}}`): mustache supports this but\nhandlebars does not. It isn't obvious that this is an intentional feature\nof mustache. I don't see an explicit test to ensure it works.\n\n### Anki features not supported\n\n#### Text to Speech\n\nIn Anki 2.1.20 or later on Windows, MacOS or iOS, or Linux with an add-on,\na field of the from `{{tts en_US:Front}}` will read the Front field in a\nU.S. English voice.\n\nA field of the from {{tts-voices:}} will produce a list of all supported\nvoices. And various options can be given to specify the voice, speed, etc.\n\nNone of these are supported in srf.\n\n#### Special Fields\n\nThe special fields `{{tags}}`, `{{Type}}`, `{{Deck}}`, `{{Subdeck}}`,\n`{{Card}}` are not supported in srf.\n\nThe special field `{{FrontSide}}` is supported in srf.\n\n#### Hint Fields\n\nFields of the form `{{hint:MyField}}` are not supported in srf.\n\n#### HTML Stripping\n\nFields of the form `{{text:Expression}}` to strip HTML from the value are\nnot supported in srf.\n\n#### Checking Your Answer\n\nFields of the form `{{type:Foreign Word}}` are not supported in srf.\n\n#### Conditional Replacement\n\nSections bracketed by `{{#FieldName}}` and `{{/FieldName}}` do not work in\nsrf as in Anki. \n\nIn srf, the section will not be displayed if the field value is false or an\nempty list. If the value is a non-empty list, the section will be displayed\nonce for each value in the list, with the list element as context for\ninterpreting the field reference (i.e. the value should be a hash/object\nwith an attribute matching the field reference). If the value is not a list\nand not false, the block will be rendered once with the value as context.\n\n#### Cloze Templates\nCloze templates are not supported in srf.\n\n### Anki implementation\n\nIn Anki, the implementation of template rendering is split between python\nand rust. While the form of the templates appears to be much like Mustache,\nand there is at least one comment in the code that refers to the `{{` and\n`}}` as mustaches, there does not appear to be an implementation of the\nmustache templates included in the code, so the implementation is all\nlocal. The rust code has comments that mention 'handlebars' but appears not\nto use a handlebars package (crate or whatever the rust incantation is).\n\n[Anki Scripting: Automate your flashcards](https://www.juliensobczak.com/write/2016/12/26/anki-scripting.html) is from 2016.\nIt doesn't mention the version of Anki investigated, but it is quite\ndifferent from Anki in 2021. Correlating with tags in the Anki git\nrepository, it would have been Anki 2.0.35 or earlier. There was a long\nbreak from 2.0.35 (2016) to the next tag 2.1.0 (2018). None the less, there\nis some good information here, including the comment: 'Anki uses a modified\nversion of Pystache to provide Mustache-like syntax.' Indeed, Anki 2.0.34\nincluded a copy of pystache, with files not modified for 7 to 9 years.\n\nAnki 2.1.0 updated the pystache readme to:\n\n```\nAnki uses a modified version of Pystache to provide Mustache-like syntax.\nBehaviour is a little different from standard Mustache:\n\n- {{text}} returns text verbatim with no HTML escaping\n- {{{text}}} does the same and exists for backwards compatibility\n- partial rendering is disabled for security reasons\n- certain keywords like 'cloze' are treated specially\n```\n\nThis persisted until Anki 2.0.52 (8 Mar 2020) but was removed from Anki\n2.1.21 (9 Mar 2020), which is about when I started using Anki. My oldest\narchive of the source is 2.1.16, Dec 22, 2019. 2.1 tags before 2.1.21 have\nbeen removed, it seems. Anyway, the template folder was still present in\n2.1.16 but gone by 2.1.28, my next archive. This was the beginning of the\npervasive changes, including the move to rust and fragmentation of the\nimplementations of various features that put me off developing Anki addons\nor trying to improve the Anki scheduler. Quite an aside to templating, but\ntemplate rendering is just one more feature that is now fragmented between\npython and rust code. And it seems no longer using anything like an\nexternal implementation of mustache: the current implementation maintains\nsome of the syntax and semantics of mustache but the implementation is all\nlocal to Anki, since about Anki version 2.1.28.\n\n\n## srf database\n\nThe database is [sqlite3](https://www.sqlite.org/index.html).\n\nThere are six tables:\n * card\n * config\n * dailystats\n * fieldset\n * revlog\n * template\n\n### card\nA card is the unit of study.\n\nA card associates a fieldset (a set of field name/value pairs) with a\ntemplate (mustache templates for the front and back of the card, and\nassociated CSS) and a set of attributes used for scheduling the card.\n\nThe tuple (fieldsetid, templateid) should be unique. A unique index ensures\nthis.\n\nFields:\n * id\n * fieldsetid\n * templateid\n * modified\n * interval\n * lastinterval\n * due\n * factor\n * views\n * lapses\n * ord\n\n#### modified\nThe time (seconds since the epoch) when the card was last modified. Not\nused in scheduling. This is present for historical reasons - because it was\nkept in Anki.\n\n#### interval\nThis is the current scheduling interval: the time (seconds) from one review\nto the next. When a card is reviewed, a new interval is determined, based\non how the card was rated (again, hard, good or easy) and the new interval\nis used to determine when the card is next due for review.\n\nThis is adjusted according to the difference between recent percent correct\nanswers and the target for percent correct. It is a factor in calculation\nof the next interval for the card.\n\n#### lastinterval\nThis field is badly named. It is the interval set when the card was\nreviewed, not adjusted in response to 'percent correct'. Better names would\nbe unmodifiedinterval or originalinterval. Don't confuse this with\nlastinterval in revlog which is the interval from the last review.\n\nWhen a card is reviewed, lastinterval is set to interval but interval might\nbe adjusted before the card is reviewed again while lastinterval is not. It\nis always the interval when the card was last reviewed.\n\nThe lastinterval is copied to the lastinterval field of revlog.\n\n#### due\nThis is the time (seconds since the epoch) when the card is due to be\nreviewed. This is the conclusion of the scheduling algorithm.\n\n#### factor\nThis should probably be called Ease Factor. It is a factor of the\nscheduling algorithm that reflects how easy the card is to remember. It is\nan exponentially weighted moving average of the card ratings (again, hard,\ngood or easy) of past reviews.\n\n#### views\nThis is a count of the number of times the card has been viewed. Not used\nin scheduling.\n\n#### lapses\nThis is a count of how many times the card interval has changed from a\nvalue greater than the (arbitrary) 'mature threshold' to a value below it.\nNot used in scheduling.\n\n#### ord\nThis is used to sort new cards. When a new card is to be presented, the\ncard with the lowest ord value is presented. If there are multiple, the\nselection is whichever the database driver returns first.\n\n### config\nThis table is a set of name/value pairs with their modification times\n(seconds since the epoch).\n\nThere are only two parameters: the database schema version and the 'correct\nfactor' used in scheduling.\n\n### dailystats\n\nThis is used to keep daily statistics, derived from revlog (see below). \n\nThe fields / statistics are:\n * date\n * cardviews - total number of reviews\n * studytime - total minutes of study\n * newcards - the number of new cards reviewed\n * percentcorrect - the percent of 'correct' reviews\n\nThis table is updated after each review.\n\nMissing historic data is added (e.g. after several days without study or if\nthe dailystats table has been cleared) but there may be errors in the data\nproduced for historic dates because the record of past data and changes is\nincomplete. In particular, there is no record of when cards are reset (i.e.\ninterval reset to 0) or deleted (e.g. when fieldsets or templatesets are\nedited). Therefore, it is not possible to determine what cards existed or\nwere in progress of being studied on a given day. All that is known is:\nwhat cards exist and have an interval \u003e 0 currently and what cards were\nstudied on a given day. This will not be problematic unless the dailystats\ntable is cleared and all data is generated again from the revlog. In this\ncase, counts of mature cards will tend to be a bit lower than actual for\nhistoric dates, due to subsequent card deletions and resets. Therefore, it\nis best not to clear the dailystats table.\n\n### fieldset\nA fieldset associates a set of field values with a templateset.\n\nOne card is produced for each template in the templateset.\n\nFields:\n * id\n * guid\n * templateset\n * fields\n * ord\n\n#### guid\nA guid from Anki import, used to identify matching records if the same card\nis imported again, in which case the field values or templateset might be\ndifferent, so need to update the matching fieldset.\n\n#### templateset\nThe name of the templateset this fieldset relates to. One card will be\nproduced for each template in the templateset (i.e. for each template with\nthe same templateset value).\n\n#### fields\nThe field values as a JSON serialization. Keys are field names and values\nare field values, which may be substituted into cards according to the\ntemplates in the template table.\n\nFor example:\n\n```\n{\n  \"Audio\":\"[sound:喂你好.mp3]\",\n  \"Hanzi\":\"喂你好\",\n  \"Pinyin\":\"Wèi nǐ hǎo\",\n  \"English\":\"Hello? (on the telephone)\"\n}\n```\n\n#### ord\nFor ordering the presentation of the cards. When a card is produced from\nthe fieldset, the card's ord is set to the same as the fieldset's ord.\n\nThis only affects the order of presentation of new cards. Cards with lower\nord are presented first.\n\nOn import from Anki, this is set to 10 * index where index is the index\ninto the cards array. This should provide the same order of presentation as\nin Anki, except that each import begins with index 0 so cards will\nintermix.\n\n### revlog\nA revlog record is produced each time a card is reviewed.\n\n```\nCREATE TABLE revlog (\n  id            integer default (cast(ROUND((julianday('now') - 2440587.5)*86400000) as int)) not null,\n  revdate       text default (strftime('%Y-%m-%d','now','localtime')) not null,\n  cardid        integer not null,\n  ease          text not null,\n  interval      integer not null,\n  lastinterval  integer not null,\n  factor        real not null,\n  viewtime      integer not null,\n  studytime     integer not null,\n  lapses        integer not null\n)\n```\n\n * id: record create time, ms since epoch\n * revdate: the date, localtime, of the review\n * cardid: fk to card.id\n * ease: the ease of the review: again, hard, good or easy\n * interval: the new interval in seconds\n * lastinterval: the previous interval in seconds\n * factor: a factor for determining interval\n * viewtime: the time spent viewing the card\n * studytime: the time spent studying the card, for study time calculations\n * lapses: the number of times the card has lapsed\n\nThe revdate field is redundant with id but it reduces processing time to\nproduce statistics charts that are grouped by date, where the date is\nlocal timezone. Performing the conversion dynamically, per record, when the\ncharts are produced adds significant time to the query or processing.\n\nlastinterval is recorded to avoid having to do a lookup of the interval of\nthe previous revlog for the card. It is used in determining if the card was\nnew. It is also used in calculating percent correct.\n\n### template\nA template record associates a templateset name with a pair of mustache\ntemplates for front and back of a card and some CSS for styling the card.\n\nThe set of templates with the same templateset value constitutes a\ntemplateset.\n\nA card is linked to a template for rendering.\n\nA fieldset and template both have attribute templateset. When a fieldset is\nsaved, a card is produced for each template with matching templateset. If\nthe templateset of a fieldset is changed, all the cards related to the old\nfieldset value are deleted and new card records are produced for each\ntemplate in the new templateset.\n\n## Anki database\n\nUnderstanding details of the Anki database was essential to the initial\nimplementation of srf: it used a slightly modified Anki database. Anki\nexports include a database that is similar but simpler than the database\nused by Anki itself, but some of these details are still helpful to\ninterpreting the database in the export. The main advantage of the export\nis that all data serialization is to JSON rather than rust serde.\n\nSee [Anki 2 annotated schema](https://gist.github.com/sartak/3921255)\n\nSee [Database Structure](https://github.com/ankidroid/Anki-Android/wiki/Database-Structure), which is a little more complete/correct.\n\nThis code parses some of the blobs in the Anki database. I could not find\ndefinition of how Anki/serde does this. The code was based on inspection of\nthe blobs, trial and error. It may be that different database instances\nwill code the data somewhat differently, or the field codes will change\nwith each release of Anki. What I have here works for me, with the database\nfrom Anki 2.1.43.\n\nRelated data is stored in notes. For example, a question and answer.\n\nA note has a type, which determines what fields may be populated (e.g.\nquestion and answer, or English and Chinese and pinyin).\n\nA note type has a set of fields.\n\nA note type has a set of card types/templates. The card types have front\nand back templates.\n\nFor each note, one card is produced for each card type associated with the\nnote type of the note. The cards are associated with the note types by\nfield ord.\n\nThe CSS for the cards is stored in notetypes.config. It isn't obvious from\nthe card type editor, where it appears that each card type has front and\nback templates and 'styling'. The styling is common to all the card types\nof a given note type.\n\nThe database doesn't have a cardtypes table. Rather, the card types are\nmanifest in the templates table. Each template is linked to a notetypes by\ntemplates.ntid. The relationship is many-to-one, with the different\ntemplates distinguished by ord, which is also in cards. The specific\ntemplate for a card is determined by matching cards.ord to templates.ord.\n\nEach templates record has two templates: front and back, serialized into\ntemplates.config.\n\nEach note type is associated with a set of fields and a set of templates\n(a.k.a. card types).\n\nEach note is associated with a note type which determines the fields and\ncards associated with the note. The fields are the attributes for which\nvalues may be saved. The templates determine how these are presented to the\nuser. Each template has a front and a back layout (the 'flashcard').\n\n### Anki source files\n\n#### rslib/src/sched/cutoff.rs\n\nThis is a set of functions related to timing, including collection creation\ntime, days since creation, day rollover time, offset from UTC, etc.\n\nThis defines v1_create_date which is the last 4:00 a.m. local time before\nthe time the function was called.\n\n#### rslib/src/storage/schema11.sql\n\nThis file contains SQL code to create the initial database, identified as\nSchema version 11. When a new database is created, this is the first set of\ntables, indexes, etc. created. Run from rslib/src/storage/sqlite.rs.\n\n#### rslib/src/storage/sqlite.rs\n\nThis is code for low level database operations on the Anki database. A key\nfunction is 'open_or_create_collection_db' which initializes the database\nconnection and 'open_or_create' which opens the database and creates Anki\ntables as necessary according to the schema.\n\n#### rslib/src/storage/upgrades/mod.rs\n\nThis defines upgrade_to_latest_schema and related constants\n(SCHEMA_MIN_VERSION, SCHEMA_STARTING_VERSION and SCHEMA_MAX_VERSION) but\nnot the procedures for the actual upgrades.\n\n### Anki Database Schema\n\nAnki supports multiple database schemas, starting with schema 11.\nPresumably there were earlier schemas, but current code doesn't deal with\nthem. When a new database is created, it begins as schema 11.\n\n#### Schema 11\ntables: col, notes, cards, revlog, graves. \n\n#### Schema 14\n\nAdd tables: deck_config, config and tags\n\nDeck configuration is moved from serialized data in col.dconf to the new\ndeck_config table, one parameter per record, then col.dconf is cleared.\nNote that the configuration is still serialized data. There is one record\nper selectable deck configuration (deck type). My database has Default and\nHard.\n\nTags are moved from col.tags to the new tags table, one tag per record,\nthen col.tags is cleared.\n\nCollection configuration is moved from col.conf to the new conf table, one\nparameter per record, then col.conf is cleared.\n\n\n#### Schema 15\n\nAdd tables: fields, templates, notetypes, decks, \n\nNote types are moved from col.models to the new notetypes, fields and\ntemplates tables, then col.models is cleared.\n\nDecks are moved from col.decks to the new decks table, then col.decks is\ncleared.\n\nMultiply deck initial ease * 100.\n\n#### Schema 16\n\nDivide initial ease of decks by 100 (revert change from Schema 15)\n\nChange initial ease \u003c= 1.3 to 2.5. and update cards with low eases.\n\n\n#### col\n\nTable col contains a single record with parameters of the entire\ncollection.\n\ncreate table col\n(\n    id     integer primary key,\n    crt    integer not null,\n    mod    integer not null,\n    scm    integer not null,\n    ver    integer not null,\n    dty    integer not null,\n    usn    integer not null,\n    ls     integer not null,\n    conf   text    not null,\n    models text    not null,\n    decks  text    not null,\n    dconf  text    not null,\n    tags   text    not null\n);\n\nFields:\n * id - always 1\n * crt - collection creation time in seconds since the epoch\n * mod - modification time in milliseconds\n * scm - schema modification time\n * ver - The database schema version\n * dty - 0\n * usn - 0\n * ls  - Last sync time in milliseconds\n * conf - serialized collection configuration, before conf table, now ''\n * models - serialized note types, before notetypes, now ''\n * decks - Empty string\n * dconf - serialized deck configuration, before deck_config, now ''\n * tags - serialized tags before tags, now ''\n\nIn schema 11, crt is the last 4:00 a.m. local time before the actual\ncreation time, as seconds since the epoch.\n\nAt database creation, scm is crt * 1000: that same 4:00 a.m. local time,\nbut milliseconds since the epoch.\n\n#### notes\n\ncreate table notes\n(\n    id    integer primary key,\n    guid  text    not null,\n    mid   integer not null,\n    mod   integer not null,\n    usn   integer not null,\n    tags  text    not null,\n    flds  text    not null,\n    sfld  integer not null,\n    csum  integer not null,\n    flags integer not null,\n    data  text    not null\n);\n\nFields:\n * id - primary key\n * guid - a GUID, probably used in syncing as id can't be consistent\n * mid - fk to notetype.id\n * mod - presumably epoch seconds of last modification time\n * usn - ??? - always -1 in my database - to do with sync\n * tags - Anki can add tags to cards, maybe notes too? Don't know how to\n     tag a note Vs a card. - probably legacy\n * flds - the field data, 0x1f as separator (escape???)\n * sfld - something to do with searching and duplicates\n * csum - some sort of checksum - some sort of sha1 digest\n * flags - I guess notes can have flags too - probably legacy\n * data - always empty - probably legacy\n\nmid was model ID in Schema 11 but Schema 15 added table notetypes so now\nmid is fk to notetypes, but without a name change (it really should be\nntid, or something like that).\n\n#### cards\n\ncreate table cards\n(\n    id     integer primary key,\n    nid    integer not null,\n    did    integer not null,\n    ord    integer not null,\n    mod    integer not null,\n    usn    integer not null,\n    type   integer not null,\n    queue  integer not null,\n    due    integer not null,\n    ivl    integer not null,\n    factor integer not null,\n    reps   integer not null,\n    lapses integer not null,\n    left   integer not null,\n    odue   integer not null,\n    odid   integer not null,\n    flags  integer not null,\n    data   text    not null\n);\n\nFields:\n * id - primary key\n * nid - fk to notes\n * did - fk to decks\n * ord - order among cards linked to same note\n * mod - card modification time\n * usn - something to do with syncing in Anki\n * type - 0: New, 1: Learn, 2: Review, 3: Relearn\n * queue - the Anki queue\n * due - when the card is due seconds since epoch or day number\n * ivl - (renamed to interval) the interval between views\n * factor - the factor for increasing the interval between views\n * reps - number of times the card has been viewed\n * lapses - number of times the card was 'repeat'\n * left - something to do with (re)learn cards progress through steps\n * odue - original due, for cards temporarily in a different deck\n * odid - original deck ID, for cards temporarily in a different deck\n * flags - ???\n * data - ???\n * seen - srf: milliseconds since epoch when card was last seen\n * new_order - srf: integer for sorting new cards\n\nThe type seems to relate to how scheduling is done, at least in the version\n2 scheduler. For example, there are distinct 'schedules' for learning and\nre-learning. These schedules are selected based on type, rather than queue.\n\nIn Anki, interval progresses through a series of values configured for new\nand relearning (lapses). These series of steps are configured in deck\noptions: New Cards and Lapses tabs. A Lapsed card goes to relearn. \n\nGetting a card seems to be based on queue, rather than type. But when a\ncard is ansered, updating the interval seems to be based on type, not\nqueue. The card type is also used when creating and deleting filtered\ndecks.\n\nsrf records all times with units of milliseconds. This includes due and\ninterval. New cards have interval = 0. When the card is seen, interval is\nset to the interval from the current time to when the card will next be\ndue.\n\nThe relevant interval, when a card is seen, is the interval from when it\nwas last seen to the time it was next seen. The card was last seen at\ncards.interval milliseconds before cards.due. The cards won't be shown\nbefore they are due but they might not be seen until some time after they\nare due - maybe hours or days after.  That's OK. The actual interval is\nmore relevant than any intended, theoretically ideal interval.\n\ncards.new_order is for ordering the presentation of new cards. On import,\nit is set to whatever due was equal to. The separate field gives the\nadvantage that the new card order isn't overwritten when the card is\nviewed. The deck can be reset, including the order of new cards. But, any\ncards viewed before import cannot be restored to their original new card\norder - the information is gone.\n\nIn Anki, review and 'day' learning cards have due set to a day number, with\nday 0 being the collection creation day, which is stored in the col table.\nIn srf, day numbers aren't used. Due is always milliseconds since the\nepoch.\n\n##### type\ntype is an integer.\n\ntype can be:\n * 0=new,:\n * 1=lrn,\n * 2=rev,\n * 3=relrn.\n\nFrom consts.py:\n```\n# Card types\nCARD_TYPE_NEW = 0\nCARD_TYPE_LRN = 1\nCARD_TYPE_REV = 2\nCARD_TYPE_RELEARNING = 3\n```\n\nThe card type determines aspects of how the cards are scheduled.\n\nThe card type is not completely orthogonal to the card queue. The\nrelationship is obscure. Several of the decisions about scheduling are\nbased on the queue, rather than the type. But others are based on type.\n\nWhen a card is answered, the first decision: to treat it as a learning card\nor a review card, is based on queue. Many subsequent decisions are based on\ntype.\n\nThere is different configuration for learning and relearning cares. In deck\nconfiguration, these are on tabs New Cards and Lapses. Each case has\ndifferent sequence of steps. New cards have two settings for initial\ninterval on graduation to review: Graduating interval and Easy interval.\nRelearning cards (lapses) only have New interval, which is a percentage of\nthe previous interval.\n\nIf queue = `QUEUE_TYPE_LRN` or `QUEUE_TYPE_DAY_LEARN_RELEARN`, card is\nrescheduled as a learning card: new or lapse according to type:\n`CARD_TYPE_REV` and `CARD_TYPE_RELEARNING` are relearning, otherwise learning.\nRelearning cards use settings from Lapse while learning cards use settings\nfrom New Cards.\n\nIf type is `CARD_TYPE_NEW`, the card hasn't been seen before. On first view,\nit is changed to `CARD_TYPE_LRN` and learning steps are initialized to the\nfirst step from New cards configuration. This includes setting left.\n\nIf type is `CARD_TYPE_REV` or `CARD_TYPE_RELEARNING`, the card has lapsed.\nLearning steps are from the Lapses configuration.\n\nIf type is `CARD_TYPE_LRN`, it progresses through the learning steps on Good,\nuntil the last step, after which it is changes to `CARD_TYPE_REV` and ivl is\nset to the Graduating interval. But on Easy, remaining learning steps are\nskipped, type is changes to `CARD_TYPE_REV` and ivl is set to Easy\nInterval.\n\n##### queue\nqueue is an integer.\n\nqueue can be:\n * 0=new,\n * 1=(re)lrn,\n * 2=rev,\n * 3=day (re)lrn,\n * 4=preview,\n * -1=suspended,\n * -2=sibling buried,\n * -3=manually buried.\n\nFrom consts.py:\n```\n# Queue types\nQUEUE_TYPE_MANUALLY_BURIED = -3\nQUEUE_TYPE_SIBLING_BURIED = -2\nQUEUE_TYPE_SUSPENDED = -1\nQUEUE_TYPE_NEW = 0\nQUEUE_TYPE_LRN = 1\nQUEUE_TYPE_REV = 2\nQUEUE_TYPE_DAY_LEARN_RELEARN = 3\nQUEUE_TYPE_PREVIEW = 4\n```\nIf queue is 1, due is epoch seconds and within the current day.\n\nIf queue is 3, due is day number, counting from collection creation day.\nThis queue is used when the next (re)learning step is past the end of the\nday, in which case there is no provision for scheduling a time within the\nday. If one is studying near the end of the day, the next step may be one\nsecond beyon the end of the day or one second less than 24 hours beyond the\nend of the day and both cases are treated the same: the queue is set to 3\nand card becomes due the next day. For example, the next steap may be 5\nminutes (in the progression [1, 2, 5, 8, 10, 20] but if 5 minutes from now\nis past the end of the day, due becomes 'next day'.\n\n\n##### due\ndue is an integer.\n\nIf type is 0 (new) then due is the note ID or some other value used to sort\nthe new cards to order their presentation.\n\nIf type is 1 (learning) or 3 (relearning) then due is either epoch seconds\nor day number according to queue = 1 (learning) or 3 (day learning)\nrespectively.\n\nIf type is 2 (review) then due is day number from collection creation day.\n\n##### ivl\nivl is an integer.\n\nivl is the number of days between card reviews.\n\nivl is only relevant to review cards, which are scheduled according to the\nSRS scheduling algorithm. Learning and relearning cards are scheduled\naccording to the steps in deck configuration for new and lapses: ivl is\nirrelevant.\n\nFor a card that has never been reviewed (i.e. new or learning), ivl will be\n0.\n\nOnce a card has progressed to the review queue, ivl will be set to the\ninterval in days. If it lapses, ivl is reduced by a configurable amount so\nthat after completing the lapse/relearning steps, it graduates with a\nlesser interval. On graduation, the interval is what it was set to on\nlapse, or one more than this if it graduated early by 'Easy'.\n\nInitially, ivl is 0. It is set to the configurable graduating interval or\nEasy interval when a card graduates from learning to review, according to\nwhether ease was Good or Easy. By default, Graduating interval is 1 day and\nEasy interval is 4 days.\n\n##### left\nleft is an integer value.\n\nIf type is 1 (CARD_TYPE_LRN) then left is initially set to the number of\nlearning steps (per deck configuration for new cards) + 1000 * the number\nof steps that could be completed before the end of the day. The latter\ndepends on the current time, when the end of the day is and the size of\neach step.\n\nleft = X + Y * 1000, where X is the number of learning steps still to be\ncompleted (i.e. it is an index into the array of learning steps, but from\nthe end of the array instead of the beginning) and Y is the number of steps\nthat can be completed in the same day, at the time the card was scheduled,\ngiven current time, time of end of day and the size of the remaining steps\nto be completed.\n\nAs the card progresses through the learning steps, X is decremented and Y\nis updated according to the time and step sizes.\n\n\n\n\n\n#### revlog\n\ncreate table revlog\n(\n    id      integer primary key,\n    cid     integer not null,\n    usn     integer not null,\n    ease    integer not null,\n    ivl     integer not null,\n    lastIvl integer not null,\n    factor  integer not null,\n    time    integer not null,\n    type    integer not null\n);\n\n#### id\ninteger\n\nepoch milliseconds.\n\n##### cid\ninteger\n\nfk to cards.id\n\n##### usn\ninteger\n\nSomething to do with syncing. -1 by default.\n\n\n##### ease\ninteger\n\nThe button that was clicked on review 1, 2, 3 or 4 for Fail, Hard, Good or\nEasy.\n\n##### ivl\ninteger\n\nNegative value is seconds. Positive value is days since collection\ncreation day.\n\n##### factor\ninteger\n\nThe factor for adjusting interval. 0 for non-review cards. This is 1000\ntimes that multiplier (i.e. the new interval is the old interval * factor /\n1000)\n\n##### time\ninteger\n\nMilliseconds spent viewing the card.\n\n##### type\ninteger\n\nAlways 0???\n\n#### graves\n\ncreate table graves\n(\n    usn  integer not null,\n    oid  integer not null,\n    type integer not null\n);\n\n\n\n\n#### fields\n\nCREATE TABLE \"fields\" (\n\t\"ntid\"\tinteger NOT NULL,\n\t\"ord\"\tinteger NOT NULL,\n\t\"name\"\ttext NOT NULL,\n\t\"config\"\tblob NOT NULL,\n\tPRIMARY KEY(\"ntid\",\"ord\")\n) WITHOUT ROWID;\n\nntid: fk to notetypes\nord: ordinal for sorting fields\nname: the field name\nconfig: not used in srf. \n\nThe config field holds a serialized data structure (rust/serde) that\nappears to relate to the Anki field content editor: sticky, rtl\n(right-to-left), font_name, font_size and 'other' for a JSON string, I\nthink.\n\nFrom rslib/backend.proto:\n\n```\nmessage NoteFieldConfig {\n    bool sticky = 1;\n    bool rtl = 2;\n    string font_name = 3;\n    uint32 font_size = 4;\n\n    bytes other = 255;\n}\n```\n\nIf sticky is true, then the input is not cleared on save when entering\nnotes. Default is false.\n\nIf rtl is true then the field text is right-to-left. Default is false.\n\nFont name and size are for the Anki note editor. This doesn't affect\ndisplay of the note/cards. Default is Arial, 20.\n\nOther is unknown. All I see is `{\"media\":[]}`.\n\nsrf uses ntid, ord and name, at least until I add a card/note editor.\n\nNote that this table only holds field names and sort order. The values\nare in the notes table: notes.flds, with all field values serialized into\nthe single field.\n\n#### notetypes\n\n * id - primary key\n * name - text name of notetype\n * mtime_secs - modification time\n * usn - ???\n * config - serialized data structure\n\nNote types are identified by ID. Fields and notes are linked to this ID.\n\nmtime_secs is used in Anki sync, to find note types modified since last\nsync, presumably. I haven't looked into sync.\n\nusn is used in Anki sync.\n\nconfig - this is a rust/serde serialized data structure. See importdb.js\n\nconfig:\n * kind: 0 - Normal, 1 - Cloze\n * sort_field_idx: \n * css: the css for the notes\n * target_deck_id: ???\n * latex_pre\n * latex_post\n * latex_svg\n * reqs: an array ???\n * other: arbitrary JSON serialized data\n\nThe css for the cards is here. This makes it common to all the templates\nrelated to the note type. In srf, the css is extracted and added to the\ntemplates table. In srf, each template has its own CSS.\n\nAnki has support for using LaTeX to format cards. The latex_\\* fields relate\nto this.\n\n#### templates\n\nCREATE TABLE \"templates\" (\n\t\"ntid\"\tinteger NOT NULL,\n\t\"ord\"\tinteger NOT NULL,\n\t\"name\"\ttext NOT NULL,\n\t\"mtime_secs\"\tinteger NOT NULL,\n\t\"usn\"\tinteger NOT NULL,\n\t\"config\"\tblob NOT NULL,\n\t\"front\"\ttext NOT NULL DEFAULT '',\n\t\"back\"\ttext NOT NULL DEFAULT '',\n\t\"css\"\ttext NOT NULL DEFAULT '',\n\tPRIMARY KEY(\"ntid\",\"ord\")\n) WITHOUT ROWID;\n\nI added front, back and css. In Anki the HTML for front and back are\nserialized into templates.config and the css is serialized into\nnotetype.config. The downside of this being that the CSS is common to all\ncard types associated with a given note type. Not a big issue, but with the\nCSS moved to the templates table, each template can have its own CSS.\n\nFrom templates.rs:\n\n```\n        CardTemplate {\n            ord: None,\n            name: name.into(),\n            mtime_secs: TimestampSecs(0),\n            usn: Usn(0),\n            config: CardTemplateConfig {\n                q_format: qfmt.into(),\n                a_format: afmt.into(),\n                q_format_browser: \"\".into(),\n                a_format_browser: \"\".into(),\n                target_deck_id: 0,\n                browser_font_name: \"\".into(),\n                browser_font_size: 0,\n                other: vec![],\n            },\n        }\n```\n\n\n\n## Anki apkg files\n\nThese files contain the transferrable state of an Anki collection: a set of\ndecks containing templates, notes, cards and revision logs.\n\nOne could examine the [Anki source\ncode](https://github.com/ankitects/anki/) to determine what is in an export\nfrom this source but the code is complex with multiple languages and tools\nto interface them that obscure the interactions unless is familiar with\nthem.\n\nI haven't reviewed the Anki source code for a long time. Here are merely my\nnotes and observations, not rooted in an understanding of the Anki source\ncode.\n\nThe zip file contains:\n * one or more sqlite database files, named `collection` with different\n   extensions depening on version (see below).\n * media files\n\n### media files\n\nMedia files (audio, images, etc.) are named numerically, from 0.\n\nThere is a file named `media` which contains JSON serialization of a map of\nthese numeric filenames to filenames.\n\nFor example:\n```\n{\n  \"0\": \"audio1-2exercise3.mp3\",\n  \"1\": \"audio1-1exercise2.mp3\",\n  \"2\": \"audio1-2exercise4.mp3\"\n}\n```\n\nThere is no record of mime type. The file type can be deduced from the\nfilename extension or by inspection of the file data.\n\n### collection database\n\nThe zip file will contain one or more sqlite database files, all named\n`collection` but with different extensions for different versions of Anki.\n\nThere is often only one sqlite databases but occasionally there are\nmultiple, allowing the collection to be imported to multiple versions of\nAnki.\n\n#### collection.sqlite\n\nThis is for versions of Anki before 2.0. I have not seen an example of\nthis.\n\nI haven't searched exhaustively but all the shared decks on\n[ankiweb](https://ankiweb.net/shared/decks/) have `collection.anki2` or\n`collection.anki21` - even those with modification dates back to 2012,\nwhich seems to be about the oldest.\n\nThe old format isn't important. Anki 2.0 has been around since at least\n2013. The oldest tag on the\n[Anki repository](https://github.com/ankitects/anki/tags?after=2.0.9) is\n2.0.4 from 8 Jan 2013.\n\n#### collection.anki2 and collection.anki21\n\nThe database schema is the same for these, so the difference must be in the\ncontained values or their interpretation.\n\nThis is for versions of Anki from 2.0 but less than 2.1.\n\nTables:\n * cards\n * col\n * graves\n * notes\n * revlog\n * sqlite_stat1\n * sqlite_stat4\n\n##### cards\n\n```\nsqlite\u003e .schema cards\nCREATE TABLE cards (\n  id integer PRIMARY KEY,\n  nid integer NOT NULL,\n  did integer NOT NULL,\n  ord integer NOT NULL,\n  mod integer NOT NULL,\n  usn integer NOT NULL,\n  type integer NOT NULL,\n  queue integer NOT NULL,\n  due integer NOT NULL,\n  ivl integer NOT NULL,\n  factor integer NOT NULL,\n  reps integer NOT NULL,\n  lapses integer NOT NULL,\n  left integer NOT NULL,\n  odue integer NOT NULL,\n  odid integer NOT NULL,\n  flags integer NOT NULL,\n  data text NOT NULL\n);\nCREATE INDEX ix_cards_usn ON cards (usn);\nCREATE INDEX ix_cards_nid ON cards (nid);\nCREATE INDEX ix_cards_sched ON cards (did, queue, due);\n```\n\n##### col\n\n```\nsqlite\u003e .schema col\nCREATE TABLE col (\n  id integer PRIMARY KEY,\n  crt integer NOT NULL,\n  mod integer NOT NULL,\n  scm integer NOT NULL,\n  ver integer NOT NULL,\n  dty integer NOT NULL,\n  usn integer NOT NULL,\n  ls integer NOT NULL,\n  conf text NOT NULL,\n  models text NOT NULL,\n  decks text NOT NULL,\n  dconf text NOT NULL,\n  tags text NOT NULL\n);\n```\n\n * id: there is only one record, with ID = 1\n * crt: record creation time\n * mod: record modification time\n * scm: schema modification time - maybe the database schema\n * ver: version - maybe the database schema version\n * dty: dirty\n   [ankisyn2](https://github.com/patarapolw/ankisync2/blob/master/ankisync2/anki21/db.py)\n   says this is unused.\n * usn: update sequence number - relates to syncing.\n * ls: last sync time\n * conf: JSON serialization of configuration options that are synced\n * models: JSON serialization of the models\n * decks: JSON serialization of the deck(s)\n * dconf: JSON serialization of deck options\n * tags: tags used in the collection\n\n###### conf\nBasic Anki configuration option settins.\n\nAn example from a collection.anki21 database\n```\n{\n  \"timeLim\":0,\n  \"nextPos\":9401,\n  \"dueCounts\":true,\n  \"sortType\":\"noteFld\",\n  \"localOffset\":-720,\n  \"schedVer\":2,\n  \"estTimes\":true,\n  \"sortBackwards\":false,\n  \"newSpread\":0,\n  \"dayLearnFirst\":false,\n  \"rollover\":4,\n  \"collapseTime\":1200,\n  \"creationOffset\":-720,\n  \"addToCur\":true,\n  \"activeDecks\":[1629058964075],\n  \"curDeck\":1629058964075,\n  \"curModel\":1409030500500\n}\n```\n\nAn example from a collection.anki2 database (sorted for consistency with above)\n```\n{\n  \"timeLim\":0,\n  \"nextPos\":2,\n  \"dueCounts\":true,\n  \"sortType\":\"noteFld\",\n  \"localOffset\":-720,\n  \"schedVer\":1,\n  \"estTimes\":true,\n  \"sortBackwards\":false,\n  \"newSpread\":0,\n  \"dayLearnFirst\":false,\n  \"collapseTime\":1200,\n  \"addToCur\":true,\n  \"activeDecks\":[1],\n  \"curDeck\":1,\n  \"curModel\":1629059140118,\n}\n```\n\nSo, anki21 has these additional values:\n * rollover\n * creationOffset\n\nFrom my recollection of early work with Anki 2.0 then Anki 2.1, where I had problems with the buggy timezone handling and rollover from day to day, I think these\nare:\n\nrollover: The time of day, local time zone, when the Anki 'day' rolls over. Anki keeps track of longer times in terms of days, not seconds or millis","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fig3%2Fsrf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fig3%2Fsrf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fig3%2Fsrf/lists"}