Students
One row per student (STUDENTS sheet). Instrument, class, and the seven subject-group columns are dropdowns from Reference tables, filtered to the matching TYPE (e.g. RHIMPR only offers RHYTHM groups). Picking a name stores the ID automatically; a blank group means that student isn't enrolled in that subject.
Lesson groups
One row per teachable group (LESSON sheet). GROUP, TEACHER, and ROOM are dropdowns from Reference tables — the scheduler pulls every student in that group and finds a slot where the teacher and all of them are free. Pick a ROOM to lock that lesson into a room from the ROOMS table (leave on — if it has no room). No two lessons that share a room may overlap, any teacher. Fill in FIXED DAY / START / END to pin this lesson to an exact slot for the next Generate. A pin that overlaps a student's class reservation, or that leaves an illegal gap next to another of that teacher's bookings, is still placed; Constraint check lists a warning. SCHEDULED DAY / START / END are filled by ✓ Accept — a record of the last accepted timetable, ignored by Generate.
Teacher availability
One row per teacher/day (TEACHER availability sheet). Blank START/END defaults to 08:00–20:00. TYPE ranks priority: AVAILABLE / PREFERRED beat FALLBACK, which beats CANDIDATE. AVOID blocks only the START–END interval (blank START/END = the whole 08:00–20:00 school day), even if another row lists that day as available. SCOPE says what may use that row: ALL (default — group lessons and 1/1 / piano), GROUP (subject lessons and small groups only), or O2O (1/1 and Required Piano only). AVOID follows the same SCOPE.
Class Reservations
One row per reserved (busy) block for a class/day, matching CLASS_CONST — the opposite of an availability sheet. The busy interval is START–END; L_STAR / L_END and AVAIL are kept from the sheet but do not change the window. Whatever's left over that day (within 08:00–20:00) is free for a lesson. A class/day with no rows is treated as free all day (currently true for 1j/2j/3j and all of grade 13).
Break Management
Matches the BREAK MANAGEMENT sheet — only lists teachers who get a break rule, not the full staff. With Generate on Breaks, a listed teacher may spend up to BREAK COUNT units of BREAK MIN across their whole week — as separate gaps or combined, same day or different days (45 × 2 means two 45-minute gaps, or one 90-minute gap). Any other length is illegal. An explicit 0 × 0 row still means never a gap. Teachers not on this list may have any gap (same as 1/1); Generate prefers the layout with the least total idle time.
Reference tables
The lookup sheets (TEACHERS, CLASS, GROUPS, INSTR, ROOMS) feeding every dropdown in the app — edit an ID here and it updates everywhere. Classes: MUCLASS_TYPE marks interchangeable grades (9a/9b/9c → "9") for Small Groups packing and Reports filters. Groups: TYPE controls which Students column it can be picked in. Instruments: TYPE (sol/acc/bass/drum) drives the Small Groups tab. Rooms matches the Drive ROOMS sheet (ROOM_ID / ROOM) and feeds the room dropdown on Lesson groups and Small Groups. Break settings live on Break Management; small group quotas live on the Small Groups tab.
Teachers
Classes
Groups
Instruments
Rooms
Save & load
Load Sheets / JSON / cloud into this browser first. That copy is not on GitHub yet — it only lives here (and in autosave). To put the same data on the public site, click published-state.json for GitHub, then add that file next to index.html in the repo and push. Anyone opening the GitHub Pages site then loads that file. Reload from GitHub file pulls it again (drops this browser’s autosave overlay).
Google Sheets
The tables start empty. Load from Google Sheets fills them from the DATABASE_2627 Drive workbook — tabs STUDENTS, TEACHER_CONST, CLASS_CONST, LESSONS, SMALL_GROUPS, SMALLGR_QUOTAS, 1_1, rpiano, TEACHERS, BREAK_MANAGEMENT, CLASS / GROUPS / ROOMS / INSTR. Load from the pre-filled link (must stay Anyone with the link → Viewer) or upload a downloaded .xlsx. Export downloads the same tabs and columns, so you can upload it to Drive or File → Import over the existing sheet. Loading replaces the working tables and the small-group roster from SMALL_GROUPS; the timetable is cleared. Generate small groups still works after a load. A missing tab keeps what is already in the app (except small groups, which stay empty until that tab is present or you generate).
Cloud database
Talks straight to a Supabase project from this browser — no server of our own. One table, planner_state, holds a single row (id='main') whose data column is the same JSON blob as “Save all edits”. The URL and key stay in this browser’s localStorage; the timetable itself lives in Supabase, so every device that connects with the same keys shares it. Connect does not auto-load — you choose when to pull cloud data over what you’re looking at.
Generate
Builds the week from every tab above, teacher by teacher. Available days are grouped by rank (PREFERRED/AVAILABLE → FALLBACK → CANDIDATE); lessons split evenly across a rank's days before spilling into the next, and pack back-to-back within a day. Every leftover class hole that still fits the teacher that day is usable (a morning leftover is not dropped just because the class's biggest hole is afternoon; a leftover flush against a pin is preferred over a larger hole that would open an illegal gap). If a leftover cannot sit flush against another lesson that day, it sits at the hole end when the only neighbor lesson is later, or at the hole start when the only neighbor is earlier (both sides: either). Hard rules: with Generate on Breaks, a teacher listed on Break Management may only gap by BREAK MIN (or a multiple), totaling at most BREAK COUNT units in that teacher's week; teachers not listed may gap freely and Generate prefers the least idle time. With the switch on Idle, Break Management is ignored for everyone (any teacher may gap); lessons that share a room never overlap. Small Groups get their own pass only after every subject lesson is placed: a free slot is found from members' availability, then a teacher is matched from ⚙ Small Group Quotas (Small Groups tab) — unless you set a manual override, which schedules it the normal teacher-first way instead. Each click tries the number of layouts in Attempts (default 100, up to 50,000) and keeps the best 10 — browse them in the Solution dropdown, which opens on ★. Above 1,000 attempts, Generate switches to a deep search: only those 10 full layouts stay in memory (the rest are signatures), per-attempt walkthroughs are skipped, the page yields every 25 tries, and Stop search keeps the best so far. The first attempt packs teachers from fewest available days — or from your Teacher order list if you set one; shuffled attempts still permute that list so any teacher may go first. After that search, layouts are previewed with a 1/1 pack and then Required Piano — All probes every distinct layout before trimming to 10, 10 probes only the 10 kept ones. ★ is the layout with the fewest leftover items of any kind, then the best 1/1 then Required Piano preview — teacher idle time during search does not affect ★. Flip Swaps on after Generate to re-run the group search with same-duration teacher swaps (Jazz Theory 60 ↔ Jazz Solfege 60): shuffled packs plus a 1/1 preview, then log the better leftover week that search would have found — Off skips that pass. The Lesson groups table and the grid never change. Shuffled attempts pick a small-group slot among the 3 tightest legal gaps (not always the single tightest). If bands stay stuck after Phase 2, Generate may evict 1–2 blocking subject lessons and retry the bands. Same inputs on a later click merge into the pool, so leftover count cannot get worse. While Generate runs, a live window narrates the current attempt (pack, shuffle, 1/1 preview) — or a progress pulse in deep search — with a Stop search button. Search log is the ★ layout's walkthrough after it finishes (packing, reds, quota, 1/1 / piano preview) — switch the list to see another layout. ✓ Accept this schedule freezes the layout into the Accepted schedule table (times, teachers, student lists) and copies those slots into SCHEDULED columns on Lesson groups and Small Groups. Generate still only reads FIXED times and small group Teacher override. If an accepted roster is on file, Generate asks whether to keep it or clear it before searching. After Generate or a drag, every other tab stays locked until you ✓ Accept this schedule. Work is autosaved in this browser so a refresh does not drop drags or tables.
Phase 1 teacher order
Drag names to set who packs first on the opening attempt. Subjects and override small groups follow this list. Shuffled attempts still permute the names — only that first layout stays in your order.
Search log
Walkthrough of the ★ layout — packing, unresolved (left-out) items, quota ledger, and the 1/1 forecast that picked ★. Switch the Solution list to see another layout.
Weekly grid
Drag any lesson to any day and time. Right-click → Delete from layout removes it completely (and clears clashes it caused). Refresh names / teachers copies the current Lesson groups / Small Groups roster, teacher, room, and title onto placed blocks without moving day or time. The move/delete always sticks — nothing is blocked. After Generate or an edit you cannot open another tab until ✓ Accept this schedule. The check uses this grid plus accepted 1/1 / Required Piano only (teacher window, class reservations, double-bookings, rooms, break gaps).
Constraint check
Could not be scheduled
No free slot satisfied every constraint (teacher availability, class windows, no clash with other lessons). Loosen a constraint above and regenerate.
Accepted schedule
Frozen the moment you hit ✓ Accept this schedule. One row per lesson/small group with day, time, teacher, and the student list as it was then. The same slots are copied into SCHEDULED columns on Lesson groups and each small group — Generate does not read those. FIXED times and small group Teacher override stay as your search constraints. CSV and Google Calendar export only this frozen table. JSON / cloud / .xlsx include it too.
Generate small groups
Builds combos: every small group gets a BASS, DRUM, ACC (guitar/piano), and SOL (vocal/trump/sax/violin) player, from the Instruments TYPE column. Priority 1: same MUCLASS_TYPE. Priority 2: nobody left out — bass/drum may repeat up to twice, extra acc/sol get added as extra members. Students with no CLASS are skipped (no grade to match). Every small group is editable: duration, Room (from the ROOMS table; new small groups default to the first room), or a manual Teacher override — otherwise the Timetable auto-matches a teacher from ⚙ Small Group Quotas. Edit students opens a checklist (name, instrument, class, MUCLASS type) with current members ticked — tick or untick to add or remove. Anyone in two small groups is highlighted yellow. + Add small group manually starts an empty small group to hand-pick; ✕ on the card header deletes one entirely. ▶ Generate small groups always asks first — it deletes the current bands. If an accepted roster is on file, you can keep it or clear it (same as Generate group lessons).
Small Group Quotas
Only teachers listed here can ever be auto-matched to a small group — matches the SMALLGR_QUOTAS sheet, which only lists teachers who actually take small groups, not the full staff.
Edit students
Tick a row to add that student, or untick to remove them. Role (BASS / DRUM / ACC / SOL) follows the instrument TYPE.
Small Groups
Not assigned (no class type)
No CLASS assigned in Students — no grade to match, so left out of every small group on purpose.
1/1 lessons
Opens only while group lessons are accepted. Generate or a drag on this tab locks every other tab until ✓ Accept 1/1 — including going back up to Group Lessons. Grey blocks are the accepted group freeze; teal is accepted Required Piano only. The table is the Drive 1_1 matrix: students down the side, teachers across. A number is weekly hours with that teacher — 1 = 60 min, 1.5 = 90, 2 = 120. After the student, a Room column locks that student's 1/1 into a studio (blank = none) — every teacher they have hours with. On Drive, put a room column after INSTR_ID on the 1_1 sheet (room name or ID from the ROOMS tab). Generate will not double-book that studio against group lessons or another 1/1. Generate packs 1/1 into the holes of the accepted week. If the teacher already has a lesson that day, 1/1 sits flush against it; if the day is empty, it starts as early as the leftover allows and the chain grows forward. Generate first tries every cell as one block. If that teacher still has students left out, only those leftover cells may split (120 → 2×60 only); a 1 = 60 min or 1.5 = 90 min cell and a 30-min lesson never split. If that still leaves someone out, every cell of that teacher is split and packed from one pool. Two pieces of the same student never share a day unless they sit flush as one long lesson. Pick Teacher to regenerate only that teacher — everyone else’s 1/1 stays. All teachers replaces the whole 1/1 layout; Required Piano is never cleared — clashes are listed below. Attempts (default 15) is how many distinct weeks Generate looks for — up to four tries per attempt, max 5,000. While it runs, the same live window as Group Lessons narrates each try and shows remaining time; Stop search keeps the layouts found so far.
Assignment matrix
Weekly grid
Shows accepted group lessons (grey), 1/1 (gold), and accepted Required Piano (teal) for the teachers picked with the button above. Drag gold 1/1 blocks — grey and teal stay put. Right-click a gold block → Split lesson… to cut e.g. 60′ into 30′+30′ (parts must add up exactly).
Constraint check
Could not place
Clashes with Required Piano
The piano layout is kept — adjust 1/1 or piano, or regenerate piano after Accept 1/1.
Accepted schedule
Frozen the moment you hit ✓ Accept 1/1. Same columns as the Timetable Accepted schedule — day, time, teacher, and the student as it was then. CSV export is only this frozen table.
Required Piano
Opens only while 1/1 is accepted. Generate or a drag here locks every other tab until ✓ Accept Required Piano. Grey and gold blocks are the accepted group + 1/1 freeze. The table is the Drive rpiano matrix (1 = 60 min, 0.5 = 30). After the student, a Room column locks that student's piano into a studio, same as 1/1 — Drive uses a room column after INSTR_ID on the rpiano sheet. Generate packs piano into leftover holes around the accepted week and 1/1. Split rules are the same as 1/1: whole blocks first, then leftover cells only, then every cell of that teacher from one pool if still incomplete. Pick Teacher to regenerate only that piano teacher — the others stay. Attempts (default 15) is how many distinct weeks Generate looks for — up to four tries per attempt, max 5,000. This number is only for Required Piano; 1/1 has its own. The live search window and Stop search work here too.
Assignment matrix
Weekly grid
Shows accepted group lessons (grey), accepted 1/1 (gold), and Required Piano (teal) for the teachers picked with the button above. Drag teal piano blocks — grey and gold stay put. Right-click a teal block → Split lesson… to cut e.g. 120′ into 60′+60′ (parts must add up exactly).
Constraint check
Could not place
Accepted schedule
Frozen the moment you hit ✓ Accept Required Piano. Same columns as the Timetable Accepted schedule — day, time, teacher, and the student as it was then. CSV export is only this frozen table.
Timetable reports
A read-only filter of everything currently on the week calendars: generated or accepted group lessons, 1/1, and Required Piano. Grey blocks are class reservations from CLASS_CONST — the label is the NOTE (Language, MET, …), not “reserved”. Tick Class reservations to overlay them, or untick to hide them. Combine filters — class, student, MUCLASS_TYPE, room, and teacher all apply together (AND). Empty means All. This tab stays locked until group lessons, 1/1, or Required Piano is accepted, and while a layout still needs Accept; it does not edit the grid.