Write a seed script that loads `data/*.json` into the database. It must be safe to run more than once, and it fills the two search tables, `CatalogEntry` and `CatalogWord`, described in "Search without raw SQL" in `docs/API.md`. **Done when:** - [ ] The seed upserts the catalog (apparatus, element groups, variant groups, elements and vaults) and never touches user data - [ ] The files in `data/` still have the old shape (variant groups as `variantIds` lists, no `variantGroupId`, no `removedAt`, no apparatus `position`), so the seed converts them: apparatus get their `position` (VT 1, UB 2, BB 3, FX 4), and elements and vaults get their `variantGroupId` from `variantGroups.json` - [ ] Elements and vaults missing from the data get `removedAt` set, and removed ones no longer in use are deleted. An element that comes back gets `removedAt` cleared - [ ] `CatalogEntry` gets one row per code, from the first variant that is not removed (normally `-a`), with `type`, `difficulty` (`value` or `dValue`), `hasName`, `nameSort` (lowercase name, or empty) and `codeSort`. A code where every variant is removed is left out - [ ] `CatalogWord` gets the distinct words of the code, name and description of **all** variants of each code, in lowercase and with accents removed. A code like `3.304` is kept as one word - [ ] Abbreviations from the Code of Points are added written out as words (for example `handstand` for `hstd`), from one list in the seed script - [ ] The abbreviation list covers the abbreviations used in the descriptions (522 of 749 contain at least one, such as `LB`, `HB`, `hstd`, `fwd`, `bwd`, `swd`), and a test checks that every abbreviation in the data is either in the list or deliberately left out - [ ] The search tables are rebuilt from the catalog in the same transaction on every run. They have no foreign keys to the catalog, so this is what keeps them in step with it - [ ] The data is checked against the catalog constraints in `DATA_MODEL.md` (1–5) before anything is written. If a check fails, the seed stops with a clear message and changes nothing - [ ] Everything is written in one transaction - [ ] The seed runs with `npm run db:seed` from the root - [ ] The README explains how to run it, and when to run it again