Everything on this site is one procedure run over and over. This page is that procedure, the files it leaves behind, and what you are allowed to do with them.
A board is a single buying question asked 44 different ways. One board is the exception and says so on its own page: /c/aeo/ was asked 24 ways.
ranking.json carries the exact model string under engines.The 44 phrasings of a board are not all the same kind of question, and the product pages
at /check/ print the split. The rule is mechanical, it is the same on every
board, and it uses nothing at all about any product: a question is first-person if it opens
with “We”, “I”, “Our” or “My” — the buyer describing their own company and asking what to
buy — and every other question is counted as a category or feature question. That is the
whole classifier. It is applied to the phrasing field of the board's own
published mentions-runA.jsonl, so you can rerun it yourself:
# the split on one board, for one product, from the file that board links
curl -s https://connexion.me/c/lpm/mentions-runA.jsonl \
| python3 -c 'import sys,json,re,collections
n=collections.Counter(); c=collections.Counter()
for l in sys.stdin:
r=json.loads(l)
fam="first-person" if re.match(r"\s*(we|i|our|my)\b",r["phrasing"],re.I) else "category"
n[fam]+=1
for name in set(r["names"]): c[fam,name]+=1
for fam in ("first-person","category"):
print(fam, n[fam], "answers,", c[fam,"PracticePanther"], "named PracticePanther")'
first-person 14 answers, 2 named PracticePanther
category 74 answers, 39 named PracticePanther
That counts one spelling. A product page counts the same way and then applies the board's
own name fold on top — the one described above, which merges Workable ATS into
Workable — so for a product the engines spell more than one way, the page's
figure is the higher one and the difference is the spellings this snippet did not add up.
A product page prints both figures whatever they say. It points out a difference
between the two only when three conditions all hold, and they are floors rather than
thresholds we tuned: each family holds at least 10 answers, the product's rate in the weaker
family is at most half its rate in the stronger one, and the weaker family was large enough
to have named that product at least 5 times at its own overall rate. A product whose two
rates are close gets the numbers and no sentence about them. The rule is symmetric — it is
not searching for the reading least flattering to whoever is on the page — and the two counts
always add back to the mention count printed above them, which is checked at build time
against ranking.json before any page is written.
Each product page at /check/ opens with one sentence about that product, and
one comparison decides which sentence it is: the answers that named the product, against
the answers that did not — whichever count is larger is what the line says. Equal counts
get their own sentence and are not broken either way. That is the whole rule. It uses two
integers from the board's own published ranking.json, mentions and
answers, it is the same on every page, and there is no floor, no weighting and
nothing tuned per product or per board:
# the line every product page on one board would open with
curl -s https://connexion.me/c/inventory/ranking.json \
| python3 -c 'import sys,json
r=json.load(sys.stdin); a=r["answers"]
for v in r["vendors"]:
m=v["mentions"]; n=a-m
say=("named in %d of %d" % (m,a)) if m>n else ("absent from %d of %d" % (n,a)) if n>m \
else ("named in %d of %d and absent from the other %d" % (m,a,n))
print(v["vendor"], "--", say)' | head -4
Zoho Inventory -- named in 61 of 88
Katana -- absent from 53 of 88
Cin7 -- absent from 55 of 88
Sortly -- absent from 59 of 88
The sentence states a count and never a label. A product named in 41 of 88 answers is absent from 47 of them and the page says that; it does not say invisible, or losing, or at risk, because none of those is something this measurement produced. Rank, share and the per-question split are printed under the line and are not part of the comparison.
Every category is measured twice, from scratch — run A and run B, the same 44 questions asked again. Both runs are published, not only the one the table is drawn from, and every board prints its own run A/run B top-ten overlap.
A category built from a request is only published behind four checks that are commands rather than judgement calls: at least 12 distinct products named, top-ten overlap of at least 8 out of 10 between the two runs, no product above 95% share, and no top-five product that was named in only one phrasing. The last two are the ones that catch a wrong board rather than a thin one — a single name in nearly every answer is usually an extractor that has collapsed a category onto one string, and a top-five product carried by one phrasing is usually a question that leaked a brand name.
A board lives at https://connexion.me/c/<slug>/. Five files sit at that
same path, and they are the entire measurement — the page is only a rendering of them.
| file | one line is |
|---|---|
answers-runA.jsonlanswers-runB.jsonl |
one engine answer, verbatim —
engine, model, index, phrasing,
text, error |
mentions-runA.jsonlmentions-runB.jsonl |
the products extracted from one answer —
engine, index, phrasing, names |
ranking.json |
the board itself — category, answers, phrasings,
engines, engine_labels, answers_by_engine,
partial, and vendors, each carrying vendor,
key, mentions, share, by_engine,
phrasings_named_in and rank |
The .jsonl files are NDJSON: one JSON object per line, no enclosing array, so
they stream and you never need to hold one in memory. index is the phrasing
number, 0 to 43, and it is the join key — the same
index and engine in the answers file and the mentions file are the
same question put to the same engine.
Nothing here needs a client, a key or an account.
curl -s https://connexion.me/c/hotel/ranking.json | python3 -m json.tool | head
# every product the extractor took out of run A, most-named first
curl -s https://connexion.me/c/hotel/mentions-runA.jsonl \
| python3 -c 'import sys,json,collections; c=collections.Counter()
[c.update(set(json.loads(l)["names"])) for l in sys.stdin]
[print(n,v) for n,v in c.most_common(15)]'
# the verbatim answer behind one cell: phrasing 0, as ChatGPT returned it
curl -s https://connexion.me/c/hotel/answers-runA.jsonl \
| python3 -c 'import sys,json
[print(json.loads(l)["text"]) for l in sys.stdin
if json.loads(l)["index"]==0 and json.loads(l)["engine"]=="chatgpt"]'
If a count on a board disagrees with what you get out of these files, the files are right and we want to hear about it: lana@connexion.me.
You may re-use the raw answers. In the words it was granted in, on 2026-08-08:
…you have the right to re-use the raw answers in later work with attribution, including inside your open-source tooling, with no restriction on scope, medium or duration.
That is the standing grant, and it is not specific to the person it was first written to. Concretely, and with nothing held back for later:
Two things this grant does not cover, because they are not ours to give: the engines' own terms for their outputs, which are between you and them, and any file we have handed someone privately at an unlisted URL — that stays under whatever was agreed when it was handed over. Everything published beside a board is covered.
It is a measurement of what answer engines said, on a stated date, in response to stated questions. It is not a quality ranking, not a review, and not advice on what to buy. A product can be excellent and rank low here; that gap is the interesting part, not a defect of the product.