Skip to content
Telematics & Integration

Linking DVIR Events to ELD Driver Identity

A DVIR arrives claiming a driver, but the claim is just a string until you can tie it to an authenticated electronic logging device (ELD) identity that the driver actually logged into. This guide implements that resolution concretely: take a DVIR’s claimed driver_id, vehicle_vin, and capture timestamp, join them against the ELD driver directory and the Hours-of-Service log within a tolerance window, verify the credential, hash the license number before it is ever stored, and reject or quarantine anything that does not match. Skip this and you accept certifications that no auditor can attribute to a real person — a direct failure of the driver-attribution intent behind 49 CFR § 396.11©(2)(iii) and the identity discipline of Part 395.

This is the implementation detail beneath ELD Identity and HOS Record Linkage, and it sits in the Telematics, ELD & Maintenance Platform Integration section whose DvirComplianceEvent carries the driver_id resolved here.

  • Python 3.10+ — the code uses X | Y unions and dataclasses.
  • Pydantic 2.x — the EldIdentityLink model from the parent guide.
  • hashlib (stdlib) — SHA-256 hashing of the license number, which is PII.
  • Access to the ELD driver directory and HOS log — via the vendor-neutral adapter layer so the same code works across Samsara, Geotab, and Motive.
  • The inbound DvirComplianceEvent — carrying driver_id, vehicle_vin, and the capture timestamp.

The resolution is a deterministic pipeline: normalize the claim, look up the identity, join the HOS window, verify, then hash and persist or quarantine.

Step 1 — Normalize the claimed identity off the event

Anchor link to "Step 1 — Normalize the claimed identity off the event"

Pull the claim from the event and treat it as untrusted input. Do not assume the driver_id corresponds to a real login yet:

python
from dataclasses import dataclass
from datetime import datetime


@dataclass(frozen=True)
class DvirClaim:
    dvir_id: str
    claimed_driver_id: str
    vehicle_vin: str
    inspection_ts: datetime  # captured on the mobile device, may be skewed


def claim_from_event(event: dict) -> DvirClaim:
    return DvirClaim(
        dvir_id=event["dvir_id"],
        claimed_driver_id=event["driver_id"],
        vehicle_vin=event["vehicle_vin"],
        inspection_ts=datetime.fromisoformat(event["emitted_at"]),
    )

Step 2 — Resolve the ELD identity and hash the license number

Anchor link to "Step 2 — Resolve the ELD identity and hash the license number"

Look up the authenticated identity in the ELD directory. If the claim resolves to nothing, stop — an unauthenticated driver cannot certify a DVIR. Hash the license number the moment you receive it, so the raw value never reaches storage or logs:

python
import hashlib


def hash_license(license_number: str) -> str:
    """SHA-256 the license number (PII) before it is stored anywhere."""
    return hashlib.sha256(license_number.strip().upper().encode()).hexdigest()


def resolve_eld_identity(directory, claimed_driver_id: str) -> dict | None:
    """Return the authenticated ELD identity or None. None means the claimed
    driver is not a real ELD login and the certification must be rejected."""
    identity = directory.lookup(claimed_driver_id)
    if identity is None:
        return None
    return {
        "eld_driver_id": identity["eld_driver_id"],
        "license_number_hash": hash_license(identity["license_number"]),
        "vehicle_ems_id": identity.get("assigned_vehicle_ems_id"),
    }

Step 3 — Join the HOS record within a tolerance window

Anchor link to "Step 3 — Join the HOS record within a tolerance window"

Confirm the driver was on duty in that vehicle around the capture time. Use a tolerance window, not an exact match, to absorb the clock skew between the mobile app and the ELD:

python
from datetime import timedelta

TOLERANCE = timedelta(minutes=15)  # tune from observed mobile-vs-ELD skew


def find_hos_window(hos_log, eld_driver_id: str, vehicle_vin: str,
                    inspection_ts: datetime) -> dict | None:
    """Return the on-duty HOS window covering inspection_ts (± tolerance) for
    this driver in this vehicle, or None if no such window exists."""
    for window in hos_log.windows_for(eld_driver_id):
        if window["vehicle_vin"] != vehicle_vin:
            continue
        if window["status"] not in ("on_duty", "driving"):
            continue
        if (window["start"] - TOLERANCE) <= inspection_ts <= (window["end"] + TOLERANCE):
            return window
    return None
Anchor link to "Step 4 — Verify, then build the link or quarantine"

Combine the resolved identity and the HOS window into a single verdict. On success, build the EldIdentityLink; on any failure, quarantine with enough context for a human to reconcile:

python
from pydantic import BaseModel


class EldIdentityLink(BaseModel):
    dvir_id: str
    eld_driver_id: str
    license_number_hash: str
    vehicle_ems_id: str
    hos_status: str
    verified_at: datetime


def link_or_quarantine(claim: DvirClaim, directory, hos_log) -> EldIdentityLink | dict:
    identity = resolve_eld_identity(directory, claim.claimed_driver_id)
    if identity is None:
        # Unauthenticated driver — reject (49 CFR 396.11(c)(2)(iii)).
        return {"quarantine": "unauthenticated_driver", "dvir_id": claim.dvir_id}

    window = find_hos_window(
        hos_log, identity["eld_driver_id"], claim.vehicle_vin, claim.inspection_ts
    )
    if window is None:
        # Resolved identity but no matching on-duty window in this vehicle.
        return {
            "quarantine": "hos_window_mismatch",
            "dvir_id": claim.dvir_id,
            "eld_driver_id": identity["eld_driver_id"],
            "vehicle_vin": claim.vehicle_vin,
        }

    return EldIdentityLink(
        dvir_id=claim.dvir_id,
        eld_driver_id=identity["eld_driver_id"],
        license_number_hash=identity["license_number_hash"],
        vehicle_ems_id=identity["vehicle_ems_id"] or window["vehicle_ems_id"],
        hos_status=window["status"],
        verified_at=datetime.now().astimezone(),
    )

Prove three properties: an unauthenticated claim is rejected, a matched claim within tolerance links, and skew at the edge of the window is absorbed rather than rejected.

python
import pytest
from datetime import datetime, timedelta, timezone


def _ts(minutes=0):
    return datetime(2026, 7, 16, 8, 0, tzinfo=timezone.utc) + timedelta(minutes=minutes)


class FakeDirectory:
    def __init__(self, known): self.known = known
    def lookup(self, did): return self.known.get(did)


class FakeHos:
    def __init__(self, windows): self._w = windows
    def windows_for(self, eld_id): return self._w.get(eld_id, [])


def test_unauthenticated_driver_is_quarantined():
    claim = DvirClaim("D-1", "ghost", "1VIN", _ts())
    out = link_or_quarantine(claim, FakeDirectory({}), FakeHos({}))
    assert isinstance(out, dict) and out["quarantine"] == "unauthenticated_driver"


def test_matched_claim_links():
    directory = FakeDirectory({"drv7": {
        "eld_driver_id": "E7", "license_number": "S1234", "assigned_vehicle_ems_id": "V7"}})
    hos = FakeHos({"E7": [{"vehicle_vin": "1VIN", "vehicle_ems_id": "V7",
                           "status": "on_duty", "start": _ts(-5), "end": _ts(30)}]})
    out = link_or_quarantine(DvirClaim("D-2", "drv7", "1VIN", _ts()), directory, hos)
    assert isinstance(out, EldIdentityLink)
    assert out.hos_status == "on_duty"
    assert len(out.license_number_hash) == 64  # stored hashed, never raw


def test_edge_of_tolerance_is_absorbed():
    directory = FakeDirectory({"drv7": {
        "eld_driver_id": "E7", "license_number": "S1234", "assigned_vehicle_ems_id": "V7"}})
    hos = FakeHos({"E7": [{"vehicle_vin": "1VIN", "vehicle_ems_id": "V7",
                           "status": "driving", "start": _ts(0), "end": _ts(60)}]})
    # Capture 14 minutes before the window start — inside the 15-min tolerance.
    out = link_or_quarantine(DvirClaim("D-3", "drv7", "1VIN", _ts(-14)), directory, hos)
    assert isinstance(out, EldIdentityLink)

Common Failure Modes and Gotchas

Anchor link to "Common Failure Modes and Gotchas"
  • Clock skew between mobile capture and the ELD. The DVIR timestamp is set by the driver’s phone, which can be minutes off the ELD clock and occasionally set to a wrong time zone. An exact-timestamp join rejects legitimate records. Use the tolerance window from Step 3, and if the observed skew regularly exceeds the window, widen it from measured data rather than assuming the record is fraudulent. Persist the actual skew per match so the window is tuned, not guessed.
  • Team-driving handoffs. Two drivers share a vehicle across a shift, and the DVIR may be certified by the co-driver while the HOS log shows the other driver as driving. Do not auto-reject: check both drivers assigned to the vehicle in the window, and accept the link if either authenticated identity was on duty. Record which driver matched so an auditor can follow the handoff.
  • Driver logged into the wrong vehicle. A driver who selects the wrong asset in the ELD app produces an HOS window whose vehicle_vin will not match the DVIR’s. This is a genuine mismatch, but a common and correctable one. Quarantine with both VINs attached so a human can confirm whether the driver simply mis-selected the vehicle or whether the certification is against a vehicle they never operated — the latter invalidates the certification.
Should the raw license number ever be stored?

No. Hash it with SHA-256 the moment you receive it and store only the digest. The hash is sufficient to match the identity and to prove attribution during an audit without holding personally identifiable information in the clear. Keep the raw value out of logs, events, and error payloads as well, not just the primary store.

How wide should the tolerance window be?

Start around 15 minutes and tune it from your fleet’s measured skew between mobile capture and the ELD clock. Record the actual skew for every successful match; if legitimate records cluster just outside the window, widen it from that data. A window that is too tight rejects valid certifications; one that is too wide lets a genuinely mismatched certification slip through, so treat the size as a tuned parameter.

What is the difference between rejecting and quarantining a certification?

Reject when the claim resolves to no authenticated ELD identity at all — there is no real driver to attribute the certification to, so it cannot stand. Quarantine when an identity resolves but the HOS window does not match, because that is often a correctable situation such as a team handoff or a wrong-vehicle selection that a human can reconcile. Quarantine preserves the record for review; rejection refuses it outright.

This guide is part of ELD Identity and HOS Record Linkage. Back to the Telematics, ELD & Maintenance Platform Integration section.