[E10] Export Participants

Summary

EP export to XLSX. Single-shape export covering both human-Excel editing and clean re-import round-trip cases. The shape includes our internal PKs alongside source-system reference columns and a timing-export reference column, so a re-import via E06 in (sourceSystem=<self>, trustPKs=true) mode preserves all linkages.

Replaces the current ad-hoc workflow (direct SQL → manual CSV) with a first-class endpoint and a permission-gated UI affordance.

Actor & Context

Actor: event organiser, results officer, tenant admin. Frequency: ad-hoc — typically before timing-system handoff, after registration close, during corrections, for offline reporting. Precondition: event exists; user has EVENT_MANAGER or read permission on the event. Entry point: Export participants button on E02 Event Participants; or from E05 event tile menu.

Main Flow

  1. User clicks Export participants on E02. Optional filters from E02 (category, status, etc.) carry into the export query.

  2. Browser triggers GET /api/event-participants/export?eventId=X&format=xlsx (filters as additional query params).

  3. Backend streams an XLSX file with the canonical EP-export shape (see Export Shape below).

  4. File downloads; user opens in Excel.

Alternative Flows

  • AF-1: Empty result set — endpoint returns a header-only XLSX so re-import round-trip semantics are preserved.

  • AF-2: Permission denied (no read access to the event) — 403, UI button disabled with tooltip.

  • AF-3: Backend error during streaming — partial file rejected; UI surfaces a toast and a retry affordance.

Export Shape

Single shape, ~30 columns. Sized for both human readability and re-import round-trip.

Column Source Notes

ourEventParticipantId

EP.id

Our PK. Drives re-import direct match (trustPKs=true).

ourPersonId

EP.person.id

Our Person PK. Drives re-import direct match.

sourceSystemParticipantId

EP.registrationId

External system’s EP UID, if EP was originally imported from one. Blank for self-source EPs.

sourceSystemName

RegistrationSystem.name (when is_self=false)

Display-only on re-import; never consumed as identifier. Blank for self-source EPs.

TimingExternalReferenceID

per Event.preferredTimingIdentifier

Populated to match what the timing system expects: EP.id (EPID mode), EP.person.id (PERSON_ID mode), EP.registrationId (REGISTRATION_ID mode).

firstName, lastName

Person

Always present.

identityNumber, identityType, identityCountry

Person

Identity tuple. identityType ∈ NATIONAL / PASSPORT / OTHER.

dateOfBirth, gender, email, phone

Person

Standard PII.

category, raceNumber, tag

EP

Event-day fields.

orderId, orderStatus

EP linkage

When applicable.

(other columns per current EP model)

Round-trip-safe only.

Acceptance Criteria

  • Endpoint GET /api/event-participants/export?eventId=X&format=xlsx returns a valid XLSX stream.

  • Permission: same as EP read.

  • Round-trip integration test: export → re-import via E06 (sourceSystem=<self>, trustPKs=true) → assert no field drift, no registrationId blanking.

  • Human readability: column ordering puts identity + race-day fields ahead of system PKs.

  • sourceSystemName populated only when RegistrationSystem.is_self=false (per F11 US #696).

  • TimingExternalReferenceID populated per Event.preferredTimingIdentifier.

  • Empty result set returns header-only XLSX (round-trip safe).

API Surface

Call Purpose

GET /api/event-participants/export?eventId=X&format=xlsx

Stream the export. Filters from E02 (category, status, etc.) accepted as additional query params.

GET /api/events/{id}

Reads preferredTimingIdentifier to populate the TimingExternalReferenceID column.

GET /api/registration-systems/{id}

Reads name for the sourceSystemName column.

Out of Scope

  • CSV format — XLSX only for v1. CSV variant is a low-cost follow-up if demand surfaces.

  • Print-friendly PDF — unrelated; covered by Registration & Financial Reports.

  • Export-with-custom-column-selection — operators get the canonical shape; custom subsets are a future enhancement.

  • Timing-system export — distinct shape, narrow column set, see E11 Timing System Export.

Design Anchors

  • Portal Pattern

  • UI Design Principles

  • design-journal/2026-05/participant-identity-correlation.adoc — canonical design (F11 US #696)

  • E02 — natural caller (export button)

  • E06 — re-import target (round-trip)

  • E11 — sibling export use-case (timing system)

Design Decisions

  • Single export shape (2026-05-04). Earlier brainstorm considered three modes: human-Excel / re-import / timing-system. Reduced to ONE shape covering human + re-import; timing-system gets its own use-case (E11) with a deliberately narrow column set. Rationale: re-import shape and human-Excel shape need exactly the same columns (operators edit values then re-upload); separating them adds complexity without value.

  • sourceSystemName is informational only (2026-05-04). Per F11 (US #696), the column is populated only when EP’s source RegistrationSystem.is_self=false. On re-import, the column is never consumed as an identifier or matching key — the operator-selected source system on the upload screen drives all matching logic. The column exists purely so a human reader knows "this EP came from <X>" when scanning a mixed-source export.

  • TimingExternalReferenceID populated per Event default (2026-05-04). Per F11 (US #696), the column carries whatever Event.preferredTimingIdentifier says. On a fresh event with default PERSON_ID, the column shows EP.person.id. After Event.preferredTimingIdentifier is locked by a timing-export call (F12 US #697), this export reflects that locked value. Operators get a clean handoff to manually-edit-friendly format if they need to feed external systems.

  • Round-trip semantics: blank-cell-never-overwrites (2026-05-04). When this export is re-imported via E06, the existing EP.registrationId is preserved when the row’s sourceSystemParticipantId cell is blank. This is the standard policy from Participant Identity Correlation § Identity Update Policy. It guards against operator deletes-cell-and-re-saves-Excel destroying a carefully-tracked external linkage.

Notes

This use-case exists today only as ad-hoc SQL queries. The 2026-04-22 wp_users incident was partly enabled by operators having no first-class export tool — they were forced to compose CSVs manually, and re-import flow was ambiguous. F11 (US #696) ships the endpoint; this .adoc is the screen + UX scoping.