Evalgent
Back to Blog
Voice AI Testing

Monitoring AI Check Calls: Wrong ETAs and Load Numbers

Deepesh Jayal
15 min read
Monitoring AI Check Calls: Wrong ETAs and Load Numbers
On this page

A check call looks simple. The agent asks the driver where they are, when they'll arrive, and whether anything is wrong. It writes the answers into the TMS. A tracking update goes to the shipper minutes later.

That last step is the problem. Nobody hears the call except the driver. A wrong load number puts the update on someone else's freight. A "3 PM" in the wrong time zone sets a dock plan around an impossible arrival. No exception is raised.

This post is for product and engineering teams running AI check calls trucking fleets and brokers depend on. It is a narrow companion to our complete QA guide for freight voice agents, which covers pre-launch testing. This one covers live calls, field by field.

11 h
Max driving in a shift for property carriers (49 CFR 395.3)
60 min
ELD location interval while moving (FMCSA ELD FAQ)
~1 mi
ELD location accuracy on duty (FMCSA ELD FAQ)
77%
Spoken IDs fully correct, baseline ASR on real data (SNuC, LREC 2022)

What an AI check call writes into the TMS

Check call: a short status call between a carrier or broker and a driver during a load. It captures where the truck is, when it will arrive, and anything that changes the plan.

Dispatchers once made these calls by hand, dozens a shift. AI check call software now places them, or answers when drivers call in. Behind the voice sits a workflow: capture, a TMS write, a tracking event, and sometimes a detention bill.

Each driver check call produces four fields. Each field has its own way of breaking.

FieldWhat the driver saysWhat the TMS needsWho reads it downstream
Location"Just past the Flying J at mile marker 142"City, state, lat/long, timestampShipper tracking page, ETA model
ETA"Tomorrow morning, probably by six"Absolute time with time zoneReceiver dock schedule, late alerts
Load reference"Four four seven one oh nine three"Exact load, PRO number or bill of ladingWhich shipment the update attaches to
Exception"Been sitting here since six"Coded reason, start time, valueDetention billing, claims, escalations
Four check-call fields as rows, tracing what the driver said, what the voice agent wrote to the TMS, and what the shipper saw, errors in red

A wrong field rarely crashes anything. The TMS accepts a valid-looking string. That is why load tracking calls need monitoring on the values, not just the call outcome. Our guide to silent tool failures covers the general pattern.

How each field gets misheard or mis-resolved

Location

Drivers rarely give an address. They give landmarks, exits and mile markers. "Just past the Flying J" needs a geocoder and a route to resolve. There are many truck stops with that name. Without the route, the geocoder picks the most popular one. That might be 100 miles away.

Mile markers have their own trap. They restart at each state line. "Mile marker 142 on I-80" exists in several states. The agent must attach a state, or ask for one.

Speech errors make this worse. "Fifteen" and "fifty" differ by a short unstressed ending. "Exit 40" and "exit 14" sound close on a noisy line.

ETA

ETA is the field that breaks most quietly. It is a relative time expression that the agent has to turn into an absolute timestamp.

"Tomorrow morning" needs an anchor date and a guess at what "morning" means. "By six" needs AM or PM. Most of all, it needs a time zone. A driver in Gary, Indiana, is on Central time. A receiver in Columbus, Ohio, is on Eastern. "I'll be there by three" could mean either. Arizona mostly skips daylight saving time, so its offset to the rest of the country moves twice a year.

There's also a meaning gap. Drivers often answer with their appointment time, not their predicted arrival. "My appointment's at eight" is not an ETA. If the agent writes 8:00 as the ETA, freight ETA accuracy reports show perfect on-time performance until the truck misses the dock.

NLP researchers have studied this for two decades. The TimeML standard's TIMEX3 tag stores a relative expression like "Monday" as an underspecified value (`XXXX-WXX-1`). It then resolves it against an anchor time, such as when the document was written. In the TempEval-3 shared task, the top English system, HeidelTime, reached 77.61 value F1 on normalization. That was on clean news text. A phone transcript with a missing time zone is harder. The lesson for your agent is simple: store the raw phrase, the anchor and the resolved value as three separate fields. Then you can audit the resolution step later.

Load reference number

Load numbers, PRO numbers and BOL numbers are digit strings with no language model to help. Every digit counts. "Oh" can mean zero or the letter O. "Double seven" is two digits from one word. Drivers carrying multi-stop or team loads may read the wrong one. Some read only the last four digits.

Research on spoken identifiers shows why per-digit accuracy misleads. In the SNuC corpus paper (LREC 2022), a baseline Kaldi model got 96.6% of words right on real factory data. Yet only 77% of full identifiers were correct. That's almost one in four strings with an error. Adapting the model to the domain raised string accuracy to 91.7%. The authors also found that speakers misread about 6% of numbers themselves. Most of those slips went unnoticed by the speaker.

So score load numbers at the string level, never the digit level. The same advice applies to any entity field, as covered in our guide to STT accuracy for names and entities.

Exceptions

Exceptions carry the money. Detention starts a billing clock. A breakdown triggers a recovery plan. A reefer temperature out of range can mean a rejected load.

The common failure is a missed exception, not a wrong one. "Yeah, I've been sitting here a while" may never map to a detention code. "It's running at thirty-four" may get written without a sign or unit. "Minus ten" may lose the minus. A Spanish answer like "voy a llegar tarde" may be transcribed but never tagged as a delay. Each of these leaves the TMS saying "on time, no issues."

Why driver audio is hard for speech recognition

Truck cab audio stacks bad conditions on one call. There's engine idle, highway road noise, wind, a CB radio, and a Bluetooth headset. The headset compresses the voice before the phone network does it again. Word error rate climbs with each layer.

Research on in-vehicle speech backs this up. The ICMC-ASR challenge at ICASSP 2024 released over 100 hours of in-car speech. It was recorded across 60 scenarios that varied road type, speed, air conditioning, music and window opening. The baseline system's character error rate was 26.24%. The winning team cut that to 13.16%. That is a passenger car, not a Class 8 truck, and the speech is Mandarin. Still, even the winning system got more than one character in eight wrong.

OpenAI's Whisper paper found a similar pattern with added noise. Models trained on clean audio did well at 40 dB SNR. All models "quickly degrade as the noise becomes more intensive." Below 10 dB of pub-style noise, Whisper beat every clean-trained model. Pick your STT based on tests at the noise levels your drivers actually call from.

Spanish-speaking drivers add another layer. Many switch between English and Spanish within one answer. They might say numbers in Spanish and street names in English. Test both languages and the mix, as covered in our guide to multilingual accuracy. Our background noise test guide shows how to build an SNR ladder for this.

Cross-checks your system already has data for

The AI check calls trucking fleets run are unusual among voice tasks. You often hold independent truth for the very fields the driver is reporting. Telematics knows where the truck is. The ELD knows how much driving time is left. The TMS knows which loads this driver has. Use them.

Spoken location vs GPS and ELD position

Geocode the spoken location. Compare it to the nearest GPS ping using haversine distance. Flag it if the gap is larger than a tolerance.

Set the tolerance with care. FMCSA's ELD guidance says ELDs record location "at 60-minute intervals when the vehicle is in motion." On-duty location accuracy is "approximately within a 1-mile radius." During personal conveyance it widens to about 10 miles. So an ELD record can be an hour stale. A truck at 60 mph covers 60 miles in that time. Use your telematics feed's live ping where you have it. Grow the tolerance with ping age.

ETA vs distance and hours of service

An ETA must be physically and legally possible. The federal hours-of-service rules set the legal part. Under 49 CFR 395.3, a property-carrying driver:

1. Needs 10 consecutive hours off duty before driving.

2. May not drive after 14 consecutive hours from coming on duty.

3. May drive at most 11 hours in that window.

4. Must take a 30-minute break after 8 hours of driving. The break can be on-duty, not-driving time.

5. Stops driving after 60 hours on duty in 7 days, or 70 hours in 8 days.

The 2020 final rule also lets drivers extend the driving window by up to 2 hours in adverse driving conditions. It also allows split sleeper-berth periods. Treat both as reasons to widen the check, not break it. Short-haul exceptions also exist.

Here is the feasibility test. Take routed miles left and divide by a planning speed. Add a 30-minute break when driving crosses 8 hours. Add a 10-hour reset when the driver runs out of drive time. That gives an earliest legal arrival. If the spoken ETA is earlier than that, the ETA is wrong, the hours data is wrong, or the driver is planning to break the rules. All three deserve a flag.

Hours-of-service timeline showing 2 hours of driving, a 10-hour reset and 4 more hours, with the spoken 6 PM ETA marked infeasible in red

Load number format and ownership

Each shipper or broker uses its own reference formats. One uses seven digits. Another uses a prefix and six digits. Some include a check digit. Keep a format rule per customer and validate every write. Then do the stronger check: is this load assigned to this driver today? A valid-format number that belongs to another truck is the worst case. It passes the format rule and corrupts two shipments.

Duplicate and stale updates

Retries and drivers calling in twice create duplicates. Flag same-load, same-place updates within 15 minutes. Also flag a location that sits behind the last one on the route.

The check-call field-accuracy monitor

This is a copyable framework. Each field gets an error definition, an automatic check that runs on every call, and an alert threshold. The thresholds are starting points. Tune them on your own baseline, as described in our guide to setting production thresholds.

FieldCounts as an error whenAutomatic check (every call)Starting alert threshold
LocationResolved point is outside tolerance of GPS truth, or state is missingHaversine vs ping, tolerance = 10 mi + speed × ping ageDaily rate above 2× trailing 14-day mean
ETAEarlier than earliest legal arrival, wrong time zone, or appointment written as ETAHOS feasibility; ETA zone vs receiver zone; ETA equals appointment exactlyAny infeasible ETA on a shipper-visible load
Load referenceString differs from truth, or load not assigned to driverPer-customer regex plus assignment lookupAny write to an unassigned load
ExceptionSpoken exception not coded, or coded value wrong (sign, unit, start time)Keyword and intent scan of transcript vs coded fields; reefer reading vs setpointHuman sample error rate above baseline (see sampling math)
DuplicateSame load, same place, within 15 minDedupe on load ID, time and positionMore than 1% of updates

Two rules make this work. Score at the field level, not the call level. A polite "drive safe" ending can hide three corrupted fields. And keep raw spoken values next to resolved ones, so you can tell a speech error from a resolution error.

Python: scoring a batch of check calls against GPS and HOS truth

This is simplified. It uses a fixed planning speed, ignores the 60/70-hour limits and the sleeper split, and the data is illustrative. It runs as-is on Python 3.9 or later.

# Simplified check-call field-accuracy scorer. Illustrative data only.
import math, re
from dataclasses import dataclass
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

EARTH_MI = 3958.8

def haversine_mi(lat1, lon1, lat2, lon2):
    p1, p2 = math.radians(lat1), math.radians(lat2)
    dp, dl = p2 - p1, math.radians(lon2 - lon1)
    a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2
    return 2 * EARTH_MI * math.asin(math.sqrt(a))

@dataclass
class CheckCall:
    call_id: str
    load_ref: str            # what the agent wrote
    customer: str
    said_lat: float          # geocoded spoken location
    said_lon: float
    gps_lat: float           # telematics ping nearest the call
    gps_lon: float
    gps_age_min: float       # ping age at call time
    called_at: datetime      # tz-aware
    eta: datetime            # tz-aware, as written to the TMS
    miles_left: float        # routed miles from GPS ping to receiver
    drive_left_h: float      # HOS driving time left (11-hour rule)
    window_left_h: float     # time left in the 14-hour window
    since_break_h: float     # driving since last 30-min break
    appt: datetime           # receiver appointment, facility local time

LOAD_FORMATS = {"acme": r"^\d{7}$", "bolt": r"^BL-\d{6}$"}
PLAN_MPH = 50          # planning speed, illustrative
LOC_TOL_MI = 10        # base tolerance, covers geocoding and ELD-grade fixes
ETA_SLACK_MIN = 30

def earliest_arrival(c: CheckCall) -> datetime:
    """Earliest legal arrival under a simplified 11/14/8-hour model."""
    need_h = c.miles_left / PLAN_MPH
    t = 0.0
    drive_left = min(c.drive_left_h, c.window_left_h)
    since_break = c.since_break_h
    while need_h > 1e-9:
        if drive_left <= 1e-9:
            t += 10.0                       # 10 consecutive hours off duty
            drive_left, since_break = 11.0, 0.0
            continue
        until_break = 8.0 - since_break
        if until_break <= 1e-9:
            t += 0.5                        # 30-minute interruption
            since_break = 0.0
            continue
        leg = min(need_h, drive_left, until_break)
        t += leg; need_h -= leg; drive_left -= leg; since_break += leg
    return c.called_at + timedelta(hours=t)

def score(c: CheckCall):
    flags = []
    dist = haversine_mi(c.said_lat, c.said_lon, c.gps_lat, c.gps_lon)
    tol = LOC_TOL_MI + PLAN_MPH * c.gps_age_min / 60
    if dist > tol:
        flags.append(f"location: {dist:.0f} mi from GPS (tol {tol:.0f})")
    earliest = earliest_arrival(c)
    if c.eta < earliest - timedelta(minutes=ETA_SLACK_MIN):
        flags.append(f"eta: infeasible, earliest legal {earliest:%a %H:%M %Z}")
    elif earliest > c.appt and c.eta <= c.appt:
        flags.append("eta: on-time ETA but appointment is unreachable")
    pat = LOAD_FORMATS.get(c.customer)
    if pat and not re.match(pat, c.load_ref):
        flags.append(f"load_ref: '{c.load_ref}' fails {c.customer} format")
    return dist, earliest, flags

CT, ET = ZoneInfo("America/Chicago"), ZoneInfo("America/New_York")
now = datetime(2026, 10, 15, 13, 0, tzinfo=CT)
calls = [
    # Accurate call: Joliet IL, 290 routed miles to Columbus OH.
    CheckCall("c1", "4471093", "acme", 41.525, -88.082, 41.530, -88.070, 5,
              now, datetime(2026, 10, 15, 20, 45, tzinfo=ET), 290, 7.5, 9.0, 2.0,
              datetime(2026, 10, 15, 21, 0, tzinfo=ET)),
    # ETA said "by six" and written as 18:00 Central, but HOS forces a reset.
    CheckCall("c2", "4471094", "acme", 41.593, -87.346, 41.590, -87.350, 3,
              now, datetime(2026, 10, 15, 18, 0, tzinfo=CT), 300, 2.0, 3.0, 6.0,
              datetime(2026, 10, 16, 8, 0, tzinfo=ET)),
    # Driver said "just past the Flying J"; geocoder picked one in another state.
    CheckCall("c3", "BL-209318", "bolt", 39.768, -86.158, 41.080, -85.139, 4,
              now, datetime(2026, 10, 15, 19, 30, tzinfo=ET), 160, 8.0, 10.0, 1.0,
              datetime(2026, 10, 16, 7, 0, tzinfo=ET)),
    # Load number lost a digit ("four four seven one oh nine").
    CheckCall("c4", "447109", "acme", 40.440, -86.910, 40.445, -86.900, 10,
              now, datetime(2026, 10, 15, 18, 15, tzinfo=ET), 180, 9.0, 11.0, 3.0,
              datetime(2026, 10, 15, 21, 0, tzinfo=ET)),
]

for c in calls:
    d, e, f = score(c)
    print(f"{c.call_id}: {d:6.1f} mi | earliest {e.astimezone(ET):%a %H:%M %Z} | "
          + ("; ".join(f) if f else "OK"))

Output:

c1:    0.7 mi | earliest Thu 19:48 EDT | OK
c2:    0.3 mi | earliest Fri 06:00 EDT | eta: infeasible, earliest legal Fri 05:00 CDT
c3:  105.3 mi | earliest Thu 17:12 EDT | location: 105 mi from GPS (tol 13)
c4:    0.6 mi | earliest Thu 17:36 EDT | load_ref: '447109' fails acme format

Call c2 is the classic silent failure. The driver had 2 hours of drive time left and 300 miles to go. The spoken "by six" was written as 6 PM the same day. The earliest legal arrival is 6 AM Eastern the next morning. Call c3 is a landmark resolved to the wrong state. Call c4 dropped one digit. Each of these would have reached the shipper as a clean update.

In production, add the assignment lookup and the dedupe check. Store `earliest` on the record too. If your agent passes these values through tool calls, our guide to tool-argument accuracy from speech covers how to score the arguments themselves.

Sampling math: how many check calls to review each day

Automatic checks cover every one of the AI check calls trucking customers see, but not every error. A missed detention or a reefer reading with the wrong sign needs a human or an audio judge. So you also review a fixed daily sample.

This is a worked example with illustrative numbers. Say your baseline exception-field error rate is 2%. You want to catch a jump to 5% within one day. You want a 5% false-alarm rate and 80% power. The one-sample formula for a proportion is:

n = [z_α · √(p₀(1−p₀)) + z_β · √(p₁(1−p₁))]² / (p₁ − p₀)²

With z_α = 1.645 (one-sided 5%), z_β = 0.842 (80% power), p₀ = 0.02 and p₁ = 0.05, you get n ≈ 191 calls.

The normal approximation is a little optimistic at rates this low. An exact binomial check is better. Review 200 calls and alert at 8 or more errors. The false-alarm rate is then 4.9%, and the chance of catching a true 5% rate is 78.7%.

Bar chart of daily check calls to review to detect an error-rate jump from 2 percent, from 1,398 calls down to 59 calls

Catching small jumps takes huge samples. A jump from 2% to 3% needs about 1,400 reviewed calls a day. Two practical moves help:

1. Pool days. Review 200 a day, but test the trailing 7 days for small shifts.

2. Stratify. Sample more from risky slices: Spanish calls, calls with high noise, new customer formats, and calls right after a prompt change.

Our post on sampling live calls for evaluation covers stratified designs in more depth.

How to monitor AI check calls in production

1. Log raw and resolved values. For every field, store the spoken phrase, the resolved value, the anchor time and time zone, and the call recording link. Our guide to monitoring voice agents in production lists the rest of the per-call schema.

2. Join each call to truth. Pull the nearest GPS ping, its age, HOS clocks from the ELD, routed miles left, and the driver's assigned loads.

3. Run the field checks on every call. Use location tolerance, ETA feasibility, time zone match, appointment-as-ETA, load format and assignment, and dedupe.

4. Block or hold risky writes. An infeasible ETA or an unassigned load number should not reach the shipper. Hold it and send it to a dispatcher.

5. Review a fixed daily sample. Use the sampling math above. Score exceptions and Spanish calls from the audio, not only the transcript.

6. Alert on rates, not single calls. Use per-field daily rates against a trailing baseline. Break them out by customer, language and agent version.

7. Turn failures into tests. Every confirmed error becomes a replayable test case before the next prompt change ships. See turning production failures into tests.

Evalgent can carry steps 5 to 7 for teams that don't have an eval function. Teams push their call recordings. Evalgent transcribes them, maps each call to a scenario and judges it from the audio. It flags tool-call failures, latency spikes and policy failures, with fix suggestions. Custom metrics run on every call, so a "load number matches assignment" check can sit next to the built-in ones.

Before a change ships, the same scenarios can be replayed. Evalgent's caller profiles set accent, pace, interruptions, background noise and language, including Spanish. Simulated calls reach the agent over phone, SIP or WebRTC. That covers the cab-noise and code-switching cases above without a driver on the line.

If your agent places outbound calls to drivers, consent rules also apply. The FCC's February 2024 declaratory ruling holds that AI-generated voices are "artificial" under the TCPA. Our outbound voice agent testing guide covers what to test there. This is not legal advice; check with counsel for your use case.

Frequently asked questions

What is an AI check call in trucking?

An AI check call in trucking is a status call between a voice agent and a driver during a load. The agent asks for location, ETA, load reference and any exceptions. It then writes those values into the TMS, which sends tracking updates to the shipper or broker. Agents may place the call or answer when drivers call in.

How accurate are automated check calls?

Automated check call accuracy has no published industry benchmark. It depends on cab noise, the STT model, and how the agent resolves times and landmarks. Research on spoken identifiers found only 77% of full strings correct for a baseline model on real data. Measure your own per-field error rate against GPS, ELD and assignment data.

How do you verify a driver's location on a check call?

Verify a driver's location by geocoding the spoken landmark and comparing it to the nearest GPS ping with haversine distance. Allow for ping age, because FMCSA says ELDs record location every 60 minutes while moving, at about 1-mile accuracy. Flag gaps beyond the tolerance and calls that give a mile marker without a state.

How do you check if a driver's ETA is realistic?

Check an ETA by computing the earliest legal arrival. Divide routed miles left by a planning speed. Then add a 30-minute break after 8 hours of driving and a 10-hour reset when the 11-hour or 14-hour limit runs out. Flag any spoken ETA earlier than that result, and any ETA that exactly matches the appointment time.

Why does AI get load numbers wrong?

AI gets load numbers wrong because digit strings have no context to help recognition. One wrong digit makes the whole string wrong. "Oh" versus zero, "double seven," dropped digits and drivers reading the wrong load all cause errors. Validate each number against the customer's format and confirm the load is assigned to that driver.

How many check calls should you review each day?

The number of check calls to review depends on the jump you need to detect. In an illustrative example, catching a rise from 2% to 5% error rate takes about 200 reviewed calls a day. Alert at 8 or more errors. Catching a rise from 2% to 3% needs about 1,400 calls, so pool days or stratify instead.

Can AI check calls handle Spanish-speaking drivers?

AI check calls can handle Spanish-speaking drivers if the STT and agent are tested in Spanish and in mixed English-Spanish speech. Drivers often say numbers in one language and place names in another. Monitor field error rates by language. A delay said in Spanish, like "voy a llegar tarde," must still map to an exception code.

Outbound AI check calls fall under TCPA rules on artificial voices. The FCC's February 2024 declaratory ruling says AI-generated voices count as "artificial," so such calls generally need prior express consent unless an exemption applies. Inbound calls that drivers place are a different case. This is not legal advice; confirm your consent flow with counsel.

The bottom line

The AI check calls trucking teams rely on are only as good as the four fields they write, and none of those fields fail loudly. Check every call against GPS, ELD hours and load assignment, then hand-review a fixed sample so wrong ETAs and load numbers never reach the shipper.

Related Articles