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

  1. Every number has a confidence and a source. No source → the field is null.
  2. confidence enum:
    • 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.quote stores 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.
  3. 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/estimated Measurements feed the comparator.
  4. Community data enters as moderation.status = pending; only approved data 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

  1. For machine A, take approved measurements at the same rom_point; sort by setting; linearly interpolate setting → resistance. Same for machine B.
  2. R = f_A(x); answer y = f_B⁻¹(R).
  3. If either machine has < 2 approved points, or x/R lies outside the measured range → to.setting = null → UI shows "No data yet" (extrapolation is refused, not guessed).
  4. Result confidence = weakest input (measured > estimated). Manufacturer specs are never inputs.

i18n

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/.