diff --git a/docs/AI_USAGE.md b/docs/AI_USAGE.md new file mode 100644 index 0000000..7078729 --- /dev/null +++ b/docs/AI_USAGE.md @@ -0,0 +1,8 @@ +## Data model (`docs/DATA_MODEL.md`) + +- **Tool:** Claude (web) +- **What the team did:** Designed the data model on paper, then discussed it + with Claude and revised it based on that discussion. +- **What Claude did:** Wrote the Mermaid code for the ER diagram and drafted + the descriptions, based on our conversation. +- **Review:** The team reviewed the result and edited it. diff --git a/docs/DATA_MODEL.md b/docs/DATA_MODEL.md new file mode 100644 index 0000000..4725459 --- /dev/null +++ b/docs/DATA_MODEL.md @@ -0,0 +1,196 @@ +# Data model + +This document describes the data model for the Code of Points catalog: what each entity holds, how the entities relate to each other, the constraints we enforce, and the reasoning behind the main design choices. + +The catalog data (apparatus, element groups, elements, vaults and variant groups) is extracted from the FIG Women's Artistic Gymnastics Code of Points 2025–2028 and is read-only in the app. The user data (coaches, gymnasts and routines) is created by users through the website. + +## Diagram + +```mermaid +erDiagram + APPARATUS ||--o{ ELEMENT_GROUP : has + ELEMENT_GROUP ||--o{ ELEMENT : contains + ELEMENT_GROUP ||--o{ VAULT : contains + VARIANT_GROUP |o--o{ ELEMENT : "has variants" + VARIANT_GROUP |o--o{ VAULT : "has variants" + COACH ||--o{ GYMNAST : coaches + GYMNAST ||--o{ ROUTINE : has + APPARATUS ||--o{ ROUTINE : "performed on" + ROUTINE }o--o{ ELEMENT : includes + ROUTINE }o--o{ VAULT : includes + + APPARATUS { string id string name } + ELEMENT_GROUP { string id string apparatusId int number string name } + ELEMENT { string id string elementGroupId string code float value string description string name string image } + VAULT { string id string elementGroupId string code float dValue string description string name string image } + VARIANT_GROUP { string id string_array variantIds } + COACH { string id string googleId string email string name datetime createdAt } + GYMNAST { string id string coachId string name } + ROUTINE { string id string gymnastId string name string apparatusId string_array elementIds } +``` + +## Reading the diagram + +The model has two parts. + +**The catalog** Each of the four apparatus (vault, uneven bars, balance beam, floor) is divided into element groups, matching the groups in the Code of Points, for example "1.000 Mounts" on uneven bars. Each group contains either elements (uneven bars, beam, floor) or vaults. Some elements and vaults can be performed in more than one way. These variants are linked together by a variant group. + +**The user data** A coach logs in and creates gymnasts. Each gymnast can have several routines on each apparatus, so the coach can compare different versions and their D-scores. A routine is an ordered list of ids: elements on uneven bars, beam and floor, and a single vault on vault. + +## Entities + +### Apparatus + +The four apparatus in women's artistic gymnastics. + +| Field | Example | Description | +| --- | --- | --- | +| `id` | `"UB"` | `VT`, `UB`, `BB` or `FX` | +| `name` | `"Uneven bars"` | Name as used in the Code of Points | + +### ElementGroup + +A group of elements or vaults on one apparatus, as listed in the Code of Points. + +| Field | Example | Description | +| --- | --- | --- | +| `id` | `"UB-1"` | Apparatus + group number | +| `apparatusId` | `"UB"` | The apparatus the group belongs to | +| `number` | `1` | Group number in the Code of Points | +| `name` | `"Mounts"` | Group name from the Code of Points | + +### Element + +One element on uneven bars, balance beam or floor. + +| Field | Example | Description | +| --- | --- | --- | +| `id` | `"UB-1.101-a"` | Apparatus + code + variant letter | +| `elementGroupId` | `"UB-1"` | The group the element belongs to | +| `code` | `"1.101"` | Element number in the Code of Points | +| `value` | `0.1` | Difficulty value (0.1–1.0) | +| `description` | `"Glide kip to support on LB"` | Description from the Code of Points | +| `name` | `"Biles"` or `null` | The gymnast the element is named after, if any | +| `image` | `"UB-1.101-a.webp"` | Drawing of the element | + +### Vault + +One vault from the vault table. + +| Field | Example | Description | +| --- | --- | --- | +| `id` | `"VT-1.22-a"` | `VT` + code + variant letter | +| `elementGroupId` | `"VT-1"` | The vault group the vault belongs to | +| `code` | `"1.22"` | Vault number in the Code of Points | +| `dValue` | `2.6` | Difficulty score for the vault | +| `description` | `"..."` | Description from the Code of Points | +| `name` | `"Kim"` or `null` | The gymnast the vault is named after, if any | +| `image` | `"VT-1.22-a.webp"` | Drawing of the vault | + +### VariantGroup + +Links elements or vaults that are different ways of performing the same element. Only elements and vaults with more than one variant belong to a variant group. + +| Field | Example | Description | +| --- | --- | --- | +| `id` | `"UB-1.101"` | Apparatus + code. An id starting with `VT-` holds vaults, all others hold elements | +| `variantIds` | `["UB-1.101-a", "UB-1.101-b"]` | The elements or vaults in the group | + +### Coach + +A user of the website. Coaches log in with Google. + +| Field | Example | Description | +| --- | --- | --- | +| `id` | | Internal id | +| `googleId` | `"109876543210…"` | Google's unique user id (the `sub` claim in the Google token) | +| `email` | `"kari.nilsen@gmail.com"` | Email from the Google account | +| `name` | `"Kari Nilsen"` | Name from the Google account | +| `createdAt` | `"2026-10-01T12:00:00Z"` | When the coach first logged in | + +### Gymnast + +A gymnast created by a coach. + +| Field | Example | Description | +| --- | --- | --- | +| `id` | | Internal id | +| `coachId` | | The coach who created the gymnast | +| `name` | `"Ingrid Haugen"` | Name of the gymnast | + +### Routine + +A gymnast's routine on one apparatus. A gymnast can have several routines on the same apparatus. + +| Field | Example | Description | +| --- | --- | --- | +| `id` | | Internal id | +| `gymnastId` | | The gymnast the routine belongs to | +| `name` | `"Competition routine"` | Name chosen by the coach, to tell routines apart | +| `apparatusId` | `"UB"` | The apparatus the routine is performed on | +| `elementIds` | `["UB-1.101-a", "UB-3.304-a"]` or `["VT-1.22-a"]` | The elements in the routine, in order. On vault: exactly one vault | +## Constraints + +### Catalog data + +These are checked by the extraction script when the data is generated. + +1. Every variant group has at least two ids, and every id exists. +2. All ids in a variant group are of the same type: only elements or only vaults. +3. An element or vault belongs to at most one variant group. +4. Variants in the same group have the same `code` and the same value (`value` or `dValue`). +5. Every element and vault has an image. Any that are missing are flagged for manual review. + +### User data + +These are enforced by the backend. + +6. A gymnast can have any number of routines on each apparatus. Routine names should be unique per gymnast and apparatus, so the coach can tell them apart. +7. A routine on vault has exactly one id in `elementIds`, and it is a vault. A routine on any other apparatus contains only elements. +8. All elements in a routine must belong to the same apparatus as the routine. +9. The catalog is open to everyone. Gymnasts and routines can only be read and changed by the coach they belong to. + +### Calculated values + +10. The difficulty letter (A–J) is not stored. It is calculated from `value`: 0.1 = A, 0.2 = B … 1.0 = J. +11. The D-score of a routine is calculated, not stored. It is the sum of the eight highest values in the routine (`value` for elements, `dValue` for vaults), where each `code` is counted only once. Two variants of the same element therefore only count once, as in the Code of Points. On vault the routine holds one vault, so the D-score is that vault's `dValue`. Composition requirements and connection values are not included yet. + +## Design decisions + +### Vault is its own entity + +Vaults are stored separately from elements because they work differently in the Code of Points. An element on bars, beam or floor has a difficulty letter (A–J) with a value from 0.1 to 1.0, and a routine is made up of many elements whose values are added together. A vault has no difficulty letter. Instead it has a fixed difficulty score (D-value, for example 2.6), and the gymnast performs one vault, which is the whole routine. + +If vaults were stored as elements, they would have a difficulty letter that is always empty. With vault as its own entity, the catalog describes each vault by its D-value, as the Code of Points does. + +In a routine, a vault is treated the same way as an element: it is an id in the routine's `elementIds`. A routine on vault holds exactly one vault. This keeps one rule for the D-score on every apparatus (the sum of the eight highest values), and with a single vault in the list, that sum is simply the vault's `dValue`. Vault ids always start with `VT-`, so the backend can tell a vault from an element by its id. + +### Variants are linked through a variant group + +Some cells in the Code of Points show two or three ways of performing the same element, each with its own drawing and description, but with the same code and the same value. We store each variant as its own element (`UB-1.101-a`, `UB-1.101-b`) so that each one has its own image and description and can be searched and added to a routine on its own. + +The variants are linked through a variant group that holds a list of their ids. Adding or removing a variant only changes the variant group, not the other elements. Because variants share the same code, the D-score counts each code only once (constraint 11). + +We use one list (`variantIds`) instead of separate lists for elements and vaults, because a variant group never contains both. The type is given by the id: vault ids always start with `VT-`. + +### Google login and `googleId` + +Coaches log in with their Google account, so we do not store usernames or passwords. When a coach logs in, the frontend receives a token from Google and sends it to the backend. The backend verifies the token with Google and reads the user's unique id (`sub`) from it. That id is stored as `googleId` and is used to find the coach on later logins, or to create a new coach on the first login. + +We store `googleId` and not the token itself. The token expires after a short time and is only used to prove who the user is when they log in. The `googleId` never changes, even if the user changes their name or email. + +### A gymnast can have several routines per apparatus + +A coach often tries out different versions of a routine, for example with or without a new element, to see how much each version is worth. A gymnast can therefore have any number of routines on the same apparatus, each with its own name. Because the D-score is calculated for each routine, the versions can be compared side by side. + +### Elements in a routine are a list of ids + +A routine stores its elements as an ordered list of element ids. The order in the list is the order in the routine, so no separate position field is needed. If we later need more information per element in a routine (for example connections between elements or coach notes), the list can be changed to a list of objects without affecting the rest of the model. + +### The D-score is calculated, not stored + +The app is a planning tool: coaches put together routines and see what they are worth. The D-score can always be calculated from the elements in the routine, so it is not stored. A stored copy would go out of date every time a coach changes the routine. It is calculated in the GraphQL resolver when it is requested. Scores from actual competitions are outside the scope of the app. + +### The difficulty letter is calculated + +The difficulty letter (A–J) and the value (0.1–1.0) always match one to one, so we only store `value`. The letter is calculated when it is shown, and filtering by difficulty is done on `value`.