01 — Product Overview (Biometric Module)
- 1. Purpose
- 2. Business goals
- 3. User goals
- 4. Stakeholders
- 5. Why this exists
- 6. Dependencies
- 7. Success metrics
- 8. Edge cases
- 9. Assumptions (module)
- 10. Open questions (module-level; global ledger in 00-shared/12)
- 11. Glossary (this module)
StudyLyon — multi-tenant ERP / School Management API. This package designs the Biometric module client (Flutter admin console, forward-looking spec) against the implemented NestJS backend. All endpoints, DTO fields, schemas, queues, and permissions are derived directly from
src/modules/biometric/**,src/modules/attendance/**,src/infrastructure/bullmq/**,src/modules/rbac/permissions.constants.ts, and the authoritative blueprint (studylyon-blueprint/04-Modules/Biometric.md). No feature is invented; gaps are flagged in Assumptions & Open Questions.
1. Purpose
Biometric is StudyLyon's device-integration layer for attendance capture
(studylyon-blueprint/04-Modules/Biometric.md:3). Hardware fingerprint/QR devices at the
school gate produce raw punches; the Biometric module ingests them as immutable raw
events (biometric_logs) and the Attendance module derives queryable attendance records
from them (RELATIONSHIPS.md:95-103).
Today the backend implements only the ingest half of that vision: one endpoint
(POST /biometric/ingest) that persists a raw log. Everything else — device
registration, sync, enrollment, verification, attendance derivation — is (planned)
per the blueprint.
| Responsibility | Status | Source |
|---|---|---|
| Ingest raw punch logs (immutable) | ✅ real | biometric.controller.ts:14-18 |
Store device registry (biometric_devices) | ✅ schema + repo (no CRUD API yet) | biometric-device.schema.ts:13-32 |
| Register / manage biometric devices | 🚧 planned | blueprint 04-Modules/Biometric.md:13 |
Poll / sync device punches (biometric-sync queue) | 🚧 queue + scheduler exist; worker stub | queue.constants.ts:7, scheduler.service.ts:70-76 |
| Map employee codes → student/staff | 🚧 planned (employeeCode field) | blueprint COLLECTIONS.md:1878-1879 |
Trigger attendance derivation (BiometricImported) | 🚧 planned event | blueprint EVENTS.md:18 |
| Template enrollment / verify / match | 🚧 planned — no schema, no endpoint | blueprint Biometric.md:13-19 |
| Verify device health | 🚧 planned | blueprint Biometric.md:19 |
2. Business goals
| Goal | Measure |
|---|---|
| Attendance marking latency < 1 s (PRD) | PRODUCT_REQUIREMENTS_DOCUMENT.md:136 |
| Zero human double-entry at the gate | raw punch → derived attendance, no clerk retyping |
| Audit-grade attendance evidence | logs are insert-only, never updated (biometric-log.schema.ts:7, blueprint COLLECTIONS.md:1866) |
| Immutable forensic record | raw payload preserved for replay (rawData, biometric-log.schema.ts:21-22) |
| Operational resilience | sync failures retried via BullMQ; exhausted → DLQ (blueprint Biometric.md:58) |
| 1-year retention bound | biometric_logs TTL + archive (blueprint DATA_RETENTION.md:25) |
3. User goals
- School admin / ops: add a gate device once, see sync status, resolve offline devices, trust that every punch lands in attendance.
- Office clerk (device operator): enroll a student's fingerprint on a device, watch the enrollment confirm, verify a disputed check-in.
- Teacher: know a student scanned in before class starts (biometric-fed attendance).
- Parent
(planned): see the child's biometric check-in silently reflected in the daily attendance record. - Platform admin: cross-tenant device fleet health, vendor-SDK risk tracking.
4. Stakeholders
Institution admins, office clerks, teachers, students/parents (passive), device-vendor support, platform operator, audit/compliance board, QA + design + engineering.
5. Why this exists
School gate attendance is the highest-frequency, highest-trust data point in a school day.
Manual marking is error-prone and unverifiable; biometric devices make the punch
tamper-evident. The PRD makes device capture a first-class requirement (FR-ATT-02,
PRODUCT_REQUIREMENTS_DOCUMENT.md:87) and the acceptance criteria demand that "biometric
capture maps to a verified student record" (ACCEPTANCE_CRITERIA.md:39).
6. Dependencies
| Dependency | Role | Source |
|---|---|---|
| Attendance module | consumes BiometricImported → derived records; AttendanceSource.BIOMETRIC | blueprint Biometric.md:47; attendance.schema.ts:18 |
| Students module | studentId ref on every log (ref: 'Student') | biometric-log.schema.ts:9-10 |
BullMQ biometric-sync | periodic device polling (*/15 * * * *) | queue.constants.ts:7; scheduler.service.ts:70-76 |
BullMQ attendance-process | attendance derivation worker | event-queue-map.ts:14-17; attendance.worker.ts:16 |
Integrations module (planned) | encrypted device credentials/config | blueprint Biometric.md:48,59 |
Audit module (planned) | immutable log-write trail | blueprint Biometric.md:49 |
| RBAC | biometric.log.create, biometric.log.read, biometric.device.manage | permissions.constants.ts:41-43 |
| Mongo collections | biometric_logs, biometric_devices | biometric.module.ts:21-24 |
7. Success metrics
- Ingest P95 < 1 s; zero log drops on the happy path.
- Punch → attendance record derived < 2 min (sync cadence 15 min)
(planned). - ≥ 99% of daily punches reconciled against attendance.
- Zero cross-tenant log leakage (tenant-scoped repos,
BaseRepository). - Offline device detected and flagged within one sync cycle
(planned).
8. Edge cases
- Duplicate punch: two logs for the same student/second; logs are immutable — dedupe
is the derivation layer's job (blueprint keeps raw punches;
processedflag planned,COLLECTIONS.md:1888). - Unknown deviceId:
ingestaccepts any string — no device registry check today (create-biometric-log.dto.ts:9-11). - Unknown studentId:
@IsMongoIdonly checks shape, not existence (create-biometric-log.dto.ts:5-7); orphan log persists. - Tenancy:
tenantIdis taken from the JWT claim byBaseRepository— butingestnever runs tenant logic of its own (see OQ-1). rawDatagap: schema storesrawData(biometric-log.schema.ts:21-22) but the DTO does not accept it (create-biometric-log.dto.ts:4-20) — the forensic payload cannot actually be ingested (OQ-2).- Employee-code mapping: blueprint field
employeeCode(COLLECTIONS.md:1878-1879) is absent from the implemented schema — mapping punches to people is done by the caller today (device pushesstudentId). as any:biometric.service.ts:15casts the DTO withas any— strict-TSno-explicit-anyis currentlyoffin ESLint (AGENTS.md hard rule flags this).
9. Assumptions (module)
- PRD scope excludes native mobile apps — web-first (
PRODUCT_REQUIREMENTS_DOCUMENT.md:144). The Flutter client in this package is therefore a forward-looking admin console, not the capture surface. Capture happens on vendor hardware + SDK, which is exactly why the roadmap is gated: "Biometric depends on hardware vendor SDK availability" (FEATURE_ROADMAP.md:56); device integration is unchecked on the roadmap (:26). - No template storage exists anywhere. The implemented module stores no
fingerprints, hashes, or templates — only punch metadata (
studentId, deviceId, timestamp). Enrollment/verify/match are(planned)and their storage format is an open product decision (OQ-3). - The
biometric-syncworker is a stub. The queue is real (queue.constants.ts:7), registered (bullmq.module.ts:34), and scheduled (scheduler.service.ts:70-76), but no@Processor('biometric-sync')exists — jobs are enqueued but never processed (OQ-4). - No RBAC metadata on the ingest endpoint — any authenticated user may call it
(no
@Permissions(...), OQ-5). AttendanceSource.BIOMETRICexists but nothing sets it (attendance.schema.ts:18); no code path links a biometric log to amark()call.- Coaching mode: blueprint plans session-based (not daily) check-in for coaching
tenants (
IMPLEMENTATION_PLAN.md:777). - Permissions: blueprint names
biometric.read/biometric.sync(Biometric.md:66-68); the implemented constants instead declarebiometric.log.readand no sync perm (permissions.constants.ts:41-43).
10. Open questions (module-level; global ledger in 00-shared/12)
| # | Item | Impact |
|---|---|---|
| OQ-1 | ingest casts dto as any and never reads TenantContextService; tenant scoping happens implicitly in logRepo.create. Is an explicit tenant guard needed? | Log integrity, cross-tenant audits |
| OQ-2 | rawData is in the schema but not the DTO — ingest the forensic payload now or drop the field? | Forensic/replay promise (Biometric.md:56) |
| OQ-3 | Template storage format (hash? encrypted blob? vendor raw?) — no schema exists. | Entire enrollment/verify UX + privacy posture |
| OQ-4 | Who writes the biometric-sync worker? Jobs currently stall. | Sync status UX is speculative until it lands |
| OQ-5 | POST /biometric/ingest has no permission metadata — device webhooks may need a machine-token path, not user JWTs. | Auth model for devices |
| OQ-6 | Duplicate-punch / dedupe policy: derive at ingest or in attendance? | Attendance correctness |
11. Glossary (this module)
| Term | Meaning |
|---|---|
| Punch | one raw device event (biometric_logs row) |
| Log | immutable raw event: {studentId, deviceId, timestamp, mode?, rawData?} |
| Device | biometric_devices doc: {name, deviceId, model?, status, location?, config?}; status ∈ active/inactive/offline (biometric-device.schema.ts:7-11) |
| Sync | periodic device poll via biometric-sync queue (*/15 * * * *) |
| Template | the enrolled biometric sample — planned, format undecided (OQ-3) |
| Attendance derivation | raw logs → attendance docs via BiometricImported event (planned) |