Claude-skill-registry fm-residency-scheduling
Family Medicine Residency Excel-based scheduling. Use when working with academic year block schedule spreadsheets to assign faculty half-days (C, GME, DFM) and resident half-days, validate constraints, process FMIT/SAFP blocks, apply post-call rules, and meet AT coverage. Triggers: Block schedule, faculty scheduling, resident scheduling, half-day assignments, FMIT, clinic coverage, resident supervision, AY 25-26.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/fm-residency-scheduling" ~/.claude/skills/majiayu000-claude-skill-registry-fm-residency-scheduling && rm -rf "$T"
skills/data/fm-residency-scheduling/SKILL.mdFM Residency Faculty & Resident Scheduling
Automates faculty and resident half-day assignments directly in Excel for Family Medicine residency block schedules.
Architecture Note
Excel is the communication layer to code. The Excel workbook serves as configuration input - the scheduling engine reads from it and has more detailed logic internally. Think of Excel as the "what" and the code as the "how."
Data Model
See:
docs/architecture/HALF_DAY_ASSIGNMENT_MODEL.md
The scheduling system uses persisted half-day assignments with actual dates (not block references). This enables:
- Natural inter-block constraint handling (PCAT/DO carries to next block automatically)
- FMIT spanning blocks without special logic
- Leap year and year boundary handled by date arithmetic
Source Priority (when multiple sources want same slot):
- FMIT, call, absences - NEVER overwrittenpreload
- Explicit human overridemanual
- Computed by CP-SATsolver
- Default from WeeklyPatterntemplate
C vs AT Distinction (CRITICAL)
This table is the foundation of all scheduling logic.
| Activity | Definition | Physical Capacity | AT Coverage |
|---|---|---|---|
| Resident C | Resident seeing patients | Counts (max 6) | Creates demand |
| Faculty C | Faculty seeing OWN patients | Counts (max 6) | None |
| Faculty AT | Faculty supervising residents | Does NOT count | Provides |
| PCAT | Post Call Attending Time | Does NOT count | Provides (= AT) |
| DO | Direct Observation | - | Auto-assigned after call |
Key Rules:
- PROC/VAS: +1.0 AT demand (dedicated 1:1 supervision)
- SM: Closed loop - does NOT use AT resources, SM Faculty's SM does NOT provide AT
Terminology (CRITICAL - do not confuse):
- PCAT = Post Call Attending Time (NOT Admin) - can precept, provides AT
- DO = Direct Observation (NOT Day Off) - auto-assigned PM after call
Physical Capacity: Max 6 people doing clinical work (C) per half-day. AT does NOT count toward this limit because AT faculty are supervising, not generating their own patient load.
Order of Operations (Canonical)
Phase 1: PRELOAD (locked before solver)
- Absences (LV, HOL, DEP, TDY)
- FMIT - both faculty and resident
- FMIT Fri/Sat call - auto-assigned with FMIT
- C-I (inpatient follow-up clinic): PGY-1 Wed AM, PGY-2 Tue PM, PGY-3 Mon PM
- Night Float - full pattern including post-call
- aSM - Wednesday AM for SM faculty (SM Faculty)
- Conferences (SAFP, USAFP, LEC)
- Protected time (SIM, PI, MM)
Phase 2: SOLVER (computed)
- Assign Sun-Thu call (min-gap decay) → auto-generates PCAT baseline
- Solve outpatient (residents) - solver applies rotation patterns
- Calculate supervision demand (resident C creates demand)
- Assign faculty AT (to meet supervision demand)
- Assign faculty C (personal clinic, counts toward physical capacity)
- Fill admin time (GME/DFM/SM)
Academic Year Calendar Structure
All blocks start Thursday, end Wednesday (except Block 0 and 13).
Academic Year: July 1 - June 30 Block 0: July 1 → First Thursday - 1 day Variable length (1-6 days) Purpose: Orientation, onboarding, calendar fudge factor Blocks 1-12: Thursday → Wednesday Fixed 28 days each (56 half-day assignments per person) Block 13: Thursday → June 30 Variable length (28+ days) Purpose: Absorb end-of-year remainder
Example AY25-26:
- July 1, 2025 = Tuesday
- Block 0 = July 1-2 (2 days)
- Block 1 = July 3-30 (28 days, Thu-Wed)
- Block 13 absorbs remainder to reach June 30, 2026
Block 0 Logic:
- New intern (no preceding year rotation): Assign FLX
- Returning resident: Follow logic from preceding year rotation
Block 13 Logic:
- Normal assignments (not just administrative)
- Longer than standard 28 days to reach June 30
Build Order (Recommended Workflow)
- FMIT/FMET first - Lock inpatient faculty assignments
- Call assignments - Distribute call with equity tracking
- Everything else - Clinic, AT, admin time
Quick Reference
Priority Order: FMIT > AT (clinic) > Admin (GME/DFM)
Key Constraints:
- Weekly clinic caps vary by faculty (0-4)
- Post-call = PCAT (AM) + DO (PM)
- FMIT weeks = no clinic, OFF Friday after
- All residents/faculty have LEC on Wednesday PM
- Mid-block rotation transitions occur at column 28 (start of week 3)
References
- Complete workbook layoutreferences/excel-structure.md
- Faculty caps and admin typesreferences/faculty-roster.md
- Resident rotations and codesreferences/residents-rotations.md
- Block N specific contextreferences/block10-context.md
Block Template2 Structure (VERIFIED)
This is the working document for scheduling.
Row Layout
| Row Range | Content |
|---|---|
| 1 | Block number, Day names (THURS, FRI, SAT...) |
| 2 | Day abbreviations (THU, FRI, SAT...) |
| 3 | Date row with actual dates (2025-03-12, etc.) |
| 4 | Staff Call (faculty name when on call) |
| 5 | Resident Call |
| 6 | Headers: TEMPLATE, ROLE, PROVIDER |
| 9-13 | PGY-3 Residents (R3) |
| 14-19 | PGY-2 Residents (R2) |
| 20-25 | PGY-1 Residents (R1) |
| 31-43 | Faculty (C19/FAC, ADJ/FAC, SPEC/PSY) |
| 49-53 | TY/Float/SM |
| 55-65 | Medical Students (USU, IPAP, MS) |
| 67-68 | CP/BHC (Pharmacist, Behavioral Health) |
| 71-82 | Metrics (Appointments, AT Needed, etc.) |
Column Layout
| Column | Content |
|---|---|
| 1 | First-half rotation (e.g., "FMC", "FMIT 2", "Remote_Site") |
| 2 | Second-half rotation if different (e.g., "Peds NF") |
| 3 | Template code (R1, R2, R3, C19, ADJ, etc.) |
| 4 | Role (PGY 1, PGY 2, PGY 3, FAC, etc.) |
| 5 | Provider name |
| 6+ | Half-day slots (AM/PM pairs per day) |
Date-to-Column Mapping (Block N: [Block Start] - [Block End], 2026)
Each date uses 2 columns: AM (even col), PM (odd col+1)
| Columns | Date | Day | Week |
|---|---|---|---|
| 6-7 | [Block Start] | Thu | 1 |
| 8-9 | Mar 13 | Fri | 1 |
| 10-11 | Mar 14 | Sat | 1 (W) |
| 12-13 | Mar 15 | Sun | 1 (W) |
| 14-15 | Mar 16 | Mon | 2 |
| 16-17 | Mar 17 | Tue | 2 |
| 18-19 | Mar 18 | Wed | 2 (LEC col 19) |
| 20-21 | Mar 19 | Thu | 2 |
| 22-23 | Mar 20 | Fri | 2 |
| 24-25 | Mar 21 | Sat | 2 (W) |
| 26-27 | Mar 22 | Sun | 2 (W) |
| 28-29 | Mar 23 | Mon | 3 ← MID-BLOCK TRANSITION |
| 30-31 | Mar 24 | Tue | 3 |
| 32-33 | Mar 25 | Wed | 3 (LEC col 33) |
| 34-35 | Mar 26 | Thu | 3 |
| 36-37 | Mar 27 | Fri | 3 |
| 38-39 | Mar 28 | Sat | 3 (W) |
| 40-41 | Mar 29 | Sun | 3 (W) |
| 42-43 | Mar 30 | Mon | 4 |
| 44-45 | Mar 31 | Tue | 4 |
| 46-47 | Apr 01 | Wed | 4 (LEC col 47) |
| 48-49 | Apr 02 | Thu | 4 |
| 50-51 | Apr 03 | Fri | 4 |
| 52-53 | Apr 04 | Sat | 4 (W) |
| 54-55 | Apr 05 | Sun | 4 (W) |
| 56-57 | Apr 06 | Mon | 5 |
| 58-59 | Apr 07 | Tue | 5 |
| 60-61 | Apr 08 | Wed | 5 (LEC col 61) |
Special Columns:
- Weekend (W): 10-13, 24-27, 38-41, 52-55
- LEC (Wednesday PM): 19, 33, 47, 61
- Mid-block transition: Column 28 (Mar 23)
Faculty Section (Rows 31-43)
See "C vs AT Distinction" table above for foundational rules.
Weekly caps apply to C only (personal clinic), NOT to AT (supervision). If staffing is critical, faculty can do AT all day every day.
| Row | Template | Name | Min C/wk | Max C/wk | Admin | Notes |
|---|---|---|---|---|---|---|
| 31 | C19/FAC | [APD] | 0 | 0 | GME | APD, 100% admin |
| 32 | C19/FAC | [Faculty 1] | 2 | 4 | GME | |
| 33 | C19/FAC | [Faculty 2] | 2 | 4 | GME | |
| 34 | C19/FAC | [DFM Admin] | 1 | 1 | DFM | 90% DFM admin |
| 35 | C19/FAC | [Faculty 3] | 0 | 0 | GME | OUT Dec-Jun |
| 36 | C19/FAC | [Faculty 4] | 2 | 4 | GME | |
| 37 | C19/FAC | SM_FACULTY, Chelsea | 0 | 0 | SM/AT | Sports Med faculty, can also do AT |
| 38 | C19/FAC | [Faculty 5] | 2 | 2 | GME | |
| 39 | C19/FAC | [Faculty 6] | 0 | 0 | GME | DEP (deployed) |
| 40 | C19/FAC | [Faculty 7] | 0 | 0 | GME | FMIT weeks |
| 41 | ADJ/FAC | [Adjunct 1] | 0 | 0 | GME | FMIT/Call only |
| 42 | ADJ/FAC | [Adjunct 2] | 0 | 0 | GME | FMIT/Call only |
| 43 | SPEC/PSY | [Spec Faculty] | 2 | 2 | GME |
If MIN cannot be met: WARN - flag for PD review (e.g., conference week, faculty leave)
Faculty Weekly Target Distribution
Target half-days per week (10 total):
| Activity | Half-Days | Notes |
|---|---|---|
| C (Clinic) | 3 | Personal patients, has MIN/MAX caps |
| GME/DFM | 2-3 | Admin time |
| AT | 3-4 | Supervising residents (no cap) |
| PCAT/DO | 1 | If took call that week |
If no call that week: Extra AT slot available
Full-day preference: Faculty prefer full-day C (AM+PM same day) when possible - increases chances of full-day GME and better work-life balance.
Resident Section (Rows 9-25)
PGY-3 (Rows 9-13)
| Row | Name | Block N Rotation |
|---|---|---|
| 9 | [R3-1] | Remote_Site (TDY) |
| 10 | [R3-2] | NF (Night Float) |
| 11 | [R3-3] | FMC |
| 12 | [R3-4] | FMIT 2 |
| 13 | [R3-5] | NEURO → NF |
PGY-2 (Rows 14-19)
| Row | Name | Block N Rotation |
|---|---|---|
| 14 | [R2-1] | FMIT 2 |
| 15 | [R2-2] | SM (Sports Med) |
| 16 | [R2-3] | POCUS |
| 17 | [R2-4] | L&D Night Float |
| 18 | [R2-5] | Surg Exp |
| 19 | [R2-6] | Gyn Clinic |
PGY-1 (Rows 20-25)
| Row | Name | Block N Rotation |
|---|---|---|
| 20 | [R1-1] | FMC |
| 21 | [R1-2] | Peds Ward → Peds NF |
| 22 | [R1-3] | Offsite_Hospital L&D |
| 23 | [R1-4] | Peds NF → Peds Ward |
| 24 | [R1-5] | PROC |
| 25 | [R1-6] | IM |
Resident Rotation Scheduling (DETAILED)
Rotation Code Mapping
| Rotation Name | Primary Code | Pattern | Notes |
|---|---|---|---|
| Remote_Site | TDY | TDY all day | Off-site, entire block |
| NF (Night Float) | NF | OFF (AM) / NF (PM) | Works nights, minimal day |
| FMC | C/CV | C (AM) / CV or C (PM) | High clinic load |
| FMIT / FMIT 2 | FMIT | FMIT all day | Inpatient team |
| NEURO | NEURO | NEURO (AM) / C (PM) | Elective + clinic |
| SM (Sports Med) | SM | SM (AM) / C (PM) | Sports Med + clinic |
| POCUS | US | US (AM) / C (PM) | Ultrasound + clinic |
| L&D Night Float | L&D | L&D all day | Labor & Delivery nights |
| Surg Exp | SURG | SURG (AM) / C (PM) | Surgery + clinic |
| Gyn Clinic | GYN | GYN (AM) / C (PM) | Gynecology + clinic |
| Peds Ward | PedW | PedW all day | Pediatrics inpatient |
| Peds NF | PedNF | OFF (AM) / PedNF (PM) | Peds night float |
| Offsite_Hospital L&D | KAP | KAP all day | Off-site L&D |
| PROC | PR | PR (AM) / C (PM) | Procedures + clinic |
| IM | IM | IM all day | Internal Medicine ward |
All Known Resident Schedule Codes
| Code | Full Name | Usage |
|---|---|---|
| C | Clinic | FM Clinic precepting |
| C30 | Clinic 30-min | 30-min appointments |
| C40 | Clinic 40-min | 40-min appointments (intern) |
| C-I | Clinic-FMIT | FMIT resident clinic day |
| C-N | Night Float Clinic | Thursday PM for oncoming NF |
| CC | Continuity Clinic | Panel patients |
| CV | Virtual Clinic | Telehealth |
| V1, V2 | Virtual 1/2 | Virtual clinic blocks |
| PR | Procedures | Procedure clinic |
| VAS, VasC | Vascular | Vascular procedures |
| ADM | Admin | Administrative time |
| FLX | Flex | Flexible/catch-up time |
| FMIT | FM Inpatient Team | Inpatient rotation |
| NF | Night Float | Night shift |
| OFF | Day Off | Post-call or scheduled off |
| TDY | Temporary Duty | Off-site rotation (Remote_Site, etc.) |
| LV | Leave | Vacation/sick |
| HOL | Holiday | Federal holiday |
| HC | Holiday Call | On-call on holiday |
| W | Weekend | Saturday/Sunday |
| LEC | Lecture | Protected didactics (Wed PM) |
| SIM | Simulation | Sim lab |
| MM | M&M | Morbidity & Mortality conf |
| PI | Process Improvement | QI time |
| EPIC | EPIC Training | EHR training |
| Orient | Orientation | New rotation orientation |
| Coding | Coding | Billing/coding education |
| SM | Sports Medicine | SM rotation |
| aSM | Academic Sports Med | Wednesday AM for sports rotation |
| HLC | Houseless Clinic | Monday PM for R2/R3 |
| CLC | Continuity Learning | Thursday PM, weeks 2 and 4 |
| GYN | Gynecology | GYN clinic |
| NEURO | Neurology | Neuro elective |
| OPTH | Ophthalmology | Ophth elective |
| ENT | ENT | ENT elective |
| URO | Urology | Urology elective |
| PAL | Palliative | Palliative care |
| ENDO | Endocrinology | Endo elective |
| VA | VA Clinic | VA rotation |
| NBN | Newborn Nursery | NBN rotation |
| NICU | NICU | Neonatal ICU |
| KAP | Offsite_Hospital L&D | Off-site L&D (intern) |
| L&D | Labor & Delivery | Program L&D |
| LDNF | L&D Night Float | R2 L&D nights |
| IM | Internal Medicine | IM ward |
| PedW | Peds Ward | Pediatrics inpatient |
| PedNF | Peds Night Float | Peds nights |
| PedSP | Peds Subspecialty | Peds specialty |
| SURG | Surgery | Surgery rotation |
| PARTNER_CLINIC | Partner_Clinic | Partner_Clinic clinic |
Remote_Site TDY Schedule (Off-site Rotation)
Off-island rotation to Remote_Site, remote location.
Pre-departure (Week 1):
- 1st Thursday: Clinic (C)
- 1st Friday: Clinic (C)
- Weekend: Travel to Remote_Site
During Remote_Site (Weeks 2-4):
- All slots: TDY
Return (Week 4/5):
- Return Tuesday: Clinic (C) - 4th Tuesday of block
- Less travel time needed than overseas location
Key Points:
- Need clinic touchpoints before leaving and after returning
- TDY = Temporary Duty (off-site, cannot be scheduled locally)
- Wednesday PM still LEC if in town
Offsite_Hospital L&D Schedule (Intern - PGY-1)
Off-site rotation at Offsite_Hospital Medical Center.
| Day | AM | PM | Notes |
|---|---|---|---|
| Mon | KAP | OFF | Travel back from Offsite_Hospital |
| Tue | OFF | OFF | Recovery day |
| Wed | C | LEC | Continuity clinic! |
| Thu | KAP | KAP | On-site |
| Fri | KAP | KAP | On-site |
| Sat | KAP | KAP | On-site |
| Sun | KAP | KAP | On-site |
CRITICAL Pattern Summary:
def get_offsite_hospital(day_of_week, is_am, is_last_wed): if is_last_wed: return "LEC" if is_am else "ADV" if day_of_week == 1: # Monday return "KAP" if is_am else "OFF" elif day_of_week == 2: # Tuesday return "OFF" # Both AM and PM elif day_of_week == 3: # Wednesday return "C" if is_am else "LEC" else: # Thu-Sun return "KAP"
Key Points:
- Mon PM = OFF (travel back)
- Tue = OFF/OFF (recovery)
- Wed AM = C (continuity clinic, NOT KAP!)
- Thu-Sun = KAP/KAP
L&D Night Float Schedule (R2 - PGY-2)
Program Labor & Delivery night shift rotation.
| Day | AM | PM | Notes |
|---|---|---|---|
| Mon | OFF | LDNF | Sleeping days, working nights |
| Tue | OFF | LDNF | |
| Wed | OFF | LDNF | NO Wed AM clinic! |
| Thu | OFF | LDNF | |
| Fri | C | OFF | FRIDAY morning clinic! |
| Sat | W | W | Weekend |
| Sun | W | W | Weekend |
CRITICAL: Friday clinic, NOT Wednesday!
def get_ldnf(day_of_week, is_am, is_last_wed): if is_last_wed: return "LEC" if is_am else "ADV" if day_of_week == 5: # Friday return "C" if is_am else "OFF" # FRIDAY clinic! elif day_of_week in (6, 7): # Weekend return "W" else: # Mon-Thu return "OFF" if is_am else "LDNF"
Key Points:
- Friday AM = C (NOT Wednesday like other rotations!)
- Mon-Thu = OFF/LDNF (sleeping days, working nights)
- R2 rotation (not intern)
Night Float Timing (CORRECTED)
Night Float Schedule Structure:
- Starts: Thursday
- Ends: Wednesday (following week)
- Post-call: Thursday after (inter-block day)
C-N Code (Night Float Clinic):
- Thursday PM when oncoming to night float
- Replaces C30 for night float resident
- This preserves 2 weeks of continuity clinic
- Marcy uses C-N as cue to drop C30s from templates
Night Float Week Example: Thu (start): C-N in PM (oncoming clinic) Fri-Wed: NF (working nights) Thu (end): OFF (post-call inter-block)
Houseless Clinic (HLC)
Schedule:
- Monday PM for R2s and R3s only
- Every Monday of every rotation
- One resident (PGY-2 or PGY-3) per slot
- Staff coverage: twice a month/block (intermittent)
Orientation Blocks (Protected Time)
Procedures Orientation (Faculty_4):
- Label: SIM (Simulation)
- Faculty_4 needs procedures orientation time
- Sometimes booked accidentally - he says "it's fine" but should be protected
MedsTo Orientation (Faculty_5):
- Beginning of each block (timing varies)
- Protected time for medication reconciliation training
CLC (Continuity Learning Curriculum)
Schedule:
- Thursday PM, twice per block
- 2nd Thursday and 4th Thursday (NOT back-to-back weeks)
- NOT on 1st Thursday (beginning of block has problems)
Block Example: Week 1 Thu PM: NOT CLC (too early) Week 2 Thu PM: CLC ← First session Week 3 Thu PM: NOT CLC (gap week) Week 4 Thu PM: CLC ← Second session
Mid-Block Rotation Transitions
Some residents switch rotations mid-block (at column 28 / Mar 23):
# Check column 1 and 2 for rotation info first_half = sheet.cell(row=row, column=1).value # e.g., "Peds Ward" second_half = sheet.cell(row=row, column=2).value # e.g., "Peds NF" MID_BLOCK_COL = 28 # Start of second half def get_rotation(col, first_rot, second_rot): if second_rot and col >= MID_BLOCK_COL: return second_rot return first_rot
Intern Continuity Clinic (Wednesday AM) - CRITICAL
PGY-1 interns have protected Continuity Clinic on Wednesday mornings.
This is a HARD CONSTRAINT - interns must see their panel patients weekly regardless of rotation.
Wednesday AM for PGY-1: - Most rotations → C (Continuity Clinic) - FMC Block 1-6 → C60 (60-min appointments, new intern) - FMC Block 7-13 → C40 (40-min appointments, experienced intern) - ER → C30 (30-min appointments) Exceptions (no Wed AM clinic): - Night Float schedules (PedW night, NF) → PedW or OFF - Off-site (Remote_Site, Offsite_Hospital) → TDY or KAP - OB Intern → OB Cl - Transitional → ADM
Senior residents (PGY-2/3) do NOT have this constraint - their Wed AM depends on rotation:
- FMC (R3) → ADM
- FMC (R2) → FLX
- Procedures → SIM
- Sports Med → aSM
Resident Clinic Caps and Flex Requirements
Clinic Half-Days per Week by PGY Level:
| Level | Clinic Half-Days/Week | Notes |
|---|---|---|
| PGY-1 | 1 (ideally) | Protected for learning |
| PGY-2 | 2-3 | No more than 3 |
| PGY-3 | 3-4 | Most clinic time |
R2 on Sports Med: Maximum 3 clinics (not 4)
Flex Time Requirements:
| Level | FLX Half-Days/Week |
|---|---|
| PGY-1 | 2 half-days |
| PGY-2 | 1 half-day |
| PGY-3 (FMC) | At least 1 half-day |
Rotation Fill Patterns
def fill_rotation(sheet, row, rotation, col): """Fill a single half-day slot based on rotation type""" is_am = (col % 2 == 0) # Even columns are AM if rotation == 'Remote_Site': return 'TDY' elif rotation == 'NF': return 'OFF' if is_am else 'NF' elif rotation == 'FMC': return 'C' if is_am else ('CV' if col % 4 == 1 else 'C') elif rotation in ['FMIT', 'FMIT 2']: return 'FMIT' elif rotation == 'NEURO': return 'NEURO' if is_am else 'C' elif rotation == 'SM': return 'SM' if is_am else 'C' elif rotation == 'POCUS': return 'US' if is_am else 'C' elif rotation == 'L and D night float': return 'L&D' elif rotation == 'Surg Exp': return 'SURG' if is_am else 'C' elif rotation == 'Gyn Clinic': return 'GYN' if is_am else 'C' elif rotation == 'Peds Ward': return 'PedW' elif rotation == 'Peds NF': return 'OFF' if is_am else 'PedNF' elif rotation == 'Offsite_Hospital L and D': return 'KAP' elif rotation == 'PROC': return 'PR' if is_am else 'C' elif rotation == 'IM': return 'IM' else: return rotation # Use as-is
Workflow
1. Load Block Template2 Sheet
from openpyxl import load_workbook wb = load_workbook('schedule.xlsx') sheet = wb['Block Template2']
2. Identify Pre-Blocked Slots
Never overwrite these codes:
| Code | Meaning | Action |
|---|---|---|
| FMIT | FM Inpatient Team | Skip |
| LV | Leave | Skip |
| W | Weekend | Skip |
| PC | Post-call marker | Keep |
| PCAT | Post-Call Admin | Keep |
| DO | Day Off | Keep |
| LEC | Lecture | Skip |
| SIM | Simulation | Skip |
| SAFP | State AFP conf | Skip |
| BLS | BLS training | Skip |
| USAFP | USAFP conference | Skip |
| DEP | Deployed | Skip |
| aSM | Sports Med assist | Skip |
| PI | Process Improvement | Skip |
| MM? / MM | M&M conference | Skip |
3. Apply Call and Post-Call Rules
Call Schedule Structure
- Row 4: Staff Call - contains faculty name at the date column when on call
- Call is typically at the AM column (even col) of the call date
Call Back-to-Back Prevention (HARD CONSTRAINT)
Rule: Don't give faculty call on consecutive days - need gap days between call assignments.
BAD: Faculty on call Mar 15 AND Mar 17 (only 1 day gap) GOOD: Faculty on call Mar 15 AND Mar 19 (3 day gap)
Weekend Call Protection
Faculty with "W" (weekend) in their schedule row should NOT be assigned call that day.
- This is a soft preference, not a hard constraint
- Call row (Row 4) is separate from schedule row
FMIT Call Rules (CRITICAL)
FMIT Week Structure:
- Starts: Friday
- Ends: Thursday (following week)
- PC (Post-Call/Day Off): Friday after FMIT ends
During FMIT week, faculty covers:
- Friday night call (first night of FMIT)
- Saturday night call (second night of FMIT)
FMIT faculty CANNOT be placed on call:
- Sunday through Thursday during FMIT (they're on inpatient service)
- The Saturday immediately AFTER their FMIT ends (need recovery)
Example: Faculty on FMIT Fri Mar 13 - Thu Mar 19: - Mar 13 (Fri) ← FMIT starts, covers Fri call - Mar 14 (Sat) ← FMIT covers Sat call - Mar 15-19 (Sun-Thu) ← CANNOT be on call (on FMIT service) - Mar 20 (Fri) ← PC (day off after FMIT) - Mar 21 (Sat) ← CANNOT be placed on call (recovery)
FMIT Weekly Call Pattern Summary
FMIT week = Friday through Thursday FMIT faculty covers: - Friday night call (FMIT start) - Saturday night call (FMIT weekend) - CANNOT be on call Sun-Thu (on service) - PC Friday after (day off) - CANNOT be on call Sat after (recovery)
Post-Call Rules (PCAT/DO)
Only applies to non-FMIT faculty:
If faculty on call Night N AND NOT on FMIT: Day N+1 AM = PCAT (Post-Call Admin Time) Day N+1 PM = DO (Day Off)
FMIT faculty do NOT get PCAT/DO - they continue FMIT coverage.
Post-FMIT Friday Off (PC)
After completing FMIT week, faculty gets Friday off:
FMIT ends Thursday -> Friday = PC (both AM and PM) This Friday CANNOT have this faculty on call.
Call Assignment Pools
AUTO-ASSIGN Pool (Mon-Thu, Sun):
- FACULTY_1
- DFM_ADMIN
- FACULTY_4
- FACULTY_5
- SM_FACULTY (when not on FMIT/PC)
MANUAL-ONLY Pool (assigned by hand, typically Sundays):
- ADJUNCT_1
- ADJUNCT_2
- SPEC_FACULTY
SPECIAL RULES Faculty:
| Faculty | Rule |
|---|---|
| FACULTY_7 | Week-on/week-off FMIT: No Sun-Thu call during "off" weeks; available after FMIT but not immediately |
| APD | Available for call starting next week after post-FMIT (not same week) |
| FACULTY_2 | Only weeks 1-2 before FMIT week; no call in week immediately preceding FMIT |
Sunday Call Equity Pool
Sundays are AUTO-ASSIGNABLE but tracked separately for equity. No single faculty member should be assigned every Sunday in a block.
Sunday equity tracking: - Maintain separate sunday_counts vs weekday_counts - When assigning Sunday, sort by sunday_counts (lowest first) - Distribute evenly: aim for max 1 Sunday per faculty per block - W (weekend) in faculty row does NOT block call assignment
Important: The "W" code in a faculty's schedule row indicates weekend/off, but call assignments (row 4) are separate. Faculty can be ON CALL on a day they have "W" in their schedule.
SM_FACULTY Sports Medicine Rules
SM_FACULTY is the Sports Medicine (SM) faculty. She does NOT do regular clinic (C).
CRITICAL: SM_FACULTY does SM REGARDLESS of whether residents are on SM rotation.
- SM is SM_FACULTY's primary clinical work (not C like other faculty)
- 2-4 SM half-days/week independent of residents
- When residents ARE on SM, they must match SM Faculty's SM slots
Key Rules:
- SM requires SM_FACULTY: If a resident is scheduled SM, SM_FACULTY must ALSO be SM that slot
- No SM_FACULTY = No SM: If SM_FACULTY is blocked (FMIT, PC, LV, etc.), resident cannot do SM - change to C
- Weekly target: SM_FACULTY needs 2-4 SM slots per week (independent of residents)
- aSM is different: Academic Sports Med (aSM) = ultrasound teaching, not patient care; Wed AM preload
- FMIT blocks SM: When SM_FACULTY on FMIT, there can be NO SM that week
- Call eligible: SM_FACULTY takes call with normal PCAT/DO rules
- SM is closed loop: Does NOT use AT resources, SM Faculty's SM does NOT provide AT for other residents
SM_FACULTY Schedule Codes:
- SM = Sports Medicine (with resident)
- aSM = Academic Sports Med (teaching)
- GME = Admin time (when not SM)
- FMIT = Inpatient team (blocks SM)
- PC = Post-FMIT Friday off
Workflow:
- Check if resident on SM rotation (column 1)
- Find slots where both resident has SM AND SM_FACULTY available
- Assign SM_FACULTY SM to match (3-4 per week)
- Change resident SM → C where SM_FACULTY unavailable
AT Supervision Ratios (ACGME REQUIREMENT - CANNOT VIOLATE)
This is a HARD CONSTRAINT - minimum supervision ratios required by ACGME.
Resident AT Demand:
| PGY Level | AT Demand |
|---|---|
| PGY-1 (Intern) | 0.5 AT each |
| PGY-2 | 0.25 AT each |
| PGY-3 | 0.25 AT each |
Non-Clinic Activities = +1 AT each:
- PROC (Procedures)
- VAS (Vascular)
- SM (Sports Med) - covered by SM_FACULTY, not C faculty
- Any specialty clinic requiring dedicated supervision
Calculation:
Total AT Needed = (PGY-1 count × 0.5) + (PGY-2 count × 0.25) + (PGY-3 count × 0.25) + (PROC/VAS count × 1.0) ALWAYS ROUND UP - cannot have half a faculty member
Example:
2 PGY-1 in clinic = 1.0 AT 2 PGY-2 in clinic = 0.5 AT 1 PROC resident = 1.0 AT -------------------------- Total = 2.5 AT → Round up to 3 AT minimum
Coverage codes that count as AT:
- C (Clinic)
- PCAT (Post-Call Admin Time) - CAN precept, counts as AT
- CV (Virtual Clinic) - if precepting
Verification: Check rows 91-92 in Block Template2:
- Row 91: "Total Attendings Needed" (calculated demand)
- Row 92: "# Attendings Assigned" (current coverage)
Row 92 must be >= Row 91 for EVERY half-day slot.
Balancing AT: Coverage vs Demand
AT compliance is a two-way equation:
AT Coverage >= AT Demand
If coverage is short, you can EITHER:
- Increase coverage - Add faculty C (if available within caps)
- Decrease demand - Reduce resident clinic load
Reducing demand options:
- Move resident from C → ADM/FLX (removes their AT weight)
- Move resident from PROC → C (PROC = +1.0 AT, C = only 0.25-0.5 AT)
- Reduce clinic load on days with limited faculty
Priority: AT compliance > weekly clinic caps > GME preferences
Faculty caps are preferences, not hard constraints. ACGME supervision IS a hard constraint. However, before going over caps, first check if reducing resident demand is feasible.
Example - Mar 13 PM with most faculty at USAFP:
- Only 1 faculty available (Faculty_2)
- 7 residents in clinic = ~2.0 AT demand
- Solution: Move 2-3 residents from C → ADM for that slot
- Result: Reduced demand to match available coverage
Rotation Templates as Guidelines
Rotation templates (column 1-2) are GUIDELINES, not strict requirements. The actual schedule may vary based on:
- Faculty availability (e.g., no SM when SM_FACULTY unavailable)
- AT demand needs
- Call/post-call blocking
- Conference/educational requirements
Reading FMIT Schedule
Check "FMIT Attending (2025-2026)" sheet for weekly assignments:
- Column C: Week 1 attending
- Column D: Week 2 attending
- Column E: Week 3 attending
- Column F: Week 4 attending
Example Block N:
| Week | Dates | FMIT Attending |
|---|---|---|
| 1 | Mar 13-19 | Faculty_7 |
| 2 | Mar 20-26 | APD |
| 3 | Mar 27-Apr 2 | Faculty_7 |
| 4 | Apr 3-9 | Faculty_2 |
4. Assign Faculty Clinic (C)
For each weekday half-day:
- Get available faculty (not blocked, under weekly cap)
- Sort by remaining weekly capacity (highest first)
- Assign top 2 faculty
- Mark cells with "C"
5. Fill Faculty Admin Time
For remaining empty faculty cells:
- Most faculty → "GME"
- DFM_Admin → "DFM"
6. Fill Resident Schedules
For each resident:
- Read rotation from column 1 (and column 2 if mid-block switch)
- For each column 6-61:
- Skip if blocked (W, LV, LEC, etc.)
- Apply rotation pattern based on AM/PM
- Handle mid-block transitions at column 28
- Ensure LEC on all Wednesday PM slots (cols 19, 33, 47, 61)
7. Last Wednesday of Block Rules (CRITICAL)
Final Wednesday of block (Week 5) has special rules:
All Residents:
- AM: Lecture (LEC) - NOT clinic
- PM: Advising (ADV)
⚠️ Common Error: Scheduling morning clinic on last Wednesday - this is WRONG.
Last Wednesday Example ([Block End]): AM: LEC (Lecture) - protected PM: ADV (Advising)
8. Internal Medicine Last Wednesday Exception
Special Rule for IM rotation:
- Final Wednesday of IM rotation → Tuesday PM clinic instead
- Reason: Preserves continuity week count (otherwise only 3 weeks instead of 4)
- "Inverted day" logic - Thursday starts new week in scheduling
- Can be shorter (2 hours of patient care)
IM Resident Last Week Example: Tue PM: C (moved from Wed) Wed AM: LEC Wed PM: ADV
Complete Python Code Template
from openpyxl import load_workbook # Faculty configuration (min_c = MIN clinic/wk, max_c = MAX clinic/wk) # Weekly caps apply to C only, NOT AT (AT is unlimited) FACULTY = { 'APD': {'row': 31, 'min_c': 0, 'max_c': 0, 'admin': 'GME'}, 'Faculty_1': {'row': 32, 'min_c': 2, 'max_c': 4, 'admin': 'GME'}, 'Faculty_2': {'row': 33, 'min_c': 2, 'max_c': 4, 'admin': 'GME'}, 'DFM_Admin': {'row': 34, 'min_c': 1, 'max_c': 1, 'admin': 'DFM'}, 'Faculty_3': {'row': 35, 'min_c': 0, 'max_c': 0, 'admin': 'GME'}, 'Faculty_4': {'row': 36, 'min_c': 2, 'max_c': 4, 'admin': 'GME'}, 'SM_FACULTY': {'row': 37, 'min_c': 0, 'max_c': 0, 'admin': 'GME'}, # SM only, no personal C 'Faculty_5': {'row': 38, 'min_c': 2, 'max_c': 2, 'admin': 'GME'}, 'Faculty_6': {'row': 39, 'min_c': 0, 'max_c': 0, 'admin': 'GME'}, 'Faculty_7': {'row': 40, 'min_c': 0, 'max_c': 0, 'admin': 'GME'}, 'Adjunct_1': {'row': 41, 'min_c': 0, 'max_c': 0, 'admin': 'GME'}, 'Adjunct_2': {'row': 42, 'min_c': 0, 'max_c': 0, 'admin': 'GME'}, 'Spec_Faculty': {'row': 43, 'min_c': 2, 'max_c': 2, 'admin': 'GME'}, } # Resident configuration RESIDENTS = { 9: {'name': 'R3_1', 'pgy': 3}, 10: {'name': 'R3_2', 'pgy': 3}, 11: {'name': 'R3_3', 'pgy': 3}, 12: {'name': 'R3_4', 'pgy': 3}, 13: {'name': 'You', 'pgy': 3}, 14: {'name': 'R2_1', 'pgy': 2}, 15: {'name': 'R2_2', 'pgy': 2}, 16: {'name': 'R2_3', 'pgy': 2}, 17: {'name': 'R2_4', 'pgy': 2}, 18: {'name': 'R2_5', 'pgy': 2}, 19: {'name': 'R2_6', 'pgy': 2}, 20: {'name': 'R1_1', 'pgy': 1}, 21: {'name': 'R1_2', 'pgy': 1}, 22: {'name': 'R1_3', 'pgy': 1}, 23: {'name': 'R1_4', 'pgy': 1}, 24: {'name': 'R1_5', 'pgy': 1}, 25: {'name': 'R1_6', 'pgy': 1}, } # Rotation to code mapping (AM, PM) # NOTE: These are DEFAULT patterns - special days override (Wed AM intern continuity, etc.) ROTATION_CODES = { 'Remote_Site': ('TDY', 'TDY'), # Off-island, see Remote_Site TDY Schedule for details 'NF': ('OFF', 'NF'), # Night Float - starts Thu, ends Wed 'FMC': ('C', 'C'), # Family Medicine Clinic 'FMIT': ('FMIT', 'FMIT'), # FM Inpatient Team 'FMIT 2': ('FMIT', 'FMIT'), # FM Inpatient Team (2nd team) 'NEURO': ('NEURO', 'C'), # Neurology elective + clinic 'SM': ('SM', 'C'), # Sports Medicine + clinic 'POCUS': ('US', 'C'), # Point-of-care ultrasound + clinic 'L and D night float': ('OFF', 'LDNF'), # R2 L&D nights - Fri AM clinic! 'Surg Exp': ('SURG', 'C'), # Surgery experience + clinic 'Gyn Clinic': ('GYN', 'C'), # Gynecology + clinic 'Peds Ward': ('PedW', 'PedW'), # Pediatrics inpatient 'Peds NF': ('OFF', 'PedNF'), # Peds Night Float 'Offsite_Hospital L and D': ('KAP', 'KAP'), # Off-site L&D - see KAP schedule 'PROC': ('PR', 'C'), # Procedures + clinic 'IM': ('IM', 'IM'), # Internal Medicine ward 'VA': ('VA', 'VA'), # VA clinic rotation 'ENDO': ('ENDO', 'C'), # Endocrinology elective 'Derm': ('DERM', 'C'), # Dermatology elective 'MSK': ('MSK', 'C'), # Musculoskeletal elective } # Special columns WEEKEND_COLS = {10, 11, 12, 13, 24, 25, 26, 27, 38, 39, 40, 41, 52, 53, 54, 55} LEC_COLS = {19, 33, 47, 61} MID_BLOCK_COL = 28 # Pre-blocked codes (never overwrite) BLOCKED_CODES = {'FMIT', 'LV', 'W', 'PC', 'LEC', 'SIM', 'SAFP', 'BLS', 'PCAT', 'DO', 'USAFP', 'DEP', 'aSM', 'PI', 'MM?', 'MM', 'HOL', 'HC', 'TDY'} def fill_resident_schedule(sheet, row): """Fill a single resident's schedule based on their rotation""" rot1 = sheet.cell(row=row, column=1).value rot2 = sheet.cell(row=row, column=2).value for col in range(6, 62): current = sheet.cell(row=row, column=col).value if current and str(current).strip().upper() in BLOCKED_CODES: continue if col in WEEKEND_COLS: sheet.cell(row=row, column=col).value = 'W' continue if col in LEC_COLS: sheet.cell(row=row, column=col).value = 'LEC' continue # Get rotation (handle mid-block switch) rotation = rot2 if (rot2 and col >= MID_BLOCK_COL) else rot1 # Get AM/PM codes if rotation in ROTATION_CODES: am_code, pm_code = ROTATION_CODES[rotation] is_am = (col % 2 == 0) sheet.cell(row=row, column=col).value = am_code if is_am else pm_code else: sheet.cell(row=row, column=col).value = rotation
Manual Scheduling Workflow (Reference)
This is the typical workflow when scheduling by hand. The automated system should follow a similar approach:
Step 1: Load Non-Negotiables
- Absences (LV)
- FMIT assignments
- Conferences (SAFP, USAFP)
- Holidays (HOL)
- Protected time (LEC, SIM)
Step 2: Assign Sun-Thu Call
- Distribute call equitably across auto-assign pool
- Track weekday and Sunday counts separately
- Sunday = separate equity pool (max 1 per faculty per block)
- Result: Generates PCAT for every working day AM (baseline 1 AT)
Step 3: Load Resident Templates
- Apply rotation patterns from columns 1-2
- These are GUIDELINES, not strict requirements
- Adjust based on availability (e.g., no SM if SM_FACULTY blocked)
Step 4: Calculate AT Demand
- See AT Supervision Ratios above
- Check rows 91-92 for demand vs coverage
Step 5: Assign Faculty Clinic (C)
- Prioritize slots with higher resident load
- Prefer full-day C when possible (faculty preference)
- Full-day C increases chances of getting full-day GME
- Better work-life balance for faculty
- Keep physical clinic ≤6 people
Step 6: Fill Admin Time
- GME for most faculty
- DFM for DFM_Admin
- Fill remaining empty slots after C assignments
Physical Clinic Constraint
See "C vs AT Distinction" table for authoritative rules.
Max 6 people doing clinical work (C) per half-day slot. AT does NOT count toward this limit.
Why this matters: Staffing constraints limit how many patients can be seen. More than 6 physicians overwhelms support staff.
Validation Checklist
After completing assignments:
- No faculty exceeds weekly C cap (MIN and MAX)
- Post-call blocks (PCAT/DO) applied correctly
- FMIT faculty have no C on FMIT days
- No assignments on weekends (W columns)
- LEC on all Wednesday PM slots (cols 19, 33, 47, 61)
- PGY-1 interns have Continuity Clinic on Wednesday AM (C/C40/C60)
- Resident rotations match column 1/2 rotation names
- Mid-block transitions applied at column 28
- Pre-blocked codes not overwritten
- ACGME: AT coverage >= AT demand (Row 92 >= Row 91) for every slot
- Physical clinic: ≤6 people doing clinical work per slot
- Sunday call distributed (max 1 per faculty)
- No back-to-back call (need gap days between assignments)
- Night float starts Thursday, ends Wednesday, post-call Thursday
- C-N used for oncoming night float (Thursday PM)
- Last Wednesday: AM=LEC, PM=ADV (no morning clinic)
- IM residents: Tuesday PM clinic instead of final Wednesday
- R2 clinic caps: no more than 3 per week
- HLC assigned Monday PM for R2/R3
- CLC on 2nd and 4th Thursday PM (not back-to-back)
Common Issues
"Not enough coverage": Check if faculty are on leave (LV), USAFP, or DEP.
"Weekly cap exceeded": Recalculate weekly totals. Caps are PER WEEK, not total.
"Post-call conflict": If faculty on call Sunday, Monday AM/PM both blocked.
"Wrong rotation code": Check column 1 for rotation name, verify mapping exists.
"Mid-block not switching": Ensure column 2 has second rotation and col >= 28.
ROSETTA Stone Testing Approach
Ground truth file:
docs/scheduling/Block10_ROSETTA_CORRECT.xlsx
This file contains the CORRECT Block N schedule with all patterns applied correctly. Use it for TDD:
- Parse ROSETTA to get expected values
- Run expansion service to get actual values
- Compare cell-by-cell
- Fix until all tests pass
Test files:
- 24 parameterized testsbackend/tests/scheduling/test_expansion_vs_rosetta.py
- Parses ROSETTA xlsxbackend/app/utils/rosetta_parser.py
Run tests:
cd backend pytest tests/scheduling/test_expansion_vs_rosetta.py -v
Key patterns verified by ROSETTA:
| Resident | Rotation | Key Pattern |
|---|---|---|
| [R1-3] | KAP | Mon PM=OFF, Tue=OFF/OFF, Wed AM=C |
| [R2-4] | LDNF | Fri AM=C (not Wed!), Mon-Thu=OFF/LDNF |
| [R1-5] | PROC | Wed AM=C (intern continuity) |
| [R1-6] | IM | Wed AM=C, works weekends |
| You, Jae | NEURO→NF | Mid-block transition at col 28 |
| [R1-2] | PedW→PedNF | Mid-block + intern continuity |
| [R1-4] | PedNF→PedW | Reverse mid-block |
Priority rules (highest first):
- Last Wednesday → LEC/ADV (cols 60-61)
- Wednesday PM → LEC (cols 19, 33, 47)
- Rotation-specific patterns (KAP, LDNF, NF)
- Intern Wed AM continuity (PGY-1 → C)
- Mid-block transitions (col 28+)
- Default rotation pattern
Edge Cases and Resolutions
Call/Post-Call Boundary Conditions
| Edge Case | Resolution |
|---|---|
| Call before FMIT | Min 3-day buffer; prefer no call week before FMIT |
| PCAT/DO inter-block | Carries over via actual dates (Wednesday call → Thursday PCAT in next block) |
| Call on last day of block | PCAT/DO applies to first day of next block automatically |
FMIT Week Boundary Conditions
| Edge Case | Resolution |
|---|---|
| FMIT spanning blocks | Date-based, not block-tied; solver handles naturally |
| Post-FMIT PC in next block | Actual dates handle this (e.g., FMIT ends Thu → PC Fri may be in next block) |
| FMIT + Wednesday call prohibition | Cannot take Sun-Thu call during FMIT week |
SM/SM_FACULTY Dependencies
| Edge Case | Resolution |
|---|---|
| Multiple SM residents same slot | Max 2 residents; SM_FACULTY supervises both; must match half-day |
| SM_FACULTY post-call + SM scheduled | Medium constraint - avoid if possible, not a hard fail |
| SM_FACULTY on FMIT + SM residents | No SM that week; convert resident SM → C |
FMIT Clinic (C-I) by PGY Level
| PGY Level | C-I Day | Notes |
|---|---|---|
| PGY-1 | Wednesday AM | Hard constraint - intern continuity preserved |
| PGY-2 | Tuesday PM | FMIT resident clinic day |
| PGY-3 | Monday PM | FMIT resident clinic day |
C-I is PRELOADED, not solved. These slots are locked before solver runs.
Inter-Block Continuity
| Scenario | Handling |
|---|---|
| NF post-call inter-block | NF ends Wednesday → post-call Thursday = next block start |
| PCAT/DO from Wed call | Thursday AM/PM may be in next block |
| Rotation ending mid-week | Date arithmetic handles naturally |
Physical Capacity
| Constraint | Limit |
|---|---|
| Clinical work per half-day | Max 6 people (residents + faculty in C/CV/PR/VAS) |
| AT supervision | Does NOT count toward physical limit |
Holidays and Special Days
| Scenario | Resolution |
|---|---|
| CLC on holiday | Skip; does NOT reschedule to another day |
| HLC on holiday | Skip; does NOT reschedule |
| Intern continuity on holiday | Skip; continuity does NOT reschedule |
Resident Call System (Separate from Faculty)
Resident call is Chief-assigned and follows different rules:
- L&D 24-hour call (Friday)
- Night Float coverage
- Weekend call patterns
Note: Resident call is tracked in
resident_call_preloads table, separate from faculty call.
Related Documents
- Data model specificationdocs/architecture/HALF_DAY_ASSIGNMENT_MODEL.md
- Design session notes.claude/Scratchpad/session-104-half-day-model.md
- Faculty caps and constraintsreferences/faculty-roster.md
- Resident rotation patternsreferences/residents-rotations.md