The week I tried to beat a solver, and lost
I spent a week building a crew-recovery engine I was certain would beat the optimizer airlines already run, and it lost. The pitch in my head was the one everyone selling AI into airline operations makes: your solver is smart, mine is smarter. So I built StormCrew's recovery step against a real, mature open-source MIP solver (CBC, via PuLP) as the control, and I benchmarked my "smarter" approach against it on the same seeded storm.
The control kept winning. On the default scenario, CBC takes the exact recovery problem, 1,815 binary variables across 115 constraints, and solves the min-cost set-partition to OPTIMAL in roughly 0.11 seconds. Cover every open flight exactly once, use each crew at most once, minimize total cost, done, provably optimal. There was no smarter objective function hiding behind my idea. There was just a well-posed integer program that a mature solver already crushes.
StormCrew's CBC stage: a 1,815-variable, 115-constraint min-cost set-partition solved to OPTIMAL in roughly 0.11 seconds. This is the engine I set out to beat.
So I did the only honest thing. I changed the claim, not the numbers. StormCrew uses CBC as its engine and says so on screen ("Recovery engine CBC"). It does not claim to out-optimize the solver, because it can't, and pretending otherwise would have been the first lie in a product whose whole reason to exist is not lying.
The moment I stopped selling a smarter optimizer
I only understood what to build after I injected a storm at a hub and watched what actually broke. Ground the busiest hub early in the day (in the seeded scenario, DEN), and the damage is not one airport. It is a cascade: the grounded flights plus every one-hop downstream flight that just lost its crew. In this instance that blast radius is 53 flights, and it is the set a manual operations control center sees far too late, after the cancellations have already started.
Inject a storm at hub DEN and the blast radius lights up: 53 downstream flights that just lost their crew, the cascade a legacy tool sees too late.
That is when the real problem got legible to me, and it had nothing to do with the objective function. The enemy in an irregular-operations meltdown is a 4 to 12 hour manual scramble (an externally sourced benchmark, not my number) run under two hard constraints, FAA Part 117 duty and rest limits and per-carrier union CBAs. IROPS costs the industry about $60B a year (IATA). Southwest's December 2022 meltdown ran roughly $1.2B, about 16,900 cancellations, and around 2 million stranded passengers. And since the DOT auto-refund rule took effect in October 2024, every 3-hour-plus cascading delay is an automatic financial hit. None of those wounds are caused by a solver being too weak. They are caused by recovery being too slow, too risky to get legally wrong, and too opaque to see coming. A better objective function fixes exactly none of them.
So StormCrew stopped competing with the solver and started wrapping it. It makes the cascade visible, then returns the plan in roughly 0.11 seconds against that sourced 4 to 12 hour manual anchor, on top of the solver the airline already trusts. Augment, don't replace.
Why I moved the legality guarantee out of the model and into the code
The decision I am most sure about is where I put the legality guarantee, and it is not in anything that reasons. Part 117 has hard ceilings (max duty period 780 minutes, max flight time 480 minutes, minimum sit 30 minutes), and the sample CBA caps a duty at 4 segments. The tempting design is to let a model or a solver propose assignments and then score or penalize the illegal ones. I refused to do it that way. In StormCrew the constraints are enforced at duty-generation time: only legal recovery duties (including deadhead repositions) are ever created as candidate columns. An illegal assignment is not penalized, it is impossible to produce.
The legality gate: Part 117 ceilings 52 of 52 legal, CBA 0 violations, and a 0 illegal badge that reads "enforced in code (column masking), not by the model."
That is why the badge on screen says "0 illegal, enforced in code (column masking), not by the model." It is a provable invariant, unit-tested (the suite checks that only legal columns are generated and that the recovered plan is a legal partition, 3 of 3 passing), not a score I hope stays high. Letting a model judge whether its own assignments were legal was never an option I trusted for a constraint you cannot afford to get wrong once. A legality guarantee belongs in deterministic code, because then no choice of solver, and no model, can ever break it. On the normal run the gate confirms 52 of 53 flights re-crewed with a single cancellation, 34 crews used, and 0 illegal. The scenario also avoids 52 cancellations and about $2.37M of DOT-refund exposure against a do-nothing baseline, though I label that illustrative of this one seeded scenario, because it compares against the worst case (strand everyone) and is not a headline or a claim of optimizer quality.
The plan I built the system to refuse to approve
The feature I am proudest of is the one where StormCrew declines to help. Toggle the severe variant, reserves exhausted, only about 30 percent of crews left, and the system still returns a fully legal plan almost instantly: 0 illegal, 33 of 53 flights re-crewed, 62 percent of the radius covered. Every "autonomous" pitch I have seen would stamp that and move on. StormCrew does not. The plan cancels 20 of 53 flights, 38 percent, which is above the OCC's 15 percent auto-approve threshold, so the status flips to ESCALATE, human sign-off required, with the reason shown and the plan still surfaced for the controller.
The severe run: a legal plan (0 illegal, 33 of 53 re-crewed) that cancels 20 of 53 flights, 38 percent, above the 15 percent threshold. Status flips to ESCALATE, human sign-off required.
I built that gate because the honest failure mode of automation is not a wrong answer, it is a confident answer on the worst day of someone's year. Cancelling 38 percent of a blast radius is a decision a human should own, even when the math is legal and fast. Escalate, don't silently auto-approve a bad day. And every recommendation, approved or escalated, exports a signed recovery_plan.json that records the plan, the Part 117 and CBA clause checked per action, the wall-clock, and the savings, so the OCC has an audit record of why a recovery was recommended.
The question I keep coming back to
I keep returning to the question that reorganized this whole build: what is actually worth being smarter about? Everything here is synthetic and seeded (no real-airline data, no live feeds, the Jeppesen/IBS integration is a mock adapter), so it proves a mechanism, not an open-world guarantee. But the mechanism is the argument. The durable value was never a cleverer optimizer, because the buyer already owns a good one and cannot tolerate lock-in or an unexplained recommendation. It was making the cascade visible, making illegal moves impossible to generate rather than merely penalized, collapsing hours into seconds, and knowing when to hand the day back to a human. You can run the seeded storm yourself at veriprajna.com/demos/airline-crew-scheduling-ai and watch it escalate.
I set out to beat the solver and lost, and losing was the most useful result I got all year. So here is what I am still chewing on: in your own domain, how much of the AI roadmap is a race to out-optimize a tool that already works, and how much of the real value is the unglamorous layer around it that no one wants to sell?