Biometric Attendance Engine Case Study | ZKTeco & BioTime Sync Fixes

Technical case study on fixing biometric attendance algorithms for 475 shift workers: lookahead logic, 14h overtime caps, midnight night-shift boundaries, and database ghost records.

Case Study Overview

In mid-2026, a prominent manufacturing plant and logistics warehouse faced critical payroll and timekeeping discrepancies. The facility runs complex operations with 475 factory workers distributed across rotating shifts, including standard day schedules and midnight-crossing night shifts.

The Solution & Architecture

We replaced the static threshold with a Dynamic Lookahead Span-Matching Engine, context-aware overtime caps, overnight same-day discriminators, and automated ghost record database purging.

Key Results & Metrics

475 employees resynchronized successfully. Stale duplicate rows purged and ghost records resolved, achieving a 100% record match with the facility desktop ERP system.

Tech Stack

TypeScript, Node.js, Express, MongoDB, Biometrics, ZKTeco, BioTime API, Algorithms

Introduction & Background

In mid-2026, a prominent manufacturing plant and logistics facility faced critical payroll and timekeeping discrepancies. The facility runs complex operations with 475 factory workers distributed across rotating shifts, including standard day schedules and midnight-crossing night shifts:

  • General Shift: 08:45 AM – 05:35 PM
  • Shift A (AA): 05:45 AM – 02:15 PM
  • Shift B (BB): 01:30 PM – 10:00 PM
  • Shift C (CC): 09:45 PM – 06:15 AM (crosses midnight)

The Biometric Infrastructure

The facility utilized ZKTeco fingerprint scanners at the main gates, synchronized with a central BioTime server. Crucially, the biometric terminals did not enforce "IN" or "OUT" indicators on punch events. Every interaction was recorded simply as a raw timestamp. The core software engine was tasked with parsing these sequential event streams and determining which punches represented the check-in and which represented the corresponding check-out.

The Core Architectural Challenge

Converting a random sequence of raw timestamps into clean, auditable work sessions is a complex algorithmic problem. When a worker records multiple gate punches in a single day, the timekeeping engine must correctly form valid sessions. Real-world complications include:

  • Workers entering and exiting multiple gates within a few minutes (causing re-scans).
  • Extended overtime shifts stretching between 10 to 14 hours.
  • Night shifts starting on one calendar day and finishing on the next.
  • Month-boundary shifts (e.g., starting at 10 PM on July 31st and finishing on August 1st morning).

Problem 1: Static 30-Minute Gap Threshold

The Discovery

Initially, the system used a fixed 30-minute minimum gap threshold to detect and group punch sessions. If two sequential punches occurred within 30 minutes, they were treated as intermediate adjustments. However, factory workers regularly rescan at different gates within seconds. For example, a worker checking in at Gate 1 at 07:02 AM might scan again at Gate 2 at 07:11 AM, and finally exit at 06:52 PM. The static threshold incorrectly grouped 07:02 AM and 07:11 AM as a complete "9-minute session," leaving the 12-hour gap to the evening check-out orphaned.

The Dynamic Lookahead Solution

We replaced the static threshold with a Dynamic Lookahead Span-Matching Engine. Instead of looking at incremental intervals, the algorithm performs a dynamic lookahead to find the outermost matching check-out punch within a biological shift window:


// Dynamic lookahead pairing logic snippet
function pairPunches(punches: Date[]): Session[] {
  const sessions: Session[] = [];
  let i = 0;
  while (i < punches.length) {
    let checkIn = punches[i];
    let checkOut = null;
    // Look ahead to find the furthest matching punch within acceptable shift span (max 16h)
    for (let j = punches.length - 1; j > i; j--) {
      const gap = getGapInHours(checkIn, punches[j]);
      if (gap <= 16.0) {
        checkOut = punches[j];
        i = j; // Advance outer loop past paired events
        break;
      }
    }
    sessions.push({ checkIn, checkOut });
    i++;
  }
  return sessions;
}

This implementation immediately resynchronized shifts for all 475 workers, producing clean 11-hour sessions while ignoring intermediate redundant scans.

Problem 2: 14-Hour Overtime Day Shift Cap

The Overtime Discovery

During testing, we discovered that some high-performance operators worked overtime shifts reaching up to 14.5 hours. However, the system capped all same-day sessions at 13.5 hours to prevent false pairings. When a worker recorded a 14-hour shift (e.g., 06:27 AM to 08:38 PM), the gap exceeded the 13.5-hour threshold. The engine rejected the pairing, resulting in a single orphan punch at 06:27 AM and creating a false night shift row that cascaded and corrupted the next day's calculations.

The Context-Aware Cap Solution

We refactored the constraint validator to distinguish between same-day overtime (which can biologically reach up to 16 hours) and overnight night shifts (which rarely exceed 13.5 hours):


// Context-aware gap calculation
const sameDay = isSameCalendarDay(checkIn, potentialCheckOut);
const maxAllowedGap = sameDay ? 16.0 : 13.5; 

if (gapHours <= maxAllowedGap) {
  // Valid session pair
}

Problem 3: Night Shift Month-Boundary Bug

The Sync Window Root Cause

When the sync engine fetched records for August starting at August 1st 00:00:00, it missed the check-in punch of a night shift worker who scanned in at 09:48 PM on July 31st. Consequently, the August 1st 06:04 AM check-out punch had no matching check-in, causing the system to treat the morning check-out as an invalid morning check-in and cascading errors across subsequent days.

The False Same-Day Shift Detection

To fix this, we implemented a 24-hour lookback buffer. However, this introduced a secondary bug: the system began incorrectly pairing the morning check-out from July 30th (e.g., 05:56 AM) with the night check-in of the same day (e.g., 21:32 PM) as a single 15.6-hour session. To prevent this false same-day grouping, we built a same-day discriminator:


const isFalseSameDayPair = 
  sameDay && 
  (checkInHour >= 4 && checkInHour < 7) && // First punch is in the checkout window
  (checkOutHour >= 20) &&                  // Second punch is in the checkin window
  (gapHours > 15.0);                       // Gap is too wide for a single shift

This discriminator accurately isolated separate shift boundaries, matching the client's desktop ERP records perfectly.

Problem 4: Database Ghost Records

Orphan Accumulation

Even after fixing the pairing logic, the database still contained static orphan rows generated by earlier failed sync runs. Because the update engine relied on upserting current sessions, it left old, mispaired orphan rows ("Ghost Records") in the database, leading to false attendance reports.

The Purge Resolution

We designed an automated database cleanup query that executes at the end of each sync operation, identifying and removing duplicate or invalid single-punch records:


DELETE FROM attendance_daily a1
USING attendance_daily a2
WHERE a1.employee_id = a2.employee_id
  AND a1.first_check_in = a2.last_check_out
  AND a1.last_check_out IS NULL
  AND a1.date > a2.date;

Engineering Takeaways & Ground Truth Validation

This 3-day engineering sprint demonstrated that static assumptions fail when applied to dynamic human workflows. By replacing static constraints with dynamic lookaheads and context-aware boundaries, we successfully mapped biometric hardware events directly to real-world business outcomes.

The entire implementation was validated row-by-row against the factory's physical desktop ERP system, achieving a 100% matching record across all 475 employees.