Progress, streaks & certificates

What the mobile app reports as somebody learns, and what the platform does with it · ← platform hub

In one sentence

As a learner moves through a lesson the app tells micro-learning, which keeps the authoritative progress record, mirrors it to assignment-service so the admin roster is accurate, issues a certificate on completion, and maintains the streak and points that make people come back tomorrow.

The app reports

The mobile app calls micro-learning's progress endpoints as the learner starts, advances through and finishes a lesson. micro-learning stores this and emits per-lesson events: learning.lesson.started, .progressed, .completed, .abandoned (micro-learning-service-v2/app/routes/progress_mobile_routes.py:783, :900, :1264, :1514). These are telemetry — they are stored on the stream and, today, read by nobody.

Quiz answers follow the same path with scoring on top; the quiz graph decides what the learner sees next based on how they answered.

Mirroring to assignments

sequenceDiagram
  autonumber
  participant APP as Mobile app
  participant ML as micro-learning
  participant AS as assignment-service
  APP->>ML: lesson progress
  ML-->>AS: training.progress.assignment_progress
  Note over AS: mirror onto assignment_progress rows
  APP->>ML: last lesson finished
  ML-->>AS: training.module.completed
  AS-->>AS: drive assignment to 100%

Two different signals, both live, and the distinction matters:

Completion & certificates

Certificates are issued inside micro-learning — there is no certificate service any more, despite the leftover cluster objects. On completion it emits training.certificates.module_completed (app/events/events.py:257), and the learner can fetch their certificate from the app.

Streaks, points and leaderboards

A scheduler scans daily for learners whose streak is about to lapse — evaluated in their local timezone, so a Tokyo learner is not warned on London's clock — and publishes learning.streak.at_risk.ten.<tenant> (app/services/streak_reminder_scheduler.py:183). notification-worker delivers it, respecting quiet hours: losing a streak is not worth waking somebody up.

Leaderboard movements are published as learning.leaderboard.*.ten.* (app/services/leaderboard_diff_service.py:664) and consumed on learning.leaderboard.>.

ARCHITECTURE.md §4.1(3) is out of date on this point. It records that streak notifications can never fire because the publisher emits a flat learning.streak.at_risk while the consumer filters …ten.*. The current publisher appends .ten.<tenant>, so both sides are five tokens and the path works. Verified from both ends — see the publisher and the consumer.

Where it breaks today

IssueDetail
Per-lesson telemetry is written and never readThe four learning.lesson.* events are stored on the MICRO_LEARNING stream via learning.> but no consumer filter matches them. If the intent was analytics, it was never built. See the micro-learning verdicts.
Two dead branches in the progress routerassignment-service's progress consumer routes three branches; only assignment_progress has a producer. The lesson-completion and completion branches are unreachable. The consumer itself is live — see the assignment verdicts.
Stale comments and reportsThe worker's own comment claims the training.progress.> subscription has no producer; bug-hunt #8 and the earlier review say the same. All three are now out of date. Flagged for a later code-comment fix; no code was changed by these docs.
Module lifecycle events go nowheretraining.module.created, .archived, .assigned and user.notification.training have no consumer, and three of the four have emitters with no call site at all.