I'm Chris Han, Head of Product at MaxAEO. We publish a monthly ranking of which AI search tracking tools AI assistants name, and MaxAEO is one of the 44 brands in it, so treat me as a participant, not a referee. This post was written with AI assistance. Every number below comes from the published research page or from the downloadable CSV, and I ran the script in this post against that CSV before writing anything.
The setup in one paragraph: every day, the same 40 buyer-style questions go to 8 assistants (ChatGPT, Claude, Copilot, Gemini, Google AI Mode, Google AI Overviews, Grok and Perplexity). Each answer becomes one row. October 2026 is the first edition. It rests on 7,805 valid answers, and the top five by named rate are Profound 83.0%, Semrush 77.0%, Peec AI 73.6%, Otterly.AI 60.3% and Ahrefs 58.1%. MaxAEO sits at 19th with 7.4%, marked "(publisher)" in the table.
This post is about the file, not the table. Every number on the page can be rebuilt from the rows, and rebuilding them turned up a few things the table doesn't show.
What a row looks like
answers-2026-10.csv.gz has 9,920 rows, one per attempted answer, failures included. The columns that matter for counting:
-
platform,date,prompt_id -
status:ok,invalid,failedorno_ai_overview -
brands_named_in_order: semicolon-separated, already matched against the 44-brand alias list -
cited_domains: the sources the assistant showed, also semicolon-separated
The answer text isn't in the download (it's too big), so you're trusting the brand matching. The alias regexes are published next to the CSV if you want to argue with them.
The script
No packages. Node's zlib reads the gzip, and a small CSV parser handles the quoted URL columns.
// rates.mjs // usage: node rates.mjs answers-2026-10.csv.gz "MaxAEO" maxaeo.ai import { readFileSync } from 'node:fs'; import { gunzipSync } from 'node:zlib'; const [file, brand, domain] = process.argv.slice(2); // The URL columns can contain commas, so split on commas outside quotes only. function parseCsv(text) { const rows = []; let row = [], field = '', quoted = false; for (let i = 0; i < text.length; i++) { const c = text[i]; if (quoted) { if (c === '"' && text[i + 1] === '"') { field += '"'; i++; } else if (c === '"') quoted = false; else field += c; } else if (c === '"') quoted = true; else if (c === ',') { row.push(field); field = ''; } else if (c === '\n') { row.push(field); rows.push(row); row = []; field = ''; } else if (c !== '\r') field += c; } if (field || row.length) { row.push(field); rows.push(row); } return rows; } const [header, ...body] = parseCsv(gunzipSync(readFileSync(file)).toString('utf8')); const rows = body.map(r => Object.fromEntries(header.map((k, i) => [k, r[i] ?? '']))); const list = s => (s ? s.split('; ') : []); // Every attempted answer is a row, failures included. Count them first. const statuses = {}; for (const r of rows) statuses[r.status] = (statuses[r.status] ?? 0) + 1; console.log(statuses); // Only "ok" rows go in the denominator. const valid = rows.filter(r => r.status === 'ok'); const names = r => list(r.brands_named_in_order).includes(brand); const cites = r => list(r.cited_domains).includes(domain); const pct = (n, d) => (100 * n / d).toFixed(1) + '%'; const groups = {}; for (const r of valid) (groups[r.platform] ??= []).push(r); const table = Object.entries(groups).sort().map(([platform, rs]) => ({ platform, valid: rs.length, named: pct(rs.filter(names).length, rs.length), cites_domain: rs.filter(cites).length, cites_and_names: rs.filter(r => cites(r) && names(r)).length, })); console.table(table); const pooled = valid.filter(names).length / valid.length; const perPlatform = Object.values(groups).map(rs => rs.filter(names).length / rs.length); const averaged = perPlatform.reduce((a, b) => a + b, 0) / perPlatform.length; console.log(`${brand}: pooled ${pct(pooled, 1)}, average of platforms ${pct(averaged, 1)}`); console.log(`answers citing ${domain}: ${valid.filter(cites).length}, naming ${brand}: ${valid.filter(names).length}`); // One day is a small sample. Look at the spread across days with enough answers. const days = {}; for (const r of valid) (days[r.date] ??= []).push(r); const daily = Object.values(days) .filter(rs => rs.length >= 100) .map(rs => rs.filter(names).length / rs.length); console.log(`${daily.length} days with 100+ valid answers, daily rate ${pct(Math.min(...daily), 1)} to ${pct(Math.max(...daily), 1)}`); Run it with "Peec AI" peec.ai and you get 73.6% pooled, same as the page. With MaxAEO maxaeo.ai you get 7.4%. The per-platform rates for the top brands match the page's by-assistant table to the decimal.
The status counts won't match the summary, and that's documented
The first line the script prints for October is:
{ ok: 7805, invalid: 379, failed: 1730, no_ai_overview: 6 } The methodology page lists 1,385 failed and 351 "no AI Overview" for the same month. The gap is deliberate. 345 of those 351 empty AI Overview results fell on days when collection for the Google assistants and Copilot was broken, so the downloads record them as failed collection rather than "Google showed nothing". On every other day an AI Overview appeared in 99.2% of October searches. Count those 345 rows as real "no Overview" results and you'd report a trend that was really a network fault.
If you're building your own tracker, expect failures to cluster. The methodology page lists 11 bad days in October and puts September's bad day in the same bucket; on those days, valid answers were 9.0% for Gemini against 93.0% on all other days. The month comes out uneven. Copilot ended October with 668 valid answers and Claude with 1,190.
That's why the script prints two numbers. "Pooled" divides all naming answers by all valid answers, so Claude counts almost twice as much as Copilot. "Average of platforms" gives each assistant one vote. For October the gap is small: Peec AI is 73.6% pooled and 72.9% averaged, MaxAEO 7.4% and 7.6%. Small this month doesn't mean small next month, so I'd print both every time.
Measuring a change without fooling yourself
The page reports change versus September in points. Peec AI is up 15.4. Frase, 20th in October at 7.4%, is down 2.9. Both numbers are accurate. They just describe very different kinds of change, and the difference only shows up when you split by platform.
Run the script on the September file (8,699 valid answers) and compare. Peec AI went up by 8 points or more on seven of the eight assistants. The exception is ChatGPT, which was already at 79.4% and moved to 79.9%. That's a broad shift.
Frase's drop is concentrated in a few assistants. Google AI Mode went from 11.9% to 3.8% and Copilot from 8.5% to 1.0%. Google AI Overviews fell from 14.5% to 8.7%. Claude barely moved (17.6% to 17.3%), and Grok rose a little, from 11.5% to 12.5%. That locates the change; nothing in the CSV explains it.
Then there's daily noise. Across the 30 days in October with at least 100 valid answers, Frase's daily named rate ranged from 3.7% to 12.5%. In September the range was 7.5% to 14.5%. October's best day beats September's worst, so with two hand-picked days you could tell a growth story about a month in which the brand dropped 2.9 points. The page's own overlap numbers point the same way: the same assistant answering the same question on consecutive days shares about half its brand list (51.4%), and only 6.5% of those pairs are identical.
If you're trying to prove that GEO work increased brand mentions, monthly per-platform rates are the smallest unit I'd trust.
Cited is not named
The two columns that surprised me most when I put them side by side were cited_domains and brands_named_in_order.
Take Rankability. rankability.com is the 14th most-cited website in October, showing up in 629 answers (8.2% of answers that cite anything). Rankability itself is named in 208 answers, 2.7% of valid answers, which puts it 36th. Of the 629 answers that cite its site, 120 name Rankability. The rest cite the page without naming the brand.
Perplexity shows it most clearly. It cites rankability.com in 123 answers and names Rankability in 2 of them.
Peec AI has the opposite shape. peec.ai is cited in 1,140 answers, and Peec AI is named in 5,747. Its own-site citation rate is 20.0%, so when an answer names Peec AI and cites sources, the cited pages usually belong to someone else.
So "why do AI assistants ignore your content" has at least one version you can measure: your pages get cited and your brand doesn't get named. My guess is that the cited page tends to be about the category rather than the brand, but the CSV can't confirm that. You can check your own domain, though. Swap in your brand and domain and compare cites_and_names with cites_domain.
What this data can't tell you
It counts names, not quality. It comes from fixed accounts that ask the same questions every day, so a fresh user may see something different. It only ranks the 44 brands on the alias list. And it comes from us, a company that is in the ranking, which is the main reason the rows are public in the first place.
The CSV, alias list, prompts and methodology are all on the research page. If the script gives you a different number than the page, I'd like to know which one.

