Import Bookend Contract
1. Overview
The async-import flow has two halves:
-
Bookends — importer-specific upload + summary screens. E06 (Import Participants), E08 (Import Results), T06 (Import Manufactured Numbers), and any future M01 / S01 flow each own their own pair. Bookends know the file shape, the source-system semantics, and the per-row outcome rendering for that import type.
-
Common stages (C05) — shared columns / cells / verifying / processing screens that the operator walks through between the bookends. Codified once in
c05-mapping/c05-host.component.tsand the fourstage-*.component.tssiblings. Agnostic to import type — driven entirely by theIImportJobmodel the bookend’s upload returns.
This page defines the contract that lets a new bookend pair reuse the C05 host without modification. Read Async Import Architecture first — it covers the state machine, wire shapes, F-feature surfaces, and the source-system gating matrix that the contract below is built on.
2. Architecture
The bookends call admin-service through their per-entity REST resource (PUT /api/event-participants/import, PUT /api/result-sets/import-bulk, …) which orchestrates the generic framework internally. The C05 host calls the generic framework’s /api/imports/{uuid}/* endpoints directly — those are import-type agnostic. This is the boundary that makes the C05 host reusable.
3. Bookend responsibilities
An importer-specific bookend pair (upload + summary) must provide the following — each item is enforced either by the type system or by the runtime contract on admin-service.
| Concern | Bookend responsibility |
|---|---|
Importer key (string) |
A short stable identifier ( |
|
One of |
Row processor |
A Spring |
Field-definition registry entries |
For every column the import recognises: name, aliases (case-insensitive), |
Source-system semantics |
Whether the import flow uses the (is_self, trustPKs, updatePII) gating. EP does — every other flow today does not. Bookends that don’t need it set |
Upload form fields |
The bookend’s REST resource accepts a flow-specific multipart form. EP-import takes (eventId, sourceSystemId, trustPKs, updatePII, templateVariantKey, acknowledgeFingerprintWarning, sheetIndex, createCustom1/2/3). Result-import takes (eventId, participantIdMode, applyNumberChanges, pointsCalculator). Whatever the form takes, it lands in |
Caller-key + routing |
The bookend writes a per-uuid caller key to |
Summary rendering |
The summary bookend renders the per-row outcomes its flow produces. Generic outcomes ( |
Recent-imports list |
Each upload bookend’s "Recent imports" panel filters |
4. Common-stage assumptions about IImportJob
The C05 host treats IImportJob as a tagged-union state object. Each stage reads a specific subset and mutates none of it directly — submission flows through service-layer methods (submitColumnMappings, submitCellMappings, resumeFromFingerprint, cancel).
| Stage | Reads from IImportJob |
|---|---|
|
|
|
|
|
|
|
|
|
|
Summary bookend |
|
The C05 host polls getJob(uuid) every few seconds while the operator is on a non-terminal stage; once the job reaches terminal state, polling stops and the host redirects to the summary bookend (resolved via the caller key).
5. Wire-shape contract
The contract above relies on these typed surfaces. Bookends should not invent their own wire shapes for these — extending the existing DTOs keeps the C05 host agnostic.
| Surface | Purpose |
|---|---|
|
The polling target. Already carries the F1–F10 surface: |
|
Columns stage. Carries |
|
Cells stage. Carries |
|
Verifying stage. Per-row sample comparisons with field-level deltas. |
|
Summary stage. The typed |
|
Summary stage. F8 merge-candidate refs surface in the per-row drill-down + the cross-row merge-review panel. |
|
F10 — the variant picker (E06) and the C05 auto-apply both consume the same shape. |
6. Cookbook — adding a new import flow
The shape that worked for EP-import scales. To add a new flow (e.g. Membership Import = M01, or Result Import-redux on the new framework):
-
Decide row-by-row vs whole-file.
-
Row-by-row when each input row maps to one output row independently (membership add). Use
processRow(…); the framework handles iteration and per-row commit boundaries.ImportRowResultis persisted per row out of the box. -
Whole-file when cross-row logic dominates (RESULT: number-change detection, per-category seq upserts). Set
supportsWholeFile()=trueand implementprocessWholeFile(…). Counter-write contract on the managedImportJobmatters — see Whole-File Bridge.
-
-
Extend
ImportType. Add the flow’s enum value inevent-database(one-char code matters for the converter). Bump the database SNAPSHOT. -
Implement the row processor. A
@ComponentextendingAbstractRowProcessorthat returns the newImportTypefromgetImportType(). Wire whatever services it needs (e.g.MembershipServiceEx,RaceResultServiceEx). Decide the F-feature opt-ins:-
If the flow has source-system semantics (SELF / EXTERNAL) → opt into the gating matrix in the controller.
-
If the flow benefits from F7 fingerprint check → override
supportsFingerprintCheck()to returntrue. -
If the flow has typed failure modes → emit
RowFailureMetadatafrom the failure paths viarecordFailureMetadata(…).
-
-
Register field definitions. Per column: name, aliases (the C05 columns stage’s auto-match runs against these case-insensitively),
required,foreignKey(drives the cells stage), mode flags (selfOnly/externalOnlyfor source-system filtering). TheImportFieldDefinitionRegistrylookup is keyed byImportType. -
Add the per-entity REST resource. Mirror
EventParticipantResourceEx.importEventParticipants:-
PUT /api/<entity>/import— multipart with the flow’s form fields. HydrateconfigJsonfrom the form params and callimportJobService.createAndAutoStartImportJob(…)(interactive C05 flow) orcreateWholeFileImportJob(…)(one-shot). -
GET /api/<entity>/import/{jobId}— domain-typed response DTO once COMPLETED/FAILED. ParseresultPayloadJsonto the flow’s response shape.
-
-
Build the upload bookend (Angular). Mirror
e06-upload.component.ts:-
Source-system dropdown (or skip if not applicable) — call
ImportService.listSourceSystems(). -
Flow-specific form fields (event picker, period picker, …).
-
Variant picker (F10) — call
ImportService.listTemplateVariants(sourceSystemId). Three cardinality states. -
Recent-imports panel —
ImportService.listRecent(N)filtered to the flow’sImportType. -
Submit handler calls the per-entity upload endpoint, writes the caller key, navigates to
/imports/<uuid>.
-
-
Build the summary bookend. Mirror
e06-summary.component.ts:-
Header switches on terminal state (COMPLETED / FAILED / CANCELLED + the FINGERPRINT_ABORT-converted-to-FAILED case).
-
Counts panel (
IImportResultsPage.counts). -
Per-row results table — render typed outcomes via the corresponding metadata fields (
rawFirstName/rawLastNamefor UNRESOLVED_PERSON,fkType/fkValuefor FK_MISMATCH, genericdescriptionfor FAILED). -
Merge-candidate panel — surfaces
mergeCandidatesCreated(F8). Hide when empty. -
Ignored-column panel — surfaces
ignoredColumns(F9). Hide when empty.
-
-
Register routes. The flow’s own bookend routes plus the shared
/imports/<uuid>C05 route. The C05 host resolves back-link targets via the caller key — no flow-specific routing inside C05. -
Tests. Per the EP-import precedent:
-
Backend:
RowFailureMetadataTestanalogue if the flow emits typed failures;<entity>ImportXLSTestfor the whole-file path;ImportRowResultDTOTestalready covers the failure-metadata parsing. -
Frontend:
ImportServiceunit tests (when added — admin-portal currently has no frontend test stack); UI smoke via the C05 host’s existing fixture-driven tests.
-
-
Docs. Update F-Feature Status if the flow consumes a new F-feature; cross-link the per-flow design doc and use-case `.adoc`s.
7. Worked example — Result Import (E08)
E08 is the next bookend pair to land. It reuses the contract above with these specifics:
| Concern | E08 specifics |
|---|---|
Importer key |
|
|
|
Row processor |
|
Source-system semantics |
Not applicable — result imports come from timing systems, not registration systems. The upload form skips the source-system dropdown and the |
Upload form |
|
Variant picker (F10) |
Same shape as E06 — same |
Typed failure metadata |
Today’s |
Summary |
|
Use-case |
|
The C05 host (columns / cells / processing stages) is reused unchanged. Verifying stage is bypassed because results don’t have a fingerprint check.
8. Worked example sketch — Membership Import (M01)
Hypothetical, pending US sequencing:
| Concern | M01 sketch |
|---|---|
Importer key |
|
|
|
Row processor |
|
Source-system semantics |
Likely applicable — federation imports may come from external systems (other federations, club databases). Same (is_self, trustPKs, updatePII) matrix as EP would apply if Person identity matching is in scope. To be confirmed during M01 design. |
Upload form |
|
Typed failure metadata |
Same producer retrofit pattern as EP — emit |
Summary |
|
Open design questions to resolve when M01 starts: whether Person matching reuses EventParticipantServiceEx.register’s F4 matching (probably yes — the same identity-correlation logic applies); whether membership-period scoping changes the cell-mapping FK targets (e.g. `MembershipType candidates filtered by period).
9. What still varies between flows
The contract isn’t perfectly uniform — three concerns where flows legitimately differ:
-
Source-system semantics. Only EP (and probably future M01) have them. The C05 host already gates on
sourceSystem != null; flows that opt out simply passnulland the verifying stage is a no-op. -
Typed failure categories.
RowFailureMetadata.categoryis aStringfor forward-compat — flows can emit category values the SPA hasn’t seen yet (frontend falls through to GENERIC). Adding a new typed branch on the summary screen is a paired backend (producer retrofit) + frontend (adapter case) change. -
Whole-file vs row-by-row. Each flow picks one and the framework dispatches automatically. The bridge is documented in Whole-File Bridge.
10. Related Documentation
-
Async Import Architecture — state machine, wire shapes, F-feature catalogue, source-system gating matrix. Read first.
-
[C05 Import Mapping Flow] — the shared host’s design spec.
-
[E06 Import Participants] — the bookend that drove the contract.
-
[E08 Import Results] — the next bookend (currently
design-todo).