What the market sells is: out of N answers, you appeared in M, so your share is M over N. That number has six defects, and each one inflates or deflates it without anyone able to audit it.
It treats first place the same as tenth
A buyer who reads an answer remembers the top of the list. Appearing last is not half as good as appearing first, it is a fraction of it.
It treats being recommended the same as being dismissed
"I would not recommend X" counts as an appearance in a simple mention count.
It treats every prompt as equal
"Best providers of X" and "what is X" are not worth the same commercially, not even close.
It ignores competitors
A share of voice that does not divide against everyone who actually appeared is not a share, it is an appearance rate.
It ignores variance
A single point measurement with no band is a number that moves on its own. Reporting it as data is reporting noise as signal.
It lies in the denominator
When some calls fail, computing the number only over the ones that succeeded, without saying so, produces a figure that corresponds to nothing.
The algorithm below corrects all six, and above all it is auditable down to the cell, which is what none of the others can offer.
The atomic unit is the cell: a tuple of prompt, engine, market and run. Everything is computed per cell and aggregated afterward. A cell has three possible states, and all three matter.
ok
The call succeeded and entities were found. It enters the calculation.
failed
The call failed. It is excluded from both the numerator and the denominator, and it lowers coverage.
no_entities
The call succeeded but the answer names nobody. This is not a zero-share cell. It is a non-informative cell, excluded from the calculation and counted separately. An answer that names nobody does not say you lost, it says that prompt does not discriminate. Counting it as zero would punish a brand for a badly written question.
For every entity found in an answer, one formula, three factors.
score(entity) = confidence(entity) x prominence(entity) x valence(entity)
confidence
Comes out of brand detection, in the range 0 to 1.
| match method | confidence |
|---|---|
| Alias confirmed by the user | 1.00 |
| Exact name with word boundaries | 1.00 |
| Exact domain match | 1.00 |
| Derived variant, unconfirmed | 0.80 |
| Fuzzy match | 0.60 |
| Flagged as a common word, no category context | 0.30 |
An entity with confidence under 0.5 is shown with its snippet and an uncertainty mark, and still counts at its reduced weight. It is never silently discarded and never bumped up to 1.0 to round out the metric.
prominence
If the answer enumerates a list, logarithmic decay applies over rank position.
prominence = 1 / log2(rank + 1)
| rank | prominence |
|---|---|
| 1 | 1.000 |
| 2 | 0.631 |
| 3 | 0.500 |
| 4 | 0.431 |
| 5 | 0.387 |
| 10 | 0.289 |
If the answer is prose with no enumeration:
| context | prominence |
|---|---|
| Inside the main recommendation | 0.50 |
| Passing mention or secondary example | 0.25 |
Logarithmic decay is used because it is the standard in ranking and because it reflects how people actually read: the drop in attention between first and second place is larger than the drop between ninth and tenth.
valence
| how it appears | valence |
|---|---|
| Recommended without reservation | 1.00 |
| Mentioned neutrally | 0.60 |
| Recommended with explicit reservations | 0.40 |
| Dismissed or mentioned negatively | 0.00 |
Zero valence means the brand appears and does not add up. That is not the same as not appearing at all, which is why the mention is still recorded: it is a finding, just a bad one.
Not every prompt is worth the same. Intent weight reflects how close the question is to an actual buying decision.
| intent | weight | why |
|---|---|---|
shortlist | 1.00 | Maximum purchase intent: who should be considered |
local | 1.00 | For a local business, this is the shortlist |
comparison | 0.80 | Buyer already evaluating, narrower universe |
problem | 0.60 | Early stage, does not always name providers |
self_description | 0.00 | Excluded from share of voice. It measures accuracy, not share |
Unknown entities count.Every brand detected in the answer enters the cell's denominator, whether or not it is on the tenant's configured competitor list.
If only the brand and its configured competitors were counted, share would rise artificially just by not tracking someone. A share of voice that improves because you stopped watching a rival is a corrupt metric.
sov_weighted(b) = sum[ w(cell) x share(b, cell) ] / sum[ w(cell) ] over every cell in state "ok" sov_raw(b) = cells where b appears / ok cells coverage = ok cells / planned cells
All three are always reported together. The weighted number is the insight, the raw number is the receipt anyone can verify by counting, and coverage is what keeps the pair honest.
worked example
A brand with a distinctive name, 3 prompts by 2 engines, 6 planned cells. One call fails. Prompt A is shortlist (weight 1.0), prompt B is comparison (weight 0.8), prompt C is problem (weight 0.6). Both engines are weighted 1.0.
| cell A, Perplexity, ok | rank | prominence | score |
|---|---|---|---|
| Competitor 1 | 1 | 1.000 | 1.000 |
| The brand | 2 | 0.631 | 0.631 |
| Competitor 2 | 3 | 0.500 | 0.500 |
| Competitor 3 | 4 | 0.431 | 0.431 |
| Competitor 4 | 5 | 0.387 | 0.387 |
List of 5, all confidence and valence 1.0. Sum of scores is 2.949. Share of the brand is 0.631 / 2.949 = 0.2140.
Cell A, ChatGPT, ok. List of 4. The brand does not appear. Share = 0.
Cell B, Perplexity, ok. Prose, 3 entities, no enumeration. The brand is in the recommendation, prominence 0.50, valence 1.0, score 0.500. Competitor 1 scores 0.500, competitor 2 scores 0.300. Sum is 1.300. Share = 0.500 / 1.300 = 0.3846.
Cell B, ChatGPT, failed. Excluded. Lowers coverage.
Cell C, Perplexity, ok. The brand appears in passing, with reservations: prominence 0.25, valence 0.6, score 0.150. Competitor 1 scores 0.500, competitor 2 scores 0.300. Sum is 0.950. Share = 0.150 / 0.950 = 0.1579.
Cell C, ChatGPT, ok. The brand does not appear. Share = 0.
| cell | weight | share | weight x share |
|---|---|---|---|
| A, Perplexity | 1.0 | 0.2140 | 0.2140 |
| A, ChatGPT | 1.0 | 0 | 0 |
| B, Perplexity | 0.8 | 0.3846 | 0.3077 |
| C, Perplexity | 0.6 | 0.1579 | 0.0947 |
| C, ChatGPT | 0.6 | 0 | 0 |
| Sum | 4.0 | 0.6164 |
sov_weighted = 0.6164 / 4.0 = 0.1541 -> 15.4% sov_raw = 3 / 5 = 0.60 -> 60.0% coverage = 5 / 6 = 0.833 -> 83.3%
The raw number says 60%. The weighted number says 15.4%. Both are true and they say different things. You appear in most of the answers, but late in the lists, in passing in the prose, with reservations, and completely absent from the more expensive engine. A product that only reports 60% is telling the client they are fine when they are not. One that only reports 15.4% gives them no way to verify it. That is why all three numbers travel together.
This is the part almost nobody does, and it is half of what makes the number defensible. Engine answers move on their own with phrasing, freshness, location and model updates. A single point with no band is noise presented as data.
what we got wrong, and how we know
Until August 2026 this band was built from a series of scans taken days apart. That is the wrong instrument for the question. Between two scans a week apart the world genuinely changes, so a band built that way silently counts the engine's own run-to-run wobble as movement.
We measured the wobble directly instead: fifty samples, the same five questions asked ten times each on the same engine, in the same market, minutes apart. Nothing changed between those runs except the run.
That is why the headline figure on every screen is now an appearance count — "named in 8 of 10 answers" — and not a percentage. Weighted share is still computed and still shown, with its band, and is withheld entirely for any brand named in under half the samples.
So the band now comes from repeats inside one scan: ten samples of each prompt on the consumer surface, three on the provider API. With r samples of the same prompt, same market, same weights:
mean = sum(sov_i) / r # sov_i is sample i, aggregated alone sd = sample standard deviation of sov_i ci95 = mean +/- t(0.975, r-1) x sd / sqrt(r)
The across-scan series still exists and still answers a different question: did this move? It is stored separately and never compared against the within-scan band, because one measures the subject and the other measures the instrument.
| runs | degrees of freedom | t(0.975) |
|---|---|---|
| 3 | 2 | 4.303 |
| 4 | 3 | 3.182 |
| 5 | 4 | 2.776 |
| 8 | 7 | 2.365 |
| 12 | 11 | 2.201 |
With fewer than 3 runs, no band is reported. The point is shown with an explicit warning that there are not enough measurements to estimate variability. A band is never invented from a single measurement.
significance of a movement
Two gates, both of which have to pass. The intervals must not overlap, and the move must clear the measured instrument noise of about 4.3 points of share. The second gate is new, and it exists because the first one alone was letting through movements that were smaller than the gap between two identical runs.
This is a conservative test on purpose: telling a client they went up when it cannot be proven is exactly how the credibility that holds up the whole product gets lost. It also means we will sometimes stay silent about a real change that is too small to prove. We would rather do that than the reverse.
"We measure ChatGPT" is not one thing. There are two ways to get an answer out of an assistant, they are different retrieval systems, and until now this page did not say which one we use for which engine. It does now.
| engine | surface | what that means |
|---|---|---|
| ChatGPT | Consumer interface, scraped | What the product actually shows a person. Model is whatever it is serving that day, reported coarsely. |
| Gemini | Consumer interface, scraped | Same. No model pinning is offered on this surface. |
| Perplexity | Provider API | No consumer surface available to us. Model is pinned to a dated snapshot, web search forced on. |
| Claude | Provider API | Same. Pinned model, forced web search. |
The two surfaces cite roughly 10% of the same sources. We ran the same fifteen questions through both, pinned to the same market and language, and compared the destinations they cited. Across the eleven questions where every arm returned, 86% of the sources appeared on exactly one surface and 3.5% appeared on all of them.
Some of that gap is the surfaces and some of it is the engine failing to repeat itself, and we can say roughly how much: identical runs on the same surface share 36% of the brands they name, against 10% across surfaces. The surface effect is real, and smaller than the raw number looks.
One thing we cannot separate at all: ChatGPT does not expose the model behind its consumer interface. So where the API surface and the consumer surface differ in what they say, we cannot tell you how much of that is the surface and how much is a different model. That confound is structural, not an experiment we have yet to run, and no amount of re-running fixes it.
Two snapshots are not compared if any of these differ. The comparator detects the difference, names it, and refuses to draw the line.
| dimension | why it breaks comparability |
|---|---|
| Prompt set version | Different questions, different universe |
| Market, country or language | Different source ecosystem |
| Engine mix | The denominator weight changes |
| Engine weight vector | The same data produces a different number |
| Model within an engine | Different behavior |
It can offer to renormalize over the intersection of common engines, but only while declaring that it did.
Honesty about its limits, because a method that claims to have no limits is the one you should not trust.
Classifying valence and prominence in prose is interpretation
It is done with rules and with a model, and it can be wrong. That is why the snippet is always saved and shown: the number is defensible because it is auditable, not because it is infallible.
Intent weights are a judgment, not a measurement
They are defensible and they are declared, but another team could reasonably choose different ones. That is why they are shown in the report rather than buried in code.
Variance needs runs
A new client has no band until the third measurement. That has to be said on the screen instead of padded out with a number that looks precise.
Engine weights should reflect real buyer usage, and today they do not
That vector should be anchored to data about which engine each buyer segment actually uses. That data does not exist yet. Until it does, the weights are uniform and declared as an assumption, not measured.
All of this runs over the raw response and the final message text, both saved on every call. Changing weights, thresholds or detection logic and reprocessing the entire stored history does not cost a single call to a provider.
This is already validated in practice: four logic corrections applied to 51 stored answers cost zero, against roughly $16 to run them again from scratch.
The consequence is deliberate: weights and thresholds are versioned configuration, not constants buried in code, and every computed metric records exactly which configuration version produced it.
Every figure on a report opens back to the cell that produced it: the prompt, the engine, the model, the date, and the answer text. That is the audit trail, and it is the whole point.
Written 30 July 2026.