The attached file provides an overview of an app we are building for pharmacies, particularly independent ones - we are currently working on a feature that will enable pharmacies to compare pricing of products from their suppliers with data we will provide centrally - this data is a combination of the NHSBSA, Trud and PCS Concessions data that we source - we are now able to source this information as you can probably see in the attached file - what we now need to do is to process the above data so that can be compared with imported supplier pricing and thereby enabling the pharmacy to make an informed decision as to which supplier they will order from
To enable your users to compare supplier pricing against centrally provided NHS data, you need to solve a critical data engineering and architectural challenge.
As noted in your overview document, the platform successfully ingests three core central reference feeds: dm+d (via TRUD), the Drug Tariff, and Price Concessions (via CPE alerts). However, the core bottleneck holding back your "compare it" phase is pack matching, because concession lines carry no standardized product codes—only textual descriptions and pack sizes.
To process this data effectively so independent pharmacies can make informed ordering decisions, your development team should implement the following multi-stage data processing pipeline:
Before any price comparison can happen, you must map the unstructured central data to a standardized drug dictionary.
Normalize Concession Descriptions: Build a string-normalization and natural language processing (NLP) pipeline on your central tier to map CPE concession descriptions to unique AMPP (Actual Medicinal Product Pack) codes or VMPP (Virtual Medicinal Product Pack) codes within the dm+d dataset.
Establish a Multi-Tier Matching Logic:
Once the data is unified under standardized dm+d codes, the application needs to calculate the true financial baseline (the Derived Price per Pack) centrally. The system must dynamically calculate this for every pack:
Because VeriPath operates on a SIAAS (Sovereign Infrastructure as a Service) model, commercial data is strictly segregated. Wholesaler pricing files live locally on the client’s own server database, while the reference calculations sit centrally.
┌────────────────────────────────────────────────────────┐
│ CENTRAL TIER │
│ [dm+d] + [Drug Tariff Parameters] + [Concessions] │
└───────────────────────────┬────────────────────────────┘
│ (Read-Only Secure Tunnel)
▼
┌────────────────────────────────────────────────────────┐
│ CLIENT-OWNED SERVER │
│ - Reads Derived Central Price Vector │
│ - Joins locally imported Wholesaler CSV Price Files │
│ - Computes True Purchase Margin │
└────────────────────────────────────────────────────────┘
To run comparisons safely without violating your data privacy framework:
Now that the data is aligned, present it to the Responsible Pharmacist or Dispensing Technician during the procurement workflow to drive immediate cost savings:
To refine this pipeline, let me know:
In terms of unique identifiers I'm not sure - if suppliers have two different types of unique ID then we will obviously need to take account of this - whatever it may be , it will be displayed on the csv file that the pharmacy downloads to be imported into the app - I'm not sure about how the data should be displayed, but as I said in the beginning, the pharmacy needs to be able to see a comparison to make am informed decision - so we could try what you're suggesting and then maybe use that arrangement to refine as necessary
To accommodate the uncertainty around supplier identifiers and ensure you can test this comparison feature quickly, you can build a flexible, phased approach. Since you are using a living prototype model (where screens are verified step-by-step against the running app), the best path forward is to deploy a simple, highly visual comparison interface on the test client first [1, 9] and iterate based on real feedback.
Here is how you can handle the unknown CSV identifiers and structure the comparison UI for your initial rollout:
Wholesalers typically use one of two identifier types in their CSV files. Your app's existing editable CSV mapping layer [4] can be extended to handle both scenarios without requiring code changes for new layouts:
Scenario A: Global Identifiers (GTIN/EAN/Barcode)
If a wholesaler includes a GTIN (Global Trade Item Number) or barcode in their CSV, your task is straightforward. The app already holds this data in its central dm+d dataset. The local app engine can join the wholesaler’s CSV row directly to the dm+d record via the GTIN.
Scenario B: Wholesaler-Specific Product Codes
If a wholesaler uses an internal custom item code (e.g., an Alliance or AAH proprietary code), the app will not have a central reference for it.
To give pharmacies the clear, scannable overview they need to make an informed decision, you can introduce a Comparison & Margin Grid on the ordering build screen. This satisfies your data-minimisation design by showing commercial details strictly to authorized roles (Responsible Pharmacist, Dispensing Technician, and Business Manager).
The layout should display the central NHS data right next to the pharmacy's local supplier data:
| Product Pack (dm+d) | Central Base Price Source | True Reimbursement (Derived) | Supplier A Price (e.g., AAH) | Supplier B Price (e.g., Alliance) | Best Purchase Margin |
|---|---|---|---|---|---|
| Amoxicillin 500mg cap x21 | Concession Override [8] | £4.20 (Net of tariff deduction) [5, 8] | £2.10 | £1.95 | +£2.25 (Supplier B) |
| Atorvastatin 20mg tab x28 | dm+d List Price [5] | £1.10 (Net of tariff deduction) [5] | £1.15 ⚠️ | £1.30 ⚠️ | -£0.05 (Supplier A) |
Since Matthew's steering note indicates that Phase 5 pack matching (§18) is the current gate, you can begin with a manual prototyping approach:
test-client-pharmacy server.To help the development team prepare the database schema for this prototype, would it be possible to check a few lines of a sample wholesaler CSV to see what the header names are for their item codes (e.g., "PIP Code", "GTIN", "EAN", or "ProductCode")? Knowing if there is an estimated release date for the Phase 5 pack-matching milestone would also be very helpful.
I don't have any sample csv files from suppliers, but as I understand they all follow similar formats
Since supplier CSV formats generally follow predictable industry standards in the UK, your development team can easily design a flexible ingestion schema that accommodates them without needing sample files upfront.
Most major UK pharmaceutical wholesalers (like AAH, Alliance Healthcare, and Phoenix) typically format their price files using one of three standard identifier pillars:
Because you already have a functional, editable CSV mapping interface built into the app for the Business Manager, the team can build an "Identifier Selector" drop-down into that existing screen.
When a pharmacy sets up a new supplier layout, they will simply map their columns like this:
Product NamePrice per Pack [4]PIP Code, GTIN, or Supplier Internal CodeIf they select PIP Code or GTIN, the application engine can automatically match the lines against the central dm+d lookup vector on the fly. If a supplier uses a completely custom internal code, you can fall back on the user-driven mapping screen mentioned previously to let the pharmacy bridge the link once.
Since you agree with trying the comparison layout layout, the team can move straight into building it out as a living prototype on the test-client-pharmacy server.
By defaulting the backend to look for standard PIP Codes or GTINs first, the developer can wire up the database join between your central NHS price calculation vector and the local client database tables immediately. This allows you to test the interface with real users, using whatever standard CSV files they pull from their real wholesaler portals.
Added after the research above, after checking each claim against the running platform and the live store rather
than against the document. The transcript is left exactly as written — this is a dated review beside it, not an edit
of it. What it led to: Data Feeds — NHS Pricing View
(v1 of phase 6, authorised 2026-09-27).
Right, and worth keeping. The pipeline shape is sound; the concession overrides the dm+d price; concession lines
are discount not deducted; the comparison belongs on the stock/ordering screens for the three stock roles; and
pack matching is the gate. All of that matches what the plan
and the phase 5 spec already decide.
Corrections — five claims that would have produced wrong work if built as written.
| The research says | What is actually true (verified 2026-09-27) |
|---|---|
| "Apply the correct sliding-scale deduction rate … based on the pharmacy's dispensing volume" | Neither. Since January 2024 the deduction scale is three flat rates by product category — 9.85% appliances, 20% generics (Part VIIIA Cat A and M), 5% otherwise. It has nothing to do with dispensing volume. The graduated table survives in Part V as history only. Our tariff release carries exactly these parameters, and the pack's category is in dm+d (DmdDrugTariff.pay_category_code → DT_PAYMENT_CATEGORY), so the rate is read from data, never inferred |
| *"The Derived Price = Base Price (dm+d or Concession) − deduction + fees", computed "for every pack" | dm+d's AMPP price is a published/manufacturer price, not the reimbursement base for Cat A/M generics (the reimbursement level is the VMPP, where dm+d carries its own Drug Tariff price). And 94,862 of our 187,156 AMPP packs have no price at all — the figure cannot be computed for every pack, and an unpriced pack must read "no published price", never £0.00 |
| A fee (152p) layered into the derived per-pack price | The Single Activity Fee is per prescription item, not per pack. Inside a per-pack figure it inflates every margin by 152p. The app already does this correctly at item level (price + fee per item) |
| "Expose a read-only price vector … over a secure tunnel", and the wholesaler file may use "PIP Code — the app already holds this data in its central dm+d dataset" | No tunnel or push is needed: the pharmacy app already reads the store read-through on its own connection. And dm+d carries no PIP codes — it carries GTIN, AMPP/VMPP codes and the tariff category. The trap is real, the mechanism is not |
| "We don't have sample CSVs / what identifiers do suppliers use?" | Already answered by our own live system: the import (stages 1–4) is live and matches PIP code, then barcode, against the pharmacy's own StockItem, with the column mapping held as configuration and no fuzzy matching. Nothing here needs a guess about AAH or Alliance |
The gap none of the above covers — and the one that matters most. StockItem carries no dm+d link of any kind
(item_code, pip_code, barcode, pack_size, form, strength are free text apart from the codes). So a supplier
line reaches dm+d only where its barcode is a genuine GTIN. The matching problem is therefore one problem in two
places: a concession row (free text + pack size) → the pack, and a stock item (PIP) → the pack. Same mechanism, two
queues — and the VMPP is the level both must land on, because that is where reimbursement lives.
And one processing gap in what is already live. The tariff ingestion deliberately does not parse Part II (drugs
for which discount is not deducted), so a line that is DND by list rather than by concession would today have a
discount wrongly subtracted. Recorded for the next phase, not fixed here.
Not worth building (yet). The NLP/Levenshtein pipeline. The concession side is 216 lines a month — normalise,
match exactly, and put the residue in a human review queue with an override table. The phase 5 spec already says the
tolerance must be set by someone who knows the packs, not by a similarity score.