| Availability |
Odoo Online
Odoo.sh
On Premise
|
| Odoo Apps Dependencies |
•
Attendances (hr_attendance)
• Discuss (mail) • Employees (hr) |
| Community Apps Dependencies | Show |
| Lines of code | 4170 |
| Technical Name |
eh_hr_face_kiosk |
| License | LGPL-3 |
| Website | https://www.erpheritage.com.au/ |
| Versions | 16.0 17.0 18.0 19.0 |
Face Kiosk Attendance
Turn any browser with a camera into a self hosted face attendance kiosk where the device does the embedding and your Odoo does the match.
Why this module
Face Kiosk Attendance
Browser embeds, your Odoo matches
face-api.js produces the 128 float embedding on the device and only that array is POSTed to /eh_hr/kiosk/face/match. Raw frames and intermediate images never touch the network. The server cosine matches against active templates scoped to the device company and posts to hr.attendance. No third party cloud, no per employee subscription, no proprietary firmware.
Standard attendance, standard isolation
Check in and check out are plain hr.attendance rows, so timesheets, reporting, and the rest of the suite see normal attendance. Templates carry company_id and the match path filters on the device company, so cross company match is impossible at the controller. ACL is split across suite namespaced user, manager, admin, and auditor groups.
A convenience match, not anti spoofing
A printed photo or a phone screen showing an enrolled face can match, and a modified client can submit any embedding, so this raises the bar on casual buddy punching but does not prove who physically attended. The face-api.js bundle is not shipped in the zip; a pinned CDN fallback works on first run and one command vendors it locally. The Python match loop is comfortable to a couple of thousand templates per company.
Day in the life
A worker walks up, looks at the tablet, and the day starts.
The reception tablet sits open to /eh_hr/kiosk/<site code>. A worker walks up. The browser asks for camera permission once on first run, face-api.js detects a face and generates a 128 float embedding locally, and the page POSTs only that embedding with the kiosk device token in the X-EH-Kiosk-Token header. The server checks the rate limit, resolves the token to a device, site, and company, optionally enforces the per site geofence by haversine distance, then cosine matches the embedding against active consent granted templates for that company. On a hit it looks up the worker's open hr.attendance row, closes it if found or creates a check in if not, and writes the attempt, the result, and the attendance event to the kiosk audit trail. The kiosk shows the worker's name, ticks, and resets. Raw frames stay on the device the whole time.
Edge cases
The cases most modules quietly ignore.
In the shipped code today, each one a place where a cheaper module silently does the wrong thing.
The public match endpoint is guarded by a DB backed fixed window rate limit that increments with a single INSERT ON CONFLICT DO UPDATE RETURNING, so two workers racing the same bucket can never both read a stale count and a stolen device token cannot hammer the endpoint.
Templates carry company_id and the device token resolves to exactly one device, site, and company. The candidate search filters on that company, so an embedding can only ever match enrolments inside the device's own company.
Withdrawing a face consent deactivates every active template tied to it in the same transaction and logs the cascade to the audit trail. The retention sweep cron also deactivates templates whose consent has expired, and the match search filters on consent state granted, so a withdrawn or expired consent stops matching immediately.
A best match above the cosine distance threshold raises an identity low confidence attendance exception, and a geofence breach raises a geofence violation. Both the breach and the low confidence attempt are written to the kiosk event log with the measured distance for diagnostics.
Every attempt, success, failure, geofence pass and fail, and resulting check in or check out is written to eh.hr.kiosk.event. No suite role has write permission on that model and only admin can unlink, so the trail is append only in practice for everyone operating the kiosk.
The face-api.js bundle is deliberately not vendored in the zip. On first run the kiosk serves a pinned immutable CDN build and warns the operator to vendor locally with tools/fetch_face_api.sh. Setting eh_hr_face_kiosk.disable_cdn_fallback shows a setup required panel instead of calling out, for strict no third party deployments.
Embeddings are validated at write time by a model constraint that checks valid JSON, list shape, declared dimension match, and numeric values, so a malformed embedding is rejected on the model, not silently stored. The match controller also rejects a non array or non numeric embedding before it touches the database.
What is inside
Built to do the job, end to end.
- eh.hr.face.template model. Stores the JSON serialised 128 dim embedding, the model identity and version, capture method, quality score, and a required link to a granted face consent for the same employee. Captured on, captured via, and model name are read only; reactivation refuses if the consent is no longer granted.
- Face match controller. The public POST route /eh_hr/kiosk/face/match authenticates by device token, rate limits, runs the optional per site haversine geofence, normalises the incoming embedding, cosine matches against active consent granted templates for the company, and toggles the worker's hr.attendance check in or check out on a hit above threshold.
- Kiosk shell route. The public page at /eh_hr/kiosk/<site code> loads face-api.js (vendored locally or via the pinned CDN fallback), stamps kiosk JS and CSS with an mtime based cache version, and renders the capture and match UI. No Odoo login required; the device token issued at pairing is the auth boundary.
- Face enrolment wizard. An HR admin wizard that captures three to five samples via an OWL widget, requires a ticked consent acknowledgement, grants the face consent in the same transaction if none exists, stores each sample as a separate template row, and logs the enrolment to the audit trail.
- Employee integration. hr.employee gains a computed Face enrolled flag driven by active template existence, a one2many of templates, an active template count, and buttons to open the enrolment wizard or review templates per employee with deactivate and re enrol actions.
- Consent and audit hooks. Inherits eh.hr.consent to cascade template deactivation on withdrawal and on retention expiry, and writes every match attempt, outcome, geofence result, and attendance event to the suite wide eh.hr.kiosk.event audit model with confidence and distance.
Honest about the edges
What this does not do, so nothing surprises you.
- Not an anti spoofing or liveness control. A printed photo or a phone screen showing an enrolled face can match, and because the embedding is computed on the device a modified client can submit any embedding. Use it for attendance convenience, not to prove who physically attended.
- The face-api.js library and model weights (roughly six megabytes) are not bundled in the module zip. On first run the kiosk serves a pinned CDN build; for production or air gapped sites you run tools/fetch_face_api.sh once to vendor the files locally.
- The match is a Python cosine loop over active templates per kiosk hit, comfortable to a couple of thousand templates per company. Past that an approximate nearest neighbour index is the right answer and is not in the box today.
- The audit trail is append only by access control, no suite role has write permission and only admin can unlink, rather than by a database level write block on the model.
- Requires the suite base (eh_hr_attendance_base) for sites, devices, pairing tokens, consent, geofence config, the match threshold, and the kiosk event audit model. It is not a standalone kiosk.
- Browser camera access needs a secure context, so the kiosk page must be served over HTTPS or via localhost on the device.
face recognition attendance odoo, biometric clock in odoo 19, kiosk attendance odoo community, face-api.js odoo, contactless attendance, no badge attendance, employee clock in kiosk, self hosted face attendance, buddy punch prevention, hr attendance kiosk, tablet time clock odoo, browser face match attendance
Please log in to comment on this module