The attached file cannot be the weekly input. It is a report of orders that already happened.
Every column in Orders_Detail_2026-09-02 is an outcome: orderstatus=delivered, deliverytime, rider, assigntime, kms. Feeding it back would re-create twelve deliveries that were already made, already ridden and already charged.
It also fails the console’s existing upload contract outright. Of the six columns the bulk uploader marks required, two are absent from the report entirely and one is blank on all twelve rows. This is fixable — the underlying customer data is all there — but it needs a purpose-built intake template, and that is decision one below.
Analysis · the gap
Report export vs. the upload contract
The console already accepts CSV/XLSX bulk uploads via orders/multipleOrders.js, which maps incoming headers through a fixed headerMap and rejects the file if a required column is missing. Here is that contract against what the report actually provides.
Upload column
Report field
Filled
Status
sendernamereq
locationname
12/12
Mappable
senderphonereq
locationcontactno
12/12
Mappable
senderaddressreq
Pickupaddress
0/12
Present but empty
receivernamereq
deliverycustomer
12/12
Mappable
receiveralternatephonereq
—
—
Absent
itemdescriptionreq
—
1/12
Absent
receiverphone
deliverycontactno
12/12
Mappable
receiverfulladdress
deliveryaddress
12/12
Mappable
receiverlatitude
deliverylat
12/12
Mappable
receiverlongitude
deliverylong
12/12
Mappable
pickupdate
deliverydate
12/12
Timezone-suspect
Quantity
—
—
Absent
Analysis · the harder constraint
Orders attach to customer records, not to addresses
This is the part that decides the architecture. createorders posts an array in which every element carries customerid and deliveryid. A row of free text is not enough — each line of the weekly file has to resolve to a customer that exists, or one has to be created first. That is exactly why the current UI says “Press Continue to add as drop customers” between upload and creation.
Three more values are never in any sheet and must come from elsewhere at commit time:
The pickup location — applocationid, partnerid, locationid, moduleid and the pickup coordinates all come from the selected location record.
The pickup slot — chosen per batch, and it drives deliverytime.
The charge — computed from tenant pricing and distance, never copied from the sheet. On this data the rule fits ₹60 up to 4 km then ₹12/km, but that is inferred, not configuration.
Consequence: the weekly file supplies who and what. The pickup point, the slot and the money are supplied by the run, not the sheet. Any automation has to bind them explicitly rather than inherit them by accident.
Design
Where an agent earns its place — and where it does not
Most of this pipeline is not an AI problem. Parsing, validating, pricing and posting are deterministic and must stay that way, because they are the steps where a confident wrong answer costs money. The agent belongs at the three genuinely ambiguous joints.
Deterministic — no model
Parse XLSX/CSV, drop TOTAL rows
Validate required columns, reject early
Compute charge from tenant pricing
Duplicate detection by run key
Build and POST the createorders payload
Reuse scanDataQuality as a pre-commit gate
Agent — proposes, never commits
Customer resolution: match name + phone + address to an existing record, or propose a new one
Address normalisation: split free text into locality/landmark/pincode, including Tamil-script entries
Anomaly triage: explain why a row looks wrong, in operator language
The rule: the agent returns a proposal with a confidence score and its reasoning. Deterministic code applies a threshold. Anything below it goes to the operator queue rather than into the batch. A model never writes an order.
Build
Phases
Ordered by dependency — each phase is useful on its own, and nothing writes an order until phase 4.
00
Define the intake template
A real order-intake sheet: sender, receiver, phone, address, item, quantity, requested date, collect-cash. Published as a downloadable template from the console so the weekly file is generated against a known contract instead of being whatever someone exported.
GateNothing else starts until the column list is agreed and a sample week exists.
01
Parser and validator, dry-run only
Pure module beside opsAnalysis.js, same testing approach
Per-row result: ok / needs-review / rejected, each with a reason
Reuses the existing scanDataQuality findings
GateRun a real week through it and read the report. Zero writes.
02
Customer resolution
Deterministic first: exact phone match against gettenantcustomers
Agent second, only for what did not match exactly
Every proposal carries confidence + rationale; below threshold goes to the queue
GateMeasure match accuracy on a known week before it influences anything.
03
Review and commit screen
Proposed batch shown as a table: resolved customer, address, charge, flags
Operator picks pickup location and slot — the values the sheet cannot supply
One commit button, posting to orders/createorders
GateThis is the first phase that creates real orders. Ship it behind a role check.
04
Scheduling and idempotency
Weekly trigger: watched folder, mailbox, or an upload endpoint — see decision 3
Run key per row = hash(tenant, week, receiver phone, address, item). Persisted.
A replayed file is a no-op. The endpoint has no dedupe of its own, so this layer must.
GateDeliberately re-run last week’s file and confirm zero new orders.
05
Supervised autonomy
Auto-commit only rows that are fully resolved, unflagged and above threshold
Anything else waits in the queue; the operator gets a digest, not a surprise
Kill switch, and an audit row per created order naming the run that made it
GateEnable only after several consecutive clean weeks at phase 4.
Risk
What will go wrong, and the guardrail for each
Risk
Why it is real here
Guardrail
Duplicate orders
The same file re-sent, or a retry after a timeout. createorders does not dedupe.
Persisted run key per row; replay is a no-op.
Wrong customer
Fuzzy matching on names like HAMEEZ RAMEEZ with trailing spaces, and two scripts for one locality.
Exact phone match first; agent proposals below threshold never auto-commit.
Bad geocode
Addresses carry plus-codes and free text; the store itself resolves to two precisions.
Reject rows whose coordinates fall outside the tenant’s service radius.
Wrong slot / date
The deliverydate timezone defect is unresolved and reads 5½ hours early.
Operator sets the slot per batch; never inherit a date from the sheet until the backend is fixed.
Silent field drift
Already observed: the API sends ridername/deliverycharges, the export writes rider/deliverycharge. Both fail as blanks and zeros, not errors.
Validate the parsed shape and fail loudly on an unknown or missing column.
Blocked on you
Decisions needed before phase 0
What is actually in the weekly file? Recurring standing orders for the same customers, or a fresh list each week? The first makes customer resolution nearly free; the second makes it the hard part.
One pickup location, or several? This tenant has two — Bawa Medical (King Nagar) and Bawaa Medicals 2 (Weavers Colony). If a single file mixes both, it needs a sender column per row rather than one choice per batch.
How does the file arrive? Operator upload in the console is simplest and needs no new infrastructure. A watched folder or mailbox is more automatic and more to build and secure.
Where does the agent run? Still open from before: inside backend_jupiter, or a separate DailyGrubs service it calls. Nothing in phases 0–1 depends on the answer, so building can start regardless.
Who may commit a batch? Auth in this console is localStorage and the route guards are currently commented out. A screen that creates real orders needs that settled first.