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.
| Category | Weight | Modules | Depth | Applies |
|---|---|---|---|---|
| Indexability & Crawl | 22 | M1, M2, M3 | Full | Always |
| Core Web Vitals / Performance | 16 | M15 | Partial | Always |
| On-Page & Meta | 12 | M7 | Full | Always |
| Structured Data | 12 | M5 | Partial | Always |
| Rendering | 8 | M4 | Partial | Always |
| Internal Linking & Semantics | 8 | M10 | Partial | Always |
| E-E-A-T | 7 | M16 | Partial | Always |
| Images / Media | 5 | M9 | Partial | Always |
| Sitemaps & Discovery | 5 | M17 | Full | Always |
| Freshness | 3 | M13 | Partial | Always |
| Social Cards | 2 | M8 | Partial | Always |
| E-commerce | 15 | M18 | Not built | Only when detected |
| Local | 10 | M19 | Not built | Only when detected |
| International | 8 | M20 | Full | Only 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.
| Category | Weight | Modules | Depth | Applies |
|---|---|---|---|---|
| Answer Extractability | 20 | M11 | Partial | Always |
| Structured Data | 16 | M5 | Partial | Always |
| Fact Density / Original Data | 14 | M12 | Partial | Always |
| AI Crawler Access | 12 | M14 | Partial | Always |
| Rendering (non-JS) | 10 | M4 | Partial | Always |
| Entity / Knowledge-Graph | 10 | M6 | Partial | Always |
| E-E-A-T / Authority | 9 | M16 | Partial | Always |
| Freshness | 6 | M13 | Partial | Always |
| Images / Multimodal | 3 | M9 | Partial | Always |
| llms.txt | 0 | M21 | Full | Reported, 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.
| Band | From | What it means |
|---|---|---|
| A | 90 | Your site is easy for Google and AI assistants to read; what remains is refinement. |
| B | 80 | Your site is easy to read, with a few gaps worth closing before your competitors do. |
| C | 70 | The basics work, but gaps are holding you back; a few well-chosen fixes move the score the most here. |
| D | 60 | The basics work only in part; several problems stop Google or AI assistants reading key pages. |
| F | 0 | Google and AI assistants struggle to read your site; answers about your line of business come from other businesses' pages. |
What does each severity mean?
| Severity | Meaning |
|---|---|
| 5 | The site cannot be indexed or reached at all. Nothing else on the report matters while it holds, and it caps the axis at 40. |
| 4 | A primary signal is broken or absent. Directly costs indexing, ranking or citability. |
| 3 | A real defect with measurable impact, but the page still works without it. |
| 2 | Hygiene. Worth fixing, cheap to fix, rarely decisive on its own. |
| 1 | Minor. 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.
| Module | Name | Depth | Not covered |
|---|---|---|---|
| M4 | Rendering / JS dependency | Partial | Raw 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. |
| M5 | Structured data | Partial | Type 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. |
| M6 | Entity / knowledge graph | Partial | The 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. |
| M8 | Social cards | Partial | Open Graph only. No twitter:card family. |
| M9 | Images / media | Partial | Alt coverage only. No alt quality, no modern-format check, no VideoObject. |
| M10 | Internal linking & semantics | Partial | Orphan detection only. No anchor-text grading, pillar/cluster mapping or click depth. |
| M11 | Answer extractability | Partial | Structure 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. |
| M12 | Fact density / original data | Partial | Counts 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. |
| M13 | Freshness | Partial | Presence 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. |
| M14 | AI crawler access | Partial | robots.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. |
| M15 | Core Web Vitals / performance | Partial | Crawl-only runs measure proxies: TTFB, compression, caching, image weight, script loading. Real field data needs --pagespeed, which raises this module to full (PS1-PS5). |
| M16 | E-E-A-T / authority | Partial | Presence 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. |
| M18 | E-commerce | Not built | No 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. |
| M19 | Local | Not built | No 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.
| ID | Module | Axis | Severity | Check |
|---|---|---|---|---|
| T1 | M3 | Search SEO | 4 | HTTPS redirect |
| T2 | M2 | Search SEO | 4 | non-canonical host folds to canonical |
| T3 | M2 | Search SEO | 3 | alt host redirects, not merely canonical |
| T4 | M2 | Search SEO | 3 | /index.html redirects to / |
| T5 | M17 | Search SEO | 3 | sitemap.xml reachable |
| T6 | M17 | Search SEO | 2 | sitemap URL count |
| T7 | M17 | Search SEO | 2 | sitemap lastmod |
| T8 | M1 | Search SEO | 3 | robots.txt present and references a sitemap |
| T10 | M3 | Search SEO | 2 | custom 404 |
| T11 | M2 | Search SEO | 4 | homepage canonical |
| T12 | M7 | Search SEO | 3 | meta viewport |
| T13 | M1 | Search SEO | 5 | robots.txt crawl access |
| C1 | M2 | Search SEO | 4 | homepage canonical exact |
| C2 | M2 | Search SEO | 3 | /index.html 301s |
| C3 | M2 | Search SEO | 3 | alt-host canonical when it serves 200 |
| C4 | M2 | Search SEO | 2 | no internal index.html links |
| C5 | M2 | Search SEO | 2 | no links to the non-canonical host |
| S1 | M5 | Both | 4 | JSON-LD on homepage |
| S2 | M5 | Both | 3 | homepage schema types |
| S3 | M5 | Both | 3 | JSON-LD on article |
| S4 | M5 | Both | 2 | article schema type |
| S5 | M2 | Search SEO | 5 | no page is noindex |
| S6 | M5 | Both | 4 | markup/body parity |
| O1 | M7 | Search SEO | 4 | title tag |
| O2 | M7 | Search SEO | 2 | meta description |
| O3 | M7 | Search SEO | 3 | single H1 |
| O4 | M7 | Search SEO | 2 | html lang |
| O5 | M8 | Search SEO | 2 | Open Graph tags |
| O6 | M8 | Search SEO | 2 | OG image reachable |
| O7 | M13 | Both | 2 | visible timestamp |
| O8 | M7 | Search SEO | 2 | article title lengths |
| O9 | M9 | Both | 3 | image alt coverage |
| O10 | M13 | Both | 3 | visible freshness |
| P2 | M15 | Search SEO | 2 | LCP fetchpriority |
| P3 | M15 | Search SEO | 2 | LCP preload |
| P4 | M15 | Search SEO | 3 | image dimensions (CLS) |
| P5 | M15 | Search SEO | 2 | script defer/async |
| P6 | M15 | Search SEO | 3 | large images |
| I1 | M17 | Search SEO | 1 | sitemap URL list |
| I2 | M3 | Search SEO | 4 | sitemap URLs return 200 |
| I3 | M10 | Search SEO | 3 | orphan detection |
| X1 | M7 | Search SEO | 3 | duplicate titles |
| X2 | M7 | Search SEO | 2 | duplicate meta descriptions |
| X3 | M2 | Search SEO | 4 | self-referential canonical |
| X4 | M3 | Search SEO | 4 | mixed content |
| HD1 | M3 | Search SEO | 2 | HSTS header |
| HD2 | M3 | Search SEO | 1 | X-Content-Type-Options |
| HD3 | M3 | Search SEO | 1 | frame protection |
| HD4 | M3 | Search SEO | 1 | Referrer-Policy |
| HD5 | M15 | Search SEO | 3 | compression |
| HD6 | M15 | Search SEO | 2 | Cache-Control |
| HD7 | M15 | Search SEO | 3 | TTFB |
| HD8 | M3 | Search SEO | 3 | redirect chain |
| HL1 | M20 | Search SEO | 3 | hreflang tags |
| HL2 | M20 | Search SEO | 3 | hreflang values valid |
| HL3 | M20 | Search SEO | 2 | self-referencing hreflang |
| HL4 | M20 | Search SEO | 2 | hreflang x-default |
| HL5 | M20 | Search SEO | 3 | hreflang reciprocity |
| HL6 | M20 | Search SEO | 3 | hreflang targets 200 |
| L1 | M21 | AI Visibility | 2 | llms.txt present |
| L2 | M21 | AI Visibility | 1 | llms.txt starts with H1 |
| L3 | M21 | AI Visibility | 2 | llms.txt links resolve |
| L4 | M21 | AI Visibility | 1 | llms.txt declared in head |
| R1 | M4 | Both | 4 | content without JS |
| R2 | M4 | Both | 3 | headings without JS |
| R3 | M4 | Both | 4 | links without JS |
| R4 | M4 | Both | 4 | client-side app shell |
| PS1 | M15 | Search SEO | 4 | mobile performance |
| PS2 | M15 | Search SEO | 4 | LCP (mobile) |
| PS3 | M15 | Search SEO | 3 | CLS (mobile) |
| PS4 | M15 | Search SEO | 3 | TBT (mobile) |
| PS5 | M15 | Search SEO | 2 | desktop performance |
| G4 | M2 | Search SEO | 5 | homepage indexed |
| B2 | M3 | Search SEO | 3 | Bing page alerts |
| B3 | M3 | Search SEO | 2 | Bing crawl stats |
| AI1 | M14 | AI Visibility | 5 | AI answer engines can reach the site |
| AI2 | M14 | AI Visibility | 4 | citation crawlers individually allowed |
| AI3 | M14 | AI Visibility | 3 | user-triggered fetchers allowed |
| AB1 | M11 | AI Visibility | 4 | question headings answered directly |
| AB2 | M11 | AI Visibility | 3 | extractable structures present |
| AB3 | M11 | AI Visibility | 3 | content is segmented into passages |
| AB4 | M11 | AI Visibility | 2 | sections open self-contained |
| FD1 | M12 | AI Visibility | 4 | statistic density |
| FD2 | M12 | AI Visibility | 3 | outbound citations to third-party sources |
| FD3 | M12 | AI Visibility | 2 | claims supported by figures |
| EN1 | M6 | AI Visibility | 4 | primary entity declared |
| EN2 | M6 | AI Visibility | 4 | identity links (sameAs) |
| EN3 | M6 | AI Visibility | 2 | the entity is named consistently |
| EE1 | M16 | Both | 3 | content carries an author |
| EE2 | M16 | Both | 3 | the business is identifiable |
| EE3 | M16 | Both | 2 | published policies |
| EE4 | M16 | Both | 3 | a real contact route |
| PP1 | M7 | Search SEO | 3 | inner-page H1 |
| PP2 | M7 | Search SEO | 3 | inner-page titles |
| PP3 | M7 | Search SEO | 2 | inner-page meta |
| PP4 | M2 | Search SEO | 4 | inner-page canonical |
| PP5 | M5 | Both | 3 | inner-page schema |
| PP6 | M9 | Both | 3 | inner-page image alt |
| PP8 | M2 | Search SEO | 4 | noindex pages |
| PP9 | M2 | Search SEO | 3 | duplicate content |
| OF1 | M11 | AI Visibility | 3 | prices in an extractable structure |
| OF2 | M5 | AI Visibility | 3 | offer markup present |
| OF3 | M5 | AI Visibility | 3 | offer markup matches the page |
| L5 | M21 | AI Visibility | 2 | llms.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).
| ID | Why it is not scored |
|---|---|
| T9 | ads.txt — only meaningful if the site sells ad inventory. Absence is not a defect. |
| P1 | GA4 tag — analytics choice, not an SEO property. Plenty of good sites use something else or nothing. |
| A1 | AdSense snippet — monetisation, not visibility. |
| A2 | AdSense pub-ID match — monetisation, not visibility. |
| I4 | cross-site partner links — our own inter-property linking convention, driven by partnerDomains config. Meaningless for a client. |
| IN1 | IndexNow 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. |
| G1 | GSC property access — a diagnostic of our own connection, not of the site. |
| G2 | GSC search performance — data for the narrative, not a verdict. |
| G3 | GSC top queries — data for the narrative, not a verdict. |
| B1 | Bing access — a diagnostic of our own connection. |
| B4 | Bing performance — data, not a verdict. |
| B5 | Bing top queries — data, not a verdict. |
| B6 | Bing top pages — data, not a verdict. |
| AI4 | training-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. |
| PP7 | thin content — reported under M12 as context. Word count is not fact density; scoring it there would overstate what we measure. |
| O11 | Visible 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.