[E08] Import Results

Summary

Result-import bookends of the async import flow. Mirrors E06; reuses C05 for the mapping middle steps. Drives an event organiser through uploading a results CSV/XLSX, resolving any mappings, and reviewing the per-row reconciliation including unresolved EPs and DNF/DNS/Drop entries.

Actor & Context

Actor: event organiser, results officer, tenant admin. Frequency: per event (initial result import, corrections during the verification window). Precondition: event has finished; EPs exist; user has EVENT_MANAGER or RESULTS_OFFICER permission. Entry point: Import results button on E04 Event Results; from E05's Results unverified pill click.

Main Flow

  1. Upload screen. File picker (CSV/XLSX), result-import-specific options:

    • Identifier mode (participantIdMode) — EPID / PERSON_ID / REGISTRATION_ID. Defaults from Event.preferredTimingIdentifier (per Participant Identity Correlation F14, US #699). Operator can override per-import; override logged in summary. The three modes drive how the inbound ExternalReferenceID column is interpreted to match the EP record:

      • EPID → match EP.id

      • PERSON_ID → match (Event.id, Person.id) composite

      • REGISTRATION_ID → match EP.registrationId (cross-system mode, used when EPs were imported from an external registration system)

    • DNF / DNS / Drop status handling toggle (per US #457).

    • Repeated header tolerance (per Bug #453).

  2. User clicks Upload — write localStorage.importCaller = 'e08', then POST /api/imports?participantIdMode=…​&statusHandling=…​&repeatedHeaders=…​.

  3. Redirect to C05 for column / cell mapping + progress.

  4. C05 redirects back on completion to the summary screen.

  5. Summary screen. Per-row reconciliation:

    • Created / updated result counts.

    • Unresolved EPs surfaced as actionable issues (US #456).

    • DNF / DNS / Drop entries reported separately (US #457).

    • Non-data-line counts (US #455).

    • Identifier-mode mismatch warning — surfaces when the operator’s chosen mode differs from Event.preferredTimingIdentifier. Per F14 (US #699).

    • Link to E04 for follow-up.

Alternative Flows

  • AF-1: Re-import during correction window — existing results updated; summary distinguishes new vs corrected.

  • AF-2: Re-import after correction window closed — soft warning; user must confirm before submit.

  • AF-3: Cancel / fatal error — same patterns as E06.

Acceptance Criteria

  • Use-case page authored.

  • Status design-todo → handoff-ready after Claude Design pass.

  • :design-url: populated.

  • Cross-references C05 + E04 + E05.

  • DNF / DNS / Drop entries surfaced separately per US #457.

  • Unresolved EPs surfaced as actionable issues per US #456.

API Surface

Call Purpose

POST /api/imports?participantIdMode=…​

Create import job from uploaded file + result-import options. participantIdMode defaults from Event.preferredTimingIdentifier; operator override permitted with summary warning when divergent.

GET /api/events/{id}

Reads preferredTimingIdentifier to default the upload-screen identifier-mode dropdown.

GET /api/imports/{uuid}/results

Driving the summary screen. Per F14 (US #699), each RaceResult row carries the inbound ExternalReferenceID value (persisted to RaceResult.externalReferenceId) for audit trail.

(delegates to C05 for column / cell / progress)

Shared mapping flow.

Out of Scope

  • The mapping middle steps — C05.

  • EP-import bookends — E06.

  • Mass result correction tooling — separate Feature.

Design Anchors

Design Decisions

  • Three identifier modes for participant resolution (2026-05-04). Per Participant Identity Correlation § Decisions Made (Timing round-trip), the inbound ExternalReferenceID column is interpreted as one of:

    • EPID — our internal EventParticipant.id. Used when our system is authoritative for the event lifecycle.

    • PERSON_ID — our internal Person.id. Default for new events; safe across multiple events for the same person.

    • REGISTRATION_IDEP.registrationId (the source system’s EP UID). Used when EPs were imported from an external registration system and we want timing to round-trip their identifier back.

      The mode default per Event lives in Event.preferredTimingIdentifier (set during EP creation, lockable via the timing-export UI gate per F12 US #697). Result import reads this default but lets the operator override per-import — useful when re-importing legacy result files exported with a different mode than the Event currently has set. Mismatch surfaced in summary as a warning, not an error.

  • Persist inbound ExternalReferenceID to RaceResult.externalReferenceId for audit (2026-05-04). Per F14 (US #699). Detects mode-change drift, supports forensic comparison ("what we last exported" vs "what they returned"), aids Bug #417 (duplicate RaceResult records) debugging. Light cost (one VARCHAR column); high audit value.

  • Bookend pattern mirrors E06 (2026-05-04). Same upload + summary structure as E06; same C05 caller-key contract ('e08' literal); same async-import state machine. The result-import-specific options live entirely on the upload screen + summary; the mapping middle steps are content-type-agnostic.

Notes

Backend result-import improvements: PR #146 (merged) + PR #147 (in flight). USs #455–#458 detail the row-level reporting nuances the summary screen must surface. The 2026-05-04 design adds the identifier-mode strategy and RaceResult.externalReferenceId audit field; backend changes scoped under Participant Identity Correlation F14 (US #699).