--- name: fm-residency-scheduling description: "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." audience: coworker version: "1.5" last_updated: "2026-01-14" --- # FM 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):** 1. `preload` - FMIT, call, absences - NEVER overwritten 2. `manual` - Explicit human override 3. `solver` - Computed by CP-SAT 4. `template` - Default from WeeklyPattern ## 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)** 1. Absences (LV, HOL, DEP, TDY) 2. FMIT - both faculty and resident 3. FMIT Fri/Sat call - auto-assigned with FMIT 4. C-I (inpatient follow-up clinic): PGY-1 Wed AM, PGY-2 Tue PM, PGY-3 Mon PM 5. Night Float - full pattern including post-call 6. aSM - Wednesday AM for SM faculty (SM Faculty) 7. Conferences (SAFP, USAFP, LEC) 8. Protected time (SIM, PI, MM) **Phase 2: SOLVER (computed)** 1. Assign Sun-Thu call (min-gap decay) → auto-generates PCAT baseline 2. Solve outpatient (residents) - solver applies rotation patterns 3. Calculate supervision demand (resident C creates demand) 4. Assign faculty AT (to meet supervision demand) 5. Assign faculty C (personal clinic, counts toward physical capacity) 6. 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) 1. **FMIT/FMET first** - Lock inpatient faculty assignments 2. **Call assignments** - Distribute call with equity tracking 3. **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 - `references/excel-structure.md` - Complete workbook layout - `references/faculty-roster.md` - Faculty caps and admin types - `references/residents-rotations.md` - Resident rotations and codes - `references/block10-context.md` - Block N specific context ## 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:** ```python 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!** ```python 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): ```python # 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 ```python 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 ```python 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:** 1. **SM requires SM_FACULTY**: If a resident is scheduled SM, SM_FACULTY must ALSO be SM that slot 2. **No SM_FACULTY = No SM**: If SM_FACULTY is blocked (FMIT, PC, LV, etc.), resident cannot do SM - change to C 3. **Weekly target**: SM_FACULTY needs 2-4 SM slots per week (independent of residents) 4. **aSM is different**: Academic Sports Med (aSM) = ultrasound teaching, not patient care; Wed AM preload 5. **FMIT blocks SM**: When SM_FACULTY on FMIT, there can be NO SM that week 6. **Call eligible**: SM_FACULTY takes call with normal PCAT/DO rules 7. **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:** 1. Check if resident on SM rotation (column 1) 2. Find slots where both resident has SM AND SM_FACULTY available 3. Assign SM_FACULTY SM to match (3-4 per week) 4. 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:** 1. **Increase coverage** - Add faculty C (if available within caps) 2. **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: 1. Get available faculty (not blocked, under weekly cap) 2. Sort by remaining weekly capacity (highest first) 3. Assign top 2 faculty 4. 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: 1. Read rotation from column 1 (and column 2 if mid-block switch) 2. 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 3. 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 ```python 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: 1. Parse ROSETTA to get expected values 2. Run expansion service to get actual values 3. Compare cell-by-cell 4. Fix until all tests pass **Test files:** - `backend/tests/scheduling/test_expansion_vs_rosetta.py` - 24 parameterized tests - `backend/app/utils/rosetta_parser.py` - Parses ROSETTA xlsx **Run tests:** ```bash 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):** 1. Last Wednesday → LEC/ADV (cols 60-61) 2. Wednesday PM → LEC (cols 19, 33, 47) 3. Rotation-specific patterns (KAP, LDNF, NF) 4. Intern Wed AM continuity (PGY-1 → C) 5. Mid-block transitions (col 28+) 6. 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 - `docs/architecture/HALF_DAY_ASSIGNMENT_MODEL.md` - Data model specification - `.claude/Scratchpad/session-104-half-day-model.md` - Design session notes - `references/faculty-roster.md` - Faculty caps and constraints - `references/residents-rotations.md` - Resident rotation patterns