Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🧭 Cadence

CI License: Apache-2.0

Build a study plan from a syllabus and a calendar — three courses of action, exported to the learner's calendar.

A learner has a syllabus (what's due, and when) and a finite amount of time. Cadence connects the two things that usually live in separate files: it reads where the learner stands, then lays out day-by-day study blocks across three courses of action —

  • Catch up (behind) — triage the essentials, front-loaded to the earliest days, no time wasted on review.
  • Maintain (on track) — a steady pace with spaced review baked in.
  • Get ahead (ahead) — deeper study, spaced review, and extra practice on the heaviest topics.

Availability is a hard ceiling: no day — study, review, or practice — is ever scheduled past the minutes you actually have, and closed days stay empty. Work that doesn't fit is spread across more days and, if it still can't land in time, counted in lateRisk rather than silently dropped.

Everything is pure and deterministic — no model, no network, no key. And the plan exports to a real .ics calendar (Outlook / Google / Apple) with reminders, plus a flat list for a text or email nudge.

Install

npm install cadence

Requires Node 18+. ES modules only.

Quick start

import { plan, assessStatus } from 'cadence';

const syllabus = [
  { id: 'A', title: "Ohm's law",                 due: '2026-09-20', hours: 2, weight: 2 },
  { id: 'B', title: 'Series & parallel circuits', due: '2026-09-25', hours: 3, weight: 3 },
  { id: 'C', title: 'Soldering practical',        due: '2026-10-02', hours: 1, weight: 1 },
];

assessStatus(syllabus, { asOf: '2026-09-15' });   // 'behind'

const p = plan({ syllabus, asOf: '2026-09-15', availability: 90 });
p.status;              // 'behind'
p.recommended;        // 'catch_up'
p.coas.catch_up;      // { label, recommended: true, blocks: [...], totalMinutes, days, lateRisk }
p.coas.catch_up.blocks[0];
// { date: '2026-09-15', itemId: 'A', task: "Ohm's law", minutes: 90, kind: 'study' }

Each COA gives you the scheduled blocks, the totalMinutes and days it spans, and a lateRisk count — how many items can't be finished before their due date at the given pace, plus any work that can't be placed in the available time at all.

Items are keyed by id (falling back to title); a duplicate is given a unique id so every task is scheduled — none is dropped.

A syllabus item

field meaning
title what to learn
due 'YYYY-MM-DD', when it's assessed
hours estimated study hours to master it (falls back to weight, else 1)
weight relative importance (default 1) — breaks due-date ties and drives status
completed already done (also accepts done) — skipped in the plan

Availability — the calendar half

availability is either a flat number of minutes per day, or a weekday map keyed 0 (Sun) .. 6 (Sat). A weekday absent from the map counts as 0 — a day off — so a partial map only opens the days you list:

plan({ syllabus, asOf: '2026-09-15', availability: { 0: 0, 1: 90, 2: 90, 3: 90, 4: 90, 5: 90, 6: 45 } });
// Sundays off, 90 min on weekdays, 45 on Saturday — no block of any kind ever lands on a closed day.

plan({ syllabus, asOf: '2026-09-15', availability: { 1: 60, 3: 60, 5: 60 } });
// Only Mon/Wed/Fri open at 60 min; every other day is 0 (off).

Status

assessStatus compares the weighted share of work completed against the share due by asOf. Outside a tolerance band (default 0.15) it's behind or ahead; inside, on_track. Pass a status to plan() to override the read.

Export to a calendar

import { toICS, toReminders } from 'cadence';

const ics = toICS(p.coas[p.recommended].blocks, {
  calendarName: 'BEC Study Plan',
  reminderMinutes: 30,   // a VALARM this many minutes before each block
  startHour: 18,         // blocks start at 18:00, floating local time
});
// → a valid VCALENDAR string: one VEVENT + VALARM per block. Write it to study-plan.ics and import.
//   Blocks sharing a day are sequenced back-to-back from startHour (no overlap); a block that runs
//   past midnight rolls its DTEND onto the next day; every line is folded to ≤75 octets per RFC 5545.

toReminders(p.coas[p.recommended].blocks);
// → [ { date: '2026-09-15', text: "Ohm's law — 90 min", minutes: 90, kind: 'study' }, ... ]

API

export purpose
plan({ syllabus, availability?, asOf, status? }) status + a schedule for all three COAs
assessStatus(syllabus, { asOf, tolerance? }) 'behind' | 'on_track' | 'ahead'
toICS(blocks, opts?) study blocks → an iCalendar (.ics) string with reminders
toReminders(blocks) study blocks → flat reminder rows for text/email
COAS the three COAs and their scheduling parameters

Every function validates its input and throws TypeError on a bad shape or date — including dates that aren't real calendar days ('2026-02-30' throws rather than rolling into March).

Run the demo

node example/demo.mjs

License

Apache-2.0.

Releases

Packages

Contributors

Languages