Status: I1–I3 BUILT, DEPLOYED AND VERIFIED LIVE (2026-10-01), and all six questions on this page are now DECIDED
(§Decisions below). The price-file page renders the inbound contract-3 signal and the satellite activates the
portal's download control, catches exactly one new file and hands it to our own upload box — 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 I4 (the record) and I5 (the first real wholesaler). Originally written 2026-09-29 as a DRAFT for
Matthew's review, nothing authorised, no application code by me — it is the inbound counterpart to
Supplier Portals — the Next Stage (Scoping Note):
that note takes the pharmacy's order out to a wholesaler's portal, this one brings the wholesaler's price file in
to the pharmacy's own database on the client VPS. Both directions ride the same satellite, the same signal contract
and the same security footing — that footing is stated on the sibling note and is not restated at length here.
Where this route would need something the sibling note did not allow, this page says so and asks, rather than assuming.
The pharmacy already asks the platform for its order file and a human carries it to the wholesaler. The other half of
the same cycle is the price file coming back: today the Business Manager logs into the wholesaler's portal, downloads
the price file, returns to the app, uploads it by hand, maps its columns and confirms. That is documented as a
deliberate position, not an omission — "v1 has no stored credentials and no automated pull, by design"
(Data Refresh Schedule §1).
This page scopes the more automated way of getting that data imported and mapped — the download and the handover
performed by the satellite — while the manual route stays exactly as it is. Where the two overlap, the code, the screens
and the record are the same objects; the satellite only changes who fetches the file.
| His words | What it fixes |
|---|---|
| "an alternative and more automated way of getting data imported and mapped to the pharmacy app" | The route is an alternative entry to the existing import, not a new import. Nothing about parsing, matching, storing, superseding or the comparison changes |
| "it does not replace the manual procedures we were doing earlier as they must always exist" | The manual upload is an invariant, not a fallback we may retire once the satellite works |
| "I cannot confirm that every single wholesaler has a download facility for CSV files, however I would find it strange how any such organization in this business could get by without this facility" | The route assumes a download exists and must detect and report its absence where it does not — a supplier with no download control is a finding, never a silent failure |
| "with appropriate AI/machine learning, we can make this process almost as automated as the traditional, more expensive PMR systems that rely on EDI integration" | The point of the work: approach the automation of an EDI-integrated PMR without holding a portal credential |
| "how exactly we apply AI in this area and when is something we can discuss once we have the essential components in place" | A scope instruction: this note designs components, not intelligence. AI/ML is named at the end as a later discussion |
| Thing | What it actually does now | Where |
|---|---|---|
| The import | Upload → column mapping (recorded per supplier) → confirm → rows stored against the supplier and the price date; supersede, freshness and the comparison follow | pharmacy_stock price-file import (stages 1–4, live and verified) |
| The download | Manual by design — the pharmacy's own staff download the file from the portal and upload it by hand; no stored credential, no automated pull | Data Refresh Schedule §1, §5 |
| The manual route as documented | Guide C6 covers the order-export side; the price-file route is walked on Pricing & the Reimbursement Comparison | guides + walkthrough |
| The outbound signal | Live: the supplier record carries the portal method, target and its evidence line, and the order page renders the inert #veripath-probe-signal block under SIGNAL_CONTRACT = 2 (v2 carries the order's last export time as well as the portal configuration) |
Stage 1 — the Portal Record and the Signal |
| The satellite | Specified, not built: attach to the user's browser, find our page, read the signal, take the named file, stop and report. Linux rig first, then Windows | Stage 2 — the Satellite Skeleton |
| Anything local on a pharmacy PC | None, and no portal address, selector or credential is stored anywhere | whole repository, searched 2026-09-28 |
The satellite skeleton is direction-agnostic. Nothing in it knows whether the file it takes is an order going out or
a price file coming in — which is why this route is a continuation of that work, not a new programme.
In one sentence: the Business Manager presses one control, and the wholesaler's current price file lands in the
pharmacy's own database — mapped, dated and compared — with the manual upload still there beside it.
| Piece | Why it is separable | |
|---|---|---|
| A | A download the app can name — the portal's own download control, in the portal tab the technician has already signed into | Never a guessed URL and never a hidden endpoint; it is the same control the human clicks today |
| B | A catch — the satellite identifies the file that download produced, exactly | The portal names the file, so the app cannot name it in advance; the rule has to be "exactly one new file", not "the newest CSV" |
| C | A hand-over into our page — the caught file goes into the app's existing upload step on the live page | The app stays the only writer, and the operator still sees the file arrive |
| D | The operator's confirmation — mapping and confirm stay human-visible | Unchanged screens; the recorded mapping makes a repeat one confirmation |
| E | A record — which supplier, which file, when, in which mode, and what it produced | The counterpart of the export audit the order already carries |
| F | The comparison that already exists takes over untouched | Freshness, supersede and the reimbursement comparison are live and are not touched by this route |
R0 is the prerequisite and is not duplicated here: outbound stage 2, the satellite skeleton, must be built and
verified first — it is the only thing that attaches to a browser at all, and both directions need it. The stages below
are lettered I for inbound deliberately, so they cannot be confused with the sibling note's stages 1–6.
The price-files page (and, where it belongs, the supplier record) learns that a supplier's price file may be fetched
from its portal: the portal target and method are already stored (stage 1 fields), and the page renders an inbound
signal in the same contract shape as the order page's — naming the supplier, the method and what the satellite is
expected to fetch. It states, in the app's own words, that the upload below is unchanged and always available.
Mothership only: nothing local, no download, nothing that opens a portal, no credential anywhere. Evidence needed:
none — synthetic. After it: a page can say "this supplier's prices can be fetched", and a human can still ignore it.
Having read an inbound signal, the satellite switches to the supplier's portal tab — already open and signed in by the
human — finds the portal's own download control from the configuration carried on our page, activates it, and stops.
It submits nothing, reads nothing back from the portal, and touches nothing but the one control the configuration names.
This is the first stage in the whole programme that acts rather than reads, and it is therefore where the
origin allow-list owed by outbound stage 2 must be closed first: the satellite must not act on a page that merely
imitates our signal. The sibling note records that choice as owed; this stage is where it is paid.
Needs: the synthetic portal (Matthew's yes/no, already flagged on the sibling note) — a page of our own, shaped
like a wholesaler's, with a download control that produces a synthetic price file. After it: a file exists in the
downloads folder, produced by a real download in a real browser, with nothing about the portal stored anywhere.
The satellite identifies the file that download produced and hands it to the app's own upload step on the live page —
driven the way the outbound route drives a portal's file input — leaving the mapping and the confirm step to the
operator.
The catch rule, which is a real decision. Outbound stage 2 could insist on "the exact filename, or nothing"
because the app names the exported file. Here the portal names it, so the app cannot name it in advance. The
faithful translation of "exact or nothing" is: the satellite records what was in the downloads folder before it
triggered the download and accepts exactly the one file that appeared; two new files, or none, are refused and
reported. No "newest CSV", no wildcard, no pattern guessed from a supplier we have not seen.
Must be true afterwards: nothing was written to the database by the satellite; the import is the app's own; the
manual upload still works on the same page; an ambiguous, missing or stale file is refused in the app's words.
Which supplier, which file (name, size, when), which mode the file arrived by (manual or satellite), how many rows
the import read and what it produced. A failed or abandoned run is visible as such. This is the counterpart of the
sibling note's stage 5, and the two should share one record shape rather than inventing two.
One real portal, configured from dated evidence, with a guide that says so — and the manual route still beside it.
Requires a real account and Matthew's say-so; everything before it is verifiable on our own synthetic portal.
The Download & Scrape transcript is an input, not a
specification (it is written against a Frappe front end and a different platform assumption). Right, and kept: that
a downloaded file is parsed rather than screen-scraped — which is exactly what our import already does, and why this
route reuses it; and that the price data must stay in the client tenant. Dead — do not re-propose:
veripath_order_<supplier>_<id>.csv, "read the filename to attach theC:\VeriPath\Pipeline\) and the instant purge. The browser owns the downloadsRecorded here so the discussion Matthew deferred has a place to start, and so no stage quietly grows one:
Whenever it is picked up, three constraints come with it: it may decide nothing a human has not been asked about; it
must not sit inside the ordering path; and nothing commercial leaves the client tenant to do it. The essential
components first — the stages above — and the AI question opens once they exist and are verified.
**All six are now DECIDED — see §Decisions below: Q4
yes, narrowly; Q3 as written; Q5 the operator, with no default; Q6 and Q1 as recorded. Matthew delegated them on
2026-10-01 ("you can decide what is most suitable and practical for these matters"), and nothing here gates the
build. The table stays as the record of what was asked.
| # | Question | What it blocks |
|---|---|---|
| 1 | Is this stage list right (I1–I5, after the shared skeleton), or would you rather see the inbound route specified only as far as the skeleton is proven? | the whole route |
| 2 | May we build a synthetic portal of our own? (Already the sibling note's one open gate.) | stages I2 and I3 |
| 3 | The catch rule — exactly one new file, two refused. Acceptable, or do you want a per-supplier filename pattern declared as evidence arrives? | stage I3 |
| 4 | May the satellite drive our own page (the hand-over in I3)? It is the first time any stage writes into a page; the skeleton is deliberately read-only. | stage I3 |
| 5 | Who sets the price date when the satellite fetches the file — the operator as today (the upload step's date box defaults to today, which the 2026-09-29 run showed supersedes the wrong row when it is left alone), or the app reads the newest date the file itself carries? | the freshness states, and stage I3 |
| 6 | A supplier with no download facility — do we treat that as "the manual route, recorded as such", or does it need naming on the supplier record? | I1's wording, and the evidence rule's consequences |
Matthew delegated these on 2026-10-01 — "you can decide what is most suitable and practical for these matters" — so
they are decided here, each with its reason, and they are not questions again. Nothing is left gating the build:
stage I3 is unblocked.
Q4 — may the satellite drive our own page? YES, narrowly. The hand-over is the inbound journey: a fetched file the
operator must still hunt for and drag in saves almost nothing over downloading it by hand. The risk is contained by the
fact that this is our own page — a first-party form we version, not a third party's. The satellite may target only
our own origin (the allow-list already covers this) and only the one file input that the page's own inbound signal
names; it submits nothing, writes nothing to the database, posts to no endpoint, and stops before the operator maps the
columns and presses confirm. Every never-do rule in the list above stands unchanged. And the fallback already
exists: the manual upload sits on the same page and stays available.
Q3 — the catch rule: exactly one new file, as written; no guessed patterns, and no "newest file". The satellite
records what was in that PC's Downloads folder before it triggers the download, and accepts exactly the one file that
appeared; two new files, or none, is refused and reported in the app's own words. That is the faithful translation of
the outbound rule ("the exact filename, or nothing") to a case where the portal names the file. A per-supplier
filename pattern is not permitted as a guess — it may be added later only as declared evidence for a supplier
whose portal has actually been seen, recorded on that supplier's portal profile beside its other dated evidence, never
inferred. The awkward cases fall out of the rule rather than needing new ones: a file that appeared but cannot be
identified, or that is older than the last imported file, is refused in the app's words.
Q5 — who sets the price date: the operator, unchanged — but the box must never silently default to today. The
operator keeps the authority; the satellite does not invent a date. One change removes the trap: the date is
pre-filled only from the file itself when the file carries a usable date (a mapped date column, or a date the parse
can read); otherwise the field is left empty and required, so a value has to be chosen deliberately. Grounding,
read out of the code on 2026-10-01: an import supersedes the earlier parsed file for the same supplier and the same
date (pharmacy_stock/price_import._supersede_live_files), so a wrong date does not corrupt the other dates — it
creates a new, newest record that the price queries then answer from, silently. Making the date explicit is what
removes the silence. It is a small change to the upload step's form, taken with the hand-over in I3 and recorded on the
pricing page when it is built.
Two more on this page, decided the same way so nothing is left open:
portal1–portal4, and are the