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
| Command | Trigger | Result |
|---|---|---|
IngestStatement | POST /xapi/statements or event consumption | Statement persisted + events emitted |
OpenAttempt | First statement with unseen attemptId | Attempt row created |
CloseAttempt | completed/failed/abandoned verb observed | Attempt state closed; outcome set |
RecordCompletion | First passed statement in attempt | CompletionRecord created idempotently |
ReplayStatements | Admin replay job | Rebuild projections from statement log |
3. Queries
| Query | Endpoint |
|---|---|
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.v1within 7 days. - Participates in Completion Flow: emits
progress.completion.recorded.v1which triggers certification issuance and assignment-window completion.
5. Policies
- Idempotency: Statement with duplicate
statementIdsilently 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);
timestampsorts the ledger,storedsorts 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.