Thirty rules and one day of screenings
Which film goes on which screen, how many times, and at what minute — under more than thirty business rules, with a target share for every film. This is how a cinema's daily schedule became a solvable decision problem, and a small version of it you can run.
Why a schedule is hard to write by hand
The schedule decides attendance, occupancy, concession sales and how well prime time is used. It is also bound by a long list of rules. The same film needs a minimum interval between starts; different films need spacing for entry, exit and the concession queue; popular films belong in prime time; dubbed children's films need child-friendly hours; each film has a target share of the day. With more than thirty such rules, producing a schedule that obeys all of them while also chasing revenue is close to impossible by hand — so in practice some rules quietly give way.
Rules become constraints
The work translated each business rule into a mathematical constraint of an integer program over screens, show slots, films and start times on a five-minute grid. A few representative families:
| Rule family | Becomes |
|---|---|
| Show chain | Each screen is a chain of shows; the next start is at least the cleaning time after the previous end, and not so late that the screen sits idle. |
| Opening and closing | The first show starts inside an opening window; the last ends before closing. |
| Audience fit | Children's dubbed titles start early enough; adult-rated titles start late enough. |
| Lobby flow | No two screens start within the same ten minutes. |
| Prime time | The film with the largest target plays the biggest screen in prime time. |
| Target share | Each film's share of seats, not shows, stays within a tolerance of its target. |
Because variables and constraints multiply across films, screens and slots, a direct formulation is slow. Much of the engineering went into tractability: sharing variables between rules, choosing which structures to model explicitly, and reformulating constraints so the solver can prune early.
Live model, solved on a Python server with Google OR-Tools CP-SAT in about eight seconds. Top: a rule-of-thumb plan — one film per screen, back to back, round start times. Bottom: the optimized plan. Red outlines mark shows that break a rule; the share bars compare each film's seat share with its target. Change the theatre size or draw a new day. Films, screens and demand are generated. Open the live model on its own page ↗
Architecture, stack and core formulation
A constraint-programming service that turns a day's films, screens and business rules into a timetable, with staged relaxation so a schedule always comes back — and an attendance forecast alongside.
From JSON to a model
Films, screens, opening hours, pre-sold sessions and rule settings mapped to typed objects; ineligible screens and films dropped.
CP-SAT on a 5-minute grid
Slots per screen with optional intervals, film choice, start/end/gap times and a seat-weighted share constraint.
Staged relaxation
Hard rules only → hard + soft → relaxed hard rules as priced penalties, each stage hinted by the previous one.
Independent rule check
A validator re-checks every rule on the result and reports any rule given up during relaxation.
Attendance by site × film × day
Gradient-boosted trees with a Tweedie objective and a Temporal Fusion Transformer, against a log-target baseline.
| Layer | Technology | What it does here |
|---|---|---|
| Service | Python 3.11, FastAPI / uvicorn, Redis, Docker, Kubernetes | Async solve with callback, one pod per workload |
| Solver | Google OR-Tools CP-SAT | Intervals + NoOverlap, element and domain constraints, decision strategies, hints |
| Search control | Staged time budget; worker count matched to the container CPU limit | Predictable runtime and memory per request |
| Speed-ups | Redundant cuts, DP-built exact seat-sum domains, symmetry breaking | Tighter model and faster proofs |
| Forecast | LightGBM (Tweedie), Temporal Fusion Transformer, blending | Occupancy by site and time slot, ≈10% MAE |
y[s,k,g] ∈ {0,1} slot k of screen s plays film group g active[s,k] ∈ {0,1}
start, end, gap integer minutes on a 5-minute grid
end[s,k] = start[s,k] + Σ_g y[s,k,g] · (ads + runtime_g) if active[s,k]
start[s,k+1] = end[s,k] + gap[s,k], clean_min ≤ gap ≤ clean_max
active[s,k+1] ≤ active[s,k] fill slots from the front
seat share (share_m − δ)·SEATS ≤ Σ_{sessions of m} seats ≤ (share_m + δ)·SEATS
relaxed stages min Σ_r price_tier(r) · violation_r + soft terms- Always a schedule. Each relaxation stage caps total penalty at the previous stage's value and starts from its solution as hints.
- Exact domains. Reachable seat totals per film are pre-computed by dynamic programming, so the share constraint uses exact domains instead of loose bounds.
- Memory. Fewer parallel workers cut peak memory sharply with no loss in quality, so worker count follows the container's CPU limit.
| Component | In production | In the live model above |
|---|---|---|
| Rules | 30+ client rules as constraints and priced penalties | Ten rule families on a generated multiplex |
| Solve | Staged relaxation inside a total time budget | Hard → relaxed → greedy fallback within 8 s on a 2-core server |
| Forecast | LightGBM / TFT attendance model | Not reproduced; demand comes from the generator |
Inside the model
Seat-weighted share, preferred time slots
The objective balances three things: closeness to the target share of seats, the number of shows, and expected attendance — shows of films that draw better in the evening are worth more in the evening. Share is written linearly by multiplying through by total seats, so the solver never divides.
Occupancy prediction
Alongside the scheduler, an occupancy model estimates how well a film will do by site and time slot, using film performance, site characteristics, time of day, show sequence and the number of daily screenings as inputs. It reached about 10% MAE, and it is what gives "expected attendance" a meaning inside the objective.
When rules collide
Some days have no schedule that satisfies every rule — a pre-sold show fixed in an awkward place, or a film mix that cannot fit. The production design separates rules that may never bend from rules that can be relaxed with a penalty, and steps down through stages instead of returning an empty board. The live model keeps that idea in miniature: if the strict model finds nothing in time, a relaxed model runs, and as a last resort a rule-respecting greedy plan is returned.
What it changed
The model generates schedules that respect the full rule set — something very hard to achieve by hand — and the occupancy model gives the objective a measure of demand. The larger shift was in the process: scheduling moved from a manual, experience-driven task to a structured decision problem built from data, rules, constraints, prediction and optimization, which is the foundation for revenue-oriented scheduling end to end.
Limitations
- The live model encodes a dozen representative rules, not the full rule set, and uses a synthetic attendance curve in place of the prediction model.
- It solves with a short time limit on a small server, so its plans are good feasible solutions rather than proven optima.
- The rule-of-thumb baseline is a stand-in for manual scheduling, built for illustration; it is not a record of any real theatre's schedule.
- Revenue effects of the production engine are not reported here.
About the demo and confidentiality
Screens, seat counts, film titles, runtimes, targets and demand curves in the embedded model are generated from a seed. No theatre, rule-code system, interface field or operational figure from the real project appears here, and the solver shown is a re-implementation of the modelling approach, not the production engine.