Scoring rubric

Last updated 11 September 2026

No black box. Every point in your score comes from a check we publish here, with its weight. You, your developer or another agency can check our working. If a check is wrong, tell us and we fix it for everyone.

Both scores come from this rubric: 104 checks in 21 modules, weighted by category. It is generated from the same files the scorer reads, so it cannot drift from what the scanner does.

How is a score computed?

The scan runs 104 checks, grouped into 21 modules. Each check returns a pass, a partial pass or a failure, and carries a severity from 1 to 5. A pass counts 1, a partial pass 0.5 and a failure 0.

  • Category value: category_value = 100 * sum(status_factor * severity) / sum(severity), over scored findings only.
  • Axis score: score = sum(category_value * weight) / sum(active weight).

Two outcomes are left out of the calculation in both directions, so they neither penalise nor credit a site:

needs api
an API we did not connect — never counts as a pass and never penalises; report the count
not applicable
the check does not apply to this site

Active only when their signals are detected. An inactive category leaves the denominator entirely and the remaining weights re-normalise, so a blog is never penalised for having no Product schema.

Search SEO: categories and weights

How well the page is set up to rank in classic search engines. The weights sum over the active categories only, and re-normalise when a conditional category does not apply.

CategoryWeightModulesDepthApplies
Indexability & Crawl22M1, M2, M3FullAlways
Core Web Vitals / Performance16M15PartialAlways
On-Page & Meta12M7FullAlways
Structured Data12M5PartialAlways
Rendering8M4PartialAlways
Internal Linking & Semantics8M10PartialAlways
E-E-A-T7M16PartialAlways
Images / Media5M9PartialAlways
Sitemaps & Discovery5M17FullAlways
Freshness3M13PartialAlways
Social Cards2M8PartialAlways
E-commerce15M18Not builtOnly when detected
Local10M19Not builtOnly when detected
International8M20FullOnly when detected

AI Visibility: categories and weights

How extractable and citable the page is for AI answer engines (GEO/AEO). The weights sum over the active categories only, and re-normalise when a conditional category does not apply.

CategoryWeightModulesDepthApplies
Answer Extractability20M11PartialAlways
Structured Data16M5PartialAlways
Fact Density / Original Data14M12PartialAlways
AI Crawler Access12M14PartialAlways
Rendering (non-JS)10M4PartialAlways
Entity / Knowledge-Graph10M6PartialAlways
E-E-A-T / Authority9M16PartialAlways
Freshness6M13PartialAlways
Images / Multimodal3M9PartialAlways
llms.txt0M21FullReported, not scored

Why are the two scores never combined?

The two scores are never combined into one number. Two scores are published side by side and never averaged into one. A site can rank well and be uncitable by AI, or the reverse; collapsing that hides the finding we are paid to surface.

When is a score withheld?

Each axis reports how much of its rubric weight the scan could actually measure. Below 70% of that weight, we compute the number but do not publish it, and the report names the categories that could not be assessed. A score built on a fraction of its rubric would look precise and describe a different rubric from the one on this page.

What caps a score?

A failure at severity 5 means the site cannot be indexed or reached at all, so nothing else on the report can take effect while it holds. Any such failure in an axis means that score is capped at 40, band F. The cap only ever lowers a score; it never raises one.

M14: AI1 is this axis's severity-5 trigger. It fires only on the site-wide condition — every retrieval crawler refused, or a robots.txt that does not respond — which is the same evidence T13 reads for the search axis, assessed for its own consequence rather than borrowed from T13's.

What do the letter grades mean?

A published score maps to a letter band by its lower bound, with one adjustment: 8 or more open findings move it down by one band, and 12 or more open findings move it down by 2 bands. The score itself is unchanged.

BandFromWhat it means
A90Your site is easy for Google and AI assistants to read; what remains is refinement.
B80Your site is easy to read, with a few gaps worth closing before your competitors do.
C70The basics work, but gaps are holding you back; a few well-chosen fixes move the score the most here.
D60The basics work only in part; several problems stop Google or AI assistants reading key pages.
F0Google and AI assistants struggle to read your site; answers about your line of business come from other businesses' pages.

What does each severity mean?

SeverityMeaning
5The site cannot be indexed or reached at all. Nothing else on the report matters while it holds, and it caps the axis at 40.
4A primary signal is broken or absent. Directly costs indexing, ranking or citability.
3A real defect with measurable impact, but the page still works without it.
2Hygiene. Worth fixing, cheap to fix, rarely decisive on its own.
1Minor. Reported for completeness; a site can be excellent without it.

What does each module not cover?

14 of the 21 modules are not yet built to the full depth the rubric describes. What each one does not examine is stated here, and in every report that relies on it, rather than left for a reader to infer from a number.

ModuleNameDepthNot covered
M4Rendering / JS dependencyPartialRaw HTML only. We see what a non-JS crawler receives, which is the question that matters for AI citability, but with no rendered-DOM diff we cannot separate 'missing for everyone' from 'missing only for AI', nor classify SSR/SSG/ISR.
M5Structured dataPartialType presence, markup/body parity (S6, 2026-09-13) and, on the offer page, Offer presence and Offer parity (OF2, OF3, 2026-10-02). No validation of the required properties per schema.org type, so an Organization block with no name still passes.
M6Entity / knowledge graphPartialThe site's own declaration only: whether a primary entity exists, what identity links it publishes, and whether it names itself consistently. Nothing is reconciled against Wikidata or a company register, so we can see that a site claims an identity, never that the claim resolves to the right one. No NAP consistency across third-party directories.
M8Social cardsPartialOpen Graph only. No twitter:card family.
M9Images / mediaPartialAlt coverage only. No alt quality, no modern-format check, no VideoObject.
M10Internal linking & semanticsPartialOrphan detection only. No anchor-text grading, pillar/cluster mapping or click depth.
M11Answer extractabilityPartialStructure only: question headings and the length of what answers them, extractable blocks, segmentation, and whether a section opens on an unresolved reference. Whether an answer is correct, complete or better than a competitor's is not judged, and self-containment is tested on the opening sentence rather than the whole passage. Since 2026-10-02 (OF1) the offer page is read for where its prices sit; whether the offer itself is clear is not judged.
M12Fact density / original dataPartialCounts figures, outbound sources and promotional claims. It cannot tell original data from restated data — a proprietary survey and a quoted industry statistic look identical to a counter — so the original-data half of this category is named, not measured. The claim list is English-only, which under-reads a Chinese-language page. PP7 (thin content) is reported under this heading as context but is not scored: word count is not fact density.
M13FreshnessPartialPresence of a visible timestamp, sitemap lastmod, and a claimed freshness date judged against the crawl date (O10, 2026-09-13). No staleness judgement against topic volatility: a rates page a year old is stale, a evergreen guide a year old is not, and telling them apart needs a per-vertical model we do not yet have. A visible updated date is compared with dateModified (O11, 2026-10-02, reported, not scored); whether a date is older than the content's last change is not observable from outside and is not claimed.
M14AI crawler accessPartialrobots.txt policy only — what the site declares, not what its edge actually serves. A WAF or bot-management rule can refuse a crawler that robots.txt admits, and that is invisible from a crawl; confirming it needs a request sent under each crawler's user-agent, or Bing Webmaster's AI performance report. The user-agent lists are verified first-party in docs/ai-visibility-research-2026-09-09.md.
M15Core Web Vitals / performancePartialCrawl-only runs measure proxies: TTFB, compression, caching, image weight, script loading. Real field data needs --pagespeed, which raises this module to full (PS1-PS5).
M16E-E-A-T / authorityPartialPresence of the trust signals only: a byline or author node on content pages, an about and a contact page, a published privacy policy, a contact route in the markup. We can see that a page names an author; we cannot see that the author is qualified, and no credential, affiliation or citation of one is verified.
M18E-commerceNot builtNo Product/Offer checks. The category stays inactive and re-normalises out, which is correct for every seed site and for most HK/SG SME prospects.
M19LocalNot builtNo LocalBusiness, NAP or opening-hours check. This one matters commercially — most HK/SG SME prospects are local businesses — and it re-normalises out today rather than scoring them on it.

Which checks are run?

All 104 scored checks, with the module each scores under, the axis it counts toward and its severity.

IDModuleAxisSeverityCheck
T1M3Search SEO4HTTPS redirect
T2M2Search SEO4non-canonical host folds to canonical
T3M2Search SEO3alt host redirects, not merely canonical
T4M2Search SEO3/index.html redirects to /
T5M17Search SEO3sitemap.xml reachable
T6M17Search SEO2sitemap URL count
T7M17Search SEO2sitemap lastmod
T8M1Search SEO3robots.txt present and references a sitemap
T10M3Search SEO2custom 404
T11M2Search SEO4homepage canonical
T12M7Search SEO3meta viewport
T13M1Search SEO5robots.txt crawl access
C1M2Search SEO4homepage canonical exact
C2M2Search SEO3/index.html 301s
C3M2Search SEO3alt-host canonical when it serves 200
C4M2Search SEO2no internal index.html links
C5M2Search SEO2no links to the non-canonical host
S1M5Both4JSON-LD on homepage
S2M5Both3homepage schema types
S3M5Both3JSON-LD on article
S4M5Both2article schema type
S5M2Search SEO5no page is noindex
S6M5Both4markup/body parity
O1M7Search SEO4title tag
O2M7Search SEO2meta description
O3M7Search SEO3single H1
O4M7Search SEO2html lang
O5M8Search SEO2Open Graph tags
O6M8Search SEO2OG image reachable
O7M13Both2visible timestamp
O8M7Search SEO2article title lengths
O9M9Both3image alt coverage
O10M13Both3visible freshness
P2M15Search SEO2LCP fetchpriority
P3M15Search SEO2LCP preload
P4M15Search SEO3image dimensions (CLS)
P5M15Search SEO2script defer/async
P6M15Search SEO3large images
I1M17Search SEO1sitemap URL list
I2M3Search SEO4sitemap URLs return 200
I3M10Search SEO3orphan detection
X1M7Search SEO3duplicate titles
X2M7Search SEO2duplicate meta descriptions
X3M2Search SEO4self-referential canonical
X4M3Search SEO4mixed content
HD1M3Search SEO2HSTS header
HD2M3Search SEO1X-Content-Type-Options
HD3M3Search SEO1frame protection
HD4M3Search SEO1Referrer-Policy
HD5M15Search SEO3compression
HD6M15Search SEO2Cache-Control
HD7M15Search SEO3TTFB
HD8M3Search SEO3redirect chain
HL1M20Search SEO3hreflang tags
HL2M20Search SEO3hreflang values valid
HL3M20Search SEO2self-referencing hreflang
HL4M20Search SEO2hreflang x-default
HL5M20Search SEO3hreflang reciprocity
HL6M20Search SEO3hreflang targets 200
L1M21AI Visibility2llms.txt present
L2M21AI Visibility1llms.txt starts with H1
L3M21AI Visibility2llms.txt links resolve
L4M21AI Visibility1llms.txt declared in head
R1M4Both4content without JS
R2M4Both3headings without JS
R3M4Both4links without JS
R4M4Both4client-side app shell
PS1M15Search SEO4mobile performance
PS2M15Search SEO4LCP (mobile)
PS3M15Search SEO3CLS (mobile)
PS4M15Search SEO3TBT (mobile)
PS5M15Search SEO2desktop performance
G4M2Search SEO5homepage indexed
B2M3Search SEO3Bing page alerts
B3M3Search SEO2Bing crawl stats
AI1M14AI Visibility5AI answer engines can reach the site
AI2M14AI Visibility4citation crawlers individually allowed
AI3M14AI Visibility3user-triggered fetchers allowed
AB1M11AI Visibility4question headings answered directly
AB2M11AI Visibility3extractable structures present
AB3M11AI Visibility3content is segmented into passages
AB4M11AI Visibility2sections open self-contained
FD1M12AI Visibility4statistic density
FD2M12AI Visibility3outbound citations to third-party sources
FD3M12AI Visibility2claims supported by figures
EN1M6AI Visibility4primary entity declared
EN2M6AI Visibility4identity links (sameAs)
EN3M6AI Visibility2the entity is named consistently
EE1M16Both3content carries an author
EE2M16Both3the business is identifiable
EE3M16Both2published policies
EE4M16Both3a real contact route
PP1M7Search SEO3inner-page H1
PP2M7Search SEO3inner-page titles
PP3M7Search SEO2inner-page meta
PP4M2Search SEO4inner-page canonical
PP5M5Both3inner-page schema
PP6M9Both3inner-page image alt
PP8M2Search SEO4noindex pages
PP9M2Search SEO3duplicate content
OF1M11AI Visibility3prices in an extractable structure
OF2M5AI Visibility3offer markup present
OF3M5AI Visibility3offer markup matches the page
L5M21AI Visibility2llms.txt agrees with the pages

Which checks are reported but not scored?

21 further checks appear in a report's appendix and contribute zero points. Reported in the appendix, contributing zero points. Two standing reasons: the check encodes a convention of ours rather than a property of the client's site, or it returns data rather than a verdict. A check may fault a site for being inconsistent with its own declared intent, never for disagreeing with ours. A third, temporary reason: a new check is reported for one cycle of client data before it scores (O11, since 2 October 2026).

IDWhy it is not scored
T9ads.txt — only meaningful if the site sells ad inventory. Absence is not a defect.
P1GA4 tag — analytics choice, not an SEO property. Plenty of good sites use something else or nothing.
A1AdSense snippet — monetisation, not visibility.
A2AdSense pub-ID match — monetisation, not visibility.
I4cross-site partner links — our own inter-property linking convention, driven by partnerDomains config. Meaningless for a client.
IN1IndexNow key file — our deployment practice, and only observable when we hold the filesystem.
H1.htaccess present — needs filesystem access we do not have on a prospect, and duplicates the redirect and security-header tests, which we can see from outside.
H2.htaccess HTTPS rule — same; the outcome is already measured by the HTTPS-redirect test.
H3.htaccess www rule — same; the outcome is already measured by the www-redirect test.
H4.htaccess index.html rule — same; the outcome is already measured by the index.html redirect test.
H5.htaccess security headers — same; the outcome is already measured by the HSTS-header test.
G1GSC property access — a diagnostic of our own connection, not of the site.
G2GSC search performance — data for the narrative, not a verdict.
G3GSC top queries — data for the narrative, not a verdict.
B1Bing access — a diagnostic of our own connection.
B4Bing performance — data, not a verdict.
B5Bing top queries — data, not a verdict.
B6Bing top pages — data, not a verdict.
AI4training-crawler policy — which model-training crawlers robots.txt refuses. Refusing them is a legitimate business choice that costs no citations, per docs/ai-visibility-research-2026-09-09.md, so this returns data for the report to state rather than a verdict to score.
PP7thin content — reported under M12 as context. Word count is not fact density; scoring it there would overstate what we measure.
O11Visible updated date against dateModified. Reported, not scored, for its first cycle, and not for either reason above: the check is new and scores once a month of client data shows how often a correct site trips it.

What is the rubric based on?

Published guidance from the search engines and the standards bodies, not guesses about ranking factors. Rubric version 0.3.0, check map version 0.2.0.