[E06] Import Participants

Summary

EP-specific bookends of the async import flow. Drives an event organiser through uploading a participants CSV/XLSX and reviewing the per-row reconciliation outcome. The mapping middle steps are shared with E08 via C05.

Actor & Context

Actor: event organiser, tenant admin. Frequency: a few times per event (initial import, late additions, corrections). Precondition: an event exists; user has EVENT_MANAGER+ permission on the tenant. Entry point: Import participants button on E02 Event Participants; or resume from My imports for a paused job.

Main Flow

  1. Upload screen. File picker (CSV/XLSX) with EP-specific options (revised 2026-05-04 per Participant Identity Correlation):

    • Source system — dropdown listing all RegistrationSystem rows for the org. Default is the org’s first is_self=true row. Operator selects "this file is from <X>".

    • ModeOverwrite (default) or Append.

    • Trust our PKs (trustPKs) — checkbox; default depends on source-system selection (true when is_self=true, locked false when is_self=false). Tooltip explains: "When ticked, EPID/UID columns in the file are treated as our own primary keys and used for fast matching. Only available for files exported from our own system."

    • Update PII (updatePII) — checkbox; default false. Tooltip explains: "When unticked, names/DOB/gender are only filled when blank. Tick to overwrite existing populated values from this file. SAID is never replaced with an invalid value regardless of this setting."

  2. User clicks Upload — POST /api/imports?sourceSystemId=X&trustPKs=…​&updatePII=…​&mode=…​. On success, write localStorage.setItem('importCaller:' + uuid, 'e06') using the uuid from the response (per C05 caller-key per-uuid scoping).

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

  4. Fingerprint check (when is_self=true AND trustPKs=true) — between CELL_MAPPING and PROCESSING, async-import samples 10 rows and verifies they match our database. On warning, returns to E06 with an acknowledgeFingerprintWarning confirmation modal explaining the row-level findings; operator confirms to proceed. On abort, summary screen renders with FINGERPRINT_ABORT reason.

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

  6. Summary screen. Per-row reconciliation:

    • Created / updated counts.

    • Unresolved-person count + drill-down list.

    • FK-mismatch count + drill-down list (e.g. category not found).

    • Merge candidates created (N) section — per Participant Identity Correlation F8 (US #693). Lists rows that surfaced EXTERNAL_UID_COLLISION or SAID_COLLISION candidates; deep-link to the merge review queue (US #381) filtered to candidates created during this job.

    • Link to E02 for follow-up.

Alternative Flows

  • AF-1: All rows clean — no mapping steps; C05 jumps straight to PROCESSING then summary.

  • AF-2: Cancel during PROCESSING → summary renders with Cancelled state.

  • AF-3: Fatal job error → summary renders with error reason + Try again returning to upload.

Acceptance Criteria

  • Use-case page authored.

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

  • :design-url: populated.

  • Cross-references C05 + E02.

  • EP-specific options round-trip to the server correctly.

  • User can complete a full import end-to-end against the dev environment.

  • Pause + resume works via My imports.

API Surface

Call Purpose

POST /api/imports?sourceSystemId=X&trustPKs=&updatePII=&mode=

Create import job from uploaded file + EP options. New parameters land via Participant Identity Correlation F6 (US #691). (is_self=false, trustPKs=true) rejected with 400 per validation matrix.

GET /api/imports/{uuid}/results

Driving the summary screen. Response DTO extended with mergeCandidatesCreated per F8 (US #693).

PUT /api/imports/{uuid}/start-processing (with acknowledgeFingerprintWarning=true)

Acknowledge a fingerprint warning and proceed. Only valid when state is FINGERPRINT_WARN.

GET /api/registration-systems?orgId=X

Populate the source-system dropdown.

(delegates to C05 for column / cell / progress)

Shared mapping flow. Per F9 (US #694), C05 column-mapping target fields are filtered by sourceSystem.is_self.

Out of Scope

  • The mapping middle steps — C05.

  • Result-import bookends — E08.

  • Membership-import bookends — future delivery.

  • Mapping templates (save / auto-detect / preview) — owned by C05; consumed transparently here. See C05 Future Use Cases (FU-1 / FU-2 / FU-3). Not part of the current UI phase.

Design Anchors

Design Decisions

  • Upload screen — single option: Mode (Overwrite default, Append) (2026-04-29). Earlier draft listed three options (overwrite/append, default category fallback, ID-type column hint). Reduced to one. Rationale:

    • Default category fallback moves to C05 cell-mapping — that screen already handles unresolved FK values, and forcing the operator to set a fallback up-front is premature.

    • ID-type column hint (the Bug #471 workaround of adding an IDType=NATIONAL column) is upgraded from UI workaround to backend fix — admin-service should resolve a sensible default server-side rather than asking the operator. Implies a follow-up backend ticket against Bug #471.

    • Overwrite as default matches the dominant operational case (re-importing the corrected sheet); Append remains available for the late-additions case.

  • "About this screen" information panel (2026-04-29). Persistent collapsible right-rail panel with a short purpose paragraph + bulleted operational details — different copy for the upload bookend vs the summary bookend. Default expanded, collapse state persisted per screen in localStorage. Cross-cutting pattern — applied symmetrically in C05 and C04; candidate for promotion to UI Design Principles.

  • Summary screen is a pure post-flight view (2026-04-29). E06 summary does NOT poll for progress and does NOT render a progress component. C05 owns PROCESSING end-to-end; on completion C05 redirects here, this screen calls GET /api/imports/{uuid}/results once, and renders the reconciliation view. Implies arrival on this screen is always post-terminal-state (COMPLETED / FAILED / CANCELLED).

  • Source-system + trustPKs + updatePII upload params (2026-05-04). Three new options on the upload screen, sourced from Participant Identity Correlation:

    • Source system dropdown lists all RegistrationSystem rows for the org. Selection drives every downstream decision (target-field filter in C05, fingerprint-check eligibility, default trustPKs).

    • Trust our PKs (trustPKs) defaults from source-system: true when is_self=true, locked false when is_self=false. Combination (is_self=false, trustPKs=true) is rejected at the API (400) — UI prevents the combination from being submitted.

    • Update PII (updatePII) defaults false (protective-by-default). When ticked: names/DOB/gender allowed to overwrite populated fields. SAID never replaces a validating value with an invalid one regardless. Blank-in-row never overwrites populated field, regardless of flag.

      Rationale: each toggle is orthogonal — provenance, identifier-semantics, PII-policy. Conflating them would reintroduce the bug class that caused the 2026-04-22 wp_users incident.

  • Fingerprint-check confirmation modal (2026-05-04). Per F7 (US #692), async-import inserts a FINGERPRINT_CHECK sub-state between CELL_MAPPING and PROCESSING when is_self=true AND trustPKs=true. Three outcomes:

    • ABORT (≥3/10 sample rows inconsistent) — terminal failure, summary explains.

    • WARN (1-2/10 inconsistent) — modal lists the inconsistent rows, requires explicit acknowledgeFingerprintWarning=true to proceed.

    • PASS (0/10 inconsistent) — no UI, transitions silently.

      Settled 2026-05-04: stage-bar representation is a fifth pill in C05 — Columns · Cells · Verifying · Processing · Done. Pill renders SKIPPED (greyed) with explanatory tooltip when is_self=false or trustPKs=false (per the project rule that disabled / hidden affordances must be self-explanatory at hover; see project memory feedback_explain_disabled_ui.md). Decision recorded in C05's Design Decisions ("Five-pill stage bar — Verifying between Cells and `Processing`").

  • Merge-candidates section in summary (2026-05-04). Per F8 (US #693), ImportRowResult.mergeCandidatesCreated carries EXTERNAL_UID_COLLISION / SAID_COLLISION candidates surfaced during F4’s matching priority chain. Summary screen renders a dedicated section with row count + deep-link to the merge review queue (US #381) filtered to candidates created during this job. Reuses Feature #373 plumbing — no parallel review workflow.

Notes

Backend deployed to dev; testable end-to-end. Bug #471 — ID-type defaulting — is being moved off the upload screen and into the backend (see Design Decisions); the existing CSV workaround stays valid until the backend fix lands. The full structural fix is now scoped under Participant Identity Correlation F2 (US #688) + Bug F3 (#701).

Design pass complete 2026-05-04. Bundle archived under design-journal/claude-design/2026-05-04-P8bHfTGZ/. E06’s two screen states (project/E06-import-bookend/upload.jsx and project/E06-import-bookend/summary.jsx) are siblings to C05’s four stage screens; folder-level READMEs encode the caller-key contract, source-system gating, and failure-mode rendering. Next step: implementation under Feature #540, USs #552 / #553 paired with C05 stages.

Appendix A: Claude Design Prompts

Prompts persisted for audit trail. Most recent first. The v4 prompt is the active hand-off prompt and lives in C05's appendix (canonical copy) — E06 simply references it because the bundle covers E06 upload → C05 columns → C05 cells → C05 verifying → C05 processing → E06 summary as one journey. Earlier versions are retained for lessons-learned reference.

v4 — 2026-05-04 — see C05 appendix

Status: drafted; ready for hand-off. Source: this .adoc plus C05 at :status: in-design, with the 2026-05-04 Design Decisions folded in. Bundling: covers E06 upload → C05 columns → C05 cells → C05 verifying → C05 processing → E06 summary as one cohesive flow. Canonical prompt text persisted in C05’s appendix to avoid drift. Differences from v3: drops the Mode radio; adds sourceSystem dropdown + trustPKs + updatePII upload-screen controls; adds target-field filtering on column-mapping; adds template auto-apply / multi-match banner; adds a fifth Verifying stage pill with three sub-states; adds mergeCandidatesCreated and Ignored columns sections to the E06 summary screen.

v3 — 2026-04-29 — superseded by v4 (kept for reference)

Status: superseded. Reason: drafted 2026-04-29 against the pre-2026-05-04 spec (single Mode option, 4-stage stage bar, no fingerprint sub-state, no source-system selector). The 2026-05-04 participant-identity-correlation design pass added four new upload fields (sourceSystem, trustPKs, updatePII, fingerprint-acknowledge), a new summary section (Merge candidates created), target-field filtering, and a fifth stage pill. v3 is structurally aligned with v4 but missing those wiring points; do not hand off v3.

Source: this .adoc at :status: in-design. Bundling: this prompt covers E06 upload → C05 columns → C05 cells → C05 processing → E06 summary as one cohesive flow; the same prompt was also persisted in C05's appendix.

I'm designing a multi-step screen flow for the EMS admin portal — a Spring
Boot + Angular SPA admin tool used by event operators (race directors)
to manage participants, events, and operational data. Visual language
matches existing portal screens you've designed in this project (C01, C03,
E01, E05) — JHipster 8 / Angular 16 / PrimeNG / ng-bootstrap stack;
match fonts, spacing, palette, density.

Design the **import flow**: an event organiser uploads a participants
CSV/XLSX, walks through a strict 2-phase mapping flow, watches an async
job run, and lands on a reconciliation summary. The flow spans two
use-case screens that must feel like one journey:

  E06 upload  →  C05 columns  →  C05 cells  →  C05 processing  →  E06 summary
   bookend         shared           shared        shared            bookend

The middle three steps (C05) are SHARED — the same screens are reused
later by E08 (result import) with no behaviour change, just different
caller key. Design accordingly: nothing in C05 may be EP-specific.

============================================================
[E06] Upload screen
============================================================

Single full-screen page with a right-rail "About this screen" panel
(see "Information panel pattern" below).

Main area:
- Page title: "Import participants — <event name>".
- File picker (CSV/XLSX) with drag-and-drop zone.
- One option: **Mode** — radio: `Overwrite` (default) | `Append`.
- "Recent imports" list below the picker — date, filename, row count,
  status. Clicking a row opens a read-only summary of that import (the
  same E06 summary screen, but for that uuid).
- Primary action: "Upload" (disabled until file picked).

On Upload click:
- POST /api/imports (multipart: file + mode).
- On 201 response, take the returned `uuid` and write
  `localStorage.setItem('importCaller:' + uuid, 'e06')`.
- Navigate to C05 at `/imports/{uuid}`.

============================================================
[C05] Mapping flow — shared 4-stage screen
============================================================

ONE full-screen page that progresses through 4 stages driven by the
backend state machine: COLUMN_MAPPING → CELL_MAPPING → PROCESSING →
COMPLETED/FAILED/CANCELLED.

Persistent UI on every stage:
- **Stage bar** — 4 pills at the top: Columns · Cells · Processing · Done.
  Current stage highlighted; prior stages marked complete; pills are
  READ-ONLY (not clickable). Forward-only flow; going back requires
  cancel + re-upload.
- **Right-rail "About this screen" information panel** — collapsible,
  default expanded, collapse state persisted in localStorage per screen.
  Content is STAGE-AWARE: different short purpose paragraph + bulleted
  operational details for each of the 4 stages.
- **Top-right** "Cancel import" button — DELETE /api/imports/{uuid},
  then redirect to caller's summary bookend (E06) with cancelled state.

Stage 1 — COLUMN_MAPPING
  Two-column layout:
  - Left: source CSV columns (header + first 3 sample rows for context).
  - Right: each source column's row showing what it maps to in the
    canonical schema. Three visual row states:
      • AUTO-MATCHED — quiet/grey treatment, target field shown,
        small `auto` tag, expandable to override.
      • USER-SET — neutral/strong treatment (no tag), target field shown.
      • NEEDS MAPPING — accent colour + required indicator, dropdown
        prominent.
    Operator's job is to scan auto-matched rows (the `auto` tag is the
    review affordance) and resolve any NEEDS MAPPING rows.
  - Primary action bottom-right: "Continue" — disabled while any row is
    NEEDS MAPPING. Calls PUT /api/imports/{uuid}/column-mappings.

Stage 2 — CELL_MAPPING
  List of unmatched FK values, grouped by FK type (e.g. "Category" group
  with the 3 unrecognised category strings beneath it). Each unmatched
  value gets a single filterable dropdown — ONE component reused across
  all FK types. Filter input auto-appears when the candidate list
  exceeds ~10 options; smaller lists render as a plain dropdown.
  Practical sizing: client-side filtering, hundreds of entries max.
  - Primary action: "Start import" — disabled until every unmatched FK
    is resolved. Calls PUT /api/imports/{uuid}/cell-mappings.

Stage 3 — PROCESSING
  Centred progress component:
  - Optional thin sub-stage label above the bar (hidden when the API
    returns no sub-stage). Designed for future API: `Validating → 15%
    → 47% → 84% → Summarising → Done`. v1 backend exposes only one
    PROCESSING state with a row-by-row counter, so the sub-stage label
    is hidden today — but the visual scaffolding is in place.
  - 0–100% progress bar.
  - "<rows processed> of <total> rows" counter.
  - Elapsed time.
  - "Cancel" button (DELETE /api/imports/{uuid}).
  - Polling cadence ~1s.

Stage 4 — Terminal (COMPLETED / FAILED / CANCELLED)
  IMMEDIATE redirect — no intermediate "Done" card.
  - Read `localStorage.getItem('importCaller:' + uuidFromRoute)`.
  - Remove that localStorage entry.
  - Navigate to the matching bookend's summary screen.
  - Fallback if caller key missing: navigate to `My imports` list.

State-error UX:
  - 409 on PUT /column-mappings or /cell-mappings — toast "This step has
    already been completed" + auto-refresh state via GET /api/imports/{uuid}.
  - Fatal job error — render the failure with reason + a "Try again"
    action that returns to the caller's upload screen.

============================================================
[E06] Summary screen — pure reconciliation view
============================================================

Operator arrives here from C05's terminal-state redirect. The job is
already in a terminal state — this screen does NOT poll, does NOT show
a progress bar, and never reverts to "in-flight" rendering.

On mount:
- Call GET /api/imports/{uuid}/results once.
- Render the reconciliation view.

Layout:
- Page header — "Import complete" (or "Import cancelled" / "Import
  failed"), filename, finished-at timestamp, "N rows processed" counter.
- Right-rail "About this screen" panel (same pattern as C05; copy
  describes what the summary represents and what to do next).
- Body: per-row reconciliation, in tabbed sections:
  - Created / updated counts.
  - Unresolved-person count + drill-down list.
  - FK-mismatch count + drill-down list (e.g. category not found).
- Bottom: "Open event participants" (links to E02) + "Import another
  file" (returns to E06 upload).

Failure-mode rendering:
- CANCELLED → header reads "Import cancelled" + the same per-row
  breakdown for whatever was processed before cancel.
- FAILED → header reads "Import failed" + reason + "Try again" returning
  to the upload screen.

============================================================
Information panel pattern (cross-cutting)
============================================================

Persistent collapsible right-rail panel ("About this screen") on every
full-screen step in this flow. Default expanded. Collapse state
persisted per-screen-key in localStorage. Content per screen:
- Short purpose paragraph (1–2 sentences explaining what THIS step is for).
- Bulleted operational details (the things an operator might be unsure
  about — what mode does, what `auto` means, what happens on cancel,
  etc).

Goal: train operators in-flow. They shouldn't need external docs to
complete an import.

============================================================
Output
============================================================

For each of the 5 screens (E06 upload, C05 columns, C05 cells, C05
processing, E06 summary):
- HTML/JSX + matching styles.
- Screen README that explains what it does and which API endpoints
  it consumes.
- Cross-screen README that explains:
  - The caller-key contract (per-uuid scoping, who writes, who reads,
    who removes, fallback).
  - The stage-ownership split (C05 owns processing; bookend owns summary).
  - How to reuse C05 unchanged when E08 (result import) becomes the
    caller — the only difference is the literal value written to the
    caller key (`'e06'` vs `'e08'`).

v1 — 2026-04-29 — discarded

Status: discarded. Reason: drafted from session memory without reading this .adoc. Modelled E06 as one screen with three morphing states (Pre-import / Active import / Post-import summary), which conflicts with the canonical bookend split (E06 upload + E06 summary, with C05 owning the active-import middle steps). Also implied a live progress feed inside E06, conflicting with C05’s caller-key convention.

Continuing the EMS admin portal design language (E01, E05, C01, C03 you've
already designed). E06 is the bookend screen for participant CSV imports
— the page an operator lands on when they click "Import participants" from
an event's Control Centre.

Context: the import is async — we kick off a backend job, then poll for
progress, then show the result. E06 has 3 states a single screen flips
between:

State A — Pre-import: empty page with a big "Choose CSV" / drag-drop zone.
File picked → goes to C05 (shared mapping screen). Below the picker:
"Recent imports" list (5 most recent) showing date, filename, row count,
status. Clicking a row opens a read-only summary of that import.

State B — Active import: progress bar, current row counter (e.g. "2,341
of 8,452 rows processed"), elapsed time, and a live tail of "issues
discovered" — duplicate emails, ID Luhn check failures, missing required
fields, foreign-key resolution failures (unknown category, etc). Cancel
button. Issues tail is scrollable; shows row #, severity (warning/error),
and one-line description.

State C — Post-import summary: top stats (rows processed, succeeded,
warned, failed). Below: a tabbed view — "Errors" tab (rows that didn't
import + why), "Warnings" tab (rows that imported but with caveats),
"Imported" tab (success rows, with quick search). For errors, an inline
"Edit and retry" action that reopens just those rows for re-mapping.
Bottom: "Done" + "Import another file".

Screen must feel like a single page that morphs between states (no
full-page navigation). No leaving the URL during async work.

Style: same visual language as the existing portal designs (E01, E05).
Match colour palette and information density.

Output: HTML/JSX + styles, README explaining each state's transitions
and the data contract for the async progress feed (probably SSE or
WebSocket; backend is TBD but the UI should support both).