Methodology

Every threshold, and where it came from.

This page is the full reference: every scored row, every gate, every guardrail the extension ships: what it measures, the exact threshold, and the citation, quoted exactly as the code states it. If a citation is vague in the code, this page says so instead of dressing it up. Nothing here is an LLM's opinion; every number below is a rule you can read.

On this page

The two-layer model

finalScore = round(contentScore × eligibility), 0–100, fully rule-based (no LLM). Eligibility gates are multiplicative because a live citation pilot proved they are gates, not slices: a blocked AI bot, a JavaScript-only page, or an unindexed URL each make a page uncitable. Treating them as small additive point deductions wrongly let good content "outweigh" being invisible. Content quality is additive and sums to 100. Source: computeGeoReadiness, assets/js/functions.js.

AI-citation readiness (GEO): five content-quality signals

Default weights below; see intent re-weighting for how a page's topic shifts them. Each signal keeps its earned fraction when re-weighted; only the maximum changes.

SignalDefault weightAnchor
Evidence density30#geo-evidence-density
Answer-shape25#geo-answer-shape
Trust (entity-type-aware)22#geo-trust
Freshness (page-type-conditional)13#geo-freshness
Fundamentals10#geo-fundamentals
Evidence density30
Answer-shape25
Trust (entity-aware)22
Freshness13
Fundamentals10

Evidence density: 30 pts

Measures: statistics (≥3 numeric tokens per 500 words), a quotation (≥1 <blockquote>), and ≥1 authoritative outbound link (gov/edu/academic/major-news, corpus-scoped).

Threshold: 2+ evidence types present → 30 pts (full). Exactly one type present is scored by that lever's own measured lift: quotation alone → 18, statistic alone → 14, citation alone → 13, none → 0. A puffery guard (marketing-language density > 1.2%) subtracts 15 pts when at most one evidence type is present.

Citation, verbatim from code: "Quotations +41%, statistics +31%, citations +30% (Aggarwal et al., 2024)." The ordering that sets the single-lever point split (18 / 14 / 13) is explicitly keyed to those effect sizes in the code comment: quotations (+41%) > statistics (+31%) > citations (+30%).

Answer-shape: 25 pts

Measures: a lead answer (opening block 40–420 chars, ≥8 words) AND scannable structure (≥3 list items, or ≥3 H2s, or ≥1 question heading, or ≥1 table).

Threshold: both present → 25. Either alone → 13. Neither → 0. FAQ/HowTo/QAPage schema present → +5, capped at 25.

Citation, verbatim: "Answer-first + 44% of citations come from a page's first third (Aggarwal 2024; Indig)."

Trust: 22 pts, entity-type-aware

Trust scores the presence of the markers appropriate to the page's entity type, never actual trustworthiness (the tool cannot observe that). Entity type is read from JSON-LD first, URL heuristics second.

Entity typeFactors (sum to 22)Citation, verbatim
YMYL topic (any entity type, when detected)Credential-gated: credentialed named author (MD/CFA/JD-class) + "reviewed by" line + ≥1 gov/edu/journal citation. 3/3 → 22, 2/3 → 13, 1/3 → 7, 0 → 0."YMYL AI citations concentrate on credentialed, reviewed, primary-sourced pages (arXiv:2601.17109, 2026)."
articleNamed byline (11) + credential line (→22 total, credential-gated, not simply additive)"A visible byline answers "who created this content" — the accountability check raters apply (Search Quality Rater Guidelines §2.5.2)."
person / portfolioCredited name (8) + bio/credential (6) + linked professional profiles (8)"A named, identifiable creator answers the raters' "who created this" accountability check (Search Quality Rater Guidelines §2.5.2)."
productOrg/LocalBusiness schema (6) + sameAs (5) + About (5) + Contact (3) + ratings markup (3)"Schema identifies the publisher (entity understanding — Google structured-data docs, not a ranking factor); About/Contact answers the rater "who is responsible" check (Search Quality Rater Guidelines §2.5.2)."
local businessSchema (6) + visible NAP (7) + About (5) + Contact (4)"Visible NAP + contact info is the local-business "who is responsible" rater check (Search Quality Rater Guidelines §2.5.2); LocalBusiness schema aids entity identification (Google structured-data docs)."
organizationSchema (7) + sameAs (6) + About (5) + Contact (4)same as product (CITE_SCHEMA_WHO)
generic pageSchema (7) + sameAs (5) + About (6) + Contact (4)same as product (CITE_SCHEMA_WHO)

A small bonus (+4 verified schema sameAs to Wikipedia/Wikidata/LinkedIn, +2 for ≥1 authoritative outbound link) is added on top of every branch, capped at 22.

Freshness: 13 pts, page-type-conditional

Measures: ISO dateModified age (language-neutral, date-parse only, no keyword heuristics).

Threshold: ≤90 days → 13 (ok). ≤365 days → 8 (warn). >365 days → 3 (bad). No date found: an "evergreen" page type (non-article) gets 9 (benefit of the doubt); an article with no date gets 0. A future dateModified (>2 days ahead) is treated as untrustworthy and falls through to the no-date branch.

Citation, verbatim: "AI cites ~26% fresher content; Perplexity 3.2× for <12-month pages (Ahrefs, 2026)." Flagged as-is: the code names the source (Ahrefs) and year but not a specific report title.

Fundamentals: 10 pts

Measures: title present (2) + meta description present (2) + word count (≥300 → 6, ≥120 → 3, else 0). Indexability itself is scored separately as a gate, not here.

Citation, verbatim: "Strong fundamentals are what win AI visibility (Google, 2026)." Flagged as vague: no specific Google document is named in the code. This is the weakest citation on the GEO tab.

Eligibility gates: multiplicative, 0–1 each

A blocked bot, a JS-only page, or an unindexed URL makes a page uncitable regardless of content quality, so these three factors multiply the content score rather than subtract from it: eligibility = indexGate × renderGate × crawlerGate.

Indexing gate

Threshold: Search Console reports the URL indexed → factor 1 (ok). Reported not-indexed → factor 0.1 (bad). Index status unknown (not owned / no GSC) → factor 1, but flagged "warn," assuming indexable rather than penalizing an unverifiable page.

Citation, verbatim: "An unindexed page can't appear in any AI answer." This is a mechanical/logical claim in the code, not an external citation, flagged plainly rather than dressed up as a study.

Crawlable content (render) gate

Measures: the share of visible content present in raw server HTML vs. requiring JavaScript execution (a headless-vs-rendered DOM diff, "coverage").

Threshold: ≥60% in raw HTML → factor 1 (ok). 30–59% → factor 0.7 (warn, "partly JS-rendered"). <30% → factor 0.3 (bad, "JS-only"). Coverage unmeasurable → factor 1, flagged "warn," assuming readable.

Citation, verbatim: "AI crawlers that don't execute JS only see server HTML." Mechanical claim, not an external citation.

AI crawler access gate

Measures: whether robots.txt allows the citation/search bots (Googlebot, Bingbot, OAI-SearchBot, ChatGPT-User, PerplexityBot, Claude-SearchBot) for the current path. Training-only bots (GPTBot, Google-Extended, CCBot, ClaudeBot, Meta-ExternalAgent, Bytespider) are tracked separately and do NOT affect this gate; blocking them is a business choice with no GEO impact, per the code's own comment.

Threshold: zero search bots blocked → factor 1. Some blocked → factor = (allowed / total), floored at 0.1.

Citation, verbatim: "Blocking a search/RAG bot removes you from that engine's answers." Mechanical claim, not an external citation.

Intent re-weighting (QDF / QDD / YMYL)

Default weights are re-weighted by the page's own detected query intent (from title/H1/body, language-scoped en/id/de; an unlisted/undetected language gets flat default weights, never a penalty):

Each signal keeps its earned fraction; only its weight (its maximum) changes; the vector always sums to 100 (largest-remainder rounding). The UI states the applied profile ("weighted for: …") and calls this a heuristic, not a certainty.

Penalties & warnings: unscored, shown as "nice to have"

These never add or subtract a scored point except where explicitly stated (hidden-instruction penalty). Adding a new scored row here would force a rebalance of every content-quality weight above, the standing reason these ship as warnings instead.

FlagTriggerCitation / detail, verbatim
YMYL authority gapPage reads as YMYL and the trust row isn't "ok""This reads as a health/finance/legal (YMYL) topic, where AI weights author/organization credibility heavily. The trust gap above is a real citation risk — close it first."
FAQ schema presentJSON-LD includes FAQPage"Counts toward answer-shape when paired with real Q&A content, but schema alone didn't move citations across 1,885 pages (Linehan & Guan, 2026)." See the year-inconsistency note below.
Possible keyword stuffingA phrase repeats mechanically: clustered, low-variety context, and it isn't the page's own stated topic (density alone never triggers this)"Stuffing is judged by unnatural repetition, not a density % ; vary the wording or cut it (Google Search Central, 2026; Aggarwal et al., 2024)."
Hidden instructions (prompt injection)Page hides text that reads as instructions to an AI−20 pts penalty on content score. "Hidden prompt-injection to manipulate AI ranking is deceptive and gets sources demoted or removed — remove it (Pfrommer et al., EMNLP 2024)."
Off-page reality: reputation intentURL/title matches review/scam/complaint/versus-style patterns"AI usually cites independent third-party sites (Trustpilot, Reddit, review platforms) here, not a brand's own page. On-page GEO tuning won't win this query."
Off-page reality: commercial "best/compare" intentProduct page, no on-page ratings markup, title/lead matches best/review/vs/compare"For these queries AI mostly cites third-party review sites (G2, Trustpilot, Reddit), not the product's own page. This is a ceiling on-page tuning can't lift."

On-page SEO: nine signals, additive to 100

DOM-only, no Search Console needed, runs identically on owned and competitor pages. Each row scores the presence/shape of a technical signal; it does not claim a ranking outcome (the code's own framing).

SignalMaxThresholdCitation, verbatim
Title tag1630–60 chars → 16. 15–70 → 11. Outside that / missing → 6 or 0."The <title> is a described ranking signal and sets the clickable SERP headline (Google Search Central, "Influencing your title links in search results" documentation)."
Meta description12120–160 chars → 12. 50–200 → 8. Outside / missing → 4 or 0."The meta description drives click-through even though it isn't a ranking factor (Google, 2026)." Vague: no document named.
Heading structure16Exactly one H1 (8 of 16) + no skipped heading level with ≥1 H2 (8 of 16)."A single H1 and an ordered heading outline help both crawlers and readers parse the page (Google, 2026)." Vague: no document named.
Image alt text12Coverage of non-empty alt: ≥90% → 12, ≥60% → 7, else 2. No images → 12 (nothing to caption)."Alt text is an accessibility requirement and the primary relevance signal for image search (WCAG; Google, 2026)." WCAG is a real named standard; the Google half is unspecified.
Canonical tag8Self-referencing canonical → 8. No canonical → 3. Bad target → 2. Ambiguous target → 4."rel="canonical" tells Google which URL to index among duplicate or parameter versions (Google Search Central, "Consolidate duplicate URLs" documentation)."
Indexability (meta)10No noindex → 10. nofollow only → 7. noindex present → 0 (hard fail)."A noindex directive removes the page from every search index (Google, 2026)." Vague / largely definitional.
Social preview10og:title (4) + og:description (3) + og:image (3)."Open Graph tags control how links unfurl on social and chat platforms (Open Graph protocol)."
Mobile viewport6Responsive <meta name="viewport"> present → 6, else 0."Google indexes mobile-first; a missing viewport hurts mobile usability (Google, 2026)." Vague: no document named.
Internal linking10≥5 same-host links → 10. ≥1 → 6. 0 → 2."Internal links distribute PageRank and aid crawl discovery (Google Search Central, 2026)." Vague: no specific article named.

Demand match & topic coverage

Question: does this page's own vocabulary match the demand Google already sends it? Method: mines the page's real Search Console query rows (owned pages only, never invented), stems and de-brands the terms, and flags which ones appear fewer than 2 times on-page (REL_MENTION_FLOOR = 2), restricted to content-bearing terms (IDF ≥ 0.35) with ≥10 query impressions to filter noise.

Citation: Google's phrase-based indexing patent, US7536408B2 (A. Patterson). A document is scored by how many of a topic's "related phrases" it contains, where a related phrase is one whose actual co-occurrence with the topic exceeds its expected rate (information gain). The code is explicit that this is the described mechanism, not the debunked "LSI keyword" folklore, and that related phrases are read from the page's own real ranking queries, never generated/invented (an earlier related-phrase generator was tested and killed at ~0 precision).

An "over-invested" flag (informational only, never "remove this") requires ≥5 distinct queries on the page before it will call a subtopic zero-demand, because GSC samples and a true zero can't be told apart from "doesn't rank yet" below that floor.

Near-duplicate / merge-candidate detection

Question: which of the site's own pages have overlapping enough Search Console query footprints that they're effectively competing to be the same document?

Citation: Google's phrase-based duplicate-detection patent, US8108412B2. Google fingerprints a document by its high-value related phrases and treats two documents as near-duplicates when those fingerprints match. This tool proxies the fingerprint with each page's real GSC query footprint (the phrases Google already ranks it for), not a text-similarity hash.

Thresholds: both pages need ≥200 impressions and ≥5 distinct queries to qualify as a merge candidate (hub/home pages excluded by design; they overlap with everything). A pair flags when either (a) impressions-weighted Jaccard overlap ≥0.40, or (b) containment (smaller footprint inside the larger) ≥0.70 with overlap below the Jaccard floor. The containment path catches an asymmetric "thin page absorbed by a big one" case that Jaccard alone misses. Requires ≥5 shared queries regardless of overlap ratio.

Decay: refresh candidates

Question: which queries/pages are really losing clicks vs. the prior period, not just wobbling?

Method: every candidate must clear six independent gates: (1) a baseline floor (≥10 prior clicks), (2) a ramping-page guard (≥50 prior impressions), (3) a relative-drop floor (current ≤70% of prior), (4) a statistical-significance test, (5) a spike-normalization check (a prior period that was itself an unusual spike vs. two periods back is excluded), (6) a "recovered dip" check (if the last 7 days of the current window already sit at/above the window average, the apparent decline is suppressed as transient).

The significance test itself is a real statistical test, not a percentage eyeball: a Poisson z-test on the click counts (z = (curClicks − prevClicks) / √(curClicks + prevClicks), flagged only at z ≤ −1.96, p < 0.05 two-sided), plus, because a pure click test can miss a CTR/snippet problem where impressions grew but clicks didn't, a two-proportion z-test on click-through rate for the same p < 0.05 bar. The standard-normal CDF used for the p-value is the Abramowitz-Stegun 26.2.17 approximation, not a lookup table.

Classification, verbatim from the UI: "Rank fell" (position dropped ≥3 with real impressions), "CTR collapsed" (impressions held/grew and rank held, but clicks fell), "Demand fell" (search interest itself dropped, explicitly labelled "not a page problem, do not rewrite for this"), "Mixed" (investigate before acting). The panel's own copy: "Every row passed a significance test (p<0.05) versus the prior period — not a percentage eyeball."

Traffic verdict significance

The plain-language "traffic up/down/steady" verdict banner reuses the exact same Poisson z-test and thresholds as decay (DECAY_Z = −1.96, DECAY_MIN_PREV_CLICKS = 10) so a small-sample wobble is never rendered as a verdict. Below the click floor, or |z| < 1.96, the banner reports "no meaningful change" rather than a direction. Any LLM-generated one-line interpretation of the verdict is re-checked through the same vet gate described below before it can reach the page (see next section). This was previously the one generative surface that bypassed the vet, and the code comment marks the fix explicitly.

The one generative surface, and its gate

Title/meta rewrite drafting is the only surface on the SEO tab where an LLM produces user-facing text (everything above is deterministic). Every candidate must pass all of these checks before display. Failing candidates are dropped silently, and zero survivors returns an honest "couldn't produce a grounded draft" rather than a plausible-but-wrong one:

A parallel guard, vetInsight, gates every generated interpretive sentence anywhere in the extension (not just drafts) against seven hard rules: no hallucinated number not traceable to the data shown; no unhedged causal language ("because/driven by/due to") the underlying click/impression data can't support; no calling a statistically-insignificant click delta a "finding"; no connecting a GEO/E-E-A-T/schema signal to a traffic movement (a non-sequitur the tool has no data to support); no direction contradiction against the real click delta; no asserting a verified "zero" movers result when the per-term breakdown was never actually loaded (absence of data ≠ a verified zero); no markdown when plain text was requested. Any generated text that fails a hard rule gets one corrective retry, then falls back to a fully deterministic, numbers-only rendering of the same data, never silently shipping the ungrounded version.

Audit export & since-last-audit diff

buildAuditExport compiles the GEO, SEO, topic-coverage, and content-brief panels into one copy-paste report. It reuses verdictSignif for the traffic-verdict line (so an export can never claim a change the significance test didn't clear) and pulls every row's own cite field into a trailing "Sources" sub-list per section, so the export can never say something the live UI didn't already say and cite.

diffSnapshots compares two stored audits of the same page and reports only computed state changes: score deltas, row state transitions (e.g. warn → ok), and gate open/close events. The code's own framing: diffs report state changes, "never a cause, never a projection." A row is only compared when it exists in both snapshots (so a schema change that adds/removes a row is never misread as a content regression).

Honesty commitments

Readiness ≠ live citations. The GEO tab's own banner: "Scores whether the page is built to be cited by AI — not whether AI cites it today." Its "why the difference?" tooltip: "Whether ChatGPT, Perplexity, or Google actually cite you shifts by run and by day, so it needs repeated live checks against each engine — a share-of-answer tracker, not a page audit." The extension does not run that live-citation check; it audits structural readiness only.
No authority score. The Topic Coverage tab carries no numeric ring by design. Code comment: "No numeric ring: Google has no 'topical authority' score — the tab shows coverage/structure/effort EVIDENCE, not a made-up number." Its banner tells the user the same thing: "Evidence you can act on, not a score."
No rank tracking, no traffic estimation. Every click/impression/position number shown comes from the Google Search Console API (searchconsole.googleapis.com), authorized by the site owner's own OAuth token, never a scraped SERP position or a modeled/estimated traffic number. There is no SimilarWeb or third-party traffic-estimation call anywhere in the codebase. Every on-page score row's own framing states it "does not claim a ranking outcome". It measures the presence/shape of a signal, not a rank.
An empty brief beats a plausible-but-wrong one. The content-brief generator's own comment: "PRECISION-FIRST. We emit a suggestion only when we can stand behind it — a wrong 'add this' costs the user's trust in the whole panel." Its question-gap list is GSC-grounded only: "Without search data we have no accurate way to know which questions the page fails to answer, so we emit none."
Draft vet gate. See above: every LLM-drafted title/meta must pass validateDraft's five checks before it's shown; zero survivors is shown honestly as "couldn't produce a grounded draft," never a lowered-bar fallback.

What it refuses to score

Two tactics are explicitly debunked in the code and its grounding notes (docs/geo-research.md §9) and are flagged, never scored as a win:

Add to Chrome · free Back to overview