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

01 — Product Overview (Attendance Module)

StudyLyon — multi-tenant ERP / School Management API. This package designs the Attendance module client (Flutter, forward-looking spec) against the implemented NestJS backend. Every endpoint, DTO field, schema, index, event, worker, and permission is derived directly from src/modules/attendance/**, src/modules/biometric/**, src/infrastructure/workers/attendance.worker.ts, src/infrastructure/bullmq/**, src/modules/scheduler/**, src/modules/reports/**, and src/modules/rbac/**. No feature is invented; gaps are flagged in Assumptions & Open Questions (§10) and marked (planned) / (forward-looking) / (proposed) inline.


1. Purpose

Attendance records one status per student per day — the daily truth a school runs on: who came, who didn't, who was late, who took leave. It is captured by teachers in the classroom (manual marking), ingested from biometric devices (raw punch logs), corrected after the fact (edit), and consumed everywhere: monthly reports, dashboards, absentee alerting, and parent/student visibility.

The domain is deliberately small: one attendance document = one student, one date, one status (attendance.schema.ts:24-55), enforced by a unique index (attendance.schema.ts:59-62).

ResponsibilitySource
Mark one student's attendance for a date (idempotent upsert)attendance.service.ts:24-46; attendance.repository.ts:17-37
Batch-mark many students at onceattendance.service.ts:48-54 (bulkMark)
Read class roster status for a dateattendance.service.ts:56-61
Read a student's history (optional date range)attendance.service.ts:63-73
Read a single record / correct it (edit)attendance.service.ts:75-79, 81-101
Class summary over a date range (per-status counts)attendance.service.ts:103-113
Emit AttendanceMarked / AttendanceUpdated domain eventsattendance.service.ts:37-44, 88-99
Queue events → BullMQ attendance-process workerevent-queue-map.ts:14-21; attendance.worker.ts:16
Absentee watch (≥3 absences → alert log)attendance.worker.ts:69-80
Biometric punch ingestion (raw logs)biometric.controller.ts:14-18; biometric.service.ts:14-16
Attendance summary report (async job)reports.service.ts:110-137; report-job.schema.ts:8-9
Daily attendance report schedule (07:00 UTC)scheduler.service.ts:99-104; attendance-report.job.ts:13-24
Dashboard attendance KPI (today, present %)dashboard.service.ts:28-65

2. Business goals

GoalMeasure
Classroom marking is fastSingle tap-cycle status per student; batch covers the whole class in one request (mark-attendance.dto.ts:51-54)
No double recordsUnique index {tenantId, studentId, date} (attendance.schema.ts:59-62) + upsert findOrCreate (attendance.repository.ts:17-37)
Corrections are auditableAttendanceUpdated event carries the changed field keys (attendance.service.ts:94-98); every doc has createdBy/updatedBy/version (base.schema.ts:13-31)
Absentee parents get alertedAttendanceMarkedattendance-process queue → ≥3-absence alert path (attendance.worker.ts:69-80) — currently log-only, dispatch (planned) (OQ-1)
Reports without blocking the APIAsync report-generate queue + report_jobs status polling (reports.controller.ts:14-24)
Multi-tenant isolationEvery query tenant-scoped by BaseRepository.scopedFilter (base.repository.ts:20-30); cross-tenant IDs → 404

3. User goals

  • Teacher: mark a whole class in under a minute (bulk), fix a wrong status fast (PATCH), see today's marked/unmarked at a glance, never lose work on a slow connection.
  • Org admin: monthly attendance rates per class, drill into per-student history, ensure devices are pushing punches (biometric-device.schema.ts:7-11).
  • Parent: get alerted the moment a child is marked absent, view the child's month at a glance.
  • Student: see own attendance % and history (END_TO_END_USER_FLOWS.md:324-325).
  • Biometric operator: ingest device punches, verify device health, understand what becomes attendance (and what currently doesn't — see OQ-3).

4. Stakeholders

Teachers (primary marker), class teachers (roster ownership), org admins (reports and policy), parents/students (consumers), biometric device operators (integration), scheduler (jobs), support staff (correction disputes), QA/design/engineering.

5. Why this exists

Attendance is the highest-frequency daily data-entry task in a school and the basis of report cards, fee policy, and parent trust. The backend makes the write path idempotent and cheap; the client's job is to make the capture surface so fast that teachers actually use it, and the consumption surfaces so clear that parents trust it.

6. Scope boundaries (exact, from code)

In scope today (backend): single mark, bulk mark (sequential loop over mark(), attendance.service.ts:48-54), class-by-date read, student history read, summary counts, PATCH edit, biometric log ingest (store-only), async attendance summary report, dashboard KPI, scheduler cron registration, absentee watch (log-only).

Not implemented (flag labels used below):

  • Session-based attendance (sessionId, attendanceMode: 'session') — (planned), schema additions sketched in docs/IMPLEMENTATION_PLAN.md:432-468; the live schema has no session field (attendance.schema.ts).
  • POST /attendance/import bulk import endpoint — (planned); blueprint 04-Modules/Attendance.md:30 lists it; only the worker's bulk-import branch exists (attendance.worker.ts:49,83-102), and it creates records directly with per-record error swallowing (duplicate keys fail silently — OQ-2).
  • Biometric logs → attendance derivation — (planned)/(forward-looking); biometric-sync queue + */15 * * * * cron exist (scheduler.service.ts:71-76, queue.constants.ts:7) but no worker consumes it; biometric.service.ts:14-16 only writes the log. Blueprint RELATIONSHIPS.md:100 intends biometric_logs.processed → attendance.
  • Absentee notifications to parents — (planned); attendance.worker.ts:69-80 only logs "alert recommended". PLAN.md row 5.4 describes the target (AttendanceMarkedin-app + push queues).
  • Push/QR device flows — (forward-looking) (shared ledger B3/B4: no device-token registry, no QR backend).
  • Client-side analytics — (proposed) per 00-shared/10_QA_Baseline.md §8.
  • RBAC enforcement — permissions attendance.mark/attendance.edit are defined (permissions.constants.ts:29-30) and seeded (role.schema.ts:31), global guards are RateLimitGuard → JwtAuthGuard → RbacGuard (app.module.ts:129-131), but the attendance controller declares no @Permissions() decorator (attendance.controller.ts:19-23) — treat 403 as expected contract (OQ-5).
  • Pagination on attendance list endpoints — not implemented; reads return plain arrays without meta (OQ-4, see 12_API_Mapping.md).

7. Success metrics

  • Teacher marks a 40-student class in < 60 s (bulk request ≤ 1 payload).
  • 100% of mark attempts succeed or surface a clear error — the upsert means retries are safe (idempotent, attendance.repository.ts:23-29).
  • Zero duplicate attendance docs per tenant (unique index enforcement, plus client dedupe in offline queue).
  • Correction latency: PATCH round-trip < 500 ms p95; conflict-free because PATCH replaces only sent fields (update-attendance.dto.ts:4-22 + $set, base.repository.ts:57-66).
  • Attendance report generated async without request timeout (reports.service.ts:28-44).

8. Edge cases (backend-derived)

  • Remark same student+date → overwrite (upsert), source stays manual unless supplied; classId on an existing doc is not updated by remark (attendance.repository.ts:23-29).
  • Same student marked for a different class on the same date → still one doc (unique on studentId+date), original classId wins — client must prevent cross-class remarking.
  • Invalid status on POST → 400 VALIDATION_ERROR (IsEnum, mark-attendance.dto.ts:27-28; e2e p1-operations.e2e-spec.ts:222-228).
  • Invalid status on PATCH → not enum-validated (update-attendance.dto.ts:8 is IsString only) — server may persist a bad status via $set (no runValidators); client must constrain the picker (OQ-6).
  • Absentee count in the worker counts all absent records for the student, not consecutive days within a window (attendance.worker.ts:70-74) — UI must not promise "3 consecutive absences" semantics (OQ-1).
  • Duplicate records in bulk-import → per-record catch → logged warn, not failed (attendance.worker.ts:91-101).
  • date is sent as YYYY-MM-DD string and stored as Date (mark-attendance.dto.ts:21; attendance.schema.ts:31-32) — timezone discipline is a client responsibility (see 14_QA_Checklist.md §timezone).
  • Deleted/cross-tenant record → 404 "Attendance record not found." (attendance.service.ts:77).
  • Summary with no records → {total: 0, summary: {}} (attendance.service.ts:112).

9. Assumptions (module)

  • Client is forward-looking (see shared ledger A1 — PRD Phase 1 is backend; these docs specify the full Flutter client per user instruction).
  • One attendance per student per day is the only current model; "per session" is (planned) and the client's calendar/history must not assume per-period rows.
  • Teacher role carries ['student.read','attendance.mark','attendance.edit'] (role.schema.ts:31); admin has everything (role.schema.ts:23). Parent and student roles have no attendance permission today (role.schema.ts:55,63) — the student profile and parent views must be built against student.read + the routes those roles reach (see 12_API_Mapping.md §9, OQ-7).
  • Enrollment is the roster source: Student.classId/sectionId (student.schema.ts:38-39) and class_enrollments (class-enrollment.schema.ts:14-38); the marking grid renders students of one class.
  • Retry safety: because mark is an upsert, a re-sent offline batch is harmless (repeated sends converge), but events fire per mark — network-layer dedupe is still recommended to avoid duplicate AttendanceMarked events.

10. Open questions (module; global ledger in 00-shared/12)

#ItemImpact
OQ-1Absentee worker counts all absences (not consecutive, no window) and only logs "alert recommended" — no notification is dispatched. When does the real parent alert land, and what are its semantics?Alert copy, "consecutive vs total" messaging
OQ-2bulk-import worker swallows per-record failures (duplicate keys). Should failures surface in a result report for the importing admin?Import UX, partial-success UI
OQ-3biometric-sync queue + 15-min cron exist but no worker consumes them; logs never become attendance docs. When is the punch→attendance derivation built?Device status screen promises
OQ-4Attendance reads return unpaginated arrays (a 60-student day is fine, but a term of history is not). Is pagination added?History screen strategy (client-side paging fallback)
OQ-5No per-endpoint RBAC decorators on attendance; global RbacGuard present (app.module.ts:131) but no permission metadata on the controller. Confirm the server contract for 403.Permission-gated UI visibility
OQ-6PATCH status is a free string (no IsEnum) and findOneAndUpdate runs without runValidators — a bad status may persist. Server fix or client-only constraint?Status picker validation strategy
OQ-7Parent/student roles have no attendance permissions; yet user flows (END_TO_END_USER_FLOWS.md:324-325,378-379) show students/parents reading attendance. Which permission should gate read-only history?Parent/student screen gating
OQ-8getSummary and reports aggregate in memory (no aggregation pipeline) — a full term × all students could be slow. Watch for latency at scale (INDEXING.md:67,93 notes sharding candidacy).Report UX, loading states

11. Glossary (this module)

TermMeaning
Attendance docOne row: studentId + classId + date + status (+ checkIn/checkOut/source/remarks)
Statuspresent, absent, late, half_day, leave, holiday (attendance.schema.ts:7-14)
Sourcemanual, biometric, import, api (attendance.schema.ts:16-21)
MarkPOST create/upsert of one day's record
Remark / OverwriteSame student+date POST again → fields overwritten in place
BulkPOST /attendance/bulk — sequential marks for many records (attendance.service.ts:48-54)
PunchRaw biometric_logs row (student, device, timestamp) — not yet attendance
RosterStudents of a class (via student.classId) rendered for marking
Summary{total, summary:{status:count}} from GET /attendance/summary
Attendance reportAsync attendance_summary report job (report-job.schema.ts:9)
Envelope{success,message,data,meta?,timestamp,requestId} (shared 00-shared/07)