AI Visibility Score Explained: 5 Parts, Weights and the Math

An AI visibility score combines mention rate, share of voice, position, citations and sentiment. See the formula, a worked example on 280 answers and a script.

BugViso

15 min read

An AI visibility score is a 0–100 composite of how often AI engines name your brand, how much of the recommendation space you hold, how early you're named, whether they cite your site, and how they describe you. BugViso's version weights those five parts 35/20/20/15/10. On 280 real unbranded answers, our own brand scored 26.0 and the category leader 39.4.

The weights look arbitrary until you see how each part behaves on real data. By the end of this post you'll be able to recompute the score by hand and spot the two mechanics that make it jump. You'll also know which part to work on first, and you'll have a script that rebuilds the score from your own exported answers.

Data used: 35 unbranded buyer prompts × ChatGPT, Gemini, Perplexity and Claude × 2 runs on 2026-10-20 (280 answers, web search on), collected by BugViso's AI Visibility Tracker for bugviso.com against five anonymised competitors (A–E). Scores were recomputed offline with the production scoring module.

The Formula, Part by Part

The score is a weighted sum of five parts, each scaled 0–100:

text
index = 0.35·V + 0.20·S + 0.20·P + 0.15·C + 0.10·T
PartWeightDefinitionOur brand (pooled)
V: Visibility35answers that name the brand ÷ successful answers7.5
S: Share of voice20brand appearances ÷ all tracked-brand appearances, once per answer10.9
P: Position20100 × (1 − (avg rank − 1) ÷ (N − 1)), 0 if never named72.9
C: Citations15answers citing the brand's own domain ÷ successful answers10.7
T: Sentiment10mean 0–100 sentiment of answers that name the brand, 0 if never named50.4

Four rules sit around the formula, and each one changes the result:

  1. Unbranded prompts only. A prompt that names you is answered with your name every time. In our data, all 120 branded answers named the brand. Counting them would double the score, as our post on branded vs unbranded prompts shows.
  2. Successful answers only. Refusals and empty answers are excluded rather than counted as misses. An engine outage shouldn't look like a visibility drop.
  3. Per engine first, then averaged. Each part is computed separately for ChatGPT, Gemini, Perplexity and Claude, then averaged with equal weight. An engine that returns more answers doesn't get a bigger vote.
  4. N is the size of the tracked set. With you plus five competitors, N = 6. Rank 1 scores 100, rank 6 scores 0, and each step in between is worth 20.

These choices follow the usual practice for composite indicators: normalise each part to the same 0–100 scale, then weight them. Fix the rules before you weight anything, because each rule moves the score more than any weight does.

Worked Example: Rebuilding 26.0 by Hand

Here are our brand's parts on each engine, computed on 70 unbranded answers per engine:

EngineVSPCTEngine index
ChatGPT8.615.896.78.663.833.2
Gemini4.33.0100.04.374.030.1
Perplexity17.125.095.025.763.940.2
Claude0.00.00.04.30.00.6
Average7.510.972.910.750.4—

Plug the averages into the formula:

text
0.35 × 7.5  =  2.63
0.20 × 10.9 =  2.18
0.20 × 72.9 = 14.58
0.15 × 10.7 =  1.61
0.10 × 50.4 =  5.04
               -----
               26.0

Two things stand out. Position contributes 14.6 of our 26.0 points, more than the other four parts combined. And Claude, the engine that never named us, drags the average down on P and T because both are defined as 0 there.

A high P doesn't mean we're winning. Engines named our brand mostly on narrow prompts where it was the obvious answer, and there they named it first. The parts are designed to be read together, never alone.

Where the Points Separate Brands

The weights tell you what the formula values. The spread tells you what actually decides the ranking. Here's the pooled leaderboard for all six brands:

BrandVSPCTIndex
Competitor A25.424.889.17.166.539.4
Competitor B22.519.875.710.755.434.1
Competitor E12.911.789.79.365.532.7
Competitor C18.618.477.57.955.932.5
Competitor D14.614.473.35.064.129.8
BugViso7.510.972.910.750.426.0

Multiply each part's range across the six brands by its weight and you get how many index points it can separate them by:

PartRange across brands× weightPoints of separation
V7.5 → 25.40.356.3
P72.9 → 89.70.203.4
S10.9 → 24.80.202.8
T50.4 → 66.50.101.6
C5.0 → 10.70.150.9

Visibility does most of the ranking work, which is the intent: being named at all is what a buyer experiences. Citations separate brands least. Big brands get named without a link to their own site, so their C stays low even when V is high (see when AI names you, does it link your website).

Why Sentiment Gets Only 10%

Sentiment is the part people want to weight most and the part that deserves it least. Three reasons, all visible in the data:

  • It barely discriminates. Most brands sit between 55 and 67. Sentiment scorers are calibrated so that 50 means "neutral or merely listed", and most answers just list vendors.
  • It's measured on few answers. T is the mean over answers that name the brand. Our brand had 21. A single critical sentence moves a 21-answer mean by several points.
  • It's conditional on being named. A brand named once, warmly, can out-score one named 70 times in neutral lists. Weighting T heavily would reward being rare.

Sentiment still matters as a warning light. A drop below about 40 means answers raise concerns about you, and that belongs in a report whatever the index says. It just shouldn't decide the ranking.

The Zero Cliff: How One Mention Moves the Score 6.7 Points

The definitions of P and T contain a cliff. On any engine where a brand is never named, both are 0. The first mention lifts P from 0 to as much as 100, and T from 0 to whatever that answer's sentiment is.

We simulated one extra first-place mention of our brand (with sentiment 60) on each engine, keeping everything else fixed:

Engine that gains one mentionIndex change
ChatGPT (already named 6 times)+0.3
Gemini (already named 3 times)+0.2
Perplexity (already named 12 times)+0.3
Claude (never named)+6.7

One answer out of 280 moves the score by 6.7 points when it lands on a zero engine. The same answer on an engine that already names you is worth 0.3. An extra own-site citation, by comparison, is worth 0.1 wherever it lands.

This isn't hypothetical. Competitor D's index rose from 21.2 in the first run to 32.3 in a re-run 23 minutes later. Its ChatGPT mentions went from 0 of 35 answers to 4 of 35, so its ChatGPT index went from 0 to 34.8, and the four-engine average jumped 11 points. Nothing about the brand changed.

💡 Reading rule: when an index moves more than 5 points between runs, check the per-engine table first. A 0 → 1 change on one engine explains most big jumps.

How Sensitive Is the Ranking to the Weights?

If the weights are a judgement call, the ranking should survive reasonable alternatives. We re-ranked the six brands under four other schemes:

WeightingOrder (best → worst)
Production (35/20/20/15/10)A, B, E, C, D, BugViso
Equal (20 each)A, E, B, C, D, BugViso
Without sentimentA, B, C, E, D, BugViso
Sentiment doubledA, B, E, C, D, BugViso
Visibility onlyA, B, C, D, E, BugViso

The top and bottom never move. The middle reshuffles because C and E sit within a point of each other (32.5 vs 32.7). A change that small is inside the run-to-run noise, so a report shouldn't present it as a result.

Our brand ranked last under every scheme. That's useful in itself: the score isn't hiding a weak result behind a friendly weighting.

Rebuild the Score from Your Own Answers

This script recomputes V, S, P and C per engine from a CSV export, averages them across engines and ranks the brands. It also reports what one more first-place mention would be worth on each engine, so you can see where the cliff is. Sentiment needs a language model to score, so the script leaves T out and rescales the other weights, which is what the production scorer does when sentiment can't be measured.

python
#!/usr/bin/env python3
"""AI visibility index (V/S/P/C) per engine and engine-averaged, plus what one more mention is worth.

Usage: python3 ai_visibility_index.py answers.csv --brand Acme --domain acme.com \
         --competitor Globex --competitor-domain globex.com [--competitor ... --competitor-domain ...]
answers.csv columns: engine, answer, cited_urls (space-separated), branded (1/0; branded rows are skipped).
Sentiment (T) needs a model, so it is left out and the other weights are rescaled.
"""
import argparse, csv, re
from collections import defaultdict
from urllib.parse import urlparse

W = {"V": 0.35, "S": 0.20, "P": 0.20, "C": 0.15}

def host(u):
    return (urlparse(u).hostname or "").lower().removeprefix("www.")

def parts(answers, brands):
    """answers: list of (ordered brand list, set of cited hosts)."""
    n = len(answers)
    appear, ranks, cites = defaultdict(int), defaultdict(list), defaultdict(int)
    for order, hosts in answers:
        for rank, b in enumerate(order, 1):
            appear[b] += 1; ranks[b].append(rank)
        for b, d in brands.items():
            cites[b] += any(h == d or h.endswith("." + d) for h in hosts)
    total, N = sum(appear.values()), len(brands)
    out = {}
    for b in brands:
        p = 100 * (1 - (sum(ranks[b]) / len(ranks[b]) - 1) / (N - 1)) if ranks[b] else 0.0
        out[b] = {"V": 100 * appear[b] / n, "S": 100 * appear[b] / total if total else 0.0, "P": p, "C": 100 * cites[b] / n}
    return out

def index(p):
    return sum(W[k] * p[k] for k in W) / sum(W.values())

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("csv"); ap.add_argument("--brand", required=True); ap.add_argument("--domain", required=True)
    ap.add_argument("--competitor", action="append", default=[]); ap.add_argument("--competitor-domain", action="append", default=[])
    a = ap.parse_args()
    brands = {a.brand: a.domain.lower(), **{c: d.lower() for c, d in zip(a.competitor, a.competitor_domain)}}
    rx = {b: re.compile(r"(?<![\w-])" + re.escape(b) + r"(?![\w-])", re.I) for b in brands}
    by_engine = defaultdict(list)
    with open(a.csv, newline="", encoding="utf-8") as f:
        for r in csv.DictReader(f):
            if r.get("branded") == "1" or not r["answer"].strip():
                continue  # branded prompts name you by construction; empty answers aren't misses
            hits = {b: m.start() for b, x in rx.items() if (m := x.search(r["answer"]))}
            by_engine[r["engine"]].append((sorted(hits, key=hits.get), {host(u) for u in r["cited_urls"].split()}))
    per = {e: parts(ans, brands) for e, ans in sorted(by_engine.items())}
    avg = {b: {k: sum(per[e][b][k] for e in per) / len(per) for k in W} for b in brands}
    print(f"{'brand':<16}" + "".join(f"{k:>7}" for k in W) + f"{'index':>8}")
    for b in sorted(brands, key=lambda b: -index(avg[b])):
        print(f"{b:<16}" + "".join(f"{avg[b][k]:7.1f}" for k in W) + f"{index(avg[b]):8.1f}")
    print(f"\nWhat one more {a.brand} mention (first place, own site cited) on each engine is worth:")
    base = index(avg[a.brand])
    for e, ans in by_engine.items():
        i = next((i for i, (order, _) in enumerate(ans) if not order), None)
        if i is None:
            continue
        trial = ans[:i] + [([a.brand], {brands[a.brand]})] + ans[i + 1:]
        p = dict(per); p[e] = parts(trial, brands)
        new = {k: sum(p[x][a.brand][k] for x in p) / len(p) for k in W}
        print(f"  {e:<11} {index(new) - base:+.1f} points")

if __name__ == "__main__":
    main()
bash
python3 ai_visibility_index.py answers.csv --brand "Acme" --domain acme.com \
  --competitor "Globex" --competitor-domain globex.com \
  --competitor "Initech" --competitor-domain initech.io

On our 280 answers it reproduces the production V, S, P and C exactly (7.5, 10.9, 72.9, 10.7). Without sentiment, our index comes out at 23.3 and the Claude cliff at +5.8. Exact-name matching is deliberately simple; add aliases and domains as described in our brand mention detection guide.

How BugViso Computes the AI Visibility Score

BugViso's AI Visibility Tracker computes the score after every run. It asks each prompt on ChatGPT, Gemini, Perplexity, Claude and Google AI Overviews with web search on (for example OpenAI's web search tool and Gemini's grounding with Google Search), then applies the formula above to the unbranded answers, engine by engine. The AI visibility score you see is the engine average, with the same five parts listed underneath so you can see which one moved.

The same computation also produces:

  • A leaderboard that ranks you against at least three competitors. Ties share a rank and visibility breaks them.
  • An engine scorecard with mention rate, prompt coverage, citation coverage, share of voice and accuracy for each engine, which is where a zero engine shows up first.
  • Segments by engine, buyer stage and topic. Any segment built on fewer than 20 answers is flagged low confidence.
  • A 95% Wilson score interval on visibility. Ours was 5.0–11.2% around a 7.5% point estimate.
  • Score drivers. Each part's points and how far it moved since the comparison run.

AI Overviews joins the average only once Google has shown an overview on at least five unbranded queries. Otherwise one engine with three answers would weigh as much as one with seventy. Removing a wrong mention recomputes the run's score from the stored answers. The full breakdown is on the AI visibility tracker page.

Mistakes When Reading an AI Visibility Score

Comparing scores across competitor sets. S and P both depend on who else is tracked. A score against three competitors can't be compared with one against five.

Treating P as good news. High position with low visibility means "named first on the few prompts where you're named", not "leading".

Ignoring the per-engine table. An 11-point jump can be one engine going from zero to one mention, as Competitor D's did.

Mixing in branded prompts. It inflates every part at once. Keep branded answers for accuracy and sentiment checks.

Calling a one-point gap a lead. Competitors C and E differ by 0.2. Our two runs differed by 0.9 on the same prompts. For the sample sizes behind a stable number, see how often to track AI visibility.

Frequently Asked Questions

What is a good AI visibility score?

There's no universal benchmark, because the score depends on your competitor list and prompts. Compare yourself with the leader on the same prompts, engines and competitor set. In our category, the leader scored 39.4 and the last of six brands 26.0.

How is an AI visibility score calculated?

Compute five parts per engine on unbranded answers: visibility, share of voice, position, own-site citations and sentiment. Average each part across engines, then weight them 35/20/20/15/10 and add them up.

Why did my AI visibility score jump overnight?

Usually because one engine went from never naming you to naming you once, which lifts both position and sentiment from zero. Check the per-engine parts before you celebrate.

Should sentiment count more in an AI visibility score?

Not for ranking. Sentiment varies little between brands, it's measured only on answers that name you, and it can reward brands that are rarely named. Track it separately as a warning signal.

Does an AI visibility score include Google AI Overviews?

In BugViso, yes, once Google has shown an overview for at least five unbranded queries in the run. Until then, AI Overviews is reported on its own and kept out of the average.

The Score Is Only as Honest as Its Rules

Fix the competitor set, exclude branded prompts, average per engine and read the parts next to the total. Then the AI visibility score tells you which lever to pull, which is how BugViso's AI visibility tracking presents it after every run.

Found this useful? Share it.

See where your site stands

Run a free BugViso audit for SEO, speed, accessibility and AI search readiness — with fixes you can ship today.