08 — Form Specifications (Biometric Module)
- 1. Ingest Punch (device webhook →
POST /biometric/ingest) - 2. Device Register —
POST /biometric/devices(planned) - 3. Device Edit —
PATCH /biometric/devices/:id(planned) - 4. Enrollment — device capture
(planned, blocked — OQ-3) - 5. Manual Punch Entry (clerk fallback) —
(proposed) - 6. Log Filters (Log List) —
(planned) - Form-level rules (all)
- Client-side error priority
Every form field-by-field. Validation mirrors class-validator decorators exactly from
src/modules/biometric/dto/create-biometric-log.dto.ts; planned forms follow the schema fields (biometric-device.schema.ts,biometric-log.schema.ts) and blueprint endpoints. Client validates inline then submits; server 400 shadows client.
1. Ingest Punch (device webhook → POST /biometric/ingest)
Real today (create-biometric-log.dto.ts, biometric.controller.ts:14-18). Not a
user form — the device/agent posts it; documented here because the client may preview a
manual "enter punch" path for clerks.
| # | Field | Type | Required | Validation (server) | Notes/UX |
|---|---|---|---|---|---|
| 1 | studentId | text (MongoId) | ✓ | @IsMongoId() (create-biometric-log.dto.ts:5-7) | shape-only check; no existence check (edge, 01 §8) |
| 2 | deviceId | text | ✓ | @IsString() (:9-11) | no registry check — any string accepted today |
| 3 | timestamp | datetime | ✓ | @IsDateString() (:13-15) | device clock time; keep original punch time for derivation correctness (blueprint COLLECTIONS.md:1883) |
| 4 | mode | text | – | @IsOptional() @IsString() (:17-20) | free string (finger/QR/card…) — no enum server-side |
Server response: created BiometricLogDocument (schema biometric-log.schema.ts:7-27).
rawData is schema-only — cannot be posted today (gap, 01 OQ-2).
2. Device Register — POST /biometric/devices (planned)
Endpoint blueprint Biometric.md:26; fields from biometric-device.schema.ts:15-31.
| # | Field | Type | Required | Validation (mirror) | Notes/UX |
|---|---|---|---|---|---|
| 1 | name | text | ✓ | @IsString() | autofocus; max 48 chars (client) |
| 2 | deviceId | text | ✓ | @IsString() | monospace hint "printed on device"; unique per tenant (:36) |
| 3 | model | text | – | @IsOptional() @IsString() | vendor model string |
| 4 | location | text | – | @IsOptional() @IsString() | "Main gate", "Library" |
| 5 | status | select | – (default active) | enum DeviceStatus (:7-11,24-25) | segmented Active/Inactive/Offline |
| 6 | config | JSON | – | @IsOptional() object | advanced disclosure; non-secret config only (07 §8) |
Errors: 409 duplicate deviceId → inline banner; 400 validation → per-field.
3. Device Edit — PATCH /biometric/devices/:id (planned)
Same fields as §2, all optional (partial update); status change to offline via "Disable"
confirm dialog rather than raw edit.
4. Enrollment — device capture (planned, blocked — OQ-3)
No HTTP form fields yet; the flow is device-driven (screen 06 §5). The eventual wire
payload per blueprint COLLECTIONS.md:1872-1892 + Biometric.md:57:
| Planned field | Type | Meaning |
|---|---|---|
employeeCode | string | vendor/user code — maps punch → studentId/userId via config |
| template | blob/hash (format TBD) | storage format undecided — hash? encrypted blob? vendor raw? (OQ-3) |
studentId / userId | MongoId | resolved identity (ref Student as in biometric-log.schema.ts:9-10) |
Enrollment form (client): student search → confirm. Never renders template bytes.
5. Manual Punch Entry (clerk fallback) — (proposed)
Post-sync-clinic fallback form = §1 fields + student search picker (resolves
studentId). Gated biometric.log.create (permissions.constants.ts:41); flagged
"manual entry" in mode so derivation/audit can distinguish (blueprint keeps raw payload
for replay, Biometric.md:56).
6. Log Filters (Log List) — (planned)
| Field | Type | Notes |
|---|---|---|
| student | search chip | deep-link ?student=:id |
| device | dropdown | from GET /biometric/devices |
| date range | date picker presets | Today / This week / Custom |
| mode | chips | multi-select; free strings client-side |
No server filter contract yet (planned GET /biometric/logs, blueprint Biometric.md:30).
Form-level rules (all)
- Double-submit: disabled while pending; device ingest must be idempotent-safe client side (retry sends identical body; dedupe is derivation's job — logs are immutable).
- Optimistic: no optimistic writes for register/disable/sync (server-confirm, 00-shared/08 §4).
- Keyboard:
.nextsequence,.donesubmits; register form 6 fields single column. - Error copy: from
messageof envelope for business 4xx (00-shared/07); codes for the rest.
Client-side error priority
- 400 VALIDATION → field.
- 401 UNAUTHENTICATED → session-expiry overlay (global).
- 403 FORBIDDEN → permission screen ("You don't have device access").
- 409 DUPLICATE_RESOURCE → inline (deviceId duplicate).
- 429 RATE_LIMITED → countdown (ingest is high-volume — device path should use a machine token, not user rate tiers — OQ-5).
- 5xx → AppErrorState.