{"id":15945724,"url":"https://github.com/ig3/srf-scheduler","last_synced_at":"2026-02-03T01:37:59.315Z","repository":{"id":198753818,"uuid":"701501238","full_name":"ig3/srf-scheduler","owner":"ig3","description":"Default scheduler for srf - spaced repetition flashcards","archived":false,"fork":false,"pushed_at":"2025-06-14T20:31:51.000Z","size":597,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-21T04:43:40.550Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"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":null,"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":"2023-10-06T19:19:21.000Z","updated_at":"2025-06-14T20:31:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"61216c3f-5798-4f93-adad-b317901b1d1c","html_url":"https://github.com/ig3/srf-scheduler","commit_stats":null,"previous_names":["ig3/srf-scheduler"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ig3/srf-scheduler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ig3%2Fsrf-scheduler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ig3%2Fsrf-scheduler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ig3%2Fsrf-scheduler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ig3%2Fsrf-scheduler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ig3","download_url":"https://codeload.github.com/ig3/srf-scheduler/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ig3%2Fsrf-scheduler/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29026411,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T00:53:18.321Z","status":"ssl_error","status_checked_at":"2026-02-03T00:51:45.186Z","response_time":58,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2024-10-07T09:05:50.978Z","updated_at":"2026-02-03T01:37:59.309Z","avatar_url":"https://github.com/ig3.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @ig3/srf-scheduler\n\nThis is the default scheduler for\n[srf](https://www.npmjs.com/package/@ig3/srf) - spaced repetition\nflashcards.\n\nIt's primary functions are:\n * selecting a card for review\n * re-scheduling a card after review\n\nIt controls two aspects of study:\n * average study time per day\n * percentage of cards successfully recalled\n\n## installation\n\n```\n$ npm install @ig3/srf-scheduler\n```\n\nIf you install [srf](https://www.npmjs.com/package/@ig3/srf), this\nscheduler will be installed as one of its dependencies. There should be no\nneed to install this independently.\n\n## Algorithms\n\n### new cards\n\nAverage study time per day is controlled by adjusting the presentation of\nnew cards. New cards are usually presented interleaved with review cards.\nIf average study time is very low, they may be presented without\nintervening review cards. If average study time is high, new cards will not\nbe presented.\n\nNew cards are presented if:\n * average study time \u003c configured target study time;\n * new cards in the past 24 hours \u003c configured max new cards per day; and\n * there are no overdue cards\n\nIf average study time is less than configured minimum study time, then new\ncards are presented if there are no cards due, until the daily limit on new\ncards is reached.\n\nFor these controls, average study time is determined from:\n * actual study time in the past 24 hours\n * predicted study time in the next 24 hours\n * actual average study time in the past 14 days of study\n\nThe number of reviews between new cards is adjusted according to the ratio\nof average study time to target study time per day, the number of cards due\nin the next 24 hours and the recent average number of new cards per day.\n\nFor this control, average study time is the actual average study time over\nthe previous 14 days of study. This excludes the current day and days on\nwhich no reviews were done.\n\n### review card selection\n\nEvery card that has been viewed has a time when it is scheduled to be\nreviewed.\n\nIf there is more than one card past its scheduled review time, then one of\ntwo algorithms is used to select the next card for review:\n * shortest interval first\n * earliest due first\n\nThe algorithm is selected randomly, with probability of the earliest due\ncards being selected first determined by configuration parameter\nprobabilityOldestDue.\n\nFor both algorithms, the first five cards are determined and one of these\nis selected at random. \n\n### interval adjustments\n\nInterval is the interval between card reviews. The interval of a card is\nupdated each time it is reviewed, according to its 'ease' (Fail, Hard,\nGood or Easy). Interval (and due date) are also adjusted after any card\nwith an interval greater than learningThreshold is reviewed, according to\nthe difference between 'percent correct' and percentCorrectTarget.\n\n#### Fail\n\nIf the ease of a review is Fail, then the new interval is the previous\ninterval multiplied by failFactor, with an upper bound of\nfailLearningMaxInterval if the previous interval was less than\nlearningThreshold or failMaxInterval otherwise.\n\n#### Hard\n\nIf the ease of a review is Hard, then the new interval is the previous\ninterval multiplied by hardFactor, with an upper bound of\nhardLearningMaxInterval if the previous interval was less than\nlearningThreshold or hardMaxInterval otherwise.\n\n#### Good\n\nIf the ease of a review is Good, then the new interval is the greater of\n * goodMinInterval\n * recent average interval multiplied by goodMinFactor\n * recent average interval multiplied by the product of goodFactor\n   and the card factor\n\nThe new interval is limited to the lesser of maxInterval or\nmaxGoodInterval.\n\nThe recent average interval is the average of the last\nconfig.recentIntervalWindow intervals (default 3). For the most recent\ninterval, the actual interval is used rather than the scheduled interval.\nThe actual interval will be longer than the scheduled interval when the\ncard is not reviewed immediately when due.\n\nThe card factor is the exponentially weighted moving average of ease\nweights, with a decay factor of decayFactor and ease weights of weightFail,\nweightHard, weightGood and weightEasy. This reflects how easy or difficult\nthe card has been recently.\n\n\n#### Easy\n\nIf the ease of a review is Easy, then the recent average interval\nmultiplied by the easyFactor and the card factor, with a minimum of\neasyMinInterval and maximum of the lower of maxEasyInterval and maxInteral.\n\nThe recent average interval is the same as for ease Good.\n\n### Percent Correct\n\nWhenever a card is reviewed with a new interval greater than\nlearningThreshold, then the intervals and due dates of all cards with\ninterval between learningThreshold and maxInterval are adjusted according\nto the difference between 'percent correct' and percentCorrectTarget,\nmultiplied by percentCorrectSensitivity.\n\n\n## Configuration\n\nThe scheduler is configured by the following configuration parameters.\n\n### config.decayFactor\n\ndecayFactor determines the exponential decay of ease weight to determine\nthe card ease factor. The resulting card ease factor is one of the factors\nthat contributes the overall factor by which interval is increased for Good\nand Easy responses.\n\n### config.easyFactor\n\nThe interval for an Easy response is the interval for a Good response\nmultiplied by easyFactor.\n\n### config.easyMinInterval\n\neasyMinInterval is the minimum interval after an Easy response.\n\n### config.failFactor\n\nfailFactor is the factor by which interval is multiplied after a Fail\nresponse.\n\n### config.failLearningMaxInterval\n\nfailLearningMaxInterval is the maximum interval after a Fail response for a\ncard with interval less than learningThreshold.\n\n### config.failMaxInterval\n\nfailMaxInterval is the maximum interval after a Fail response.\n\n### config.goodFactor\n\nAfter a Good response, the interval is multiplied by goodFactor and the\ncard ease factor. The product of these is the overal factor by which\ninterval is multiplied.\n\n### config.goodMinFactor\n\ngoodMinFactor is the minimum factor by which interval is multiplied after a\nGood response. \n\n### config.goodMinInterval\n\ngoodMinInterval is the minimum interval after a Good response.\n\n### config.hardFactor\n\nhardFactor is the factor by which interval is multiplied after a Hard\nresponse.\n\n### config.hardLearningMaxInterval\n\nhardLearningMaxInterval is the maximum interval after a Hard response for a\ncard with interval less than learningThreshold.\n\n### config.hardMaxInterval\n\nhardMaxInterval is the maximum interval after a Hard response.\n\n### config.learningThreshold\n\nlearningThreshold is the threshold between cards being treated as 'new' and\n'learning' cards.\n\nCard intervals and due dates are modified periodically if their interval is\nbetween learningThreshold and maxInterval.\n\n### config.matureThreshold\n\nmatureThreshold is the threshold between cards being treaded as 'learning'\nand 'mature' cards.\n\n### config.maxEasyInterval\n\nmaxEasyInterval is an upper bound on interval after an Easy response.\n\n### config.maxGoodInterval\n\nmaxGoodInterval is an upper bound on interval after a Good response.\n\n### config.maxInterval\n\nmaxInterval is an upper bound on interval after a Good or Easy response.\n\n### config.maxNewCardsPerDay\n\nmaxNewCardsPerDay is the maximum number of new cards which may be presented\nin a 24 hour period.\n\n### config.maxViewTime\n\nmaxViewTime is an upper bound on view time recorded in revlog. It doesn't\nhave anything to do with the scheduler. It is here for historic reasons,\npending refactoring the processing of card reviews.\n\n### config.minPercentCorrectCount\n\nminPercentCorrectCount is the minimum number of reviews of mature cards in\nthe percentCorrectWindow in order that 'percent correct' is calculated. The\n'percent correct' is a factor in adjusting the intervals and due dates of\ncards.\n\n### config.minStudyTime\n\nminStudyTime is the minimum study time in a 24 hour period below which new\ncards will be presented in preference to due cards. \n\n### config.minTimeBetweenRelatedCards\n\nRelated cards are cares generated from the same field set.\nminTimeBetweenRelatedCards is the minimum time between reviews of related\ncards.\n\n### config.percentCorrectSensitivity\n\npercentCorrectSensitivity is a factor that determines the sensitivity to\nthe difference between 'percent correct' and percentCorrectTarget. It\ndetermines how much the intervals and due dates of cards are modified when\nadjusted for percent correct.\n\n### config.percentCorrectTarget\n\npercentCorrectTarget is the target for 'percent correct'. Below this,\nintervals are reduced and due dates moved closer. Above this, intervals are\nincreased and due dates moved further away.\n\n### config.percentCorrectWindow\n\npercnetCorrectWindow is the window over which 'percent correct' is\ncalculated. Reviews longer ago than this do not contribute.\n\n### config.probabilityOldestDue\n\nprobabilityOldestDue is the probability the scheduler will select cards\naccording to due date rather than interval.\n\n### config.targetStudyTime\n\ntargetStudyTime is an upper bound on study time in a 24 hour period above\nwhich new cards will not be presented.\n\n### config.weightEasy\n\nweightEasy is the weight of an Easy response in calculating the card ease\nfactor by a process of moving average exponential decay.\n\n### config.weightFail\n\nweightFail is the weight of a Fail response in calculating the card ease\nfactor by a process of moving average exponential decay.\n\n### config.weightGood\n\nweightGood is the weight of a Good response in calculating the card ease\nfactor by a process of moving average exponential decay.\n\n### config.weightHard\n\nweightHard is the weight of an Hard response in calculating the card ease\nfactor by a process of moving average exponential decay.\n\n## API\n\n### getCountCardsDueToday\n\nReturns the number of cards to be reviewed between now and the end of the\ncurrent day in localtime, excluding deferred cards.\n\n### getIntervals(card)\n\nReturns an object with the new interval for each possible ease for the\ngiven card.\n\n### getNewCardMode()\n\nReturns 'go', 'slow' or 'stop'.\n\nGo if new cards will be presented when there are no cards due.\n\nSlow if new cards will be presented, interleaved with due cards but not if\nthere are no cards due.\n\nStop if new cards will not be presented.\n\n### getNextCard(overrideLimits)\n\nReturns the next card to be studied or undefined.\n\nIf overrideLimits is true then getNextCard returns the next due card if\nthere is a card due, otherwise the next new card.\n\nOtherwise, getNextCard returns the next new card if:\n * average study time in the past and next 24 hours is less than\n   config.targetStudyTime; and\n * total new cards studied in the past 24 hours is less than\n   config.maxNewCardsPerDay; and\n * there are no overdue cards; and\n * sufficient due cards have been reviewed since the last new card or there\n   is no due card and average study time is less than\n   config.minStudyTime\n\nOtherwise, getNextCard returns the next due card if there is a card due.\n\nOtherwise, getNextCard returns undefined.\n\n### getNextDue(overrideLimits)\n\ngetNextDueCard returns a card if one is due, otherwise undefined.\n\nIf overrideLimits is true then getNextDueCard one of the five cards with\nthe earliest due dates, selected ramdomly, regardless of whether they are\ndue before or after the current time.\n\nOtherwise, getNextDue selects a sort algorithm randomly, according to\nconfig.probabilityOldestDue: sort by interval or sort by due.\n\nIn either case, one of the first 5 cards, according to the sort algorithm,\nis selected at random.\n\n### getNextNew\n\ngetNextnew returns a new card if one is available, otherwise undefined.\n\nNew cards (cards with interval = 0) are sorted by ord, then id.\n\nNew cards are ignored if a card from the same fieldset is due within\nconfig.minTimeBetweenRelatedCards or if a card from the same fieldset has\nbeen reviewed within config.minTimeBetweenRelatedCards.\n\n### getStatsNext24Hours\n\nReturns an object with properties:\n * count\n * time\n\nWhere count is the number of cards due in the next 24 hours and time is the\nestimated time (seconds) to study all the cards due in the next 24 hours.\n\nCount is the number of cards due within the next 24 hours, excluding\ndeferred cards (multiple cards from the same fieldset due within\nminTimeBetweenRelatedCards).\n\nTime is an estimate of the time to review all these cards, based on recent\nperformance.\n\n### getTimeNextDue\n\nReturns the time (seconds since the epoch) when the card with the earliest\ndue time is due. This may be in the past or in the future, depending on\ncurrent backlog.\n\n### review(card, viewTime, studyTime, ease)\n\nUpdates the given card, setting new interval and due, according to ease and\ncreates a revlog record recording the review of the card.\n\nThe new interval and due are calculated according to the ease.\n\n## Changes\n\n### 1.0.1 - 20231007\n * README updates\n * Default config parameters relevant to the scheduler\n * Fix error in intervalEasy\n * Fix typo in getNextNew\n\n### 1.0.2 - 20231008\n * Fix getStatsNext24Hours\n\n### 1.1.0 - 20231011\n * Simplify and improve getAverageStudyTimePerNewCard and\n   getAverageStudyTimePerOldCard\n * Refactor calculation of stats for next 24 hours\n * Improve getStatsNext24Hours - better estimates, including new cards\n * Add API method: getNewCardMode\n * refactor getNextCard to use getNewCardMode\n * Fix calculation of average new cards per day\n * Exclude current date from estimate of time per card and new cards per day\n * Refactor getNextCard and change new card interval\n\n### 1.1.1 - 20231013\n * Adjust time between new cards according to average study time\n\n### 1.1.2 - 20231014\n * Fix typo\n\n### 1.1.3 - 20231014\n * Fix call to getAverageStudyTime\n\n### 1.1.4 - 20231014\n * Add some tests - not very good but better than nothing\n\n### 1.2.0 - 20231016\n * Add test for formatLocalDate\n * Introduce new cards slowly if predicted study time in the next 24 hours\n   is more than config.minStudyTime.\n\n### 1.3.0 - 20231016\n * Refactor formatLocalDate\n * Refactor getPercentCorrect\n * Refactor adjustCards\n * Refactor deferRelated\n * Refactor getCardsToReview\n * Refactor getAverageNewCardsPerDay\n * Refactor getAverageReviewsPerDay\n * Refactor getAverageStudyTime\n * Refactor timeForNewCard\n * Refactor minReviews\n * Add minReviews to getStatsNext24Hours result\n * Add config.newCardRateFactor\n * Base minReviews on cards to study next 24 hours not average\n\n### 1.3.1 - 20231017\n * Test minReviews with 0 new cards per day\n\n### 1.3.2 - 20231030\n * Don't include average new cards in stats for next 24 hours\n * Add test for getNewCardMode\n * Fix test of getStatsNext24Hours\n * Add test for getIntervals\n * Add test for getNextDue\n * Add test for getNextNew\n * Average study time over past and next 24 hours\n * Limit range of minReviews\n\n### 2.0.0 - 20231125\n * Average study time over longer period to determine new card mode\n * Replace reviewsSinceLastNew with reviewsToNextNew\n\n### 2.0.1 - 20231125\n * truncate reviewsToNextNew to integer on start\n\n### 2.0.2 - 20240210\n * revise scheduling of new cards\n * fix typoe in newCardFactor\n * handle 'NaN' as card.factor (and other such invalid values)\n\n### 2.0.3 - 20240314\n * Fix tests to handle reviewsPerNewCard parameter\n * Change intervalEasy to use actual interval\n\n### 2.1.0 - 20240322\n * Introduce failLearningMaxInterval and hardLearningMaxInterval\n * Base interval for Good on actual interval\n\n### 2.1.1 - 20240521\n * Make getReviewsToNextNew a bit more aggressive\n * Get new value for minReviews with each call to getStatsNext24Hours\n * Save reviews to next new card to database at each review\n * Limit reviews per new card\n * Update dependencies\n\n### 2.1.2 - 20240814\n * Fix adjustCards to not set interval larger than maxInterval\n * Change test for reviewed cards from interval != 0 to interval \u003e 0\n * Change test for new cards from interval = 0 to interval \u003c= 0\n * Reduce minimum interval for Good to 2 minutes\n * Update dependencies\n * Replace tape with @ig3/test\n\n### 2.1.3 - 20240912\n * Remove lapses\n * Simplify adjustCards\n * Handle the case that no configuration is provided\n * increase maximum intervals for failed and hard reviews\n\n### 2.1.4 - 20241206\n * Change good and easy intervals to be based on longest recent interval\n * Reduce window for average study time.\n * Update dependencies\n\n### 2.1.5 - WIP\n * Reduce percentCorrectSensitivity\n * Fix getCardsToReview\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fig3%2Fsrf-scheduler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fig3%2Fsrf-scheduler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fig3%2Fsrf-scheduler/lists"}