A flexible, multi-tenant scheduling platform for medical residency programs. Each program can define sites, services, call pools, shift templates, constraints, and fairness rules. The engine generates optimized schedules with explanations.
- Multi-Tenant Architecture: Support for multiple organizations, programs, sites, and services
- Flexible Configuration: Programs define rules via UI that writes to structured JSON schema
- Constraint System: Hard constraints (must never break) and soft constraints (scoring preferences)
- Explainability: Every assignment has a "why" and every conflict has a "why not"
- Plugin Architecture: Extensible constraints and scoring functions without forking
- Multi-Site Support: Sites can have different call types, coverage levels, and resident eligibility
- 6-Step Pipeline: Build demand → Eligibility → Ranking → Solve → Explain → Validate
- Greedy Solver: Fast MVP with backtracking (upgradeable to OR Tools CP SAT)
- Fairness Scoring: Configurable points system for nights, weekends, holidays, site travel
- Swap Workflow: Pre-check constraints, approval chains, automatic execution
- Next.js 16 + TypeScript
- PostgreSQL + Prisma ORM
- NextAuth for authentication
- BullMQ for background job processing
- Export: PDF, CSV, iCal
- Node.js 18+
- PostgreSQL database
- Redis (for background jobs)
- Clone the repository
git clone <repository-url>
cd scheduling-platform- Install dependencies
npm install- Set up environment variables
Create a
.envfile:
DATABASE_URL="postgresql://user:password@localhost:5432/scheduling_platform"
NEXTAUTH_SECRET="your-secret-key"
NEXTAUTH_URL="http://localhost:3000"
REDIS_HOST="localhost"
REDIS_PORT="6379"- Set up the database
npx prisma migrate dev
npx prisma generate- Start Redis (for background jobs)
# Using Docker
docker run -d -p 6379:6379 redis
# Or install Redis locally- Run the development server
npm run devscheduling-platform/
├── app/ # Next.js app directory
│ ├── api/ # API routes
│ └── admin/ # Admin pages
├── lib/
│ ├── modules/ # Core modules
│ │ ├── auth/ # Authentication
│ │ ├── configuration/ # Program configuration
│ │ ├── scheduling/ # Scheduling engine
│ │ ├── constraints/ # Constraint plugins
│ │ ├── solver/ # Solver implementations
│ │ ├── workflow/ # Swap workflows
│ │ ├── reporting/ # Fairness reporting
│ │ ├── export/ # Export modules
│ │ ├── audit/ # Audit logging
│ │ └── jobs/ # Background jobs
│ ├── types/ # TypeScript types
│ └── prisma.ts # Prisma client
├── prisma/
│ └── schema.prisma # Database schema
└── README.md
- Organization: Hospital or University
- Site: Each hospital or location
- Service: ER, Psych, IM, Surgery, etc.
- Program: Psych, FM, IM, Surgery
- ProgramYear: PGY1, PGY2, etc.
- Resident: User plus program membership
- RotationBlock: Date range, service, site, call pool mapping
- CallPool: Group eligible to cover certain shifts
- ShiftType: Night, 24h, Home call, Weekend day, Pager, Backup
- ShiftTemplate: Recurring patterns, coverage required
- CoverageRequirement: How many of what type, at which site, at which times
- ShiftInstance: Generated per date from templates
- Assignment: Shift instance plus resident
- Availability: Vacation, post call protection, academic day, leave
- RuleSet: Per program, per site, per service, per call pool
- ConstraintDefinition: Plugin type plus parameters
- ConstraintEvaluation: Violations and explanations
- Preference: Ranked choices and weights
- FairnessMetric: Points system, weekend counts, nights, holidays
- SwapRequest: With approval chain
- ExceptionOverride: Admin forced assignment with reason
- AuditLog: Everything
The platform includes a plugin architecture for constraints. Default plugins:
- max_shifts_per_period: Maximum shifts in a time period
- min_rest_between: Minimum rest hours between shifts
- no_consecutive_24h: No consecutive 24-hour shifts
To add a custom constraint:
- Create a new plugin class extending
BaseConstraint - Implement
evaluate()andexplain()methods - Register it in the constraint registry
POST /api/schedules/generate
Body: {
programId: string
startDate: string (ISO date)
endDate: string (ISO date)
callPoolIds?: string[]
siteIds?: string[]
userId?: string
async?: boolean
}
POST /api/swaps
Body: {
requesterId: string
assignmentId: string
targetId?: string
targetAssignmentId?: string
reason?: string
}
- Multi program, multi site data model
- Admin CRUD for sites, services, programs, pools, shift types, templates
- Basic calendar views
- Template expansion to shift instances
- Eligibility calculation
- Greedy backtracking solver
- Constraint evaluation and conflict report
- Manual edits with live validation
- Publish lock schedule
- Swap workflow with pre checks
- Audit logs
- PDF, CSV, iCal feeds
- Notifications
- OR Tools CP SAT integration
- Optimization tuning
- Better fairness controls
MIT