Meridian

Architecture · Engine

The opportunity model

One rule, applied identically on every screen that ranks anything: a code an account imports is Whitespace, Defend or Displace depending on whether you sell it and whether you already supply it here. Everything else — the plays, the coverage verdicts, the disqualifiers — decides how loudly to say so.

The bucket rule

Given a code an account buys, what kind of opportunity is it?

Fig. 01

Flagged rows are ranked last but never hidden. Hiding them is how a bad row goes unnoticed — which is exactly how the defects below survived as long as they did.

Named plays, not scores

A composite score would be a number nobody can argue with and nobody can act on. Each play is a named, checkable claim about the shape of the demand instead.

Open fieldNo incumbent holds 40%, across five or more suppliers. Fragmented supply means a low switching barrier.
Sole sourceOne supplier holds 85%+ of material, repeating spend. The buyer carries concentration risk; lead with second-source.
Multi-prospectThree or more covered accounts buy this code. One product line, several doors.
ConsolidatingThe leading incumbent has gained 10+ points of share in the last six months. The window is closing.
Likely related partyThis supplier covers a large share of the account's entire external procurement across many codes — the signature of captive supply rather than a contested market. Ownership is not in the data; verify before treating it as addressable.
Low attributionUnder half this code's spend has a named supplier. The shares shown describe a minority of the real market.

The last two are disqualifiers: they sort a row to the bottom and exclude it from every “clean addressable” total, but leave it on the page.

Coverage is a verdict, not a score

An extract that cannot support buy-side analysis is marked as such rather than being silently ranked low. Loading the current split… Only usable accounts are counted in any addressable total anywhere in the product.

Defects found the hard way

Each of these was live, app-wide, and inflating real numbers. They are recorded because each one still constrains how a figure may be read.

Intra-group transfers counted as third-party spend — $236M

group_key() was being called on buyer_family and supplier_family, which are cluster ids, not display names. Sika, Allnex, PPG and Sasol buying from their own regional subsidiaries read as contested third-party business. Fixed by resolving display names before computing group keys, in scripts/opportunity/loading.py.

Three-character brand roots bypassed the same filter

group_key() fell back to the whole family key when the distinctive root was under four characters, so “PPG Industries” never matched “PPG Cieszyn”. Threshold lowered to three. It cannot go to two: MS and PT are legal-form prefixes and would collapse every Indonesian and Indian company into one group. Two-character brands still slip through, which is why isSameCompany() in lib/contract.ts runs a second, independent check on display names.

“NO DISPONIBLE” ranked as a real supplier — $102M

Spanish and Portuguese placeholders for “not available” were being treated as a company name across 10,729 rows, making an absence the fourth largest incumbent in the shortlist. Fixed at source with a sentinel vocabulary in pipeline/entity_resolution.py — an unnamed counterparty is reported as undisclosed value, never ranked as a company.

Brand-family values double-counted 3.6×

A code carrying products from several families was counted once under each, so summing families gave 3.6× the real total and ranked RHENODIV® second on a figure only 8% attributable to it. Split into reach (overlaps, never sum) and exclusive (codes where the family is the only Lanxess option). Both are shown, labelled, on /lanxess.

Limits worth stating plainly

What this data cannot tell you

Customs declarations record imports. Domestic purchases never cross a border and are invisible, so every figure is a share of observed imported wallet — never total spend, and a small number is absence of evidence rather than evidence of absence.

Ownership is not in the data. Both related-party checks — the pipeline's group key and the app's root-token match — are heuristics on names. They will miss a subsidiary that shares no name with its parent, and they will occasionally join two unrelated companies that do.

Coverage is by-catch. The prospect index sees only buyers whose extracts happened to be pulled for somebody else, so a supplier's total there is a fragment of their real business and must never be presented as revenue.