03 — User Journeys (Attendance Module)
- 1. Mark today's class (teacher, happy path)
- 2. Batch update (whole class in one request)
- 3. Mark-late workflow (late + check-in time)
- 4. View monthly report (admin / teacher)
- 5. Parent absentee alert
- 6. Biometric-captured flow
- Journey → screen/component map
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
| Journey | Screens | Key components |
|---|---|---|
| 1. Mark today | S1, S2 | AttendanceGrid, StatusChip, MarkedCountBar |
| 2. Batch update | S2, S3 | BatchEditSheet, StatusChip, selection model |
| 3. Mark-late | S2 | StatusChip popover, time field |
| 4. Monthly report | S5, S6 | CalendarHeatmap, SummaryDonut, status legend |
| 5. Parent alert | S7 (+ notifications) | AbsenceAlertCard, day deep-link |
| 6. Biometric | S8 | BiometricDeviceTile, ingest trend |