A free walkthrough
How to make a call schedule with AI that doesn’t hallucinate.
That reply is half right. ChatGPT alone won’t build you a fair call schedule. It will gladly invent one that looks fine and quietly skips a vacation request. But ChatGPT plus a compact constraint solver will — deterministically, in seconds. And the whole thing is free.
I built this for the senior resident, the chief, the fellow, the one partner in the group, the person who got handed the spreadsheet and is now spending every other weekend trying to make it fair. If that’s you, follow along. If it’s someone you know, send them this page.
Full disclosure: I’m the founder of NiceSchedule. The walkthrough below is the free, DIY version of what we build for anesthesia groups that need the schedule run for them.
The distinction
Why ChatGPT alone keeps failing at this.
Once upon a time, if you asked an LLM how many r’s were in strawberry, it would confidently tell you two. Meanwhile a one-line Python program would just count the string and return three. That is the difference between a plausible answer and a checked answer.
Your call schedule needs the checked answer. A language model predicts the most likely next token; that is genuinely great for translating messy human rules into something more precise. It is the wrong tool to be the schedule. It will produce a calendar that looks reasonable and quietly skip a vacation, double-book a post-call physician, or leave a weekend uncovered.
A constraint solver works differently. You hand it variables, hard rules, and what to prefer. It searches for assignments that satisfy the rules. If the rules conflict, it tells you the schedule is infeasible instead of inventing one.
This is the part most "just use ChatGPT" posts miss. ChatGPT is not the scheduler. The solver is the scheduler. ChatGPT is the helper you use to write the solver, clean the inputs, explain errors, and add one rule at a time.
The v1 rule set
What the sample solver actually does.
I kept this intentionally small. It is not a production anesthesia scheduler. The point is to prove the workflow runs end-to-end before adding real-world complexity.
- Cover every required shift in
coverage.csv. - Respect shift eligibility in
clinicians.csv. - Block hard vacation and no-call requests in
requests.csv. - Assign each clinician to at most one shift per day.
- Enforce a minimum rest gap between assignments.
- Prefer fair total assignment counts.
- Prefer fair weekend assignment counts.
- Use prior
history.csvassignments as part of the fairness picture. - Prefer more rest spacing when the hard rules leave room.
Inputs
The four CSV files
The solver reads structured CSVs instead of free-form text. That is what keeps the schedule grounded — the AI never gets to invent who exists or who is on vacation.
clinicians.csv
Who can be scheduled, which shifts they cover, target counts, max counts, and rest rules.
coverage.csv
The demand file. Each date, each shift type, and how many people are required.
requests.csv
Vacation, no-call requests, and soft preferences. Hard requests block; soft requests pay a penalty.
history.csv
Prior assignments. Used to make the new schedule fair against recent history.
| File | Example row | Meaning |
|---|---|---|
clinicians.csv |
fox,A. Fox,1,1.0,1,1,5,6,2,3,1 |
A. Fox is active, can cover OR and OB, targets 5 shifts, maxes at 6, targets 2 weekend shifts. |
coverage.csv |
2026-06-01,OB,1 |
June 1 needs one OB clinician. |
requests.csv |
r1,cary,2026-06-05,2026-06-07,vacation,1,,Family trip |
M. Cary cannot be assigned to any shift from June 5 through June 7. |
history.csv |
2026-05-02,fox,OR,final |
A. Fox covered OR on May 2 — counts toward fairness history. |
Full column-by-column schema is in docs/csv-schema.md.
First run
Run the dummy data before touching your own data.
Do not debug Python, CSV formatting, and your real scheduling rules at the same time. Run the sample exactly as-is first, see the schedule come out, then adapt.
First, clone the repo:
git clone https://github.com/rumblelab/ai-call-scheduler.git cd ai-call-scheduler
Create a virtual environment
python3 -m venv .venv
Install OR-Tools
.venv/bin/python -m pip install -r requirements.txt
On Windows, the activate path is different. Use .venv\Scripts\python wherever you see .venv/bin/python.
Run the solver
.venv/bin/python solver.py
Inspect the output
Status: OPTIMAL Wrote 28 assignments to output/sample_schedule.csv
Open output/sample_schedule.csv for the raw data, or open output/sample_schedule.html to see the beautiful, printable grid version.
The output
Here’s what comes out of the solver.
Below is the actual output/sample_schedule.csv the sample produces, laid out as a real call calendar. Two weeks. Six clinicians. One OR shift and one OB shift per day. Status: OPTIMAL. 28 assignments. Every hard constraint respected.
A few things worth pointing out, because they prove the solver actually respected the rules:
- Cary’s vacation June 5–7 is honored. Zero assignments those days.
- Reed’s hard block on June 2 is honored. Reed has zero assignments that day — even though Reed covers five other shifts in the window.
- Johns asked off OB on June 10 only (shift-specific, not a full day). The solver honored it — Johns has no OB shift on June 10 — without blocking other shift types. That distinction matters for real groups.
- Weekend calls are spread across the group. Fox and Judy take two weekend shifts; the others take one.
- Total counts are within 1 of each clinician’s target. No one is "the one who always covers."
That is the holy-shit moment, if you spent last weekend doing this in Excel. It came out of a single Python file in seconds — and this is the exact grid you’ll get when you run solver.py on your own machine.
Demand generation
coverage.csv is the file you actually have to fill out.
Of the four CSVs, this is the demand side. If a row exists, the solver tries to staff it. If a row is missing, the solver does not know that shift exists. So this is the file you have to think about carefully.
That said: don’t type it by hand. If your coverage pattern is regular, have your agent write a small script that generates it.
Create a Python script that writes coverage.csv for June 2026. Rules: - Every Saturday and Sunday needs one NORTH call and one SOUTH call. - Weekdays do not need call coverage in this example. - Output columns: date, shift_type, required_count. - Dates in ISO format (YYYY-MM-DD).
It will produce rows like:
date,shift_type,required_count 2026-06-06,NORTH,1 2026-06-06,SOUTH,1 2026-06-07,NORTH,1 2026-06-07,SOUTH,1
The same pattern handles:
- one OB call every day
- two OR calls on weekdays, one OR call on weekends
- different sites:
NORTH,SOUTH,OB - holiday-specific call requirements
- higher required counts on known heavy days
coverage.csv, add a matching eligibility column in clinicians.csv. For example, shift type NORTH expects can_north. If you forget, the solver fails fast with a clear error — see docs/troubleshooting.md.
The messy part
Turning messy emails into requests.csv.
The biggest barrier isn't the solver — it's the data entry. You have a folder full of "Please give me June 12th off" emails and a PDF of the resident roster. Don't type them into the CSV by hand.
This is where ChatGPT or Claude excels. Give them the messy text and ask for the structured CSV.
I am using a call scheduler that needs a requests.csv file. Columns: request_id, clinician_id, start_date, end_date, request_type, hard, shift_type, note. Here is a list of emails from residents: "[Paste messy email text here]" And here is our clinician roster: "[Paste name list here]" Please generate the CSV rows. Use clinician_id from the roster. Assume hard=1 for vacation, hard=0 for 'prefer off'. Leave shift_type blank to block all shifts that day; fill it in (e.g. OB) to block only one shift type. Dates in ISO format.
Once you have the CSV, save it to data/my_data/requests.csv and run the solver. If the solver says No feasible schedule found., one of those requests made it impossible to cover the shifts. Change a 1 to a 0 in the hard column (making the request soft) and re-solve, or remove the conflicting row.
Customization
Once the sample works, change one thing at a time.
Copy the sample data into your own folder:
cp -R data/sample data/my_data
Copy the sample config:
cp config/sample_rules.json config/my_rules.json
In config/my_rules.json, point at your data:
{
"input_dir": "data/my_data",
"output_csv": "output/my_schedule.csv"
}
Run it:
.venv/bin/python solver.py --config config/my_rules.json
Change one file at a time. First clinicians. Then coverage. Then requests. Then history. If the model becomes infeasible, the newest change is usually where the conflict lives — remove that rule first and inspect what changed.
For worked examples of common adaptations — adding a third shift type, locked assignments, weekend-pairing rules — see docs/adaptation-cookbook.md.
Agent handoff
If you have Claude Code or Codex, just paste this.
This whole tutorial is designed so a coding agent can drive it. Claude Code, Codex, Cursor, ChatGPT — any of them can clone the repo, read the docs, run the dummy solve, and then help you adapt one rule at a time.
I’d strongly recommend Claude Code or Codex over a chat-only assistant for this. Local-folder agents can inspect files, run the solver, and see the actual output. Chat-only assistants can only guess at what your CSVs look like.
I want to use the free NiceSchedule call schedule solver tutorial.
Public walkthrough:
https://niceschedule.com/how-to-make-a-schedule-with-ai/
GitHub repo:
https://github.com/rumblelab/ai-call-scheduler
My goal, in three phases:
PHASE 1 — Prove it works.
Clone the repo and run the dummy solve exactly as-is. Then explain
to me in plain English what the solver did and what the output means.
PHASE 2 — Set it up for my practice.
Once the dummy works, follow docs/new-practice-setup.md to walk me
through configuring my own schedule, step by step:
- my clinician roster
- my coverage pattern (use scripts/generate_coverage.py with my
description, don't make me hand-type the file)
- my hard vs soft rules (post-call rest, partner allocation,
weekend pairing, etc.)
- my vacation and time-off requests (I'll paste whatever format
I have — emails, a list, a spreadsheet)
- my first real solve, and a triage of the output before we change
anything
PHASE 3 — Iterate.
Add one rule at a time. After each change, re-run and tell me what
shifted in the output. If the solver becomes infeasible, walk back
to the most recent change and use docs/troubleshooting.md to diagnose.
Before changing anything, please read these files in order:
- README.md
- docs/scheduler-agent-skill.md
- docs/new-practice-setup.md
- docs/csv-schema.md
- docs/adaptation-cookbook.md
- docs/troubleshooting.md
- docs/agent-privacy.md
- config/sample_rules.json
- data/sample/clinicians.csv
- data/sample/coverage.csv
- data/sample/requests.csv
- data/sample/history.csv
- scripts/generate_coverage.py
- solver.py
Safety rules:
- Don't ask me for real names or schedules until the dummy solve works.
- If you need example data at any point, invent fake data.
- Add one rule at a time and tell me how to test it.
- Per docs/agent-privacy.md, keep this data local. Don't paste real
schedules into public tools.
Start by running the dummy solve and showing me the output.
Good first asks for your agent
- "Generate a new
coverage.csvfor a different month." - "Add a third shift type (NIGHT) and the matching eligibility column."
- "Add a
locked_assignments.csvfile so I can pin specific people to specific shifts." - "Add a rule that nobody works both Saturday and Sunday of the same weekend."
- "Add a soft preference for spreading holidays fairly."
docs/adaptation-cookbook.md.
Privacy
Don’t paste private schedules into public tools.
Even without patient information, physician schedules, vacation requests, and internal group rules can still be sensitive. Start with the dummy data. When you swap in your own, replace names with IDs first.
- Don’t upload real call history to public GitHub issues.
- Don’t paste real vacation requests into tools your group hasn’t cleared.
- Check retention and training settings on whatever AI tool you use.
- When asking for help, use synthetic examples.
The longer version is in docs/agent-privacy.md.
Where it breaks
This gets hard when the rules become real.
The basic pattern is not magic. The hard part is making it work for a real group: cleaning requests, importing historical fairness, modeling local exceptions, fixing infeasible months, post-call recovery, locked assignments, holiday rotation, secure access, readable schedule views, distribution, and month-over-month maintenance.
That is roughly the moment a DIY script stops being enough. A real schedule needs a place where requests live, where history is tracked across months, where people can be added or removed without breaking previous solves, and where the schedule can be safely shared with the group after it’s reviewed.
If that’s the wall you’ve hit, see how NiceSchedule handles it for real groups — or jump to the cards below.