6.4 KiB
P0 — The hub's route order never reaches the rider
From: Miler rider-app engineering · 24 Aug 2026
To: Doormile backend
Endpoints: GET /miler/bookings, GET /miler/assignments
Related: API requirements §11b (request 13), open questions 6 and 7
The rule we are building to
The console decides the order. The rider follows it.
If operations has solved a route, the app must work the stops in that exact order — both legs, pickup and delivery — and must never re-sort by what is closest to the rider. Re-optimising a planned route is not a small difference: it changes the arrival windows customers were promised, and it makes the hub's own screen a fiction.
This is already how the app behaves. lib/data/route_order.dart is the single
place that answers it, and its rule is:
| The payload says | The app does | The rider is told |
|---|---|---|
| any stop carries a sequence | works them in that order, exactly | Hub route |
| no sequence, but booked times | orders by booked time | No route assigned — ordered by booked time |
| no sequence, no times | keeps the backend's own array order | No route assigned — shown in the order they came through |
| nothing at all, rider has GPS | nearest-first | No route assigned — ordered by what is closest to you |
Nearest-first is the last of four, it is labelled as a guess wherever it appears, and one sequenced stop is enough to switch the whole set into hub order.
What is happening instead
Every live row comes back step: 0. Verified in production, rider Rajan A
(userid 38), 21–24 Aug:
GET /miler/bookings → 29 rows · step = 0 on all 29
GET /miler/assignments → 12 rows · step = 0 on all 12, sequencedat = null
step is on the contract and is present in the payload. It is simply never
written. So the first branch of the table above has never once been taken on a
real device, and every rider in production is working a route the app ordered,
while the console believes he is working the route it planned.
Neither side is currently able to notice. That is the part we would most like to close.
Why it bites hardest on the delivery leg
GET /miler/assignments is — correctly, and you have confirmed this — the
active queue: Assigned / Accepted only. A booking leaves that queue the
moment it is collected.
The delivery leg begins the moment it is collected.
So even if step were populated on assignments tomorrow, the sequence would
vanish at exactly the point the rider starts delivering, and the delivery
half of his day would still be ordered by the app. The sequence has to be on
GET /miler/bookings, which is the only list that covers all of a rider's
work.
What we need
1. Populate step on every routed stop
On both GET /miler/bookings and GET /miler/assignments:
{
"bookingid": 59,
"stoptype": "delivery",
"step": 4, // 1-based position in the solved route
"sequencedat": "2026-08-24 07:12:00", // when the route was solved, IST
"tripid": 2 // see §3
}
stepis 1-based.0keeps its current meaning — this stop is not in a solved route — and the app already sends those to the end of the list.- Stable for the day. A stop's step must not change between two polls unless the route was genuinely re-solved. The app re-sorts on every fetch; a step that drifts makes the list reshuffle under the rider's thumb.
- Unique within a rider's route, so two stops cannot claim position 4.
- Survives the leg change. The step a booking had as a pickup, or the step
its delivery has, must still be on the row after
pickup-complete— that is the case that is broken today.
2. sequencedat, preserved
Null means never solved. A timestamp means solved then. The app uses it for one thing only: telling a rider "no route assigned" honestly instead of implying the hub planned an order it did not. It is currently null on every row even where a step exists.
3. Confirm who owns Trip 1 / Trip 2 / Trip 3
A rider's day is split into up to three runs. Today the app derives that split client-side, from the day part — morning / afternoon / evening — because nothing in the payload names a run.
That is a guess, and it is the wrong kind: a rider's "Trip 2" and the hub's "Trip 2" are not necessarily the same set of stops, and neither screen can tell.
Two workable answers, and we need to know which:
- The console owns runs → return
tripid(andslotidif they differ) on every row ofGET /miler/bookings, stable for the day. We follow it and delete the day-part split entirely. - Nobody owns runs → say so, and the day-part split stays as documented client behaviour rather than an unlabelled invention.
Two questions we cannot answer from this side
-
Is sequencing automatic or manual? Is a route solved when assignments are created, or does an operator have to trigger a route/optimizer action? If it is manual, then
step: 0is often the correct answer, and the app should say "no route assigned" plainly rather than quietly ordering the stops itself — which is a different piece of work from populating the field. -
What happens on a mid-day re-solve? If operations re-sequences a route while a rider is part-way through it, do completed stops keep their old steps? We will follow whatever you send; we need to know whether to expect the numbers to move.
How to tell it is fixed
No app release is needed for any of this — the app reads these fields today.
GET /miler/bookingsfor a rider with a solved route returnsstep ≥ 1on the routed stops and a non-nullsequencedat.- The same booking still carries its
stepafterpickup-complete. - Two consecutive polls return the same steps.
- On the rider's phone, the queue heading reads Hub route rather than Nearest first — that string is driven directly by which branch of the table above was taken, so it is a one-glance check that the sequence actually arrived.
What we are not asking for
- Not a routing engine. If the console already stores an order, exposing it is enough; we are not asking anyone to solve TSP.
- Not a new endpoint. Two fields on two existing list responses.
- Not a change to
/miler/assignments' scoping. Active-queue-only is right — it is precisely why/miler/bookingshas to carry the sequence.