Skip to content
giron.ai
← Projects

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

From camera to punch: detection, alignment, anti-spoofing, embedding and a thresholded decision. CameraRGB frame DetectionRetinaFace Alignment5 landmarks Live?anti-spoofing EmbeddingArcFace, 512-d Decisionper-site threshold a photo dies here
Anti-spoofing runs before the matcher: a photo should never reach the comparison.

Embeddings come out normalised, so cosine similarity is a dot product and comparing against a whole site is one matrix-vector multiply:

sim⁡(u,v)=u⋅v∥u∥ ∥v∥=u⋅vwith∥u∥=∥v∥=1\operatorname{sim}(u,v)=\frac{u\cdot v}{\lVert u\rVert\,\lVert v\rVert}=u\cdot v \qquad\text{with}\qquad \lVert u\rVert=\lVert v\rVert=1
# 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