# How the health score works

Every crawl produces a score out of 100 and a letter grade. This document explains how it is
computed and, more importantly, what it is and is not claiming.

The research and citations behind the current model are in
[scoring-recalibration.md](scoring-recalibration.md).

---

## The grade bands

| Score | Grade | Verdict |
|---|---|---|
| 90–100 | A | Excellent |
| 80–89 | B | Good |
| 70–79 | C | Needs Work |
| 60–69 | D | Poor |
| below 60 | F | Critical |

The verdict word is composition-aware: a low score with no critical findings is not described as
"Critical", because calling a site critical when nothing critical was found misrepresents it.

**Calibration target:** a competently built site with the ordinary imperfections every real site
has — some long meta descriptions, imperfect heading order, a few redirects — should land in the
**high B** range, not be taxed down into a C or D. A site scoring in the 50s should have something
genuinely wrong with it.

---

## The model: pages, not findings

The score is built on a **page-defect backbone** rather than a count of issues. This is the single
most important thing to understand about it, because it is what makes the number stable.

Counting findings does not work. A 5,000-page site with one templated flaw generates 5,000
findings and scores zero; a 10-page site with three catastrophic problems generates 3 and scores
well. Both answers are wrong. So each check is classified by **what it does to the page**:

| Impact | Meaning | Weight |
|---|---|---|
| `PageBroken` | Google drops or refuses to index the page | 100 |
| `PageDefective` | The page is materially defective for ranking | 55 |
| `SiteHigh` | A real site- or link-level fault, but the page is not broken | 12 (capped at 24) |
| `SiteMedium` | Secondary technical problems | 4 (capped at 14) |
| `Minor` | Real but small | 1 (capped at 12) |
| `Unscored` | Reported, never scored | 0 |

A broken page costs its full share of the site. That matches the one published industry formula —
Ahrefs' health score tracks the share of pages without errors — so a site where 20% of pages 404
loses roughly 20 points from that term alone, which is the intuition most SEOs already have.

### Why the caps exist

Every real site has some long meta descriptions and some imperfect heading order. Without a
collective cap, those near-universal findings accumulate until a well-built site scores like a
broken one. Capping each tier's total contribution is what keeps the minor stuff visible in the
report without letting it dominate the number.

### Proportion, with two guards

Page-level deductions are proportional to the share of the site's pages affected, counting each
page once however many times a check names it — 15 pages missing a description means something
different on a 15-page site than on a 1,000-page one. Two guards keep that honest at the extremes:

- A **coverage floor** (0.12) so a single instance still registers on a large site rather than
  rounding to nothing.
- A **minimum denominator** (5 pages) so one bad page on a three-page crawl is not scored as "100%
  of the site is broken".

---

## What is deliberately not scored

**Security headers do not affect the score.** Missing HSTS or CSP is a real finding and Raptor
reports it, but it is a security concern, not a ranking factor, and letting it move an SEO score
would misrepresent both.

**AI-crawler blocking does not affect the score.** Blocking GPTBot or setting `noai` is a business
decision. Raptor reports it without an opinion.

**Findings on non-indexable pages are mostly gated off.** A page excluded from the index cannot
cause a duplicate-title problem. The duplicate checks also leave out pages canonicalised to another
URL, so a filter or tracking variant that names its base page as canonical is not scored as a
duplicate of that page.

**Deliberate states are Notes (scoring model 3).** These nine checks are still reported, but they
never cost points, because they usually describe a choice the owner made on purpose or a part of
the site's template:

- `directives.noindex`, `directives.nofollow`, `directives.none`, `directives.nosnippet` and
  `directives.noimageindex`: pages that ask search engines not to index them, follow their links,
  show a snippet or index their images;
- `canonicals.canonicalised`: pages whose canonical names another page;
- `canonicals.canonical_mismatch`: pages whose canonical is declared differently in the HTML and the
  HTTP header;
- `response_codes.internal_redirect`: internal links that pass through one redirect;
- `headings.h2_duplicate`: pages that repeat an H2 used on another page. The repeats are often the
  template's own section headings, such as "Frequently asked questions", so change one only if it
  is meant to be unique to the page.

Under scoring model 2 they cost points as Minor findings, so a site with three intentional
`noindex` pages was told to undo them. Crawls stored before model 3 keep their stored score.

---

## Priority and ranking

Every finding has a **priority**, which comes from its impact:

| Impact | Priority |
|---|---|
| `PageBroken` | Fix now |
| `PageDefective`, `SiteHigh` | High |
| `SiteMedium` | Medium |
| `Minor` | Low |
| `Unscored` | Note |

The report, the terminal and MCP all list findings in one order:

Findings are ranked by priority first: every Fix now finding comes before every High one, then Medium, then Low, with Notes last. Within a priority, the finding that would win back more health points if fixed comes first, where points are measured by scoring the crawl again without that finding. Ties go to the finding that reaches more pages, then to the order of Raptor's check catalogue.

**Points are what a finding costs.** A finding's points are the deduction it adds: the crawl is
scored again without it, before the score is limited to the range 0 to 100. When deductions pass
100 the score stops at 0, so fixing one finding alone may not lift it; the report says so. A
finding in a capped tier can cost nothing on its own while the tier is over its cap; the report
shows it as part of that capped tier rather than as "+0.0". Points are shown only when the current
scoring model explains the stored score.

---

## When the score is withheld

A crawl that reached no pages, or was blocked, does not receive a grade. Raptor refuses to score
rather than invent a number from an empty crawl — a 0-page crawl rendering a confident all-green
chart was a real defect, and the fix was to say "unscorable" instead.

A crawl that saw nothing readable is withheld too, with the verdict "No readable content — crawl
blocked or client-rendered". That happens when nearly every page is thin and either has no links
or is byte-identical to other pages. Byte-identical is measured on the pages themselves, whatever
their canonicals say: an app shell whose every route names `/` as its canonical reports no
duplicates, but it is still one empty page served everywhere, and it is not graded.

---

## How to read it

The score is a **triage signal for one site over time**, not a benchmark between sites. It answers
"is this site healthier than it was last month, and where should I look first?" It does not answer
"is this site better than a competitor", because it has no access to backlinks, rankings, traffic,
intent or content quality — the things that actually decide competitive outcomes.

Treat the category breakdown as the real output and the number as the headline. Two sites can
score 83 for entirely different reasons, and the reasons are what you act on.

---

## Counting units

The crawl summary counts problems in three different units. They answer different questions, so
read the one that matches yours:

- `summary.total_issues`, `critical_issues`, `warning_issues` and `info_issues` count URL rows. A
  site-wide finding counts 1, and each URL appears once per check.
- `findings_count` counts the checks that fired, and `findings_by_priority` splits that count by
  priority.
- `pages_with_findings` counts distinct crawled pages, after following redirects.
- In MCP, `raptor_query`'s `affected_page_count` counts the distinct URLs a finding lists, as
  written. The report model's page count follows redirects to the crawled page, so two URLs that
  are one page count once.
- `scoring_model` is the analysis-and-scoring model version. It is 3 from this release (model 2
  scored deliberate states as Minor findings); crawls without the field were model 1. 0 means the
  summary was never scored.
- `ranking_model` is the version of the ranking rule above. 0 means the crawl was never ranked.
