Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

03 — User Journeys (Attendance Module)

Six end-to-end journeys. All API calls are exact (see 12_API_Mapping.md); flags (planned)/(forward-looking) mark backend gaps (see 01_Product_Overview.md §10).


1. Mark today's class (teacher, happy path)

sequenceDiagram
    actor T as Teacher
    participant G as MarkingGrid (S2)
    participant R as RosterCubit
    participant A as Attendance API
    participant Q as attendance-process queue
    participant W as AttendanceWorker

    T->>G: open Class → "Mark attendance"
    G->>R: load(classId, today)
    R->>A: GET /attendance/class/:classId?date=YYYY-MM-DD
    A-->>R: 200 {data:[...existing marks]}
    R->>G: roster rows (default present) + existing marks overlaid
    T->>G: tap student "Aarav" → cycle to absent
    T->>G: tap student "Meera" → cycle to late
    G->>R: mark(studentId, status)  [optimistic toggle]
    R->>A: POST /attendance {studentId, classId, date, status}
    A-->>R: 201 {data: doc}  (upsert; overwrites if already marked)
    R->>G: row settled, StatusChip confirmed
    A->>Q: AttendanceMarked {studentId, status, date}
    Q->>W: process-attendance job
    W-->>W: absence watch (log-only today, OQ-1)

Fast path: all-absent sweep → POST /attendance/bulk one payload (attendance.service.ts:48-54).

2. Batch update (whole class in one request)

sequenceDiagram
    actor T as Teacher
    participant G as MarkingGrid (S2)
    participant B as BatchEditSheet (S3)
    participant R as RosterCubit
    participant A as Attendance API

    T->>G: long-press "Sweep to absent" (or select-all)
    G->>B: sheet: 34 rows selected, status chips visible
    T->>B: tap "absent" on all → "Apply to 34"
    B->>R: batchMark([{studentId,classId,date,status}...])
    R->>A: POST /attendance/bulk {records:[...]}
    A-->>R: 201 {data:[docs]} (sequential marks, one event each)
    R->>G: grid settles, absent count banner "34 absent"
    Note over B: partial failure → per-record result mapping (OQ-2 pattern)

3. Mark-late workflow (late + check-in time)

sequenceDiagram
    actor T as Teacher
    participant G as MarkingGrid (S2)
    participant S as StatusChip popover
    participant R as RosterCubit
    participant A as Attendance API

    T->>G: student arrives late, row currently present
    T->>G: tap row → StatusChip popover
    S-->>T: status options + optional checkIn/remarks fields
    T->>S: choose "late", checkIn = 09:15 (defaults to now)
    S->>R: mark(status=late, checkIn)
    R->>A: POST /attendance {studentId,classId,date,status:late,checkIn}
    A-->>R: 201 upsert (present → late overwritten in place)
    R->>G: chip morphs present→late (m-fast), row tinted warning

Same flow later in the day = PATCH instead: PATCH /attendance/:id {status, checkIn} (attendance.service.ts:81-101) — emits AttendanceUpdated {changes:[...]}.

4. View monthly report (admin / teacher)

sequenceDiagram
    actor U as Admin/Teacher
    participant M as MonthlyReport (S5)
    participant D as ReportDetail (S6)
    participant A as Attendance API
    participant J as report-generate queue
    participant W as ReportsWorker

    U->>M: Reports → Attendance, pick class + month
    M->>A: GET /attendance/summary?classId&startDate&endDate
    A-->>M: 200 {total, summary:{present:812,absent:34,...}}
    M->>A: POST /reports/generate {type:'attendance_summary',...}
    A-->>J: job queued {jobId, status:'queued'}
    J-->>W: generate job
    M->>A: GET /reports/:jobId (poll)
    A-->>M: {status:'completed', result:{total, summary, period}}
    M->>D: tap a day/student → drill into history
    D->>A: GET /attendance/student/:studentId?startDate&endDate

reports.service.ts:28-44,110-137; reports.controller.ts:14-24; report.worker.ts:7.

5. Parent absentee alert

sequenceDiagram
    actor T as Teacher
    participant A as Attendance API
    participant Q as attendance-process queue
    participant W as AttendanceWorker
    actor P as Parent

    T->>A: POST /attendance {status: absent}
    A->>Q: AttendanceMarked event
    Q->>W: process-attendance
    W->>W: count student's absent docs (all-time, not consecutive)
    Note over W: >= 3 → "alert recommended" LOG ONLY (attendance.worker.ts:69-80)
    Note over P: NO dispatch today. Target (planned, PLAN.md:59): in-app + push
    P->>P: future: notification → deep link studylyon://attendance/:date

Honest copy constraint: the app must say "3 absences this month" — the worker counts all absent records without a window (attendance.worker.ts:70-74), so "consecutive" must never be claimed (OQ-1).

6. Biometric-captured flow

sequenceDiagram
    actor D as Device (operator/forward-looking)
    participant B as Biometric API
    participant L as biometric_logs
    participant Q as biometric-sync queue (cron */15)
    participant W as SyncWorker
    participant A as Attendance docs

    D->>B: POST /biometric/ingest {studentId, deviceId, timestamp, mode?}
    B->>L: create raw log (biometric.service.ts:14-16)
    Note over B: deviceId must be a registered device (OQ-3 device registry)
    Q->>W: biometric-sync repeatable job (scheduler.service.ts:71-76)
    Note over W: NO worker consumes biometric-sync today → derivation (planned)
    Note over A: target: punches → attendance docs via AttendanceMarked/import

UI implication (S8 Device status): show "N punches captured today — not yet applied" until the pipeline is built (OQ-3).


Journey → screen/component map

JourneyScreensKey components
1. Mark todayS1, S2AttendanceGrid, StatusChip, MarkedCountBar
2. Batch updateS2, S3BatchEditSheet, StatusChip, selection model
3. Mark-lateS2StatusChip popover, time field
4. Monthly reportS5, S6CalendarHeatmap, SummaryDonut, status legend
5. Parent alertS7 (+ notifications)AbsenceAlertCard, day deep-link
6. BiometricS8BiometricDeviceTile, ingest trend