Skip to main content

Application Logic

:::info Source Sourced from services/progress-service/APPLICATION_LOGIC.md in the documentation repo. :::

1. Application Services

  • StatementIngestionService — validates + persists xAPI statements.
  • AttemptLifecycleService — opens/closes attempts based on statements.
  • CompletionService — detects first passing statement → records completion.
  • TranscriptService — aggregates statements per enrollment into transcript.
  • XAPIQueryService — serves xAPI 1.0.3 statement query API.

2. Commands

CommandTriggerResult
IngestStatementPOST /xapi/statements or event consumptionStatement persisted + events emitted
OpenAttemptFirst statement with unseen attemptIdAttempt row created
CloseAttemptcompleted/failed/abandoned verb observedAttempt state closed; outcome set
RecordCompletionFirst passed statement in attemptCompletionRecord created idempotently
ReplayStatementsAdmin replay jobRebuild projections from statement log

3. Queries

QueryEndpoint
getStatement(id)GET /xapi/statements/{id}
queryStatements(filters)GET /xapi/statements?agent=...&verb=...
getAttempt(id)GET /api/v1/attempts/{id}
getEnrollmentProgress(eid)GET /api/v1/enrollments/{eid}/progress
getTranscript(eid)GET /api/v1/enrollments/{eid}/transcript
getUserTranscripts(uid)GET /api/v1/users/{uid}/transcripts

4. Sagas

  • Participates in GDPR Erasure Saga: deletes statements, attempts, completion records for erased user. Emits gdpr.subject_request.acknowledged.v1 within 7 days.
  • Participates in Completion Flow: emits progress.completion.recorded.v1 which triggers certification issuance and assignment-window completion.

5. Policies

  • Idempotency: Statement with duplicate statementId silently accepted (same row exists) — xAPI specification requirement.
  • Attempt attribution: statement without matching enrollment rejected (validation.enrollment.not_found).
  • Tenant anchoring: actor derived from JWT; tenant from tid; mismatch rejected.
  • Out-of-order handling: statements can arrive out of order (offline replay); timestamp sorts the ledger, stored sorts the ingestion log.

6. Use Case Flows

6.1 Online Statement Ingestion

Client (delivery-service) ─▶ POST /xapi/statements


IngestionService.validate(statement)


Find or create Attempt (by attemptId)


Persist statement (INSERT, idempotent on statementId)


Emit progress.statement.stored.v1 (outbox)


If verb = "passed"/"completed" → CloseAttempt + RecordCompletion


Emit progress.completion.recorded.v1 (outbox)

6.2 Offline Statement Replay

Client has queued statements in client statements-outbox.
On reconnect, client POSTs batch (up to 1000 statements).
IngestionService processes each with dedup (statementId PK).
Events emitted in order.
Analytics firehose receives with original `timestamp`.

6.3 Transcript Generation

GET /api/v1/enrollments/{eid}/transcript


TranscriptService.forEnrollment(eid)

├─ SELECT attempts WHERE enrollmentId = eid ORDER BY attemptNumber
├─ For each attempt: aggregate statements (verbs, interactions, score)
├─ Include completion record if exists
└─ Include certificate ref if exists (via certification-service)


Render (PDF or JSON)

6.4 xAPI Query

Supports xAPI 1.0.3 query params: agent, verb, activity, registration, since, until, limit, ascending, format. Cursor-based pagination under the hood.