← Back to portfolio

Case study · Public-interest operations research

Who gets the scarce teacher

A volunteer-teaching nonprofit had enough volunteers on paper, and still could not staff its hardest sites. I turned that into a lexicographic mixed-integer program on an audited teacher–class graph. Protect the worst-off sites first, then the rest, and put a number on what the protection costs.

Role
Sole developer and project lead: interviews, requirement analysis, model design, system design, implementation, experiments, paper writing
Team
Two teammates: joint interviews, experiment support, paper proofreading, suggestions, slides and documentation
Status
In trial use at Shanghai Stepping Stones Youth Development Center
Context
2026 SJTU Rongchang Cup · team 26213
Built with
Python · mixed-integer programming · Vue 3 / Flask / MySQL
Evidence
30 pre-registered synthetic instances + 1 main instance

01The problem

In city after-school programs and rural school support, the same asymmetry keeps appearing: volunteers cluster at sites that are easy to reach and well known, while the sites rated harder to serve cannot fill a single class. Volunteers are never enough at the same time. The organization ends up holding a queue of willing people and a timetable that still cannot open.

The threshold makes it worse. A class opens only when exactly two teachers are present. Three sites and five qualified teachers is not “two and a half classes”: under first-come or nearest-first, two sites take two teachers each and the fifth teacher has nowhere to go. On paper that is one unused row. For the third site it is an entire course that never runs.

First-come / nearest-first

Site A · hardest 0 / 1 · no class
Site B 1 / 1 · opens
Site C 1 / 1 · opens

Protect the hardest site first

Site A · hardest 1 / 1 · opens
Site B 1 / 1 · opens
Site C 0 / 1 · no class
Illustration, not data: 3 sites, 5 teachers, 2 per class. Both rules can open only two classes; the rule decides which site goes without. That is the decision the model has to make explicit.

Which leaves the question that actually needed answering: when supply is short, who gets protected first, to what level, and what does that protection cost in classes opened and in fit between teacher and class?

02What I deliberately left out

  • Two interviews with one program lead (June and July 2026) separated what can be verified before allocation from what cannot. Qualification, availability and service radius can be checked; a site’s organisational capacity, its students’ classroom state and a volunteer’s maturity cannot be measured before the term starts. Those stay out of the objective instead of being guessed into it.
  • Candidate edges are audited by the organisation before the solver runs. The optimiser cannot pick an edge that fails qualification, however high its utility. Many “matching engines” quietly give that property up.
  • No composite score. If fairness, coverage and fit are added into one number, a large utility term can mask one site quietly falling from two classes to one.

03The model

  1. Candidate graph

    A bipartite teacher × class graph. An edge exists only where subject qualification, service slot, service radius and stated willingness all hold at once. Everything downstream is confined to these edges.

  2. Four levels, solved in order

    Priority-group opening-rate profile, then the all-site profile, then the number of classes opened, then matching utility. Each level is solved, its optimum frozen as a constraint, and only then does the next one run. No weights are involved, so no level can be outvoted by a larger number further down.

  3. Making a discrete fair order computable

    With three classes per site, an opening rate can only be 0, 1/3, 2/3 or 1. Binary variables mark “at least q classes open”; maximising those counts in the order q = 1, 2, 3 is provably equivalent to lexicographic max-min on the sorted rate vector, because the counts of sites at each of the four levels are uniquely determined by the three threshold counts. The model therefore stays a plain mixed-integer program, and needs no bespoke solver.

  4. One equation that forbids the half-filled class

    For every class, teachers assigned equals required teachers times whether the class opens. An open class gets exactly its quota; a closed class gets nobody. The fifth teacher in the example above cannot be parked in a class that will not run.

    # opening–assignment equality, for every class j
    Σi yij = dj · xj

04What the experiments show

0.511 → 0.667 Priority-group minimum opening rate (+30.4% relative)
14 / 30 Instances improved · 16 tied · 0 got worse
−0.68% Classes opened (19.400 vs 19.533)
−3.84% Matching utility

Across 30 pre-registered instances (seeds 100–129, fixed before any result was seen). Qualification, class-size, capacity and time-slot violations: 0. Half-filled classes: 0. Optimality gap: 0. Worst solve time at the 12-site scale: 1.03 s.

The ablation is the part I trust most. A single-level max-min reaches exactly the same minimum in all 30 instances, so the first level already fixes the floor. The extra levels only act above it: in 12 instances they lift the second-lowest rate as well, in 18 nothing changes, and in none does it fall. Sensitivity runs with 32, 38, 44 and 50 teachers show the coverage price moving with bottleneck structure. At 32 and 38 the fairness gain costs no classes at all; at 44 it costs 0.2 classes on average. That is a reason to re-run the comparison on each period’s own data rather than reuse a rule of thumb.

05What this does not prove

  • Every number comes from synthetic instances under one generator. They compare objectives under controlled conditions; they are not an estimate of how much a real program would improve.
  • One interviewee framed the problem. That supports problem abstraction, not a claim about the sector.
  • The platform is in trial at one nonprofit, but the optimiser is not wired into it. The trial runs the incumbent matching rule; this model produces offline candidate plans that a manager compares and confirms, so no allocation decision has yet been made by the solver.
  • Classes are treated as homogeneous service units. What the model equalises is coverage: how many classes open, not how well any one student does.

06What comes next

The platform is now in trial use at Shanghai Stepping Stones Youth Development Center, which puts the workflow in front of real coordinators for the first time. The measurement that matters needs their records: replay old allocation batches under both the incumbent rule and this model, then track solver stability, how often a manager edits the proposed plan, and how often volunteers accept it. Only after those hold up does a wider rollout become worth arguing for.

07What I took from it

A fairness rule you cannot write down is a preference. Written down as an order, it gets a price, and a price is something a programme manager can argue with.

The solver took about a second. Almost all of the work went into deciding what counts as a delivered service: a teacher assigned, or a class that actually opens. Once that was settled, the rest was bookkeeping.