Companion to Doormile Parcel Flow
Each leg of the journey drawn on its own — first, mid, last — with its real states, endpoints and failure branches. Then all three as one chain. Then where an AI-optimised design would actually put its intelligence, which is not where Doormile puts it now.
Everything from a booking existing to a parcel being in a rider's hands. The happy path runs down the centre; every exception branch on the right is one the backend actually implements.
pickup-complete collecting COD twice.Two details worth holding onto. ALREADY_PICKED_UP and INVALID_STATE are stable machine-readable codes the rider app branches on, so the state machine is enforced server-side rather than trusted from the client. And commitAssignment writes booking, assignment and miler availability in a single transaction — there is no window where a rider is holding a job the booking does not know about.
The line-haul leg. This is the only one with no automation at all — every decision below is made by a person at a screen.
Dispatch timing is the central mid-mile trade: the marginal cost of running an extra vehicle against the SLA-breach risk of the parcels you hold back. Both sides are computable — you already store sladueat on every consignment. Today it is a dispatcher's instinct.
The only leg with real road-network routing — and the only one with a retry loop, because a delivery can fail in a way a pickup cannot.
Attemptcount and the skip reason are already being recorded on every failure. That is a labelled training set for first-attempt success prediction, sitting unused.Every trip around that retry loop is a second full delivery run for one parcel. First-attempt delivery rate is the dominant cost line in Indian last-mile, and the loop above is currently entered on discovery rather than predicted in advance.
Every status a parcel can hold, in the order it holds them, colour-coded by leg. Read it as a snake: left to right, drop, right to left, drop, left to right.
PickupBooking, everything after Converted_To_Consignment is a Consignment. The teal bypass and the gold loop are the two places a parcel's path stops being linear.| Leg | Entity | Ends when | Loops | Optimised today by |
|---|---|---|---|---|
| First | PickupBooking | parcel in rider's hands | reject → reassign | per-parcel geo-search + LLM pick |
| Mid | Consignment | at destination hub | multi-leg re-inward | nothing — hand-built tripsheets |
| Last | Consignment | OTP verified, proof filed | skip → next attempt | Valhalla sequencing, post-assignment |
This is the most consequential thing in the codebase, and it is a sequencing question about the software rather than the parcels. Doormile assigns first and sequences second. Those are not two problems — they are one problem, and solving them in series throws away most of the available gain.
Greedy nearest-neighbour assignment is a known-bad heuristic for vehicle routing — on realistic instances it lands well short of what a proper solver finds on the same data, and no amount of smarter per-parcel scoring recovers the gap, because the loss comes from committing to each parcel before seeing the rest. A better model choosing one rider at a time is still choosing one rider at a time.
The expert move is to stop treating "AI" as one layer. Logistics decisions live on three clocks, and each clock wants a different technique. Doormile currently has a language model on the second clock, which is the one place it fits worst.
arrivedat → pickup-complete pair is already a labelled example.sladueat, and consolidate thin lanes through a transfer hub rather than running half-empty direct trips — Batchkind's local/transfer split already anticipates this.Attemptcount and skip reasons are your training labels. Then resolve addresses with the pgvector you already run: embedding delivery addresses and snapping them to confirmed DeliveryProof coordinates turns your delivered history into a private geocoder that beats any general one inside your own zones.calculateETA is (distance / 20) × 60 + 10 — a flat 20 km/h against straight-line distance, plus a fixed ten-minute buffer. It sets rider expectations, customer-facing ETAs and support load. Road time from the Valhalla matrix you already pay for, multiplied by a time-of-day factor and added to learned dwell, is a same-day change with effect on every leg.
Sequenced by ratio of effect to effort, not by ambition. The first two need no new infrastructure at all.
| # | Move | Leg | Depends on | Why it's here |
|---|---|---|---|---|
| 1 | Score AgentDecision against outcomes | all | nothing — data exists | You cannot currently tell whether AI dispatch beats greedy-nearest. Until you can, every other change is unmeasurable. |
| 2 | Real ETA from road time + learned dwell | all | Valhalla matrix | Replaces a flat 20 km/h constant. Touches rider trust, customer ETA and support volume at once. |
| 3 | Adaptive candidate set | first | nothing | Removes both failure modes of the fixed 10 km / top 10 window. |
| 4 | Batched joint assign + sequence | first, last | solver, batch window | The ordering fix in §05. Largest routing gain available. |
| 5 | Address resolution on pgvector | last | delivered history | Turns your own proof-of-delivery coordinates into a private geocoder for your zones. |
| 6 | First-attempt success model | last | #5, Attemptcount history | Attacks the dominant cost line in last mile. |
| 7 | Automated tripsheet load planning | mid | #2, volume forecast | The only leg with no optimisation at all today. |
| 8 | Hold-vs-go and dynamic cut-offs | mid | #7, sladueat | Converts a dispatcher's guess into a priced decision. |
| 9 | Beat districting | first, last | #4, a Beat entity | The strategic layer. Only worth it once daily routing is solid. |
Every item above gets cheaper under a round model rather than more expensive. When stops are stable, the heavy optimisation moves to beat-design time — monthly, offline, with as much compute as you like — and the daily solve shrinks to the deltas: today's new stops, today's skips, today's absences. Real-time combinatorial search over the whole city, every wave, is a cost you only pay because the beats do not exist yet.