Scoring
How the 0–100 score is computed — formula, severity weights, and rank-damped severity summing (score model 2).
The formula
The score is a function of the deduplicated, non-ignored issue set only. File count never appears in it, and no single criterion is capped:
weights_by_criterion = severity_weight of each issue, grouped by WCAG criterion
(unmapped issues are grouped per rule, key `_<scanner>:<rule_id>`)
penalty = Σ over each criterion of Σ w(i) / i
— weights sorted descending, i = 1-indexed rank
score = max(0, round(100 × exp(-k × penalty) × 100) / 100)With k = 0.01. The curve is asymptotic — no scan ever scores exactly 0 — and the result carries
two decimal places: small fixes inside a heavily repeated criterion move the number by
fractions of a point, and integer rounding would swallow them.
Severity weights
| Severity | Weight |
|---|---|
| critical | 10 |
| serious | 5 |
| moderate | 2 |
| minor | 1 |
Critical issues cost 10× more than minor ones. Two critical issues outweigh twenty minor ones.
Rank-damped severity summing
Within a criterion, weights are sorted heaviest-first and each is divided by its rank
(w₁/1 + w₂/2 + w₃/3 + …). Repetition saturates smoothly instead of hitting a hard cap — a 30th
identical failure on the same criterion weighs little, but it's never zero, and every fix is
credited at its own severity rather than the group's maximum. Going from 30 missing alt
attributes to 5 now strictly raises the score; under the previous model, capped per-criterion
penalties made that fix invisible.
File count is not an input at all. Adding clean files, or scanning a single in-memory buffer instead of a full repo, cannot move the score in either direction — the previous density scaling (dividing the penalty by a log of files scanned) let padding a repo with inert files raise the score, and structurally punished single-buffer scans.
Score model versioning
Every ScanResult (and --json output) carries score_model, a version stamp bumped only when
the formula or its input semantics change. This document describes model 2. Scores from
different score_model values are not comparable — re-scan both sides of any before/after
comparison with the same CLI version. The score is a trend indicator (is this codebase's
accessibility getting better or worse across scans of the same model?), not a conformance grade —
the conformance level below is the absolute, WCAG-aligned verdict.
Criteria beyond your target level
Only criteria at or below your target level count toward the score. A Level AAA
finding (for example 3.1.5 Reading Level) under the default AA target does not
penalize the score — AAA is not part of AA conformance. These beyond-target findings
are shown in a separate Advisory section of the output rather than under "WCAG
Violations", and they never appear in the critical/serious conformance counts. Raise
--level AAA if you want them scored.
Conformance level
Separately from the score, Equall computes a WCAG conformance level: None, Partial A, A, AA, or AAA. The rule:
- Any Level A violation →
Partial A - Zero Level A, any Level AA violation →
A - Zero Level A and AA, any Level AAA →
AA - All clean → the target level, provided the scan actually exercised at least one criterion — otherwise
None
"Exercised" here means summary.criteria_tested (the coverage-derived, genuinely-tested set), not "an issue was found there" — so a clean scan with real coverage reports its target level, not None. None is now reserved for scans where nothing was genuinely tested at all (e.g. no scannable files matched).
Best-practice issues (no WCAG mapping) never affect the conformance level — they only appear in the Best-Practice Recommendations section of the output.
Verifying page-level rules
Landmark, title and language rules can't be judged from a single component. Scan your built output (dist/) to verify them on the real, composed page.
Which standard?
WCAG 2.1 AA is the public-sector legal bar (WAD / EN 301 549); WCAG 2.2 is the latest. Pick the conformance view with --standard — it never changes the score.