Status: STAGES 3–4 BUILT, DEPLOYED AND VERIFIED LIVE (2026-10-01). The satellite now drives a portal's own upload
control (FILE_DROP) or fills its quantity grid (GRID) from the order page's contract-3 signal, stopping before
any submission — proved against the live synthetic rig by me on 2026-10-01 (veripath-ops c2238be; record on
agents/handoff/from-opencode). What is left here is stage 5 (the probe-run record) and stage 6 (a real wholesaler,
which needs a real account and Matthew's say-so). Originally AUTHORISED 2026-09-28 — Matthew authorised the stage
that day — the route and the security footing
below stand as written, and stage 1 was specified the same day:
Supplier Portals — Stage 1: the Portal Record and the Signal.
This page stays as the stage's scope and the record of what was decided and why; the questions that remain open are
listed at the end. Everything under what exists today was read out of the code and the live client on 2026-09-28,
and no application code has been written by Hermes. Where this page and the stage-1 spec differ, the spec wins (its
§1 records the one refinement).
The data side of the order cycle is finished: dm+d, the Drug Tariff, price concessions, the derived reimbursement
figure and the client comparison are built, deployed and verified. This stage is the other end of the same cycle —
what happens between an order the pharmacy has built and the wholesaler's own portal that the order has to reach.
The pharmacy already asks the platform for a file and a human carries it to the wholesaler. The question this stage
answers is whether the app should go further and interact with the supplier portals themselves — the idea the
research page has been carrying since
before the v1 build: a small local program ("satellite") that rides on the technician's own signed-in browser and
stages the order in the wholesaler's portal, leaving the submission to the human.
That is a new class of software for this platform — the first part of VeriPath that runs on a pharmacy's own PC
rather than on infrastructure we control, and the first that touches a third party's system. It therefore needs scope,
an evidence rule and a compliance position agreed before a line of it is written. This page is that discussion.
| Thing | What it actually does now | Where |
|---|---|---|
| The order | Built from the low-stock list, grouped by supplier, edited and dropped line by line, exported, then received against | pharmacy_stock (order build / edit / detail / export views) |
| The file | Two columns and nothing else — a code and a quantity, no header row (headerless since the file contract of 2026-09-29); the PIP code where the item has one (leading zeros kept), else the barcode; whole packs | order_profiles.py (one generic profile) |
| What export records | The order turns Exported, and the app records who, when and how many times — a repeated download is never hidden | views.OrderExportView |
| The supplier record | Name, a wholesaler key (AAH / Alliance / Phoenix / Other), the pharmacy's own account reference, the export profile, an integration profile whose stored label says direct API is not implemented, and a minimum order value used as an advisory only | pharmacy_stock.models.Supplier |
| The stated position in the code | "holds no portal login or API secret — the pharmacy uploads the file to the wholesaler's own portal"; on the supplier list, "no portal login or API secret is stored" | Supplier docstring; supplier list template |
| Prices | A wholesaler price file is downloaded from the wholesaler by the pharmacy's own staff and uploaded by hand — "v1 has no stored credentials and no automated pull, by design" | Data Refresh Schedule §1 |
| At the portal | The pharmacy logs in themselves, opens the portal's bulk upload / quick add screen, attaches the file, checks the basket, submits — all by hand | Guide C6, Step 4 |
| What no code does | Opens a portal, reads a portal, stores a portal URL, stores a selector, stores a portal credential, or places an order | whole repository, searched 2026-09-28 |
So the interaction today is by hand and only by hand, and that is documented as a deliberate position, not an
omission: the app produces the file, the pharmacy does the rest.
The research page is a chat transcript with working prototype code, written before the v1 build and against a
different assumption about the platform. It is an input to this stage, not a specification for it. Two things in it
cannot be built as written:
Wholesaler Integration Settings, afrappe.whitelist endpoint, a Frappe page controller. This project's position is already settled the other way:What survives, and is worth keeping (all of it is the shape, none of it is the code):
No real wholesaler's portal URL, upload endpoint, form field, selector or file layout enters this codebase until
that wholesaler's own portal has actually been seen working, and the sighting is recorded with a date and who saw
it. This is the same rule the order-file profiles and the price-file mapping hints already live under, simply
extended to the portal.
Consequences, so the stage can start without waiting:
synthetic-wholesaler-*-price-list-*.csv).| Route | What it means | What it buys | What it costs / blocks |
|---|---|---|---|
| 1. File and a human (today) | The app exports the CSV; staff upload it in the portal and submit | Zero credentials, zero local software, nothing to support on the pharmacy's PC; the portal's own validation speaks for itself | The retyping is gone but the file handling is not — and nothing tells the app what the portal actually accepted |
| 2. Local satellite staging (the research's intent — this stage) | A small program on the pharmacy PC attaches to the technician's signed-in browser, opens the portal, stages the order there (file or grid), and stops | Removes the last manual step between our file and the portal's basket; no credential is ever stored; MFA untouched | New software on customer PCs: packaging, updates, support, and a local security surface. Needs the per-wholesaler layouts, which need evidence |
| 3. Wholesaler B2B / API | Direct integration with a wholesaler's ordering API or EDI | Cleanest technically, no browser at all | The app's stored label already says not implemented; needs a commercial agreement, real credentials, and its own compliance position. Not a this-stage item |
Route 2 is the stage. Route 1 stays the fallback and never stops working: if the satellite is absent, unconfigured or
unsupported, the pharmacy exports the file exactly as today.
The mothership (our Django app, client DB) stays authoritative.
The satellite (local to the pharmacy PC).
The two methods, in the order the evidence supports them: file drop first (the portal's own upload screen is
the thing the pharmacy already uses by hand, so the change is smallest), grid injection second.
One design question the stage must settle: whether the configuration always travels on the page (nothing to update
on the PC, but the satellite only works while the app page is open) or is baked into a downloaded package (works
standalone, but goes stale and has to be re-downloaded when a portal changes). My recommendation is the page, with
the package carrying only the launcher.
The stage's whole justification rests on this, so it is stated as commitments rather than intentions:
| Stage | What ships | Evidence needed |
|---|---|---|
| 1. The portal record and the signal | The supplier's portal target and method, with their evidence, on the supplier record; the order page renders the configuration and an inert signal. Nothing local, and no download | none — synthetic |
| 2. The satellite skeleton and the download | Local launcher attaches to the user's browser, finds our page, reads the signal, picks up the exported file, stops | none — synthetic |
| 3. The first route — file drop | The satellite hands the exported file to a portal's upload input and stops at the staged basket | our synthetic portal |
| 4. Grid injection | Code + quantity rows typed into a portal's quick-order grid | our synthetic portal |
| 5. The record | The probe run on the order page and in the audit trail; what to do when a run fails or is abandoned | synthetic |
| 6. The first real wholesaler | One wholesaler's portal configured, with its evidence line, and a guide that says so | a real portal, seen and dated |
Everything before stage 6 is verifiable on our own synthetic portal; stage 6 is the first thing that cannot be done
without a real account and Matthew's say-so.
Settled 2026-09-28 by his authorisation:
Still open:
| # | Question | What it blocks |
|---|---|---|
| 2 | Partly settled 2026-09-28 — the test rigs are the primary host (Linux) first, then Matthew's Windows PC, and the pilot fleet is expected to be Windows. Still open: Chrome or Edge at the pilot, and whether the pilot's IT accepts a browser started with a local debugging port | the pilot's browser and their IT position — stage 6; stage 2 is no longer blocked |
| 3 | Is there a named wholesaler whose portal we may work against, with a real pharmacy account, and who would watch it being driven? | stage 6 only; everything else is synthetic |
| 4 | Who maintains the supplier/portal directory? The MHRA wholesaler register lists licensed wholesalers — it does not give portal URLs, and portal URLs move | the directory feature, not the satellite |
| 5 | What happens when a wholesaler changes its site? The research proposed an AI auto-heal loop; that puts a model inside a commercial ordering path. Is that wanted, or is the position "it breaks, it says so, it is fixed by hand"? | stage 3 onwards, as a support model |
| 6 | May the app ever see what the portal did (basket, prices, confirmations)? My recommendation is no | the record's scope (stage 5) |