El Empleado Juan
Facial recognition stops being a demo when it runs on two hundred dollars of hardware.
- With
- co-founder, MTCNN, RetinaFace, FaceNet, ArcFace, anti-spoofing, edge, Hikvision, GPS
Co-founder. A B2B SaaS for biometric attendance and automated payroll aimed at Latin American small and medium businesses.
Three capture layers
No customer has a single operational reality, so the system clocks people in three ways and reconciles them:
- A kiosk with in-house facial recognition — detection with MTCNN/RetinaFace, embeddings with FaceNet/ArcFace, anti-spoofing, deployed on low-cost hardware.
- Hikvision terminals, for plants that already had hardware installed.
- Mobile clock-in with GPS and geofences, for staff on the road.
The other half: the law
Biometrics only answers who clocked in and when. What the customer actually buys is what comes next: Colombian payroll computed against the labour reform — Law 2101 of 2021, the 42-hour week, auditable premiums computed minute by minute. Every day worked is settled under the law in force on that day, even when a pay period straddles a change in legislation.
Juanito, the AI assistant, closes the loop over WhatsApp: clocking in, checking schedules and hours, requesting time off.
Real scale
Anchor customer: a food logistics operation with 900 employees in Medellín.
It started as a WhatsApp bot on Twilio that turned into a platform. The biometrics I began on the Electoral Tribunal kiosks, now in the hands of small businesses.
The pipeline, from camera to punch
Embeddings come out normalised, so cosine similarity is a dot product and comparing against a whole site is one matrix-vector multiply:
# The index is per SITE, not global: comparing against the 900 vectors of
# one plant is not the same operation as against the customer's 40,000.
index = site.embeddings() # (n, 512) float32, L2-normalised
def identify(face: np.ndarray, threshold: float) -> Punch | None:
if not is_live(face): # anti-spoofing first: a photo has no
return None # business reaching the matcher
v = arcface.embed(align(face)) # (512,)
similarities = index @ v # cosine, since both are unit
i = int(similarities.argmax())
# The threshold is calibrated on THIS site's data. The lab value does
# not survive the first shift: a cheap camera, backlight at 6 a.m. and
# people in a hurry move the whole distribution.
return Punch(site.employee(i)) if similarities[i] >= threshold else None
All of this runs on the kiosk itself. Uploading the frame to a server would be easier to write and would turn every network outage into a plant that cannot clock in.
Write to me
If you are thinking about building something with AI and want to talk it through with someone who has put it into production, write to me. I answer myself.
hola@giron.ai