JSON in. Fair, explainable groups out.
RosterFair is a local-first CLI and JavaScript library for assigning people to tables, teams, classrooms, workshop rotations, volunteer shifts, or any other capacity-limited groups. It verifies every hard rule, optimizes weighted preferences, remembers past pairings, and produces JSON, CSV, or a self-contained visual report.
It is not another random-name shuffler:
- hard constraints are either satisfied or the command fails;
- a search-budget timeout is never mislabeled as proof of impossibility;
- impossible inputs get specific repairs and an irreducible conflict set;
- a seed reproduces the same result byte for byte;
- multi-round mode penalizes repeated pairings across history and newly generated rounds;
- all solving and reporting happen locally with zero runtime dependencies.
Requires Node.js 20 or newer.
git clone https://github.com/KanadeK/rosterfair.git
cd rosterfair
npm ci
node ./bin/rosterfair.js demo workshop --out workshop.htmlOpen workshop.html. It contains the full report and makes no network requests.
To install the signed-off release package:
npm install -g https://github.com/KanadeK/rosterfair/releases/download/v0.1.0/rosterfair-0.1.0.tgz
rosterfair demo classroom --out classroom.htmlVerify the archive first with the SHA256SUMS.txt attached to the same GitHub Release.
The included examples are deliberately not variants of one demo:
| Scenario | What RosterFair must handle |
|---|---|
| Classroom tables | fixed/allowed positions, separated pairs, support caps, score balance, language spread |
| Wedding tables | couples, accessibility, social conflicts, children per table, family-side mixing |
| Workshop rotations | three rounds, prior-session history, company diversity, experience balance |
| Community kitchen | shift leads, first-timer caps, pace balance, preferred assignments |
rosterfair solve examples/wedding.json --format html --out wedding.html
rosterfair solve examples/workshop.json --rounds 4 --seed autumn --format csv
rosterfair check examples/classroom.json
rosterfair explain examples/impossible.jsonHere is a complete small input:
{
"$schema": "./schema/rosterfair.schema.json",
"version": 1,
"name": "Project teams",
"seed": "demo-2026",
"participants": [
{ "id": "ava", "name": "Ava", "attributes": { "experience": 8 } },
{ "id": "bo", "name": "Bo", "attributes": { "experience": 3 } },
{ "id": "chen", "name": "Chen", "attributes": { "experience": 6 } },
{ "id": "dara", "name": "Dara", "attributes": { "experience": 5 } }
],
"groups": [
{ "id": "alpha", "name": "Alpha", "capacity": 2 },
{ "id": "beta", "name": "Beta", "capacity": 2 }
],
"constraints": [
{ "id": "separate", "type": "apart", "members": ["ava", "bo"] },
{ "id": "balance", "type": "balance", "attribute": "experience", "weight": 4 }
]
}Hard rules:
| Type | Meaning |
|---|---|
together |
Every listed participant must share one group |
apart |
Every pair in the list must use different groups |
fixed |
One participant must use one group |
allowed |
One participant may use only a listed group |
attribute-limit |
At most N matching participants per selected group |
Weighted preferences:
| Type | Meaning |
|---|---|
prefer-together / prefer-apart |
Penalize an unwanted social arrangement |
prefer-group |
Penalize assignment outside preferred groups |
balance |
Bring numeric group averages closer together |
spread |
Distribute categorical values across groups |
See the schema guide and the machine-readable JSON Schema for every field.
rosterfair solve <scenario.json> [--rounds N] [--seed TEXT]
[--iterations N] [--max-search-nodes N]
[--format json|csv|html] [--out PATH]
rosterfair check <scenario.json>
rosterfair explain <scenario.json> [--json]
rosterfair demo [wedding|classroom|workshop|volunteers] [--out PATH]
- JSON preserves scores, diagnostics, seeds, metrics, and all rounds.
- CSV creates one row per participant per round for spreadsheets and imports.
- HTML creates an offline visual report with round switching, printing, and JSON download.
The program writes machine-readable output to stdout unless --out is supplied. Diagnostics go to stderr, so pipelines do not receive mixed data.
| Exit | Meaning | Repair |
|---|---|---|
0 |
solved or hard rules are feasible | keep the seed with the result |
1 |
unexpected I/O/runtime failure | inspect stderr and retry after fixing the environment |
2 |
invalid JSON, schema, reference, or CLI usage | fix every path reported by validation |
3 |
hard constraints were proven impossible | run rosterfair explain; change one named rule or capacity |
4 |
search budget ended before a proof | raise options.maxSearchNodes; do not treat this as “no solution” |
An intentionally impossible fixture is committed:
node ./bin/rosterfair.js explain examples/impossible.json
# exits 3 and explains that a together block of 3 cannot fit capacity 2The complete repair playbook is in docs/troubleshooting.md.
RosterFair separates two jobs:
- A bounded backtracking search enforces capacities and every hard rule. A returned assignment is rechecked before scoring.
- Deterministic local search improves soft preferences without being allowed to break a hard rule.
Hard-rule correctness is exact for returned results. Soft optimization is heuristic: the score is transparent and reproducible, but RosterFair does not claim a global optimum. See docs/algorithm.md for the model, repeat-pair accounting, complexity, and limits.
These commands exercise source syntax, unit and CLI tests, real examples, failure paths, and release packaging:
npm ci
npm run verify
npm run test:coverage
npm run pack:release
npm run release:checkExpected evidence:
- all tests pass with no skips;
- four feasible fixtures solve and render JSON/CSV/HTML;
examples/impossible.jsonfails for the expected reason;release/rosterfair-0.1.0.tgzinstalls without downloading runtime packages;release/SHA256SUMS.txtmatches every published asset;- release preflight finds a clean tree, matching versions, no common secret pattern, and no co-author trailer.
CI repeats the verification matrix on Linux, Windows, and macOS. The packaging job uploads the exact release candidates as a workflow artifact.
RosterFair has no telemetry, account, hosted backend, or runtime dependency. Names and sensitive attributes never leave the machine unless you publish or send an output yourself. Reports intentionally embed the assignment data so they remain portable; treat the HTML file with the same care as the source roster.
This project allocates people according to rules supplied by its operator. It cannot decide whether those rules are ethical, lawful, accessible, or appropriate. Review sensitive attributes and final assignments with the affected people.
The selection research, rejected crowded ideas, and competitive gap are recorded in docs/project-rationale.md. RosterFair focuses on a missing layer—verifiable, reusable assignment logic and conflict explanations—rather than cloning a wedding planner or classroom canvas.
Read CONTRIBUTING.md before opening a pull request. Reports of security or privacy issues should follow SECURITY.md.
MIT © 2026 KanadeK