Introduction
Every fall, administrators at thousands of schools face the same impossible task. Teachers have preferences. Rooms have capacities. Students need courses. Time is finite. The combinations number in the millions. Yet by the first day of class, students sit in desks, teachers stand at boards, and the year begins. Someone solved the puzzle.
For decades, that someone was a veteran administrator with institutional memory and a wall covered in sticky notes. They knew that Mrs. Patterson absolutely cannot teach first period because she drives from two towns over. They remembered that the physics lab is being renovated until October. They internalized years of informal negotiations about who gets the good rooms and who teaches the dreaded Friday afternoon slots.
This approach works until it doesn't. The veteran retires. The school grows. Requirements compound. What once took a week of shuffling takes a month. What once felt like mastery feels like drowning. The sticky notes fall off the wall.
Computer-assisted scheduling emerged from operations research, a field that applies mathematics to complex logistical problems. The techniques developed during World War II to optimize supply chains and military operations found peacetime applications in airlines, factories, and eventually schools. The core insight was that many apparently impossible puzzles become tractable when expressed as mathematical models.
Integer Linear Programming represents one of the most powerful approaches to scheduling problems. The technique expresses what you want as an objective to maximize or minimize, what you must have as constraints that cannot be violated, and what you'd prefer as soft constraints that carry penalties when violated. A solver—a sophisticated mathematical engine—explores the space of possible solutions and finds one that optimizes the objective while satisfying the constraints.
The scheduling problem is NP-hard, which means no algorithm can guarantee finding the optimal solution quickly for all cases. But practical instances usually aren't worst cases. Modern solvers incorporate decades of algorithmic improvements—branch and bound, cutting planes, preprocessing, parallel search—that make real-world problems tractable. A schedule that would take a human weeks to construct by trial and error can often be computed in minutes.
Building a school scheduling system with vibe coding proved to be an ideal match for several reasons. The mathematical formulation is well-documented in academic literature. The constraints are explicit and logical—no room can host two classes simultaneously, no teacher can be in two places at once. The objective function is a matter of policy decisions, not technical discovery. What remains is translating these requirements into working software, which is exactly what AI-assisted development excels at.
The system that emerged from my Claude Code sessions handles the complete workflow. Data entry captures teachers, classes, rooms, and time slots. Constraint specification lets administrators express both hard requirements and soft preferences. The solver generates schedules that satisfy requirements while optimizing preferences. The interface displays results, highlights conflicts, and enables manual adjustments. Export produces calendar files that integrate with other systems.
Alternative approaches exist but fall short in various ways. Genetic algorithms are popular but unreliable—they get stuck in local optima and offer no guarantee of finding feasible solutions. Constraint programming works for small problems but struggles to scale. Manual scheduling is error-prone and exhausting. Commercial scheduling software costs tens of thousands of dollars annually, often runs as inflexible black boxes, and locks institutions into vendor relationships.
Integer Linear Programming gives you mathematical guarantees, scalability, transparency, and control. The model is visible and understandable. You can explain why an assignment was made by pointing to the constraints it satisfies. When the solver says no feasible schedule exists, you can analyze which constraints conflict. This transparency builds trust in a way that black-box solutions never can.
Building this system revealed patterns that apply to any optimization problem. How to represent decision variables. How to express constraints that solvers understand. How to weight competing objectives. How to interpret solver results. How to handle infeasibility gracefully. These patterns transfer to employee scheduling, sports league fixtures, exam timetabling, and dozens of other domains where ILP shines.
The book that follows documents this journey. Not as a tutorial to copy line by line, but as a guide to the concepts and techniques that make such systems possible. The specific implementations belong to your vibe coding sessions. The understanding of why those implementations work belongs here.
By the end, you'll grasp how to model a scheduling problem as ILP, how to build a solver integration that produces real schedules, how to present those schedules through a usable interface, and how to handle the edge cases that make production systems robust. More importantly, you'll understand a category of problems that mathematical optimization solves elegantly—problems that appear impossible until you learn to think in constraints.
Project Setup
The architecture of a scheduling system differs from typical web applications in one crucial respect: at its heart sits a mathematical solver that transforms data into solutions. The rest of the system—data entry, visualization, export—exists to feed that solver and present its results. Understanding this inversion clarifies every architectural decision.
The solver itself is a black box, and deliberately so. You don't need to understand the branch-and-bound algorithm or the cutting plane methods that make ILP tractable. You feed the solver a model—variables, constraints, objective—and it returns a solution or reports that none exists. The complexity lies in building that model correctly. Every constraint you add shapes the solution space. Miss a constraint and you get invalid schedules. Add contradictory constraints and you get no schedule at all.
The stack that emerged from my vibe coding sessions was deliberately conventional outside the solver integration. Node.js with TypeScript provides type safety for the complex data structures that scheduling requires. PostgreSQL stores the relational data—teachers, classes, rooms, time slots—and the relationships between them. Express handles the API layer. A Vite frontend displays schedules and captures user input.
HiGHS serves as the solver. Originally developed at the University of Edinburgh, HiGHS has become one of the fastest open-source optimization solvers available. The project is part of COIN-OR, the Computational Infrastructure for Operations Research initiative. What makes HiGHS particularly suitable for this project is its WebAssembly build, which means you can run the solver in Node.js without native dependencies. Installation is simply adding the highs package.
The vibe coding technique that worked best for initial setup was describing the overall architecture and letting Claude generate the project structure. Not starting from configuration files, but from what the system needs to do. A data service handles CRUD operations for teachers, classes, rooms, and time slots. An ILP builder transforms that data into the mathematical model. A solver service runs HiGHS and returns results. A schedule service orchestrates the flow from data to solution and back.
The database schema deserves careful thought because it shapes how data flows into the model. Teachers have names, departments, maximum teaching loads, and availability patterns. Classes represent course definitions—the template from which sections are created. Sections are instances of classes assigned to specific teachers. Rooms have capacities and types—regular classroom versus laboratory versus auditorium. Time slots define the scheduling grid, typically six to eight periods across five weekdays.
The distinction between classes and sections matters for scheduling. Algebra I is a class. Three different sections of Algebra I, each taught by different teachers to different groups of students, are what actually get scheduled. This separation enables tracking which teacher handles which section and how many periods each section needs per week.
Constraints in the database use a flexible structure that can represent many different requirement types. A constraint has a type, a priority indicating whether it's hard or soft, a weight for soft constraints, and parameters that vary by constraint type. Teacher unavailable constraints reference specific teachers and time slots. Room required constraints bind sections to specific rooms. Maximum consecutive period constraints set limits on how long teachers work without breaks.
The solver wrapper that HiGHS requires follows a straightforward pattern. Initialize the solver, which loads the WebAssembly module. Set options for time limits, optimality gaps, and thread counts. Pass the model in LP format, a text-based representation of the optimization problem. Receive the result, which includes solution status, objective value, and variable assignments.
LP format is the lingua franca of linear programming solvers. It's a human-readable text format where you specify the objective, list constraints as linear equations, define variable bounds, and mark which variables are binary or integer. The format is verbose but unambiguous, making it ideal for debugging—you can look at the generated LP file and trace exactly what constraints are being passed to the solver.
The API layer exposes CRUD endpoints for each entity type plus specialized endpoints for scheduling operations. Generate a new schedule, get schedule status, view assignments, move assignments manually. The scheduling endpoints connect to the solver orchestration and return results including any warnings or errors.
Docker Compose simplifies development by running PostgreSQL in a container. The database initializes from migration files that create the schema. Sample data helps with testing—a realistic set of teachers, classes, and rooms lets you verify the solver produces sensible results.
The technique that accelerated development most was building the system in layers, verifying each before moving to the next. First, confirm that data entry and retrieval work correctly. Then build the model generation without solving, examining the generated LP file to verify it captures the intended constraints. Then integrate the solver and verify it produces solutions. Finally build the interface that presents those solutions to users.
Testing a scheduling system requires realistic data. A toy example with three teachers and five rooms finds solutions instantly but doesn't reveal performance problems. A school-scale dataset with fifty teachers, thirty rooms, and two hundred sections exercises the solver meaningfully. The test suite should include cases that are feasible, cases that are infeasible, and cases that stress the time limits.
The project structure that emerged follows familiar patterns. Source code in a source directory, organized by layer. Database code including migrations and the client. Services containing the business logic. API routes handling HTTP requests. Types defining the data structures that flow through the system. The frontend in a separate directory with its own build tooling.
Configuration through environment variables keeps sensitive information out of the codebase. Database connection strings, API ports, solver options—all configurable without code changes. A development configuration file sets sensible defaults while production deploys with appropriate values.
The foundation exists to support what comes next: modeling the scheduling problem as a set of mathematical constraints that the solver can understand and optimize.
The Scheduling Data Model
School scheduling answers one question across every possible combination: when does who teach what where? The entities embedded in that question—teachers, classes, rooms, time slots—form the data model that everything else builds upon. Getting this model right determines whether the system captures real-world complexity or fights against it.
Teachers are the most constrained resource. A teacher can only be in one place at a time—the most fundamental hard constraint. But teachers also have limited availability. Part-time faculty work only certain days. Teachers with coaching duties leave early on game days. Some teachers commute long distances and can't teach first period. Personal preferences layer on top of availability: morning people versus night owls, Friday avoiders, those who prefer back-to-back classes for focus versus those who need breaks between sessions.
The teacher data model captures all of this. Basic information like name, email, and department. Maximum periods per day and per week to prevent burnout. Availability records that specify, for each time slot, whether the teacher is available and how strongly they prefer or avoid that slot. A preference scale from strongly avoid to strongly prefer gives the optimization something to work with beyond simple yes or no.
Classes and sections require careful distinction that casual thinking often blurs. A class is a course definition—Algebra I, World History, Introduction to Chemistry. It has a name, a course code, a department, required periods per week, and flags for special requirements like laboratory space. A class is an abstract template.
A section is a concrete instance of a class. When fifty students sign up for Chemistry and the lab only holds twenty-five, you create two sections. Each section has an assigned teacher, a specific student capacity, and its own scheduling needs. The class says Chemistry needs lab space three times per week. The two sections are what actually get scheduled into specific rooms and time slots.
This separation enables modeling that matches how schools actually work. Multiple teachers can teach the same class without confusion. Constraints can apply at the class level, affecting all sections, or at the section level, affecting just one. A lab requirement on the class propagates to every section. A specific room requirement on a section applies only to that instance.
Rooms vary in ways that matter for scheduling. Capacity determines how many students can fit. Room type distinguishes regular classrooms from labs, auditoriums, computer rooms, and specialty spaces. Equipment listings track what each room offers—projectors, whiteboards, computers, science benches. These attributes connect to section requirements: a chemistry section needs a lab with appropriate equipment; a lecture section just needs enough seats.
Time slots define the scheduling grid. Most schools use five days per week—Monday through Friday—with six to eight teaching periods per day. Each slot has a day, a period number, and actual clock times. Some slots are breaks rather than teaching periods—lunch, passing time between classes. The break flag keeps these slots out of the assignment pool while maintaining accurate time tracking.
The relationship between time slots and availability enables fine-grained control. A teacher might be unavailable on Monday first period but available every other Monday slot. They might be unavailable all of Wednesday for another commitment. The availability records capture this granularity without requiring special cases in the scheduling logic.
Constraints deserve their own data model because they vary so widely. Hard constraints must be satisfied for the schedule to be valid. No teacher in two places at once. No room double-booked. Teacher available when scheduled to teach. These constraints admit no flexibility—violating them produces an invalid schedule.
Soft constraints should be satisfied but can be violated with a penalty. Minimize consecutive periods for teachers. Honor period preferences when possible. Keep sections of the same class in the same room across the week. These constraints guide optimization toward better solutions without making perfection mandatory.
The constraint data model uses a flexible structure. Each constraint has a type that identifies what kind of constraint it is. A priority flag distinguishes hard from soft. A weight for soft constraints indicates relative importance—higher weights mean greater penalties for violation. Parameters store the constraint-specific details in a structure that varies by type.
This flexibility enables adding new constraint types without schema changes. A teacher unavailable constraint stores a teacher ID and time slot ID. A maximum consecutive constraint stores the maximum allowed and which teacher it applies to. A room required constraint binds a section to a specific room. The parameters field accommodates whatever data each constraint type needs.
Validation before solving catches problems early. Every section needs an assigned teacher—you can't schedule what has no one to teach it. Total required periods across all sections must not exceed total available room-slots—otherwise no feasible schedule exists. Lab sections need enough lab periods available. Teachers with many sections assigned need enough available periods to teach them all.
These validation checks surface impossible situations before the solver wastes time proving infeasibility. A helpful error message explaining that the chemistry department needs more lab time than the school has labs beats a cryptic solver failure after five minutes of computation.
The vibe coding approach to building this data layer worked well because the domain is explicit. Describe what a teacher record needs to contain. Describe the relationship between classes and sections. Describe how constraints should be stored. Claude generates the database schema and the TypeScript types that match it. The services that create, read, update, and delete these entities follow from their structure.
Testing the data layer requires realistic scenarios. Load a hundred teachers with varied availability. Create two hundred sections across different departments. Define thirty rooms with different capacities and types. Add dozens of constraints representing actual school policies. This realistic data reveals edge cases that toy examples miss.
The data model is the foundation on which the ILP model builds. Every teacher, every section, every room, every time slot becomes input to the optimization. Every constraint becomes a mathematical equation. The cleaner the data model, the cleaner the translation to mathematics.
ILP Fundamentals
Linear Programming begins with a deceptively simple idea. You have decisions to make, represented as variables. You have rules those decisions must follow, expressed as linear constraints. You have a goal, captured as an objective function. Find the values for the variables that satisfy all constraints while optimizing the objective.
Consider a factory making chairs and tables. Each chair requires two hours of labor and earns twenty dollars profit. Each table requires four hours and earns fifty. You have forty hours of labor available. How many of each should you make to maximize profit?
The decisions are how many chairs and how many tables. The constraint is that total labor cannot exceed forty hours. The objective is maximizing profit. Write this mathematically and a solver finds the answer instantly: make zero chairs and ten tables, for five hundred dollars profit. Tables earn more profit per labor hour, so the solver produces only tables.
Integer Linear Programming adds a crucial refinement. Standard LP allows fractional solutions—three and a half tables, for instance. Many real problems require whole numbers. You can't schedule half a class. A binary variable goes further, allowing only zero or one. This represents yes-or-no decisions: does this section get assigned to this room at this time? Binary variables are the building blocks of scheduling models.
The scheduling problem maps naturally onto this framework. The decision variables are binary: does section S get assigned to room R at time slot T? If yes, the variable equals one. If no, zero. For a school with a hundred sections, thirty rooms, and forty time slots, this creates a hundred times thirty times forty possible assignments—one hundred twenty thousand binary variables.
That number sounds enormous, but it's actually manageable. Most combinations are infeasible from the start. A section needing a lab can't use a regular classroom. A section with fifty students can't use a room that holds thirty. A teacher unavailable at 8 AM can't teach any section at 8 AM. Filtering out impossible combinations before building the model dramatically reduces problem size.
Hard constraints form the backbone of any scheduling model. A teacher can only be in one place at a time. For each teacher and each time slot, the sum of all assignments involving that teacher's sections at that time slot must be at most one. In mathematical terms, you sum the assignment variables for all sections taught by teacher T and all rooms R at time slot t, and constrain that sum to be less than or equal to one.
Room conflict constraints work the same way. For each room and each time slot, sum all assignments using that room at that time—the result must be at most one. No room can host two sections simultaneously.
Period requirements ensure every section gets scheduled appropriately. If a section needs five periods per week, sum all its assignments across all rooms and all time slots—that sum must equal exactly five. Not four, not six, exactly five.
Teacher availability constraints prevent scheduling when teachers can't work. If teacher T is unavailable at time slot t, then all assignment variables for T's sections at t must equal zero. The solver can't select these assignments because they're constrained to be impossible.
Room compatibility prevents mismatches. If section S requires a lab but room R isn't a lab, all assignment variables for S and R must be zero regardless of time slot. If room R holds thirty students but section S has forty, again all those variables must be zero.
Soft constraints work differently. Instead of requiring satisfaction, they penalize violation. Introduce auxiliary variables that measure how much each soft constraint is violated. Add these to the objective function with weights representing their importance. Minimizing the objective then minimizes constraint violations.
Teacher preference handling illustrates this pattern. If teacher T dislikes period p with preference negative two, every assignment of T's sections at p adds a penalty to the objective. The solver can still make those assignments, but it will prefer alternatives. The weight controls how hard the solver tries to honor preferences—higher weights mean stronger avoidance.
Consecutive period limits use auxiliary variables creatively. Define a variable that equals one if teacher T has more than three consecutive periods starting at a specific point. Constrain this variable to be at least as large as the excess over three. Add this variable to the objective with a significant penalty. The solver will avoid long stretches unless unavoidable.
The objective function combines all these penalties. Minimize the sum of all soft constraint violations multiplied by their weights. A teacher preference violation might weight five. A consecutive period violation might weight ten. The relative weights determine tradeoffs—the solver will accept two preference violations to avoid one consecutive period violation.
Building the model in code follows the mathematical structure. Create variables by iterating through sections, rooms, and time slots, filtering to only feasible combinations. Add constraints by iterating through teachers and time slots, summing relevant variables. Add soft constraint penalties by augmenting the objective function.
The vibe coding technique for model building was describing constraints in English and letting Claude generate the mathematical translation. No teacher can be in two places at once—express this as a sum constraint. Each section needs exactly its required periods—express this as an equality constraint. These translations are well-documented in optimization literature, making them ideal for AI-assisted development.
LP format output lets you verify the model before solving. The format is text-based and readable. You can examine constraints to verify they capture your intent. When solving fails, the LP file becomes a debugging tool—you can identify which constraints conflict or which variables have no feasible assignment.
Understanding these fundamentals enables applying ILP to any scheduling problem. Employee shifts follow the same patterns as school periods. Conference sessions follow the same patterns as class sections. The entities change, but the mathematical structure remains constant. Learn to think in constraints, and you gain a powerful tool for optimization problems across many domains.
Modeling the Schedule as ILP
The complete scheduling model combines everything discussed so far into a unified mathematical object that solvers can optimize. Building this model correctly determines whether the system produces usable schedules or fails mysteriously. The translation from domain requirements to mathematical constraints is where vibe coding shines—describing what you need in English and receiving the mathematical formulation in return.
Indexing efficiently becomes critical as model size grows. Rather than repeatedly scanning entity lists, build index structures during model construction. Map each section to its teacher. Map each teacher to the list of sections they teach. Map each section to its required periods. Map each room to its capacity. Map each time slot to its day and period number. These lookups happen thousands of times during constraint generation; making them fast makes model building practical.
Variable creation starts with the full combinatorial space and filters down to feasibility. For each section, each room, each time slot—that's the outer loop structure. Within that loop, check feasibility. Does this section need a lab that this room doesn't provide? Skip it. Does this section have more students than this room's capacity? Skip it. Is the section's teacher unavailable at this time slot? Skip it. Only create variables for combinations that could actually occur in a valid schedule.
This filtering dramatically reduces model size without losing any valid solutions. A school with a hundred sections, thirty rooms, and forty time slots has a theoretical maximum of a hundred twenty thousand variables. After filtering for room compatibility and teacher availability, the practical count might be fifty thousand or less. Smaller models solve faster.
The model builder maintains two parallel data structures. The variables collection stores information about each decision variable—its name, its type, its bounds, its objective coefficient. The constraints collection stores the linear constraints—each constraint's name, the coefficients for each variable involved, the sense of the comparison, and the right-hand side value.
Teacher conflict constraints iterate over teachers and time slots. For each combination, find all variables involving any section taught by that teacher at that time slot. If multiple such variables exist, add a constraint requiring their sum to be at most one. If only one exists or none exist, no constraint is needed for that combination.
Room conflict constraints follow the same pattern. For each room and time slot combination, find all variables using that room at that time. Add a constraint if multiple variables exist. The constraint ensures only one section can occupy each room-slot pair.
Period requirements iterate over sections. For each section, sum all variables assigning that section anywhere—across all rooms and time slots. Constrain this sum to equal exactly the section's required periods. If a section needs five periods, it gets exactly five, no more and no less.
One section per time slot prevents a section from appearing in multiple rooms simultaneously. For each section and time slot, sum the variables across rooms. This sum must be at most one—a section can use at most one room at any given time. This constraint seems redundant with teacher conflicts, but it catches edge cases where different constraints interact unexpectedly.
Soft constraint handling adds objective coefficients to variables. When teacher T dislikes time slot t, find all variables for T's sections at t. Increase their objective coefficients by the penalty amount. The solver, minimizing the objective, will avoid these assignments when alternatives exist.
Consecutive period handling requires auxiliary variables. For each teacher and each potential start of a consecutive sequence, create a helper variable. Add a constraint linking this helper to the actual assignment variables—if the sum of assignments in a window exceeds the maximum allowed consecutive periods, the helper must be positive. The helper carries an objective penalty that discourages but doesn't forbid long teaching stretches.
The builder class accumulates variables and constraints, then exports the complete model. The export format matters for solver integration. LP format, the standard text-based representation, lists the objective first, then constraints, then variable bounds, then integer and binary markers. This format is human-readable, making it useful for debugging.
Model statistics provide insight into complexity. Count total variables, binary variables, constraints, and non-zero coefficients. The ratio of non-zeros to the product of variables and constraints indicates matrix density—sparser matrices often solve faster. A typical medium-sized school produces around fifty thousand variables and five thousand constraints with perhaps two hundred thousand non-zero coefficients.
Validation before solving catches model errors early. Every section should have at least one feasible assignment—some combination of room and time slot that passes all filters. If a section has no feasible assignments, the model is guaranteed infeasible. Check that period requirement constraints reference enough variables to be satisfiable. If a section needs five periods but only four feasible room-slot combinations exist, no solution is possible.
These validation checks transform cryptic solver failures into actionable error messages. The solver reports infeasibility with minimal explanation. The validator explains that chemistry section three needs a lab but no labs are available on Tuesday afternoon when the teacher is available. This specificity makes problems fixable.
The vibe coding approach to model building worked exceptionally well. Describe each constraint type in plain English. Request the mathematical formulation and the code that implements it. Review the generated constraints against your understanding of the domain. Iterate when something doesn't match expectations. The mathematical patterns are well-documented; translating them to code is mechanical; AI handles mechanical translation fluently.
Testing the model builder requires examining the generated LP files. For a small test case, manually verify that constraints exist for every teacher-slot pair with multiple sections. Verify that period requirements match section needs. Verify that objective coefficients appear where preferences apply. This verification builds confidence that the mathematical model captures your intent.
The model builder is where domain knowledge becomes mathematics. Every requirement administrators express—from hard constraints like room capacity to soft preferences like avoiding 8 AM—becomes part of the model that the solver optimizes. The cleaner this translation, the better the resulting schedules.
Integrating the ILP Solver
HiGHS transforms mathematical models into solutions. Originally developed at the University of Edinburgh, it has become one of the most capable open-source optimization solvers available. Its WebAssembly build means you can run it in Node.js without native dependencies—a significant advantage for deployment simplicity. Installation is trivial, solving is fast, and the license is permissive.
The solver accepts LP format input, the standard text representation of linear programs. Your model builder generates this format. The solver returns a result containing status, objective value, and variable assignments. The status might be optimal, indicating the best possible solution was found. It might be feasible, indicating a valid solution exists but optimality wasn't proven within time limits. It might be infeasible, meaning no solution exists that satisfies all constraints. Handling each status appropriately is essential.
Solver options control the search process. Time limits prevent runaway computation—setting a five-minute limit ensures you get some answer even if optimality requires hours. The MIP gap setting controls when to stop searching: a one percent gap means the solver stops when it finds a solution proven to be within one percent of optimal. Accepting slightly suboptimal solutions often saves substantial time. Thread counts let the solver parallelize on multi-core systems. Presolve settings control preprocessing that often simplifies the model dramatically.
Initializing the solver loads the WebAssembly module. This happens once and the solver persists for subsequent calls. The initialization delay is noticeable—perhaps a second or two—but subsequent solves start immediately. In a server context, initialize once at startup and reuse for all scheduling requests.
Parsing solver results requires understanding what HiGHS returns. The status field uses standard optimization terminology. Optimal means perfect. Feasible means good enough. Infeasible means impossible. Time limit reached means the solver ran out of time but may have found a feasible solution. The objective value tells you how much soft constraint violation the solution has—lower is better.
Variable assignments come back as an array of values, one per variable in the model. For binary variables, values near one indicate selection; values near zero indicate non-selection. The threshold of 0.5 separates them. Due to numerical precision, values might be 0.9999 or 0.0001 rather than exactly one or zero. Rounding to the nearest integer handles this gracefully.
Solution extraction translates solver output back to domain objects. Each selected variable represents an assignment: this section in this room at this time. Parse the variable names to extract the entity IDs. Build assignment records linking sections to rooms and time slots. Aggregate statistics—how many periods each section received, how many slots each room is using, how many periods each teacher is teaching.
Validation after extraction catches solver bugs or model errors. Every section should have exactly its required periods—if not, something went wrong in either model building or solution extraction. No teacher should appear in two places at once—if they do, teacher conflict constraints failed somehow. No room should be double-booked. These checks seem redundant with the constraints themselves, but they provide defense in depth.
Infeasibility diagnosis transforms a cryptic solver failure into actionable information. When the solver reports infeasible, something in the model contradicts itself. The diagnosis process checks for obvious causes. Does the school need more total periods than exist across all rooms and slots? Does the chemistry department need more lab periods than the labs can provide? Does any teacher have more assigned sections than their available time allows? Each diagnosed cause suggests specific remediation.
The orchestration service ties everything together. Load all data from the database. Build the model using the ILP builder. Export to LP format. Invoke the solver with appropriate options. Check the result status. If optimal or feasible, extract the solution and save assignments to the database. If infeasible, run diagnosis and report causes. If time limit reached, extract whatever solution exists and note that it may be suboptimal.
Error handling throughout this flow prevents partial failures. Database errors during loading should abort cleanly. Model building errors should provide diagnostic information. Solver crashes—rare but possible—should be caught and reported rather than crashing the entire service.
The API endpoint that triggers scheduling wraps this orchestration. Accept schedule name, semester identifier, and optional solver settings. Invoke the orchestration service. Return a result indicating success or failure, the schedule ID for successful generations, solve time, assignment count, and any warnings or errors. The frontend can poll this endpoint for progress or wait for completion.
Async execution suits long-running solves. Rather than blocking the HTTP request for five minutes of optimization, return a job ID immediately and let the solve run in the background. A separate status endpoint lets the frontend poll for completion. When solving finishes, results persist in the database where the frontend can retrieve them.
The vibe coding approach for solver integration focused on describing the workflow rather than the implementation details. What should happen when we want to generate a schedule? Load data, build model, solve, extract solution, save results. Each step became a service method. Error handling and edge cases emerged through iteration—what if the solver times out? What if the model is infeasible? Each question prompted additional handling.
Testing solver integration requires multiple scenarios. A simple case that solves instantly verifies the happy path. A case with known infeasibility verifies diagnosis. A large case that times out verifies handling of partial results. Edge cases like empty schedules or single-section cases catch surprising failures. The test suite exercises the solver enough to build confidence without wasting minutes on every test run.
The solver is where mathematics becomes schedules. Everything else—data modeling, constraint specification, interface design—exists to feed the solver and present its results. Getting solver integration right makes the entire system useful.
Building the Schedule UI
A schedule that exists only in a database serves no one. The interface that presents schedules to users determines whether the system is actually useful. Administrators need to see the complete picture. Teachers need to find their assignments. Students need to know where to be. The schedule grid—showing what happens when and where—is the primary view that everyone needs.
The grid visualization follows a familiar pattern: time flows down the rows, days spread across the columns. Each cell might be empty or contain an assignment. The information in each cell varies by viewing mode. An overview shows class code, room, and teacher. A teacher-filtered view shows only that teacher's assignments, omitting their name since it's obvious. A room-filtered view shows everything happening in that room.
Building the grid requires combining data from multiple sources. Time slots define the row structure. Day headers define the columns. Assignments fill the cells by matching their time slot to the appropriate row and their day to the appropriate column. Some cells remain empty—not every period has a class in every room.
Conflict highlighting catches problems at a glance. If two assignments overlap—same teacher at the same time, or same room at the same time—they appear in red. These conflicts shouldn't exist if the solver worked correctly, but manual overrides can create them, and visualization ensures they're immediately visible.
Manual overrides let administrators adjust what the solver produced. Drag an assignment from one cell to another to change its time slot. The system validates the move—does it create a teacher conflict? Does it create a room conflict? Does the new room have adequate capacity? Valid moves execute immediately and mark the assignment as manually overridden. Invalid moves produce error messages explaining why they can't happen.
The move validation logic mirrors the constraint logic in the model builder. Check whether the teacher has another assignment at the target time. Check whether the room is occupied at the target time. These checks happen server-side to prevent inconsistent state, with quick feedback to the user about success or failure.
View mode selection lets users focus on what they need. Overview mode shows everything, useful for administrators checking the complete schedule. Teacher mode filters to a single teacher, useful for teachers reviewing their weekly schedule. Room mode filters to a single room, useful for facilities staff managing space utilization. Each mode adjusts what information appears in cells and which cells are visible at all.
The entity selector appears when viewing by teacher or room. A dropdown lists all teachers or all rooms; selecting one filters the grid to show only their assignments. The list should be sorted usefully—alphabetically by name, or grouped by department. Quick filtering through typing helps when lists grow long.
Cell click interactions reveal details and enable actions. Clicking an assigned cell shows full details about that assignment—section information, teacher, room, any special notes. From this detail view, users can delete the assignment or access move functionality. Clicking an empty cell offers to create a new manual assignment, selecting from available sections.
Legends explain the visual encoding. Color indicates status—normal assignments in one color, manually overridden in another, conflicts in warning colors. Users who didn't build the system need this documentation embedded in the interface itself.
Responsive design matters if administrators access the system from tablets or phones. The grid doesn't compress gracefully to phone screens, but it should at least remain usable on tablets that administrators might carry during room walkabouts. Horizontal scrolling handles days that don't fit; vertical scrolling handles periods.
The vibe coding approach to interface development focused on describing user workflows. What does an administrator need to do? View the complete schedule, identify problems, make adjustments. What does a teacher need to do? Find their personal schedule, export it to their calendar. Each workflow became a set of interface requirements that Claude translated into components.
Testing the interface requires actual schedules to display. The mock data used for solver testing serves here too. Load a realistic schedule, navigate through views, verify assignments appear correctly, test the move functionality. Visual testing with screenshots catches layout issues that unit tests miss.
Export functionality extends the schedule beyond the web interface. iCal format produces calendar files that teachers import into Outlook or Google Calendar. The export generates recurring events for each assignment, spanning the semester dates. Teachers appreciate not having to manually enter their schedules into their personal calendars.
Calendar export involves date arithmetic. Each assignment has a day of week and a time. Convert this to actual dates across the semester range. Handle holidays and breaks when classes don't meet. Generate the iCal format with proper event structures, unique identifiers, and location information.
PDF export serves printing needs. Despite the digital age, some teachers want paper schedules on their walls. The PDF should present the same grid view in a format suitable for printing. Page layout becomes important—landscape orientation usually works best, with appropriate font sizes for readability.
The interface is where users encounter the scheduling system. All the mathematical sophistication in the solver means nothing if users can't understand, adjust, and export the results. Getting the interface right makes the difference between software that helps and software that frustrates.
Advanced Optimization
The basic ILP model works well for small to medium schools. A hundred sections, thirty rooms, forty time slots—the solver finds optimal solutions in seconds. But larger schools stress the approach. Five hundred sections, a hundred rooms, dozens of constraint types—solve times stretch from seconds to minutes to hours. Optimization techniques become essential.
Variable filtering eliminates impossibilities before the solver sees them. The model builder already skips infeasible combinations, but more aggressive filtering helps further. If a teacher is unavailable for half the periods, half the variables involving their sections disappear. If capacity requirements eliminate most rooms for large sections, those variables never get created. The goal is the smallest model that still contains all valid solutions.
The payoff from filtering compounds. Fewer variables mean fewer constraints, since constraints reference variables. Fewer constraints mean smaller matrices. Smaller matrices solve faster. A fifty percent reduction in variables might produce a seventy-five percent reduction in solve time.
Constraint aggregation combines similar constraints where possible. If a constraint is mathematically redundant with another, remove it. If two constraints share most of their variables, consider whether they can be combined. Duplicate constraints slow the solver without adding information.
Iterative solving breaks large problems into phases. The first phase solves a relaxed version—perhaps ignoring soft constraints entirely—to find any feasible solution quickly. The second phase fixes high-confidence assignments from the first solution and re-solves with soft constraints active. Each subsequent phase refines the solution while preserving what earlier phases established.
This hierarchical approach trades optimality for speed. The final solution might not be globally optimal, but it respects priorities. Hard constraints are satisfied by the early phases. Soft constraints optimize within the space that hard constraints allow. The result is good even if not perfect.
Warm starting uses previous solutions to accelerate new solves. If you generated a schedule yesterday and need to regenerate today with minor changes, the previous solution provides a starting point. The solver doesn't search blindly from scratch; it starts from a known good solution and explores nearby improvements. Minor changes to constraints produce minor changes to solutions, and warm starting exploits this locality.
Multi-objective optimization addresses competing goals formally. Some schools care most about teacher preferences. Others prioritize room utilization. Others emphasize student convenience. Rather than combining all objectives into a weighted sum, hierarchical optimization solves for each objective in priority order, constraining each solved objective before moving to the next.
Solve first for hard constraint satisfaction—any feasible solution. Then solve for the top priority soft objective, constraining that the hard constraints remain satisfied. Then solve for the next priority, constraining that the previous objectives don't degrade beyond tolerance. Continue through all priority levels. The result respects the ordering explicitly.
Solver option tuning affects performance significantly. The default MIP gap of zero requires proving global optimality, which might take hours. Accepting a one percent gap often produces excellent solutions in seconds—the last fraction of a percent rarely matters in practice. Time limits prevent runaway solves; even a suboptimal solution found in five minutes beats no solution found after an hour of waiting.
Thread counts let the solver parallelize across CPU cores. On a multi-core server, this speeds solving considerably. On a single-core environment, multiple threads add overhead without benefit. Auto-detection usually makes good choices.
Presolve preprocessing simplifies models before solving begins. The preprocessor eliminates fixed variables, substitutes constraints, tightens bounds, and applies dozens of other transformations that often reduce problem size dramatically. Leaving presolve on almost always helps.
Database optimization supports the application around the solver. Materialized views pre-compute the schedule grid data that the interface repeatedly queries. Indexes accelerate the joins that build assignments with their related entities. Connection pooling prevents database access from becoming a bottleneck.
Background job processing makes long solves user-friendly. Rather than blocking a web request for minutes, accept the solve request, return a job identifier, and process the solve asynchronously. A status endpoint lets the frontend poll for completion. When solving finishes, results persist for later retrieval.
Caching solver results prevents redundant computation. If the same schedule gets requested multiple times with no data changes, return the cached result rather than solving again. Invalidate the cache when underlying data changes.
The system that emerged from my vibe coding sessions handles schools ranging from small to medium-large. Very large schools—universities with thousands of sections—might need additional techniques beyond what's described here. But the fundamentals scale: express requirements as constraints, let the solver optimize, present results through clear interfaces.
The broader pattern applies beyond school scheduling. Employee shift scheduling follows the same structure: workers instead of teachers, shifts instead of periods, positions instead of rooms. Conference session scheduling assigns talks to time slots and rooms. Sports league scheduling assigns games to dates and venues. Manufacturing scheduling assigns jobs to machines and time windows.
In each domain, the approach is the same. Identify the decision variables—what are you assigning to what? Express the hard constraints—what must never happen? Express the soft constraints—what should be avoided if possible? Define the objective—what does a good solution look like? Build the model. Run the solver. Present the results.
Learning to think in constraints is the lasting skill. The specific techniques for school scheduling are details. The ability to recognize constraint satisfaction problems and formulate them mathematically is the power. Once you see problems this way, you find them everywhere. And you know that modern solvers can handle them, often in the time it takes to have a cup of coffee.