Skip to main content

ADR-0010 — Grades stored as published, converted approximately at read time

Date2026-08-18
Supersedes
Superseded by

Context

docs/wiki/grading-systems.mdx previously carried an absolute position: we never convert between grading systems for display, not even as small grey text, not even with an "≈". The reasoning was that conversions are not real, that the systems measure different things, and that showing a converted grade to somebody about to commit to a route is a safety problem.

Two of those three arguments survive. The third does not, and the absolute position had a cost nobody had priced: a British climber on their first trip to Spain, or an American reading a Fontainebleau circuit, is going to convert the grade anyway — in their head, badly, from memory, or from a table on a forum. Refusing to help does not prevent the conversion. It just means it happens somewhere we cannot label.

The honest position is not "never convert". It is "convert, and be relentlessly clear about what the conversion is worth".

Decision

Every grade is stored and displayed in the system it was published in. That does not change.

A rough conversion may be shown alongside it, computed at read time, never stored. It is always marked as approximate, always secondary to the published grade, and there are conversions we refuse to perform at all.

Concretely:

Storage is unchanged. Route.gradeSystem and Route.grade hold what the guidebook said. No converted value is ever written to a column, synced, indexed, or cached. Conversion is a function of the published grade and the viewer's preference, evaluated when a screen renders it — so when the mapping improves, every screen improves, and there is no stale converted grade sitting in a database being mistaken for a published one.

British trad grades always carry both parts. E2 5c is an adjectival grade and a technical grade, and a UK trad grade recorded without its technical part is incomplete data, not merely terse. This is not a display preference — it is the thing that makes conversion possible at all, because the technical grade is the only half with anything to convert to. E1 5a and E1 5c are the same adjectival grade and completely different climbs.

Conversions we refuse. These return nothing rather than a guess:

RefusedBecause
Boulder problem ↔ roped routeDifferent activities. A 7A problem and a 7a route share nothing but characters
Anything → a UK adjectival gradeThe adjectival grade encodes seriousness and protection. No other system measures it, so there is no source to convert from
Free ↔ aid, and free ↔ ice or mixedDifferent activities again
An unparseable gradenormaliseGrade already returns null rather than 0, and a conversion inherits that

How a conversion is presented. Marked approximate, never as an equality. Shown as a range where the mapping genuinely spans more than one grade, because pretending to a precision the consensus does not have is the failure this ADR is trying to avoid. The published grade is always present and always primary. A conversion is opt-in through User.preferredGradeSystems, and tapping one explains what it dropped.

gradeBand is still not a conversion, and is still never displayed. It is a lossy 0–100 integer that exists so mixed-system filtering can be expressed in SQL, and for UK trad it derives from the adjectival grade because seriousness is what a "can I get on this" filter should reflect. Display-side conversion goes through the technical grade instead. These are two different normalisations serving two different jobs, and conflating them is the mistake to watch for.

Consequences

What gets harder:

  • Two normalisation paths to keep straight. normaliseGrade() for filtering, derived from the adjectival grade; a separate conversion for display, derived from the technical grade. Anyone who "simplifies" these into one function will silently break either filtering or display, and the breakage will look plausible.
  • Conversion tables are opinions, and they need an owner. Every table on the internet disagrees with every other one. Ours has to be a deliberate choice with a written rationale, revisited, and attributed — not copied from the first search result.
  • The UI has to carry uncertainty without becoming noisy. A range and an approximation marker on every converted grade is more visual weight than a number, and the temptation to trim it down to something tidy will be constant. Trimming it is how the safety argument comes back.
  • UK trad data entry gets stricter. Requiring a technical grade means contributed routes can be rejected for a missing one, and any existing or imported record without one has to be handled rather than defaulted.

What has to be built: conversion tables per system pair, a display component that renders an approximation honestly, validation that a uk_trad grade parses into both parts, and a rule for what a route shows when its technical grade is genuinely unknown.

How we would reverse it: trivially. Nothing is persisted, so removing conversion is deleting a render path.

Alternatives considered

Never convert, as before

The previous position. Rejected because it does not prevent conversion — it only prevents labelled conversion. A climber holding a French guidebook and a UK logbook will do the arithmetic regardless, and they will do it worse than we would, with no indication of the error bars.

Convert and store the result

Compute on write, store a converted grade per system, serve it directly. Rejected outright. A stored converted grade is indistinguishable from a published one three tables later, it syncs to devices, and it goes stale the moment the mapping is revised. Everything this ADR protects depends on conversion having no persistence.

Convert everything, including boulder to route

Some tables do offer Font-to-French mappings. Rejected because they are comparing the difficulty of a single move to the difficulty of forty metres of climbing, and the number that results is not wrong so much as meaningless.

Open questions

  • The exact storage shape for UK trad. The published string ("E2 5c") is what displays, but conversion and validation need the two parts separately. Parsed on read, or stored as separate adjectival and technical columns with the string derived? This is a schema question and it sits with the multi-pitch and alpine data model, which is asking a very similar question about grade sets.
  • Which conversion table. Whose consensus, and how the disagreement is represented in the range we show.
  • What a route with no technical grade displays. Imported and legacy data will have this.
  • Whether filtering should also offer a technical-grade axis, since "E1 but only 5a" and "E1 and 5c" are the two searches people actually run.