Source file: docs/schema.md · rendered from the repository
MachineConvert – data schema (v0.1)
Machine-readable versions: types/gym-atlas.ts (TypeScript) and schema/gym-atlas.schema.json (JSON Schema 2020-12).
Seed data: data/brands.json, data/lines.json, data/machines.json, data/measurements.json (empty – no real data yet).
Non-negotiable rules
- Every number has a
confidenceand asource. No source → the field isnull. confidenceenum:measured– dynamometer / force-gauge reading.source.proof_media(photo/video) is required (enforced in the JSON Schema).manufacturer– copied from an official page / spec sheet.source.quotestores the verbatim text; values are stored as stated (lb and kg both kept, no conversion), because brands round/convert inconsistently (e.g. Hammer Strength "295 lb (138 kg)", MTS Row "2 x 150 lb (68 kg)").estimated– lever/cam/pulley ratio calculation or aggregated user reports; always labeled as such in the UI.
- Manufacturer stack specs are not equivalence data. A 100 kg stack on two brands does not feel the same (cams, levers, pulleys, friction). Only
measured/estimatedMeasurements feed the comparator. - Community data enters as
moderation.status = pending; onlyapproveddata is used.
Entities
| Entity | Purpose | Key fields |
|---|---|---|
| Brand | Manufacturer | id, name, country (ISO-2), country_source (URL or "unverified"), website, parent, catalog_status (research log) |
| Line | Product series (e.g. Hammer Strength Select, Technogym Artis) | id = brand:line, brand_id, name, source_url (official line page) |
| Machine | One model | brand_id, line_id, name, model_code, movement (enum), muscle_groups[] (enum keys), load_type (selectorized / plate_loaded / electronic …), load_spec, source |
| LoadSpec | What the manufacturer publishes about load | stacks[] (label, stated[] quantities, stack_count 1/2 for per-limb stacks, quote, confidence, source), increment, starting_resistance (plate-loaded unloaded resistance), max_load |
| Measurement | A setting → resistance data point | machine_id, setting {kind stack_pin/plates_added, value, unit}, position {rom_point start/mid/end/peak/average, seat, lever}, value (N or kg-f at the contact point), confidence, method, source {type, url, proof_media[], submitter, date}, moderation {status pending/approved/rejected, reviewer, reason}, example flag |
| EquivalenceDerivation | Computed on the fly, never stored as truth | from {machine, setting}, to {machine, setting or null}, via_resistance, inputs[] (measurement ids), confidence = weakest input, warnings[] |
Equivalence derivation
- For machine A, take approved measurements at the same
rom_point; sort by setting; linearly interpolate setting → resistance. Same for machine B. R = f_A(x); answery = f_B⁻¹(R).- If either machine has < 2 approved points, or
x/Rlies outside the measured range →to.setting = null→ UI shows "No data yet" (extrapolation is refused, not guessed). - Result confidence = weakest input (
measured>estimated). Manufacturer specs are never inputs.
i18n
- Entities store keys (
movement: "chest_press",muscle_groups: ["quads"],confidence,load_type); labels live insite/i18n/en.json(andfr.json, keys present, values to translate). - Proper nouns (brand/model names) are not translated. Free text (notes) can carry
I18nText.valuesper locale later. - All UI strings come from the i18n file; no hard-coded English in
app.jsexcept dev fallbacks.
Data provenance (seed)
Every machine row's source.url is the official manufacturer page it was read from (retrieved 2026-10-17). Raw scrapes and the normalizer are in research/ so every value can be re-derived (research/normalize.py).
Mechanical models (added 2026-10-17)
data/models.json holds MechanicalModel entries (see types/gym-atlas.ts). Each input is a ModelInput with status:
sourced (URL + verbatim quote required), to_measure (value null, with how_to_measure), or setting (the pin / plates the user picks).
Uncertainty is pm or bounds with a basis sentence; bands that are explicit assumptions are listed in assumptions[] and say "ASSUMPTION" in their basis.
Only models with status: computed emit Measurements: confidence: estimated, method: mechanical-model, a required uncertainty
(worst-case bounds, low = lowering with max friction, high = lifting with max friction) and a required derivation {model_id, doc}.
partial / parametric models emit nothing numeric; the comparator therefore still shows "No data yet" for them.
Generated by research/models/build_models.py; human-readable derivations in docs/models/<machine_id>.md; the French field protocol in
docs/protocole-mesure-fr.md with CSV templates in docs/templates/.