AG
Driver appdriver.air-gourmet.com
Mockup — nothing built yet

Signing in

Van #5 tablet · 03:42, before the first load
A separate app, on purpose. driver_server.py is its own ASGI app on its own App Service at driver.air-gourmet.com — the same separation portal_server.py already gives the client portal. It never imports server.py, so there is no URL a tablet in a van can be made to reach that shows an invoice, a customer or another day's board. Sign-in is name → PIN → van on a tablet enrolled once from ops Settings: no Microsoft licence per driver, and nobody has to remember a password.
03:42Van #5 tablet · LTE ▮▮▮ · 94%
Who's driving?
Wednesday 12 August
Type your name
On today
PO
PorfirioVan #5 · 11 stops
OS
OscarVan #6 · 7 stops
AD
AdrianaVan #3 · 8 stops
Everyone else
DA
DarrielDriver
ER
EribertoDriver
HE
HerbertDriver
LU
Lupe S.Driver
MA
MartinDriver
RA
RandyDriver
TH
ThomasDriver
TR
TraceyDriver
Supervisors — every run, not one
EL
Elmer HSupervisor
JL
Jorge LSupervisor
JT
Jorge TSupervisor
RO
Rolando VSupervisor
SA
Sandra QSupervisor
1 · Pick your name. Scrolls on down through the rest of the drivers and then the five supervisors — sixteen rows today.
03:43Van #5 tablet · LTE ▮▮▮ · 94%
PO
PorfirioNot you? Change
Enter your PIN
1
2
3
4
5
6
7
8
9
Forgot
0
Forgot goes to the supervisor on duty, not to Elmer's inbox — it is 04:15.
2 · PIN. The name stays on screen, with one tap back out of it.
03:44Van #5 tablet · LTE ▮▮▮ · 94%
PO
PorfirioSigned in 03:44
Which van today?
Ops has you in Van #5 — change it if you took another
Van #5Your run · 11 stops
Van #3Adriana signed in 03:31
Van #6Oscar signed in 03:38
OtherType the vehicle

Taking a van somebody else is in? You can — it asks why and tells the supervisor.

Start my run — 11 stops
Out at 03:44 · Porfirio · Van #5 · logged
3 · The van. Pre-selected from the run ops built; changing it is one tap.

Dark is the default, and there is no light mode design call

Deliveries start at 4–6am. Every screen here is the ops app's own dark rail (--sidebar-bg #23211C) opened out into a whole app — same terracotta, same type, no second brand. A cream screen at 04:00 in a van blows the driver's night vision and then they cannot read a tail number on a dark ramp. The only lifted values are the status colours, because --ok #1F9D6B genuinely fails contrast on that background.

Gloves

Winter mornings mean gloves, and gloves do not work on a capacitive screen at all — so the answer is not "bigger buttons", it is fewer touches. A stop is four taps. Nothing needs a swipe, a long-press, a pinch or a drag. The one fine-motor interaction, the signature, is made by the person receiving the order, who is not wearing driving gloves.

The picker is a searchable list, not a pull-down changed

Sixteen people on the board today — eleven drivers and five supervisors — and it will grow. A native <select> pull-down is the wrong control here: it hides the list behind a tap, renders as a tiny OS overlay with row heights the platform picks, and gives no room for the van and stop count that tell a driver they picked the right row. The list is the screen, with a search field at the top that filters as you type.

Two letters in — matches anywhere in the name, so her finds Herbert and lup finds Lupe S.:

er
3 matches
ER
EribertoDriver
HE
HerbertDriver
EL
Elmer HSupervisor

Grouped so the common case is still one tap

On today comes first — usually three or four people, straight off the runs ops built that morning, with van and stop count so a driver sees their own run before they touch anything. Then everyone else alphabetically, then supervisors. Nobody has to search on a normal day; search is there for the day somebody is covering.

Typing at 04:00 is fine, actually

It was worth checking against the gloves rule — but a PIN has to be tapped bare-handed anyway, so the search field costs nothing that was not already being paid. It stays optional: scrolling to your name always works.

Picking a van, after the PIN not before new

The van is a claim about today, so it happens once the app knows who is making it. It is pre-selected from the run ops built, because on a normal morning it is already right and confirming beats choosing. Changing it is one tap, for the 04:00 reality where #5 would not start and Porfirio took #6.

Two drivers, one van

Vans already signed into are shown as such rather than hidden, because sometimes it is genuinely right. Taking one asks why and tells the supervisor. This is the fix for VEHICLE # on the board being a dropdown somebody types from memory.

This is the vehicle log, most of the way

"Out at 03:44 · Porfirio · Van #5" is DATE · DRIVER · OUT BY · AM/PM on the CREDIT CARD & VEHICLE LOG sheet, written without anybody typing it. Signing out at the end is the IN BY time Elmer says is always missing. Still v2 — but the van step is what makes it nearly free.

Enrolling a tablet

One-time, from Settings → Tablets in ops: name the device ("Van #5 tablet") and it takes a long-lived device token. Lose a tablet and you revoke that one device. An unenrolled device gets the enrolment screen and nothing else — no order, no client name, no list of drivers. The tablet lives in a van but does not decide which van the run is on; the driver does.

What the sign-in buys you

Every photo, signature and delivery time is stamped with a person and a vehicle, not a device — that is what makes it evidence when a client disputes a drop, and what fills two columns of the board by itself.

The run

Porfirio · Van #5 · what he looks at all morning
A run is deliveries and pickups, in one list. The board splits NetJets from AG-direct because that is how the office files it — but a driver drives one van down one route, and switching tabs at 04:30 to find out whether stop 6 exists is how a stop gets missed. Here the split is a chip on the stop, not a tab. The order the office set in the morning is the order the driver sees; nothing re-sorts itself while he is driving.
04:58Van #5 · LTE ▮▮▯ · 88%
PO
PorfirioVan #5 · Wed 12 Aug
04:58
out since 03:50
9drops
2pickups
3done
2 waiting to send
12540850NJ
NetJets VNY
Dropped 05:15 · signed R. Aguirre
12540084NJ
NetJets VNY
Dropped 05:15 · signed R. Aguirre
12536237NJ
NetJets VNY
Dropped 05:16 · signed R. Aguirre
4
12540396NJNut allergy
ACI Jet North · SNA
3 trays · board time 7:36 AM
Pick upAG7 items
Cafe Ficelle · CMA
Ready 7:00 AM · for N1454H + 3 orders
6
12536425NJ
ACI Jet North · SNA
5 trays · board time 7:00 AM
7
AG-4417AG
Signature Aviation · SAN
2 boxes · board time 9:00 AM
Open stop 4 — ACI Jet North
Something's wrong with my run

Pickups are a stop, not an errand list new

A pickup sits in the run in route position, dashed instead of solid so it reads as a different thing at a glance. Today they live in a Google Keep note titled PICK UP ORDERS shared with seventeen people — the driver is expected to have read it. Here it is stop 5.

"2 waiting to send" is the honest answer to airport ramps decision

Signal on a ramp at VNY or SNA is not reliable, and a driver who taps Complete and sees a spinner will tap it again. Completions, photos and signatures are written locally first and pushed when there is signal, with a plain count of what is still queued. Nothing is lost, nothing double-posts, and the driver is never blocked by the network.

No route optimisation, no turn-by-turn

You build routes strategically every morning and you are better at it than a solver that has never heard of Signature North vs South. Tapping the FBO opens the driver's own maps app, which is what they already do and do well.

"Board time", never a countdown

DELIVERY TIME is not a drop-off target — Wednesday's real rows show a 10:00 AM order dropped at 02:00 and an 11:00 AM at 05:45. So the column appears as a fact with a neutral label and drives nothing: no countdown, no red, no "late". Question 1 for Elmer is what it actually means; the app does not guess in the meantime.

"Something's wrong with my run"

One button, always there. It sends the supervisor on duty the driver, the van, the stop and a note. Without it, the first thing a driver does when reality diverges from the tablet is stop using the tablet.

A delivery

12540396 · ACI Jet North SNA · start to finish
Elmer's four things, in order. "It's the invoice being signed with a timestamp. It's the box with a picture taken at the delivery location. And being able to track these orders as they leave the kitchen from point A to point B." Confirm where you are → photo → signature → printed name. The printed name matters: a squiggle alone does not answer who signed for it, which is the question that actually gets asked. This writes the same delivery_proof row and the same order_photo records that ops already writes today — same storage, photos to the order-photos blob container, never Postgres.
07:11Van #5 · LTE ▮▯▯ · 84%
Stop 4 of 11 · NetJets

12540396

ACI Jet North · SNA

Nut allergy 3 trays version 1
Board time
7:36 AMnot a target — see note
Now
07:11you are 25 min ahead
Where you areConfirmed

ACI Jet North — matches where this order is going.

Photo at handoverSharp
Tap to retake · 1 photo · queued to send
Signature
M. Delgado
4
Who signed — printed
Marco Delgado
Required. A signature alone does not name anybody.
Complete delivery
Couldn't deliver

The wrong-FBO guard, with no QR label v1

Elmer: "these three orders out of your five have to go to Signature North, these two out of five have to go to Signature South… it happens all the time. Two days in a row." The tablet knows where it is and the order knows where it should be, so confirming the FBO on screen catches it — no QR codes, no label printer, no whole workflow to build first.

This order isn't for Signature South.

12540396 goes to ACI Jet North, 1.8 miles away.

Take me there It's right — say why

The override exists, but it asks for a reason and records it in override_reason — a genuine exception survives, a mistake gets caught at the tailgate.

The photo check

Joe: "you have to hold still for a split second, which sucks… and then the whole thing is glitchy" and "you can't zoom in to see if the blueberries are rotten." Two checks on the device before a photo is accepted — sharpness, and whether the subject fills enough of the frame. Two seconds, and it saves the deviation nobody can answer. Uploads use the same client-side downscale ops already uses (1600px / 0.78), so it is small over cellular and still zooms.

"Couldn't deliver" is a first-class outcome

The aircraft left, the FBO is closed, nobody will sign. If the only button is Complete, drivers press it anyway and the record is worse than nothing. This asks a reason, takes a photo, and puts the stop back in front of the supervisor while the driver keeps moving.

Nothing new in storage

The tablet writes through the existing delivery.save() and photos.save(kind="handover"). Same blob container, same managed identity, same 5-minute user-delegation SAS on read. Step 1 was built office-side precisely so this step is a new surface, not a new record.

A pickup

Cafe Ficelle CMA · the flow nothing in the app models today
This is the genuinely missing half. The board's PICK UPS tab (ORDER OR TAIL # · PICK UP LOCATION · ORDER STATUS · P/U TIME · DRIVER · P/U STATUS) plus a Google Keep note called PICK UP ORDERS is the whole system today. It is third-party collections — Cafe Ficelle, True Foods, See's Candy — and it is non-food: pillows and pillowcases, a double duvet, ceramic soup bowls, pyjamas, and newspapers tagged per order and per tail. Every line in that Keep note already carries its order number ("2 CANS DIET COKE - 12506356"), which is exactly what makes this modellable.
07:02Van #5 · LTE ▮▮▯ · 81%
Stop 5 of 11 · Pick up

Cafe Ficelle

Camarillo · CMA

AG Ready 7:00 AM 1 not ordered
1
What you're collecting3 of 7
Cafe Ficelle order @ 7am
N1454H
Not there
True Foods pick-up
12516663Ordered
Not there
2 boxes dark chocolate — See's Candy
12541253
Not there
4 pillows & pillowcases
12541253
Not there
1 double duvet w/ cover
12541253
Not there
Pyjamas — 1 male XL, 1 female M
12541253
Not there
10 × 16–24 oz ceramic soup bowls
12516663Office: not ordered yet
2
Photo of what you loaded
One shot of the load. No signature — nobody at a bakery signs for our order.
Collected — 6 of 7Nothing was ready

A pickup is a different screen, not a delivery with the words swapped design call

No FBO to confirm, nobody to sign, no printed name — and a per-line outcome instead of one outcome for the stop, because the real failure is "they had four of the six". Reusing the delivery screen would make drivers tick things they did not get.

Every line keeps its order number the whole point

The Keep note already tags each line ("4 PILLOWS & PILLOWCASES – 12541253"), so the collected item lands against the right order in ops the moment it is ticked. That is what turns a shared note into a record: the office sees the duvet arrive on order 12541253, not "Porfirio said he got it".

"NEEDS TO BE ORDERED" is a state, not a strikethrough

Elmer uses that phrase in Keep as an ad-hoc sub-status. Here it makes the line uncollectable and says why, so a driver does not stand at a counter asking for soup bowls nobody bought. It is the office's problem, shown to the driver as a fact.

Newspapers are the awkward one

"3 NY TIMES – 12532463, N1454H" is bought, not collected from a named vendor, and the driver buys them en route. Ops already has a shopping rollup, so this is a boundary question rather than a design one — see question 5.

The board's P/U STATUS fills itself

The PICK UPS tab has a P/U STATUS dropdown someone types after a phone call. Six of seven ticked at 07:14 by Porfirio in Van #5 is that column, without the call.

The ops side

Two screens in the app you already have
The van is only half of it. Somebody has to say which stops belong to which driver in the morning — that is the one genuinely new office screen. Everything else is the existing Delivery board gaining columns it no longer has to be told.
DeliveryWednesday 12 August · 9 drops · 2 pickups
BoardBuild runsTablets
Runs sent to 3 tablets Send updates

Unassigned

drag onto a van, or tap the van
2 left
AG-4423AG-direct Signature Aviation · SANboard time 3:30 PM Van #3 Van #5 Van #6
Pick upTrue Foods Ventura · for 12516663ready 8:00 AM · 1 item not ordered Van #3 Van #5 Van #6

Porfirio · Van #5

11 stops · signed in 03:50 · tablet online
3 deliveredReorder
#OrderWhereBoard timeTypeStatus
112540850NetJetsNetJets VNY6:29 AMDeliveryDropped 05:15
212540084NetJetsNetJets VNY6:00 AMDeliveryDropped 05:15
312536237NetJetsNetJets VNY10:00 AMDeliveryDropped 05:16
412540396NetJetsACI Jet North SNA7:36 AMDeliveryIn the van
5Pick upCafe Ficelle · N1454HCamarillo CMAready 7:00 AMPickup7 items
612536425NetJetsACI Jet North SNA7:00 AMDelivery

Build runs the one new office screen

  • Unassigned is everything at built plus every pickup for the day. It should reach zero before the first van leaves, and if it does not, that is visible rather than remembered.
  • Stop order is set here and frozen. Drivers do not re-sort, and the app does not either.
  • Send updates pushes a changed run to a tablet mid-morning — a new order, a cancel, a re-route — and the tablet says plainly that stop 7 changed rather than silently redrawing.
  • Runs are per driver + van + date, so the same person in a different van on Thursday is a different run and the vehicle on the proof record is always right.

The board becomes a read-out

  • DRIVER and VEHICLE # come from the run, not a dropdown.
  • ORDER STATUS comes from the stop.
  • ORDER DROP OFF TIME — the column Elmer actually cares about and the one that is blank most often — is written by the tablet at the tailgate.
  • P/U STATUS on the PICK UPS tab comes from the ticked lines.
  • Nothing on it is typed. This is why the delivery leg comes before the digital board: build the board first and you have only moved the typing.

What it takes

Against the app as it stands on tpp-sage-import
Step 1 already did the hard part. delivery_proof, order_photo, the blob container, managed identity, the SAS read path, the client-side downscale and the 22-check test_delivery.py gate are all live and verified on prod. The driver app writes through those same functions. What is genuinely new is a surface, a run, and pickups.

Reused, not rebuilt no new risk

  • Proofdelivery.save() and delivery.get(), unchanged. The tablet fills driver, device, fbo_confirmed, override_reason, lat/lon — columns that already exist and are mostly empty because only the office writes today.
  • Photosphotos.save(kind="handover"), blob + thumbnail, never Postgres. Pickups add one kind: pickup.
  • Activityorder_event / events.py already renders the timeline. Delivery and pickup events just write to it.
  • Separationportal_server.py already proves the separate-ASGI-app pattern, right down to its own App Service and its own custom domain.
  • Tokensorder_link already does multi-use expiring tokens. Device enrolment is the same shape with a longer life and a revoke.

New tables — five, all small

driver id · name · pin_hash · active · is_supervisor
device id · label ('Van #5 tablet') · token_hash · revoked_at
run id · run_date · driver_id · vehicle · sent_at
run_stop id · run_id · seq · kind delivery|pickup
              · order_id NULL for a vendor pickup
              · location · board_time · status
pickup_item id · run_stop_id · description · order_ref · tail
              · state pending|collected|unavailable|not_ordered

No new photo storage. order_photo gains one kind value.
No lateness column anywhere — board_time is displayed, never scored.

Deliberately not in v1

  • QR labels. Confirming the FBO on screen already catches the Signature North/South mistake. A label-printing workflow is a whole build for the same outcome.
  • Turn-by-turn and route optimisation. Tap the FBO, their maps app opens.
  • The vehicle and ARCO card log. Sitting right there, though — the sign-in already knows driver, van, and time out, which is DATE · DRIVER · OUT BY · AM/PM on the CREDIT CARD & VEHICLE LOG sheet, and signing out at the end is the IN BY time Elmer complains is always missing. It is the obvious v2 and it costs almost nothing once v1 exists.
  • Break reminders. Needs the sign-in, which is why the sign-in is worth doing properly.
  • Expo prep photos. Prep/packed/loaded shots need a login for kitchen staff — a different roster and a different question (see question 4).

Order to build it

  • 1 — driver_server.py, sign-in, read-only run. Nothing writes. Real drivers, real stops, on a real tablet, for a week. Cheap to abandon if the shape is wrong.
  • 2 — The delivery stop. FBO confirm, photo, signature, printed name, complete. This is the day the phone calls stop.
  • 3 — Pickups. Stop type, checklist, per-line outcome.
  • 4 — Offline queue. Deliberately after 2 and 3: build it against real ramp coverage rather than a guess about it.
  • 5 — Board columns fill themselves. Almost free by then.

Questions for Elmer — in the order they block things

  1. What does DELIVERY TIME actually mean? Wheels-up? Required-by at the FBO? The time the client asked for? Wednesday's board shows a 10:00 AM order dropped at 02:00 and an 11:00 AM at 05:45, so it plainly is not a drop-off target. Nothing in the driver app scores against it until this is answered — but the answer changes whether the run is even sorted by it.
  2. The full driver and supervisor list, and who may sign in on which van. Eleven drivers and five supervisors are on the board; are those all of them, do supervisors drive, and can any driver take any tablet or is Van #5's tablet only for Van #5's driver?
  3. Do pickups get their own driver, or ride along with a delivery run? The PICK UPS tab has its own DRIVER column, which suggests they can be assigned separately — but Wednesday's pickup was at Camarillo at 7:00 AM, which is somebody's route anyway.
  4. Do Expo staff need logins for prep photos? They already use three tablets pointed at Google Photos. Same app, different role — or leave prep photos office-side for now?
  5. Newspapers — bought or collected, and by whom? They are tagged per order and per tail like a pickup, but they are a purchase like the shopping list. Whichever it is, it should be in one place, not both.
  6. How long do delivery photos need to be kept? The blob lifecycle rule is waiting on this, and at ~37GB a year the answer matters.
  7. Who resets a forgotten PIN, and how fast? If it is only Elmer and it is 04:15, the answer needs to be a supervisor override on the tablet itself.