Participant Identity & External Identifiers (XID)
|
Related reading
|
1. Why this exists
Events are fed by external systems — a legacy WPCA registration database, a timing provider, a partner federation. Each of those systems has its own primary keys for the same human being and the same entry. When we import participants or results we must answer two questions without creating duplicates and without trusting a number blindly:
-
Which person is this? — the same athlete may enter through several source systems over years, each with a different internal id.
-
Which entry (EventParticipant) is this? — within one event, a source system identifies its own registration row by its own key.
The model below pins both answers to durable, queryable identifiers rather than to fragile matching on names or bib numbers.
2. Vocabulary: the two XIDs
"XID" is shorthand for external identifier — an id minted by a system other than ours. There are exactly two, and keeping them distinct is the single most important thing in this design.
| Term | Grain | Stored as | Means |
|---|---|---|---|
Person-XID |
Person (across all events) |
|
"In source system S, this human being is known as U." A person accumulates many Person-XIDs — one per source system they have ever come through. 1:N. |
Participant-XID |
EventParticipant (one event) |
|
"In the source system, this entry into this event is row R." Scoped to the event; the same value may recur in a different event without collision. 1:1 with the EP. |
The distinction is not academic. A result file that carries the source system’s person id needs the Person-XID path (resolve person → that event’s EP). A file that carries the source system’s entry id needs the Participant-XID path (resolve the EP directly). Picking the wrong one silently fails to resolve — see the mode-mismatch guard.
3. Schema
person_external_reference
id bigint PK
person_id bigint FK → wp_users(id) (the Person / User)
registration_system_id bigint FK → registration_system(id) (the source system S)
external_uid varchar (the Person-XID U)
UNIQUE (registration_system_id, external_uid) (U is unique *within* S, never globally)
event_participant
...
registration_id varchar (the Participant-XID, source EP key)
UNIQUE (event_id, person_id) (one entry per person per event)
event
...
source_registration_system_id bigint FK → registration_system(id) (US #769; nullable)
Three points carry the design:
-
PersonExternalReferenceis 1:N, not a column onwp_users. A single column could only hold one source id; an athlete who came through WPCA in 2019 and a partner federation in 2024 has two, both valid, both needed for future resolution. -
Uniqueness is
(registration_system_id, external_uid), neverexternal_uidalone. Two unrelated source systems are free to mint the same string; only the pair identifies a person. -
Event.source_registration_system_idrecords which external system this event’s field came from, so result import can default and scope Person-XID resolution without the operator re-typing it every time (US #769 — see Event source registration system (US #769)).
4. The four-field identity DTO
Participant import accepts up to four identity inputs per row; the importer uses the first that resolves, in trust order. This replaced an earlier single-"externalId" column that conflated person identity with entry identity.
| DTO field | XID | Resolves |
|---|---|---|
|
(ours) |
Directly to |
|
(ours) |
To |
|
Participant-XID |
To the EP via |
|
Person-XID |
|
4.1. Trust matrix: (is_self, trustPKs)
Whether a supplied PK is trusted depends on whose system it is and whether the operator vouched for it:
RegistrationSystem.is_self |
trustPKs |
Behaviour |
|---|---|---|
|
— |
|
|
|
The operator asserts the foreign PKs in the file are reliable; resolve by them, write back the Person-XID. |
|
|
Treat foreign PKs as hints only; fall through to identity matching (ID number, then name+DOB) and a sample-based fingerprint check before trusting the link. |
The fingerprint check (a sampled field-by-field comparison of the incoming row against the stored
person) guards against a bad foreign mapping silently merging two different humans. The full
matrix, the fingerprint scoring, and the merge-candidate path are recorded in the design journal
(participant-identity-correlation.adoc).
5. Event source registration system (US #769)
Event.sourceRegistrationSystem is set once, the first time participants are imported from a
non-self source (set-if-absent during EP import). It then becomes the default for that event’s
result imports:
-
Result import defaults its source-system selector from the event; the operator does not re-pick it on every file.
-
The operator may override it on a result import. Changing it warns and asks for confirmation, then persists the new value back onto the event — the design is deliberately a hybrid of "remembered on the event" and "settable per import" so a correction sticks without forcing a separate event-edit step.
-
When
EXTERNAL_PERSON_UIDresolution runs (see below), the source system scopes the Person-XID lookup:PersonExternalReference WHERE registration_system_id = <event/import source> AND external_uid = <file value>.
6. How the importers consume this
| Importer | Identity use |
|---|---|
Reads the four-field DTO; matches/creates the Person; writes the Participant-XID onto
|
|
Resolves the finisher to an EP by one of four modes. |
7. Design rationale — alternatives weighed
| Rejected alternative | Why this design instead |
|---|---|
Single |
Holds only one source system; can’t represent an athlete known to several. Conflates person
identity with per-event entry identity. The 1:N |
Globally-unique |
Different source systems legitimately reuse ids. Uniqueness must be the
|
Trust every foreign PK in the file |
A single bad mapping merges two people irreversibly. The |
Re-prompt the source system on every result import |
Tedious and error-prone across many category files for one event. |