The operating platform
The company dispatched trips off a whiteboard. A whiteboard has no memory, no visibility, and no way to tell you that the van you just assigned is already 90 miles away.
- 696commits across two repos
- ~20,000lines in the current system
- 566production deployments
Why off-the-shelf scheduling couldn't do it
Every scheduling product assumes people and rooms. A person is busy or free, a room is booked or open, and the day is the unit. This operation schedules a dependent graph instead: a trip needs a crew, the crew needs qualifications specific to a river section, the trip needs boats and a vehicle and often a trailer, the vehicle needs a driver with the right license, and the whole thing has to be somewhere else the next morning. Change one assignment and the consequences ripple through shuttle logistics two days out.
The part that breaks every product I looked at is that resources here aren't consumed by the day. A guide can run a day trip in the morning and launch on a five-day expedition that afternoon. A trailer might serve three or four different trips in a single day, moving between put-ins and takeouts on different rivers. Booking software wants to pin one vehicle and one trailer to one trip for one day, which is tidy and wrong. What actually governs the schedule is drive time between points, how far one trip's window overlaps the next, and how long a rig takes to reset between uses. None of those are a calendar.
The constraints aren't static either. Trip types have different crew ratios, different rigs, different put-ins. Capacity is gated by permits. Off-the-shelf tools can express "this person is busy." They can't express "this trailer is committed until it clears the takeout and drives back," or "this guide isn't licensed for this section during this flow range."
Configuration as data, not code
The single most useful architectural decision was pushing business rules out of the code and into tables: trip type rules, conflict rules, capacity tiers, and a config table of thresholds. Adding a trip type is a row, not a deploy.
That decision is what makes the system portable. The rules are the part that's specific to one company. The engine that reads them isn't.
The conflict engine
The largest single component. It walks the schedule and flags three classes of problem: hard conflicts where a person or asset is committed in two places, soft conflicts that need a human look, and missing data where it can't judge safely. That third category matters more than it sounds. A system that silently passes incomplete records teaches people to distrust it.
It runs two ways: event-driven when a record changes, and on a schedule as a safety net, because event triggers eventually miss something. It went through 27 versions, and most of those versions exist because real operations kept producing edge cases the model hadn't imagined.
Integrations
Reservations sync from the booking platform through a purpose-built API adapter, hourly, with a nightly deep reconcile and a de-duplication pass. Cancellations cascade: cancel a reservation and the day's operation, its driving movements, and its trip log entries all stand down together. Alerts route to Slack. Field communications from satellite messengers land in a team channel instead of one person's phone. Texts from satellite messengers route through a company number into a team Slack channel, so a message from a canyon with no cell service reaches everyone who needs it rather than whoever happened to be holding the phone.
The adapter pattern is deliberate. Everything downstream of a standard intake table is portable; only the connector is vendor-specific.
The money the booking system can't see
Because this layer sits on top of the booking data rather than inside it, it can see things the booking system has no view into. A reservation records what someone agreed to pay. It does not record whether the money actually arrived, and those are not the same thing.
Deposits get collected and remainders never do. A card on file fails on its scheduled run and nobody sees the failure. A guest changes a reservation and the balance moves with it, quietly. In every one of those cases the trip still launches, and the gap only surfaces afterward, when chasing it means calling someone who has already gone home.
So the system reads the booking data every morning and posts what is still owed on trips going out in the next six days: which reservations changed, which cards failed, what is outstanding, and a link to contact the guest or run the charge. It is a plain report with working links, which is the whole trick. We estimate it has moved more than $20,000 of reservation balances into collected before launch, rather than chased afterward. The daily digest is covered in detail here.
Collecting money you are already owed, before the guest is on the water, isn't new revenue. It's the same revenue arriving on time instead of late or never. That is the argument for an operations layer in one sentence: the booking platform isn't wrong, it just isn't looking.
Where AI sits
It's the interface, not the decision maker. The speed difference is the whole point: instead of opening records and hand-editing every name, contact, and field one at a time, I say what changed and the system works out what that touches. Updates get made by voice from a truck. Questions get asked in plain language against live operational data. Reports that used to take days get generated in minutes. An operation moves faster than booking software was built to move, and typing was the bottleneck.
The other half is reading what the team already says. Operations don't announce themselves as structured records. A guide mentions a doctor's appointment in a channel. Someone reports a strap that tore on the last trip. A vehicle problem comes in on the ops issue form. Every one of those is a future conflict wearing plain clothes, and on a whiteboard it stays invisible until the morning it isn't. The system reads them, matches them against the schedule, and surfaces the collision while there is still time to solve it. The alternative isn't a tidier database, it's endless texts and emails and whoever happened to read them.
But anything touching money, compliance, or safety requires a human confirmation, and the system reports back exactly what it changed so a person can verify it. That constraint isn't a limitation, it's what makes the crew willing to use it.
How it was built
I'm not a career software engineer. I specify, direct, review, and ship using AI coding tools, and I've been doing it long enough to know where that approach breaks: it will confidently produce something that works on the happy path and fails on the operation's actual edge cases, which is why the version history is long and why the specifications matter more than the code.
What I'd argue this demonstrates isn't that I can write JavaScript. It's that I can hold a complex domain in my head, decompose it into something a machine can execute, and keep it running in production while 44 people depend on it during a season where mistakes hurt people.
This page is the working proof of an argument I make separately: booking software solved the transaction and left the operation unbuilt.