Skip to content
Ingestion & Parsing

DVIR Ingestion & Digital/Paper Parsing Workflows

The Driver Vehicle Inspection Report is the raw material every downstream compliance decision is built on, and the ingestion layer is where a fleet either produces a defensible federal record or quietly loses one. Under 49 CFR § 396.11(a) a driver must prepare a written report covering the service brakes, parking brake, steering mechanism, lighting devices and reflectors, tires, horn, windshield wipers, mirrors, coupling devices, wheels and rims, and emergency equipment at the completion of each day’s work, and under § 396.11(a)(3) any defect that would affect safe operation or cause a mechanical breakdown must be recorded. A report that never reaches the compliance system — because a webhook timed out, an OCR pass dropped the certification signature, or a burst of end-of-shift submissions overran the workers — is, from a DOT auditor’s perspective, a report that was never prepared. This reference specifies the ingestion architecture that turns fragmented inputs (native mobile apps, scanned paper forms, telematics feeds, and archival documents) into a single validated record that the Core DVIR Architecture & FMCSA Compliance Mapping reference can trust and the Defect Classification & Repair Order Routing engine can act on.

Why § 396.11 Capture Cannot Be Best-Effort

Anchor link to "Why § 396.11 Capture Cannot Be Best-Effort"

Ingestion is a compliance boundary, not a plumbing detail, because the regulation attaches obligations to the existence of a defect record and to specific fields inside it. Under § 396.11©(2) the motor carrier must certify that a reported defect was repaired — or that repair was unnecessary — before the vehicle is dispatched again, and under § 396.11©(2)(iii) the driver of the next tour must review and sign the prior report. Every one of those obligations depends on the ingestion layer having captured, without silent loss, the driver identity, the vehicle identification number (VIN) and unit ID, the UTC preparation timestamp, the per-component condition, the defect entries, and the signatures. If any of those fields is dropped between the client and the compliance store, the certification chain breaks and the carrier cannot prove it met its § 396.11© duty.

The failure mode that gets cited is not a malformed API call — it is a vehicle dispatched against a report that the system never fully recorded. That is why ingestion is stated in imperative terms throughout this reference: when a payload is missing a mandatory field, reject the payload and return it to the client for correction; when an OCR pass produces a VIN below the confidence floor, route the page to human verification rather than admit a guess; when a duplicate arrives, deduplicate on the client-generated identifier rather than create a second compliance record. A missing or falsified DVIR is a Vehicle Maintenance BASIC violation that raises the carrier’s Safety Measurement System percentile and increases roadside inspection frequency, so the ingestion layer’s single job is to guarantee that every inspection event that occurred in the field becomes exactly one immutable record in the store.

Architecture Overview: End-to-End Ingestion Data Flow

Anchor link to "Architecture Overview: End-to-End Ingestion Data Flow"

Ingestion is a directed pipeline with a hard validation gate near the front and a Write-Once-Read-Many (WORM) store at the back. Heterogeneous inputs — structured JSON from native apps, OCR output from scanned paper, and telematics-sourced events — converge on a single validation gate. Payloads that pass are handed to an asynchronous batch queue so that a burst of end-of-shift submissions cannot overwhelm the parsers; payloads that fail are preserved in a dead-letter queue with a structured failure manifest rather than dropped. Accepted records are normalized to the canonical schema, classified against the deterministic defect taxonomy, and appended to the immutable audit store. The diagram below shows the full path and the enforcement points where a non-compliant payload is rejected instead of allowed to progress.

End-to-end DVIR ingestion pipeline Three input channels converge on one validation gate. Mobile app payloads and telematics events enter the gate directly; scanned paper is first run through an OCR pipeline whose low-confidence extractions branch off to a human verification queue before reaching the gate. The gate is the only door into the compliance store and is closed by default: valid envelopes flow to an asynchronous batch queue, then schema normalization, then deterministic defect classification, and are appended to a write-once immutable audit store; rejected envelopes are diverted to a dead-letter queue with a failure manifest. Mobile app structured JSON Paper / scan fixed-layout form Telematics ELD event OCR pipeline deskew · extract Human review confidence < 0.85 Validation gate § 396.11(a) fields hash · signature closed by default Dead-letter queue failure manifest Async batch queue absorbs bursts Normalize canonical schema Classify defects score 0–100 Immutable store WORM · SHA-256 reject valid

The governing design principle is that the validation gate is the only door into the compliance store, and it is closed by default. No input channel — however trusted — writes directly to the normalized record. Mobile payloads, OCR extractions, and telematics events all present the same envelope to the same gate, so there is exactly one place where § 396.11 field-completeness is enforced and exactly one audit point that records why a payload was admitted or rejected.

Schema-Driven Data Standardization

Anchor link to "Schema-Driven Data Standardization"

Every channel must converge on one canonical envelope before it is admitted, so that downstream classification and routing never have to know whether a record originated on a phone or a scanned page. The ingestion envelope wraps the raw source bytes (for evidentiary hashing) together with the extracted, typed fields. The canonical extracted-field schema is owned by the Standardized DVIR JSON Schema Design reference; the ingestion layer adds only the provenance and confidence metadata it needs to make an admit/reject decision.

python
from datetime import datetime
from enum import StrEnum
from pydantic import BaseModel, Field, field_validator

class SourceChannel(StrEnum):
    MOBILE = "mobile"          # structured JSON from a native app
    PAPER_OCR = "paper_ocr"    # OCR output from a scanned form
    TELEMATICS = "telematics"  # event pushed from an ELD/telematics platform

class InspectionType(StrEnum):
    PRE_TRIP = "pre_trip"
    POST_TRIP = "post_trip"    # the § 396.11(a) end-of-day report

class DefectEntry(BaseModel):
    component: str                       # e.g. "service_brakes"
    description: str
    defect_code: str | None = None       # normalized downstream, may be null at ingest
    # § 396.11(a)(3): does this defect affect safe operation / cause breakdown?
    safety_affecting: bool

class IngestEnvelope(BaseModel):
    """The single shape every channel must produce before the validation gate."""
    dvir_id: str = Field(min_length=8)   # client-generated; the idempotency key
    channel: SourceChannel
    inspection_type: InspectionType
    vin: str = Field(min_length=17, max_length=17)
    unit_id: str
    driver_id: str
    prepared_at: datetime                # MUST be timezone-aware (UTC)
    odometer: int | None = None
    defects: list[DefectEntry] = Field(default_factory=list)
    driver_signature: str                # § 396.11(a) certification
    raw_sha256: str                      # hash of the original source bytes
    ocr_confidence: float | None = None  # populated only for PAPER_OCR

    @field_validator("prepared_at")
    @classmethod
    def _require_tz(cls, v: datetime) -> datetime:
        if v.tzinfo is None:
            raise ValueError("prepared_at must be timezone-aware to fix the § 396.11 event to UTC")
        return v

    @field_validator("vin")
    @classmethod
    def _reject_ambiguous_vin(cls, v: str) -> str:
        # I, O, Q are never valid in a VIN — an OCR misread of 1/0 is the usual cause
        if set(v.upper()) & {"I", "O", "Q"}:
            raise ValueError("VIN contains characters invalid per FMVSS 115; route to human verification")
        return v.upper()

The field-level compliance annotations matter as much as the types. prepared_at must be timezone-aware because the three-month retention clock in § 396.11©(2)(iii) runs from the preparation date, and a naive timestamp cannot be reconciled across driver time zones. raw_sha256 binds the record to its original bytes so the evidentiary chain survives an audit. ocr_confidence is populated only on the paper channel and drives the human-in-the-loop routing decision described below.

The three input channels differ only up to the point where they produce an IngestEnvelope. After that they share one validation gate, one queue, and one normalization step.

Native app submissions are the highest-fidelity stream and set the baseline every other channel is measured against. The Mobile App DVIR Export Integration workflow receives structured payloads through a tokenized webhook, verifies the transport, and constructs the envelope directly — there is no lossy extraction step. Enforce idempotency on the client-generated dvir_id so a retried mobile submission never creates a second compliance record, and compute the evidentiary hash over the exact received bytes.

python
import hashlib
import json

def build_mobile_envelope(raw_bytes: bytes) -> IngestEnvelope:
    payload = json.loads(raw_bytes)
    return IngestEnvelope(
        **payload,
        raw_sha256=hashlib.sha256(raw_bytes).hexdigest(),
    )

Paper DVIRs persist in cross-border operations, legacy fleets, and connectivity-loss contingencies, and converting them is a deterministic extraction problem, not a discretionary one. The PDF & Image OCR Pipeline Setup architecture handles preprocessing — deskew, denoise, contrast normalization, DPI floor enforcement — before character extraction, and the Tesseract OCR Setup for Fleet Inspection Forms page covers the engine configuration for fixed-layout inspection forms. The ingestion contract for the paper channel is that any extraction below the confidence floor is not admitted; it is routed to human review.

python
CONFIDENCE_FLOOR = 0.85  # below this, do not trust the extracted VIN/signature

def admit_or_review(env: IngestEnvelope) -> str:
    """Return the next stage for an OCR-sourced envelope."""
    if env.channel is SourceChannel.PAPER_OCR:
        if env.ocr_confidence is None or env.ocr_confidence < CONFIDENCE_FLOOR:
            # Do NOT silently accept a low-confidence VIN or missing signature.
            return "human_verification_queue"
    return "validation_gate"

The gate is the single enforcement point for § 396.11 field completeness. It rejects — with an imperative, not a warning — any envelope missing a driver signature, any post-trip report with a safety-affecting defect but no defect code path, and any envelope whose hash does not match its raw bytes.

python
from pydantic import ValidationError

class RejectedPayload(Exception):
    def __init__(self, reason: str, env_id: str):
        self.reason, self.env_id = reason, env_id
        super().__init__(f"{env_id}: {reason}")

def validate_gate(raw_bytes: bytes, env: IngestEnvelope) -> IngestEnvelope:
    # Evidentiary integrity: the hash must match the bytes we actually received.
    if hashlib.sha256(raw_bytes).hexdigest() != env.raw_sha256:
        raise RejectedPayload("raw_sha256 mismatch — evidentiary chain broken", env.dvir_id)
    # § 396.11(a): the driver certification signature is mandatory.
    if not env.driver_signature.strip():
        raise RejectedPayload("missing driver certification signature (§ 396.11(a))", env.dvir_id)
    # § 396.11(a)(3): a safety-affecting defect must be preserved for downstream routing.
    for d in env.defects:
        if d.safety_affecting and not d.description.strip():
            raise RejectedPayload("safety-affecting defect lacks a description", env.dvir_id)
    return env

Once past the gate, high-volume fleets decouple parsing from ingestion so that a peak of terminal check-ins does not block the request path. Envelopes that pass are enqueued through Async Batching for High-Volume Ingestion — backed by a broker such as RabbitMQ, Kafka, or SQS — and processed by horizontally scaled workers using the Asyncio Patterns for Batch DVIR Processing covered downstream. Each worker then runs DVIR Field Mapping & Data Normalization to fold vendor-specific outputs into the canonical schema — applying unit conversions, VIN checksum validation, and defect-code normalization — with Normalizing Inconsistent Driver Input Fields handling the free-text component names drivers actually type. Only after normalization does the record hand off to Severity Scoring Algorithms for DVIR Defects, where each defect earns a 0–100 score.

The ingestion layer participates in the same gated state machine the rest of the platform relies on, and it must never let a record skip a state. An envelope moves through RECEIVED → VALIDATED → NORMALIZED → CLASSIFIED, and only after classification does the record become eligible for the routing states owned downstream. Enforce these invariants in code and back them with database constraints and an append-only ledger, exactly as the Compliance Boundary Enforcement in Cloud Workflows reference specifies.

  • State-transition invariant: a record cannot reach NORMALIZED without having passed VALIDATED. There is no admin bypass; the gate is code, not configuration.
  • RBAC: only the ingestion service principal may write RECEIVED and VALIDATED events; only a normalization worker may write NORMALIZED. A human clearing the verification queue writes an explicit manual_override event that names the reviewer — the low-confidence value is never silently promoted.
  • Audit logging: every accepted or rejected envelope emits one append-only ledger event keyed by dvir_id, carrying the channel, the decision, the reason on rejection, and the reviewer identity on manual overrides. A DOT auditor can replay the full ingestion history of any VIN from this ledger.
python
def audit_event(env: IngestEnvelope, decision: str, reason: str | None = None,
                reviewer: str | None = None) -> dict:
    return {
        "dvir_id": env.dvir_id,
        "vin": env.vin,
        "channel": env.channel,
        "decision": decision,          # "accepted" | "rejected" | "manual_override"
        "reason": reason,
        "reviewer": reviewer,
        "raw_sha256": env.raw_sha256,
        "recorded_at": datetime.now().astimezone().isoformat(),
    }

Edge Resilience and Failure Modes

Anchor link to "Edge Resilience and Failure Modes"

Ingestion runs at the edge of connectivity, and its correctness is defined by how it behaves when things go wrong, not when they go right. Classify every failure into one of three bands and handle each imperatively.

  • Transient (retriable): network timeouts, broker unavailability, temporary OCR-service saturation. Retry with exponential backoff and jitter; never drop the record. Because retries key on the client-generated dvir_id, a replayed submission is deduplicated rather than double-counted.
  • Structural (schema violation): a malformed payload or a field type mismatch. Reject at the gate, preserve the original bytes in the dead-letter queue with the ValidationError attached, and return the failure to the originating client.
  • Compliance-critical (missing mandatory field): a missing signature, VIN, or preparation timestamp. Reject the payload, preserve the artifact, and notify the fleet manager by webhook — a silently dropped inspection is an uncovered § 396.11 obligation.

Offline behavior is a first-class requirement. Drivers inspect vehicles in yards and border crossings with no connectivity, so mobile clients write the inspection to encrypted local storage and replay it on reconnection using the client-generated dvir_id as the idempotency key. The server records both the client prepared_at and its own receipt timestamp to detect clock skew, and it validates each replayed envelope against the current compliance state before admitting it, so a stale offline record can never overwrite a newer certification.

python
def with_retry(fn, *, max_attempts: int = 5, base: float = 0.5):
    import random, time
    for attempt in range(max_attempts):
        try:
            return fn()
        except TransientError:
            if attempt == max_attempts - 1:
                raise
            sleep = base * (2 ** attempt) + random.uniform(0, base)
            time.sleep(sleep)
Ingestion failure-band routing An incoming envelope is classified by failure band into three lanes. Transient failures such as network timeouts or broker unavailability enter an exponential-backoff retry loop keyed on dvir_id that re-enters the queue. Structural failures such as a schema violation are preserved in the dead-letter queue with the ValidationError manifest and returned to the originating client. Compliance-critical failures such as a missing signature, VIN, or timestamp preserve the artifact and notify the fleet manager by webhook. A separate offline-replay path re-enters the validation gate deduplicated on the client-generated dvir_id. Envelope failure raised Classify band Transient timeout · broker down Backoff retry loop jitter · re-enter queue Structural schema violation Dead-letter + return ValidationError manifest Compliance-critical missing signature / VIN Preserve + webhook notify fleet manager offline replay · deduplicated on dvir_id → re-enter gate

The record the ingestion layer produces is the artifact a DOT auditor inspects, so its immutability strategy is part of the ingestion contract, not an afterthought. Persist the accepted envelope and its raw_sha256 in WORM storage — S3 with Object Lock or an append-only PostgreSQL table — so that no admitted record can be altered after the fact. Chain each ledger event’s hash to the previous one so tampering with any historical ingestion decision is detectable.

Retention scheduling is driven by prepared_at, not by receipt time, because § 396.11©(2)(iii) requires the original report, the repair certification, and the reviewing driver’s certification to be kept for at least three months from the preparation date. Records tied to an open defect are retained longer — until the return-to-service certification closes them — so an offline-delayed submission that arrives late is still retained for its full regulated window. For DOT audit access, index the WORM store by VIN and preparation date so an auditor’s “show me every inspection for unit 4471 in Q2” query resolves against the immutable store directly, with the append-only ledger providing the who/when/why of every ingestion decision behind it.

WORM retention timeline anchored at prepared_at A horizontal timeline anchored at the prepared_at preparation date. The mandatory retention window under 49 CFR 396.11(c)(2)(iii) runs three months from preparation. Records tied to an open defect extend past the three-month minimum until the return-to-service certification closes them, at which point the record becomes eligible for purge. Below the timeline, four hash-chained append-only ledger events — RECEIVED, VALIDATED, NORMALIZED, and CLASSIFIED — are plotted in sequence, each chained to the previous by SHA-256 so any tampering is detectable. 3-month minimum · § 396.11(c)(2)(iii) open-defect extension held until return-to-service prepared_at retention clock start (UTC) +3 months min purge-eligible RTS certified defect-linked purge Hash-chained append-only ledger (SHA-256) RECEIVED channel · dvir_id VALIDATED § 396.11 fields NORMALIZED canonical schema CLASSIFIED score 0–100
What gets a DVIR payload rejected at the ingestion gate?

Reject a payload when its raw_sha256 does not match the received bytes, when the § 396.11(a) driver certification signature is missing, when a mandatory field (VIN, prepared_at, driver identity) is absent, or when a duplicate dvir_id arrives carrying different content. Structural failures return to the originating client; compliance-critical failures also notify the fleet manager so the uncovered obligation is visible.

How does OCR confidence affect whether a paper DVIR is admitted?

Any paper-channel extraction with ocr_confidence below the 0.85 floor — or with no confidence value at all — is routed to a human verification queue rather than admitted. A reviewer who clears it writes an explicit manual_override audit event naming themselves; the low-confidence VIN or signature is never silently promoted into a compliance record.

How does ingestion stay compliant when a driver is offline?

Mobile clients write inspections to encrypted local storage and replay them on reconnection using the client-generated dvir_id as an idempotency key, so retries never create duplicate compliance records. The server records both the client prepared_at and its own receipt timestamp to detect clock skew and validates each replayed envelope against the current compliance state before admitting it.

From when does the three-month retention clock start?

From the report’s preparation date, per § 396.11©(2)(iii) — which is why prepared_at must be timezone-aware. Purge scheduling is driven by prepared_at rather than receipt time, and records tied to an open defect are held past the minimum until the return-to-service certification closes them.

This guide is the top-level reference for DVIR ingestion and parsing on this site. Back to Core DVIR Architecture & FMCSA Compliance Mapping.