Status: AUTHORISED 2026-09-27, BUILT, VERIFIED LIVE 2026-09-27, and the §9.1 wording split RULED and briefed — verifier 23/23, host suites
182 / 33 / 176, and the client surfaces 31/31 over HTTPS on the test client (§9). One wording gap found in
live verification is recorded there and waits on Matthew's ruling.Status (as specified): AUTHORISED 2026-09-27, and now EVIDENCED. Matthew's steer: "let's write the pack-link now and get
everything done so that by the time I am looking at the displayed data … the data is fully processed and displaying the
data correctly." So the link is specified and measured before it is built: the method below was run against the
whole published concession history (§2) and the numbers are its results, not an estimate.The measurement changes the assessment of this stage — for the better. Phase 5's spec called pack matching "the
real work … the one thing that cannot be waved through" (§18 item 2), on the premise that a free-text drug name and a
bare pack size could not be tied to a code. Measured, it is a normalisation rule: every line of the last 16 months
matches, with no fuzzy matching anywhere and an empty review queue on modern data. What remains genuinely
hard is not the match but the surfaces and re-validating the links as dm+d moves (§4, §5).Parents: Central Reference Data (phase 6),
NHS Pricing View (the v1 display
this completes), Price Concessions — Phase 5
(§18 items 2–3 — this is item 2, and §19 for the source change).
Today the pricing panel shows a pack the drug's concession lines, and states plainly that the pack-level link is not
made. This stage makes the link:
concession line → dm+d pack, where the pack is a VMPP — the level reimbursement lives at, and the levelWhat was measured. Every dated notice on the NHSBSA updates page — 68 months, August 2020 → September 2026,
8,848 (drug, pack size) lines — matched against the live dm+d release (3170926): 24,662 VMPs, 37,397 VMPPs, and
DmdLookup table UNIT_OF_MEASURE for the unit words.
| Method | Result |
|---|---|
| Normalised name + quantity only | 8,714 / 8,848 (98.5%) · 22 ambiguous · 3 with no pack of that size · 109 names not found |
| Normalised name + quantity AND unit, with the unit inferred from the form when the line gives a bare number | 8,738 / 8,848 (98.8%) · 0 ambiguous · 0 with no pack · 109 names · 1 malformed size |
| June 2025 → September 2026 (the modern format: 16 consecutive months, 2,626 lines) | every line matched — 0 ambiguous, 0 no pack, 0 not found |
The unit matters, and it was the whole of the ambiguity. Memantine 10mg/ml oral solution sugar free, pack 50 — the
one line that came back ambiguous in 22 separate months — is a unit collision: the VMP has a VMPP of 50 ml
(qty 50, unit ml) and a pump-actuation pack of 50 gram (qty 50, unit gram). Matching on the number alone cannot
choose; matching on number + unit resolves it, and for a bare number the form supplies the unit (an oral solution is
ml). Explicit units in the source (60g, 100ml) resolve the same way — that was the Piroxicam 0.5% gel 60g and
Trimethoprim 100ml cases, the only two "no pack" lines in the history.
The residue is 110 lines in 8,848 (1.2%), all older than June 2025, and every pattern is named — these are the
override table's first entries, not mysteries:
| Pattern | Example | Kind |
|---|---|---|
| Spacing between the number and its unit | Cabergoline 500 microgram tablets vs 500microgram |
text |
| Unit form of the same strength | Estriol 1mg/g vaginal cream vs 0.01% vaginal cream |
text, same product |
| Salt in the name | Benzylpenicillin 600mg … vs Benzylpenicillin sodium … |
text, same product |
| Combination products | Metformin 1g / Sitagliptin 50mg tablets |
spacing around / |
| Abbreviation and number | Cefalexin 250mg **tabs**, Senna vs Sennosides |
text |
| Singular / plural | Co-amilozide 5mg/50mg tablet vs tablets |
text |
| A malformed size in the source itself | Omeprazole 20mg gastro-resistant capsules £28 (April 2023) |
source typo |
| The format of the oldest sections | August–October 2020, where the page's own columns split the name from the form | parse, not match |
Nothing here justifies a similarity score. Every one of those lines is a stated difference that a person resolves
in seconds and a normalisation rule can hold forever after — and a price decided by a fuzzy score is exactly what phase 5
§18 refused. The rule stays deterministic; the residue goes to a queue.
W1 — normalise; never guess. One function, applied to both sides, with every rule evidenced above:
. , ; :;500 mg → 500mg, 1,000 unit → 1000unit) on both sides;/ in combination products (/ → /);tabs→tablets, singular→plural forms,senna→sennosides, the salt forms, 1mg/g↔0.01%) — a table, not a scoring function, and each entry added onlyW2 — split the pack size into (number, unit). 28 → (28, none); 60g → (60, gram); 100ml → (100, ml);
2.5 → (2.5, none). The unit text is mapped to the same unit words dm+d uses, resolved through
DmdLookup(UNIT_OF_MEASURE) — never a hard-coded unit list in a comparison.
W3 — resolve, in this order, and stop at the first that yields exactly one candidate:
ml for solutions,gram for creams, gels, ointments and pastes; the presentation's own count unitNo fuzzy matching in the automatic path. A similarity score may order the candidates shown in the queue; it may
never decide a link.
The link is central (it is about the reference data, not tenant data) and it is versioned by release, because
dm+d is a living dictionary: a VMPP can be superseded, a name can change, a pack can be discontinued.
ConcessionPackLink — one row per (concession line, dm+d release): the line's drug_key + pack_size, thevmpp_id (and the vmp_id for reading), the outcome (matched / needs_decision / unmatched),rule / human), who decided it and when where a person did, and the candidatevmpp_ids considered.needs_decision and reappears in the queue — it does not silentlyvmpp_id.needs_decision and into the queue.pharmacy-price-counts.py unchanged either side on the pharmacy host).dsp_clinic.test_settings on the app host, and a re-runnable verifier committed atveripath-ops/scripts/verify-pack-link.py.StockItem, which carries a PIP code, not a GTIN and no dm+d referenceChlordiazepoxide 5 mg 100: £85.45 under August, £92.86 under September) —Built by OpenCode — platform feature/pack-link @ 0907677 (off e0d4f9a), pharmacy feature/pack-link @
e257181 (off 3f45af7), deployed by revision on the app host with migrations reference_data/0011 and
pharmacy_medicines/0003 (the state-only mirror) applied. Verified live by me on the app host, not taken on
report:
veripath-ops/scripts/verify-pack-link.py — 23 PASS / 0 FAIL, reproduced on the app host. September 2026 is18239411000001109); the three outcomes sum to the month (216 == 216); the historical re-run matchesneeds_decision and settles again; the override holds as human and revoking returnsvmpp_id resolves against the live release.reference_data 182 OK (1 skipped), pharmacy_medicines 33 OK, the combinedtenancy pharmacy pharmacy_dispensing pharmacy_medicines 176 OK.matched, alldecided_by='rule', none with an empty vmpp_id, and 0 active overrides. The CPE release 2026-09 isimported and not live, the register reads consent_required=True, consent_granted=False,may_publish=False, so the gate is shut in the data as well as in the code. The platform admin surfaces/infrastructure/reference-data/pack-links/, /set/, /revoke/) answer 403 to an anonymous request.reference-data-rebuild-pack-links-daily at 04:30; the fold-in on publish lives intariff_news_pull — guarded, so a linking failure is logged and retried by the daily task and never fails thetc.tech.one ontest-client-pharmacy.pharmacy.veripath.co.uk — new verifier veripath-ops/scripts/verify-pack-link-live.py,£0.00; theissued 16/09/2026) and the VMPP note; a pack the month does not list at all (Anadin Paracetamol 500mg tablets,£ in thepharmacy-price-counts.py reads audit 235 / files 11 / rows 17 / cache 5ConcessionPackOverride (anThe panel's unresolved state reads "This month's concession for this pack is not yet linked. The drug may have a
published concession line, but the pack-level link is not made…" — which is exactly what §5 prescribed. Live, that
one state covers two different situations: a line that exists but could not be settled (the queue case), and a pack
the month has no line for at all — the ordinary case, since the month holds 216 lines against ~187k packs. For a
contractor looking at the pack they hold, "not yet linked" reads as pending, when the truth may be "no price
concession this month".
RULED 2026-09-27 (Matthew): split the state, and build it. The recommendation stands as written; the build is as
follows, briefed to OpenCode on 2026-09-27.
linked (unchanged — the pack's own line, price and dated notice); not_linkedno_line (new) — the month holds no effective lineno_source (unchanged — the gate is shut).linked — an absence, never a zero.no_line note and no concession table; an unsettled line rendersveripath-ops/scripts/verify-pack-link-live.py, which I extend for the new state).VeriPath internal. Dummy/synthetic data only — no patient-identifiable information appears in this work, and no
credential or internal address is recorded here.