# OMX Field Mapping

This guide says where the fields of three common entry formats, Hy-Tek semicolon files, RunSignup
participants, and Athletic.net entry exports, land in an OMX registration document
(`"kind": "registration"`). Use it if your product already reads or writes one of them. It is a
guide, not part of the format: where it and the [specification](/omx/spec) disagree, the
specification is right.

Each table was checked against a working importer. A field marked _not carried_ has no OMX home;
keep it on your side, keyed by your own id.

## Before you map anything: ids

None of these formats carries an id that is guaranteed stable for the life of a meet, and OMX needs
one on every record (see Ids in the specification). Mint your own ids when you first read a file,
store them with the meet, and reuse them on every later document. Never derive an id from a name:
correcting a misspelt name would then change the athlete's id and break the results join.

A registration number (Hy-Tek field 22, RunSignup `registration_id`, Athletic.net `ANetAthleteId`)
is a good id when your source keeps it stable. When you know which system issued it, also send it
in `external_ids` under that system's id (`rsu` for RunSignup, `anet` for Athletic.net; the
specification's System ids table lists the keys for systems that write no OMX document), so a
Publisher keyed on that system can match the athlete. Hy-Tek is a file format, not a system, so its field 22 alone names no system.

Only the meet, events, teams, and athletes carry `external_ids`. An entry never does: a Publisher
finds a result through its athlete and its event, and a relay through its team, relay letter, and
event. So in one document an athlete is entered at most once in an event, and every relay entry
carries a letter.

## Hy-Tek semicolon entry files

Hy-Tek Meet Manager's semicolon-delimited entry import has five record types: `I` (athlete
information), `D` and `E` (individual entries), `Q` and `R` (relays). `D` and `Q` carry more than
`E` and `R`; prefer them.

Fields are numbered from 1 as Hy-Tek's documentation numbers them, and field 1 is the record type.
After splitting a line on `;`, field _n_ is at index _n_ − 1: in `D;Rivera;Ana;…`, field 2 (last
name) is `Rivera`. A record may stop early when its remaining fields are blank. A
[worked example](#a-worked-example) follows the tables.

### Athletes and teams (`I`, and fields 2 to 10 of `D` and `E`)

| Field      | Hy-Tek               | OMX                                    | Notes                                                                                   |
| ---------- | -------------------- | -------------------------------------- | --------------------------------------------------------------------------------------- |
| 2          | Last name            | `athletes[].last_name`                 |                                                                                         |
| 3          | First name           | `athletes[].first_name`                |                                                                                         |
| 4          | Initial              | `athletes[].middle_name`               | One letter                                                                              |
| 5          | Gender `M`/`F`       | `athletes[].gender`                    | Same letters                                                                            |
| 6          | Birth date           | `athletes[].birth_date`                | `MM/DD/YYYY` becomes `YYYY-MM-DD`. Send it only when you send no `age`                  |
| 7          | Team code            | `teams[].code`                         | Four characters at most. `UNA` is unattached: a team with `unattached: true`            |
| 8          | Team name            | `teams[].name`                         | `Unattached` goes with `UNA`                                                            |
| 9          | Age                  | `athletes[].age`                       | On an `E` record this field may hold the competitor number instead                      |
| 10         | School year          | `athletes[].grade`                     | A string                                                                                |
| 13, 14, 16 | City, state, country | `athletes[].city`, `region`, `country` | OMX `country` is ISO 3166-1 alpha-2 (`US`); Hy-Tek uses three letters (`USA`)           |
| 18         | Home phone           | `athletes[].phone`                     | For the timer's use on meet day. Meet Operations never passes it on                     |
| 22         | Registration number  | an id when stable                      | Hy-Tek does not say whose number it is                                                  |
| 23         | Competitor number    | `athletes[].bib`                       | Also field 15 of `D`. The bib is the athlete's, never an entry's                        |
| 24         | E-mail               | `athletes[].email`                     | As phone                                                                                |
| —          | Address lines, zip   | _not carried_                          | Fields 11, 12, 15. Citizen country, office phone, fax, and shirt size: also not carried |

### Individual entries (`D`, `E`)

| Field | Hy-Tek             | OMX                    | Notes                                                                              |
| ----- | ------------------ | ---------------------- | ---------------------------------------------------------------------------------- |
| 11    | Event code         | `events[].code`        | See [event codes](#hy-tek-event-codes)                                             |
| 12    | Entry mark         | `entries[].seed`       | See [marks](#hy-tek-marks)                                                         |
| 13    | Event measure      | the seed's `unit`      | `M` metric, `E` English                                                            |
| 14    | Event division     | `events[].division_id` | A division number. Make one `divisions[]` record per number, with it as `sequence` |
| 15    | Competitor number  | `athletes[].bib`       | `D` only                                                                           |
| 16    | Finish place       | _not carried_          | A place from a prior meet. OMX places are written by Meet Operations for this meet |
| 17    | Declaration status | `entries[].status`     | `S` (scratched) is `scr`. `D`, `A`, and blank are `e`                              |
| 18    | Entry note         | _not carried_          | `D` only. An entry has no notes; a note for the timer may go in `athletes[].notes` |

One `D` or `E` record is one entry. Hy-Tek has no event list of its own in this file: build
`events` from the distinct event code, gender, and division combinations, and give each a stable id.

### Relays (`Q`, `R`)

| Field (`Q`) | Field (`R`) | Hy-Tek             | OMX                                   | Notes                                                                                         |
| ----------- | ----------- | ------------------ | ------------------------------------- | --------------------------------------------------------------------------------------------- |
| 2, 3        | 2, 3        | Team code, name    | `entries[].relay.team_id`             | Through `teams`                                                                               |
| 4           | 4           | Relay letter       | `entries[].relay.letter`              | One upper-case letter, required. Write `A` when the record leaves it blank                    |
| 5           | 5           | Relay gender       | `events[].gender`                     | `X` becomes `C`                                                                               |
| 7           | 7           | Event code         | `events[]`                            | See [relay codes](#hy-tek-event-codes)                                                        |
| 8           | 8           | Entry time         | `entries[].seed`                      | In `ms`                                                                                       |
| 10          | 10          | Event division     | `events[].division_id`                |                                                                                               |
| 12          | —           | Declaration        | `entries[].status`                    | As for individual entries                                                                     |
| 16 onward   | 11 onward   | Runners, 8 at most | `entries[].relay.legs` and `athletes` | `Q` gives 8 fields per runner, `R` 7. Each runner is an athlete; legs list their ids in order |

### Hy-Tek event codes

Hy-Tek puts the whole event in one code, and so does OMX: most Hy-Tek codes are already OMX codes.
Build each OMX event from the code, the athlete's gender, and the division number on the record.

Running events. The code says what kind of race and how far.

| Hy-Tek code | OMX `code` | Also send                                                      |
| ----------- | ---------- | -------------------------------------------------------------- |
| `100`       | `100`      |                                                                |
| `1600`      | `1600`     |                                                                |
| `1MILE`     | `1MILE`    |                                                                |
| `HMAR`      | `HMAR`     | The only code for a half marathon, never `21097` or `13.1MILE` |
| `MAR`       | `MAR`      | The only code for a marathon                                   |
| `110H`      | `110H`     | `hurdle_height_mm` if you know it: the file does not say it    |
| `3000S`     | `3000S`    |                                                                |
| `5000W`     | `5000W`    |                                                                |
| `1MILEW`    | `1MILEW`   |                                                                |

Field events. The code is the event.

| Hy-Tek code                  | `code`   | Also send                                                              |
| ---------------------------- | -------- | ---------------------------------------------------------------------- |
| `LJ`, `TJ`, `HJ`, `PV`       | the same |                                                                        |
| `SP`, `DT`, `HT`, `JT`, `WT` | the same | `implement_weight_g` if you know it, from the division and gender      |
| `SWT` (super weight throw)   | none     | Not in OMX: leave the event out of the document and tell your operator |

Multi-events. Send only the multi-event and its entries, never its component events: Meet
Operations builds them, starting from the specification's default table and following its own rules
for the meet. A multi-event may be for any gender in a track meet.

| Hy-Tek code | `code` | Notes                                                           |
| ----------- | ------ | --------------------------------------------------------------- |
| `DEC`       | `DEC`  | Any gender                                                      |
| `HEP`       | `HEP`  | Any gender, indoor or outdoor                                   |
| `IPENT`     | `PENT` | Any gender                                                      |
| Any other   | none   | Not in OMX: leave it out of the document and tell your operator |

### Hy-Tek relay codes

A relay code on a `Q` or `R` record is the total distance of the race, sometimes with a letter
after it. OMX writes an even relay as legs × each leg, and a medley as its total and `R`.

| Hy-Tek code | The race               | OMX `code`                      |
| ----------- | ---------------------- | ------------------------------- |
| `400`       | 4x100 m                | `4X100`                         |
| `800`       | 4x200 m                | `4X200`                         |
| `1600`      | 4x400 m                | `4X400`                         |
| `3200`      | 4x800 m                | `4X800`                         |
| `4`         | 4 x 1 mile             | `4X1MILE`                       |
| `1600S`     | A sprint medley        | `1600R`                         |
| `3200D`     | A distance medley      | `3200R`                         |
| `240H`      | 4x60 m shuttle hurdles | `4X60H`, and `hurdle_height_mm` |

Hy-Tek's `1600` is a relay only on a `Q` or `R` record. On a `D` or `E` record it is the 1600 m run.

### Hy-Tek marks

Field 13 of `D` and `E` (field 9 of `Q` and `R`) says whether a field mark is metric (`M`) or
English (`E`). Times are the same either way.

| Hy-Tek entry mark                      | Measure | OMX seed                                               |
| -------------------------------------- | ------- | ------------------------------------------------------ |
| `58.83`                                | either  | `{ "value": 58830, "unit": "ms", "display": "58.83" }` |
| `4:19.14`                              | either  | `{ "value": 259140, "unit": "ms" }`                    |
| `1:23:44.55` (hours, minutes, seconds) | either  | `{ "value": 5024550, "unit": "ms" }`                   |
| `13.4h` (a hand time)                  | either  | `{ "value": 13400, "unit": "ms", "display": "13.4h" }` |
| `12.33` (metres)                       | `M`     | `{ "value": 12330, "unit": "mm" }`                     |
| `1233` (centimetres, no point)         | `M`     | `{ "value": 12330, "unit": "mm" }`                     |
| `12-10.25` (feet and inches)           | `E`     | `{ "value": 154.25, "unit": "in" }`: 12 × 12 + 10.25   |
| `3020` (combined-event points)         | either  | `{ "value": 3020, "unit": "pt" }`                      |

Never convert an English mark to millimetres: send it in inches and let Meet Operations convert,
once, if its meet is metric.

### A worked example

One athlete with an information record and two entries, the second scratched:

```text
I;Rivera;Ana;J;F;03/14/2009;LHS;Longwood High School;17;11;;;Longwood;FL;;USA;;;;;;LW-2207;214;
D;Rivera;Ana;J;F;03/14/2009;LHS;Longwood High School;17;11;LJ;17-02.50;E;1;214;;D;
D;Rivera;Ana;J;F;03/14/2009;LHS;Longwood High School;17;11;1600;5:21.40;M;1;214;;S;
```

Field by field: the athlete is Ana J. Rivera, female, born 2009-03-14, age 17, grade 11, from
Longwood, FL, USA, on team `LHS`. Her registration number is `LW-2207` and her competitor number,
the bib, is `214`. Both entries are in division 1. The long jump seed is an English mark,
17 ft 2.5 in. The 1600 m seed is a time, and that entry's declaration status is `S`, scratched.

The same records in OMX. Hy-Tek gives a division only as a number, so its name comes from your
meet setup:

```json
{
	"divisions": [
		{
			"id": "d-1",
			"name": "Varsity",
			"sequence": 1
		}
	],
	"events": [
		{
			"id": "e-lj-f-1",
			"name": "Varsity Girls Long Jump",
			"code": "LJ",
			"gender": "F",
			"division_id": "d-1"
		},
		{
			"id": "e-1600-f-1",
			"name": "Varsity Girls 1600m",
			"code": "1600",
			"gender": "F",
			"division_id": "d-1"
		}
	],
	"teams": [
		{
			"id": "t-lhs",
			"code": "LHS",
			"name": "Longwood High School"
		}
	],
	"athletes": [
		{
			"id": "LW-2207",
			"first_name": "Ana",
			"middle_name": "J",
			"last_name": "Rivera",
			"gender": "F",
			"age": 17,
			"grade": "11",
			"team_id": "t-lhs",
			"bib": "214",
			"city": "Longwood",
			"region": "FL",
			"country": "US"
		}
	],
	"entries": [
		{
			"id": "n-1",
			"event_id": "e-lj-f-1",
			"athlete_id": "LW-2207",
			"seed": {
				"value": 206.5,
				"unit": "in",
				"display": "17-02.50"
			}
		},
		{
			"id": "n-2",
			"event_id": "e-1600-f-1",
			"athlete_id": "LW-2207",
			"status": "scr",
			"seed": {
				"value": 321400,
				"unit": "ms",
				"display": "5:21.40"
			}
		}
	]
}
```

- The registration number is the athlete's id, because this source keeps it stable. If yours does
  not, mint an id and keep the number in your own records.
- Each event id is made from the code, gender, and division, so the same three always give the same
  event. Store the ids you mint; never regenerate them.
- The scratched entry stays in the document with `status: "scr"`. Leaving it out would make Meet
  Operations ask the timer whether to scratch it.
- `17-02.50` is 17 × 12 + 2.5 = 206.5 inches, and `5:21.40` is 321.4 seconds, 321400 ms.

A relay entry names its runners by athlete id, in running order. A `Q` record gives each runner's
competitor number, so match runners to athletes by bib, and create an athlete for a runner you have
not seen:

```json
{
	"id": "n-40",
	"event_id": "e-4x400-f-1",
	"relay": {
		"team_id": "t-lhs",
		"letter": "A",
		"legs": ["LW-2207", "LW-2213", "LW-2219", "LW-2230"]
	},
	"seed": {
		"value": 245200,
		"unit": "ms",
		"display": "4:05.20"
	}
}
```

## RunSignup participants

These are the fields of RunSignup's race participants API that 2Timer's connector reads. RunSignup's
own API documentation is the authority for the rest of the response.

| RunSignup                                     | OMX                                                  | Notes                                                                 |
| --------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------- |
| `registration_id`                             | an id for the entry and athlete                      | Stable for a registration                                             |
| `user.first_name`, `middle_name`, `last_name` | `athletes[].first_name`, `middle_name`, `last_name`  |                                                                       |
| `user.gender`                                 | `athletes[].gender`                                  | Upper case, first letter. Leave it out when the runner gave none      |
| `user.dob`                                    | `athletes[].birth_date`                              | Only when you send no `age`. It never leaves Meet Operations          |
| `age`                                         | `athletes[].age`                                     | Age as RunSignup computed it for the race                             |
| `bib_num`                                     | `athletes[].bib`                                     | A string in OMX. The bib is the athlete's (see below)                 |
| `chip_num`                                    | `athletes[].chip`                                    | For the timer; Meet Operations never passes it on                     |
| `team_name`                                   | `teams[].name`                                       | Derive a short `code`; RunSignup's name is not one                    |
| `user.address.city`, `state`, `country_code`  | `athletes[].city`, `region`, `country`               | Street and zip: _not carried_                                         |
| `user.email`, `user.phone`                    | `athletes[].email`, `phone`                          | For the timer's use on meet day. Meet Operations never hands them off |
| The race's `event_id`                         | `entries[].event_id`, through `events`               | One entry per participant per event they registered for               |
| `status` other than `active`                  | an entry with `status: "scr"`, once you have sent it | A withdrawal you already sent is a scratch, never a missing entry     |

The race's `name`, `next_date`, and events (`event_id`, `name`, `start_time`) give `meet.name`,
`meet.start_date`, and `events[]`. A start time is not carried. A road race is
`meet.sport: "run"`.

A bib in OMX belongs to the athlete. RunSignup gives each registration its own bib, so a runner
registered for two races, say Saturday's 5K and Sunday's half, has two registrations and two bibs.
Send them as two athletes, one per registration, each with its own bib and entry. Award groups
RunSignup defines go in `groups`, and an entry's `group_id` names the one RunSignup placed it in
when age and gender alone do not decide it.

## Athletic.net entry exports

Athletic.net's meet entry CSV has one row per entry, with the columns below. Athletic.net can also
export a Hy-Tek semicolon file; map that as [Hy-Tek](#hy-tek-semicolon-entry-files).

| Column          | OMX                                           | Notes                                                                     |
| --------------- | --------------------------------------------- | ------------------------------------------------------------------------- |
| `TeamCode`      | `teams[].code`                                |                                                                           |
| `TeamName`      | `teams[].name`                                |                                                                           |
| `ANetAthleteId` | an athlete id                                 | Stable across meets, so a Publisher can build season history from it      |
| `ANetTeamId`    | a team id                                     |                                                                           |
| `FirstName`     | `athletes[].first_name`                       |                                                                           |
| `LastName`      | `athletes[].last_name`                        |                                                                           |
| `Gender`        | `athletes[].gender`, and the event's `gender` |                                                                           |
| `Grade`         | `athletes[].grade`                            |                                                                           |
| `EventCode`     | `events[]`                                    | Hy-Tek style codes: see [Hy-Tek event codes](#hy-tek-event-codes)         |
| `Seed`          | `entries[].seed`                              | A time (`06:22.6`) in `ms`; an English field mark (`5' 7"`) in `in`: `67` |
| `Division`      | `events[].division_id`, through `divisions`   | A name, such as `Varsity`                                                 |
| `Birthdate`     | `athletes[].birth_date`                       | Only when you send no `age`                                               |
| `Comp Number`   | `athletes[].bib`                              |                                                                           |

Relay rows carry the team, a relay letter, gender, a total distance (`1600` for the 4x400), a seed,
and a division, followed by the legs.

## Not covered yet

- **Hy-Tek `.hy3` and other fixed-width files.** Only the semicolon format is mapped here.
- **MileSplit.** No import format is mapped.

If you map one of these, or find a row here that is wrong, send it to
[jason@2timer.app](mailto:jason@2timer.app?subject=OMX%20field%20mapping) and it will be added.

This guide is dedicated to the public domain under
[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/).
