# The error taxonomy

Every classification is constrained to a fixed set of category keys at the
schema level — a model response naming a category outside the set is treated
as a failed classification, not silently coerced into an existing bucket or
added as a new one. That constraint is the whole point: it's what keeps the
`/english` report's categories stable and comparable month over month.

The set comes from `DEFAULT_TAXONOMY` in `src/taxonomy.ts` unless you supply
an override.

## The built-in categories

| Key | Label | In the ranked report |
| --- | --- | --- |
| `subject_verb_agreement` | Subject-verb agreement | yes |
| `article_determiner` | Articles and determiners | yes |
| `preposition` | Prepositions | yes |
| `verb_tense` | Verb tense | yes |
| `verb_form` | Verb form | yes |
| `noun_number` | Noun number | yes |
| `word_form` | Word form | yes |
| `word_choice` | Word choice | yes |
| `word_order` | Word order | yes |
| `pronoun_reference` | Pronoun reference | yes |
| `sentence_structure` | Sentence structure | yes |
| `punctuation` | Punctuation | yes |
| `capitalization` | Capitalization | yes |
| `spelling` | Spelling | no — logged, not ranked |

They are coarse and deliberately non-overlapping. An error that could
plausibly land in two categories splits its own count across both, which
destroys the aggregation the enum exists to produce — so "articles" is one
bucket rather than three, and `word_form` (wrong part of speech: *succeed* vs
*success*) is kept distinct from `word_choice` (right form, wrong word).

`spelling` is non-reportable: mechanical typos are worth logging for
completeness but not worth ranking, because they crowd out the patterns you
can actually do something about.

## Overriding them

Write `~/.pi/english-tutor/taxonomy.json` as a JSON array of
`{ key, label, reportable }` objects:

```json
[
  { "key": "article_determiner", "label": "Articles", "reportable": true },
  { "key": "preposition", "label": "Prepositions", "reportable": true },
  { "key": "spelling", "label": "Spelling", "reportable": false }
]
```

The file **replaces** the default outright — it is not merged with it. Only
the keys in your file can be assigned, so this is how you prune the list down
to the two or three patterns you're actively working on, or add a category the
default doesn't cover.

Resolution is fail-soft in one direction only: an absent, unreadable,
unparseable, non-array, or all-entries-invalid file falls back to the built-in
default. `loadTaxonomy()` never returns an empty list, so there is no state in
which the extension is installed but silently classifying nothing.

## Changing keys mid-log

Category keys are what the log records store, and nothing rewrites past
records. Renaming a key starts a new bucket: the old key's history stays in
the log but no longer matches any active category, so it drops out of the
report, and the new key's counts begin at zero with no prior month to compare
against. Changing only the `label` is free — that's display text, resolved at
report time.
