Defect Classification & Repair Order Routing
Driver Vehicle Inspection Reports are the legal hinge between a vehicle being operationally ready and a carrier being liable. Under 49 CFR § 396.11(a)(3) a driver must record any defect that would affect the safety of operation or result in a mechanical breakdown, and under § 396.11©(2) the motor carrier must certify that the reported defect was repaired — or that repair was unnecessary — before the vehicle is dispatched again. § 396.13(a) then requires the next driver to review that certification and sign the prior report before operating the vehicle. Those three clauses are not paperwork; they are state-transition invariants. A defect that is discovered but never classified, routed, and certified is a Vehicle Maintenance BASIC violation waiting to surface at the next roadside inspection.
This section is the engineering reference for the layer that consumes a normalized inspection record and turns it into a certified repair. It sits downstream of the Core DVIR Architecture & FMCSA Compliance Mapping reference that defines the canonical record and the gated state machine, and it is fed by the DVIR Ingestion & Digital/Paper Parsing Workflows pipeline that produces validated payloads. What this section owns is everything between “a defect exists in the record” and “the record is certified return-to-service”: deterministic severity classification, routing to the correct maintenance pathway, idempotent work-order generation, technician dispatch, and the append-only audit trail that survives a DOT audit.
The design principle throughout is that compliance decisions must be deterministic, auditable, and imperative. When a score crosses a threshold, the system does not “flag for review” — it triggers an out-of-service hold. When a payload is missing a certification signature, the system does not warn — it rejects the payload. Every rule that maps an observation to a regulatory action is expressed in code, backed by database constraints, and mirrored in an immutable ledger.
Why § 396.11 Classification Cannot Be Discretionary
Anchor link to "Why § 396.11 Classification Cannot Be Discretionary"Every defect-routing system exists to satisfy a federal mandate, and the mandate does not tolerate discretion at the boundary that matters. Under § 396.11(a), the report must cover at minimum the service brakes, parking brake, steering mechanism, lighting devices and reflectors, tires, horn, windshield wipers, mirrors, coupling devices, wheels and rims, and emergency equipment. When any of those components is reported defective, the carrier is obligated under § 396.11©(1) to repair it or determine that repair is unnecessary, and to keep the original report, the certification of repairs, and the certification of the reviewing driver for at least three months per § 396.11©(2)(iii).
The failure mode carriers actually get cited for is not missing a defect — it is dispatching a vehicle before the defect was certified as resolved. That is why classification must be deterministic. If a brake-adjustment defect scores as critical on one run and major on another because the logic depends on a model’s confidence or a race in the scoring service, the audit trail contradicts itself, and a contradictory audit trail is worse than none. The Out-of-Service (OOS) criteria published in the CVSA North American Standard are the ground truth: a brake violation that exceeds the adjustment limit, a steering component with excessive lash, a tire with tread below 2/32" on a steer axle — each is a binary, defensible condition. The classifier’s job is to reproduce those binary conditions faithfully and then layer graded severity on top for the conditions the regulations leave to carrier judgment.
The consequence of getting this wrong is measured in CSA points and roadside inspection frequency. A defect that should have triggered an OOS hold but was routed to a scheduled-maintenance queue becomes an operating-a-vehicle-with-a-known-defect violation the moment that vehicle rolls. Treating the severity threshold as a hard gate the data cannot bypass — rather than a suggestion a dispatcher can override — is the only defensible posture during an audit.
Architecture Overview: From Classified Defect to Certified Repair
Anchor link to "Architecture Overview: From Classified Defect to Certified Repair"The routing layer is a directed state machine, not a queue of tickets. A validated DVIR record enters with one or more defect entries, each defect is scored, the score is mapped to a severity band, and the band selects exactly one routing pathway. From there the record cannot reach a RETURNED_TO_SERVICE state without a repair-certification event that references the specific defect code, and it cannot reach a FINALIZED state without the mechanic certification required by § 396.11©(2). Every transition is appended to the audit ledger before the next transition is allowed to begin.
The critical design property is that the transitions are gated, not implicit. A dispatcher cannot manually move a critical record to a scheduled queue; the transition table simply has no legal edge for it. This is what makes the pipeline auditable: the set of paths a record can travel is finite and enumerable, and every path terminates in either a certified repair or an explicit, logged rejection. The severity mathematics that assigns the score feeding this diagram is developed in full in the Severity Scoring Algorithms for DVIR Defects reference; the branching that selects the pathway is developed in Critical vs Non-Critical Routing Logic.
Schema-Driven Data Standardization
Anchor link to "Schema-Driven Data Standardization"Classification is only as reliable as the record it operates on. The routing layer consumes the canonical DVIR defect record produced upstream — the same field names and types defined in the Standardized DVIR JSON Schema Design — and never accepts free-text as an input to a compliance decision. The canonical defect record used across every page in this section is:
| Field | Type | Compliance annotation |
|---|---|---|
dvir_id |
str (UUID) |
Immutable idempotency key for audit-trail reconstruction |
vehicle_vin |
str (17-char) |
Asset identity; cross-referenced against recall and OOS history |
component_code |
str |
Standardized taxonomy (SAE J1939 SPN / OEM fault tree); see defect taxonomy mapping |
defect_description |
str |
Sanitized driver input; never an input to the score, only to the record |
driver_severity_flag |
enum{minor,major,critical} |
Driver’s § 396.11(a)(3) initial classification |
inspection_ts |
datetime (tz-aware) |
§ 396.11 inspection time; drives SLA and retention windows |
odometer_reading |
int |
Wear context for scoring adjustments |
compliance_tags |
list[str] |
Regulatory markers, e.g. FMCSA_396.11, DOT_OOS_CRITERIA |
Enforcing this contract in code with Pydantic 3.10+ syntax makes the schema the first compliance gate. A record that does not validate never reaches the classifier:
from datetime import datetime
from enum import StrEnum
from uuid import UUID
from pydantic import BaseModel, Field, field_validator
class SeverityFlag(StrEnum):
MINOR = "minor"
MAJOR = "major"
CRITICAL = "critical"
class DefectRecord(BaseModel):
"""Canonical DVIR defect record consumed by the routing layer.
Validation here is the first compliance gate: a record that does not
satisfy the schema is rejected at the API boundary (49 CFR § 396.11).
"""
dvir_id: UUID
vehicle_vin: str = Field(min_length=17, max_length=17)
component_code: str
defect_description: str
driver_severity_flag: SeverityFlag
inspection_ts: datetime
odometer_reading: int = Field(ge=0)
compliance_tags: list[str] = Field(default_factory=list)
@field_validator("inspection_ts")
@classmethod
def require_timezone(cls, ts: datetime) -> datetime:
# Naive timestamps break SLA and retention math; reject them.
if ts.tzinfo is None:
raise ValueError("inspection_ts must be timezone-aware (UTC)")
return ts
@field_validator("vehicle_vin")
@classmethod
def normalize_vin(cls, vin: str) -> str:
vin = vin.strip().upper()
# I, O, Q are not valid VIN characters; a misread here mis-routes the record.
if any(c in vin for c in ("I", "O", "Q")):
raise ValueError("VIN contains invalid characters (I/O/Q)")
return vin
The component_code field is the join key between the driver’s observation and the regulatory meaning of the component. Mapping raw SPNs and OEM codes onto a single taxonomy is handled by the Defect Taxonomy Mapping for Heavy Trucks reference; the routing layer assumes that mapping has already run and that component_code is canonical.
Core Implementation Patterns
Anchor link to "Core Implementation Patterns"Deterministic classification
Anchor link to "Deterministic classification"The classifier converts a validated DefectRecord into a 0–100 severity score and a band. The score is computed by rule-based logic over component criticality, the driver’s own flag, and wear context — never by a stochastic model whose output cannot be reproduced during an audit. The band boundaries are fixed and reused on every page that references them: 0–34 minor, 35–69 major, 70–100 critical.
from dataclasses import dataclass
# Component criticality weights are the deterministic core of the score.
# Steer-axle brakes and steering are OOS-critical under CVSA criteria.
COMPONENT_WEIGHT: dict[str, int] = {
"service_brakes": 45,
"steering": 45,
"tires_steer": 40,
"coupling": 38,
"lighting": 20,
"wipers": 12,
"horn": 8,
}
DRIVER_FLAG_FLOOR = {"minor": 0, "major": 35, "critical": 70}
@dataclass(frozen=True)
class Classification:
score: int
band: str # "minor" | "major" | "critical"
def classify(record: DefectRecord) -> Classification:
base = COMPONENT_WEIGHT.get(record.component_code, 15)
# Wear escalation: high-mileage assets carry more residual risk.
wear_bonus = 10 if record.odometer_reading > 500_000 else 0
# The driver's § 396.11(a)(3) flag is a floor, never a ceiling: a driver
# may under-call severity, but the system must not under-route it.
floor = DRIVER_FLAG_FLOOR[record.driver_severity_flag]
score = min(100, max(base + wear_bonus, floor))
band = "critical" if score >= 70 else "major" if score >= 35 else "minor"
return Classification(score=score, band=band)
Treating the driver’s flag as a floor rather than an override is the single most important compliance decision in the classifier: a driver who under-reports a critical brake defect as “minor” must not be able to route the vehicle back to service. The way carrier-specific thresholds are tuned around these fixed bands — so a marginal brake-lining observation on a steep-grade route escalates while the same reading on a flat regional lane does not — is developed in Dynamic Threshold Tuning for Fleet Types.
Idempotent work-order generation
Anchor link to "Idempotent work-order generation"Every band maps to exactly one routing action, and work-order creation must be idempotent so that a network retry, an offline replay, or a duplicate DVIR submission never opens two tickets for the same defect. The deterministic key is derived from the fields that uniquely identify the defect event:
import hashlib
from enum import StrEnum
class Route(StrEnum):
OOS_HOLD = "oos_hold" # 70-100: immobilize, § 396.11 known-defect gate
REPAIR_24H = "repair_24h" # 35-69: regulated repair window
SCHEDULED = "scheduled" # 0-34: next maintenance cycle
def route_for(band: str) -> Route:
return {
"critical": Route.OOS_HOLD,
"major": Route.REPAIR_24H,
"minor": Route.SCHEDULED,
}[band]
def work_order_key(record: DefectRecord) -> str:
"""Deterministic id so retries and replays are idempotent, not duplicated."""
seed = f"{record.vehicle_vin}:{record.component_code}:{record.inspection_ts.isoformat()}"
return hashlib.sha256(seed.encode()).hexdigest()[:32]
def build_work_order(record: DefectRecord, cls: Classification) -> dict:
route = route_for(cls.band)
return {
"work_order_id": work_order_key(record),
"dvir_id": str(record.dvir_id),
"vehicle_vin": record.vehicle_vin,
"component_code": record.component_code,
"severity_score": cls.score,
"route": route.value,
"immobilize": route is Route.OOS_HOLD,
"compliance_tags": record.compliance_tags,
}
Because work_order_key is a pure function of the defect event, the CMMS write can be an idempotent upsert keyed on work_order_id: a duplicate submission resolves to the same row instead of a second ticket. This is what lets the pipeline tolerate the retry storms that follow a cellular dropout without corrupting the ledger.
Dispatch and workload balancing
Anchor link to "Dispatch and workload balancing"Routing a work order into the CMMS does not guarantee a timely repair. The dispatch layer matches each open order to a technician by ASE certification, current shift status, geographic proximity, and active workload, and it refuses to assign a complex electrical fault to a technician who lacks the certification for it. Production dispatch runs on async task orchestration (Celery, RQ, or AWS Step Functions) with priority queues and circuit breakers for CMMS downtime, and it factors mean-time-to-repair baselines into queue priority so an OOS hold never waits behind routine work. The certification-weighted assignment model and capacity-aware scheduling are developed in Mechanic Assignment & Workload Balancing.
Compliance Boundary Enforcement
Anchor link to "Compliance Boundary Enforcement"The routing layer is where several § 396.11 invariants are physically enforced, and enforcement is expressed as gates the data cannot bypass rather than checks a user can waive. The same principles that govern the upstream ingestion boundary are formalized for cloud workflows in the Compliance Boundary Enforcement in Cloud Workflows reference; within this section they take the following concrete form.
- Severity-to-action gate. A
criticalband deterministically produces anOOS_HOLDwithimmobilize = True. There is no code path and no role that can route a critical defect to a scheduled queue. - State-transition invariants. The legal transitions are
OPEN → IN_REPAIR → CERTIFIED → RETURNED_TO_SERVICE. A record cannot skipCERTIFIED; return-to-service without a certification event that references the specificcomponent_codeis not a representable state. - RBAC on certification. Only an identity holding the
mechanic.certifyrole may emit the certification event required by § 396.11©(2). The driver who reported the defect cannot certify their own report. - Append-only audit logging. Every accepted transition writes a structured JSON event — payload, rule evaluation, resulting score, actor identity, and the previous event’s hash — before the next transition is permitted.
Encoding the transition table explicitly keeps the state machine auditable:
LEGAL_TRANSITIONS: dict[str, set[str]] = {
"OPEN": {"IN_REPAIR"},
"IN_REPAIR": {"CERTIFIED"},
"CERTIFIED": {"RETURNED_TO_SERVICE"},
"RETURNED_TO_SERVICE": set(),
}
def assert_transition(current: str, nxt: str, actor_roles: set[str]) -> None:
if nxt not in LEGAL_TRANSITIONS[current]:
# Illegal edge: reject rather than coerce the record into a bad state.
raise ValueError(f"illegal transition {current} -> {nxt}")
# § 396.11(c)(2): only a certifying mechanic may finalize a repair.
if nxt == "CERTIFIED" and "mechanic.certify" not in actor_roles:
raise PermissionError("certification requires the mechanic.certify role")
Database-level enforcement backs the application logic: repair-order creation and status updates run at serializable isolation so two concurrent workers cannot both advance the same record, and a unique constraint on work_order_id makes duplicate creation impossible even under a race.
Edge Resilience and Failure Modes
Anchor link to "Edge Resilience and Failure Modes"Real fleets generate DVIRs from mobile clients on intermittent cellular links, so the routing layer must assume out-of-order, delayed, and duplicated events are normal rather than exceptional.
- Idempotency. Every write is keyed on
work_order_key, so a replayed submission upserts the same row. The CMMS integration uses the same key as its idempotency token so a webhook redelivery never opens a second ticket. - Offline reconciliation. A DVIR captured offline may arrive hours after its
inspection_ts. Retention and SLA windows key offinspection_ts, never wall-clock arrival time, so a late-arriving critical defect is still correctly gated as of the moment of inspection. - Retry logic. CMMS and dispatch calls run behind a circuit breaker with exponential backoff. When the CMMS is unreachable, work orders queue in a durable dead-letter store rather than being dropped, and a reconciliation job drains the queue when the CMMS recovers.
- Rejection handling. A payload that fails schema validation, carries an unresolved safety defect without a certification, or references an unknown
component_codeis rejected at the boundary and returned to the client for correction — it is never partially processed.
def ingest(raw: dict) -> dict:
try:
record = DefectRecord.model_validate(raw)
except Exception as exc:
# Reject at the boundary; do not admit an unvalidated record.
return {"status": "rejected", "reason": str(exc)}
cls = classify(record)
order = build_work_order(record, cls)
# Upsert is idempotent on work_order_id, so retries are safe.
upsert_work_order(order)
append_audit_event("OPEN", order, record)
return {"status": "accepted", "work_order_id": order["work_order_id"], "route": order["route"]}
Retention and Audit Readiness
Anchor link to "Retention and Audit Readiness"Every state transition in the routing pipeline must be reconstructable years later on demand, because a DOT audit can request the full chain of custody from defect discovery through repair certification. The retention strategy has three parts.
Immutability. The signed defect record and its certification are written to Write-Once-Read-Many storage — AWS S3 Object Lock in compliance mode or Azure Immutable Blob — so that no actor, including an administrator, can alter a finalized record. Each record is hashed with SHA-256 at write time and each audit event carries the hash of the prior event, producing a tamper-evident chain: altering any historical record breaks every downstream hash.
Retention scheduling. § 396.11©(2)(iii) sets a floor of three months for the original report, the repair certification, and the reviewing-driver certification. The pipeline keys retention off inspection_ts and, because many carriers extend retention for insurance and litigation holds, the retention policy is configuration, not a hard-coded constant — but the configured value can never fall below the federal floor.
Audit access. Records are indexed by vehicle_vin and preparation date so an auditor’s most common query — “show every DVIR and its certification for this unit over this window” — resolves without a full scan. OpenTelemetry instrumentation across the classification, routing, and dispatch services traces latency and error rates, and a nightly reconciliation job cross-references DVIR submissions, CMMS work orders, and driver sign-off records, flagging any discrepancy for compliance review before it becomes an audit finding.
By enforcing deterministic classification, idempotent state management, and a cryptographically chained audit trail, the routing layer turns § 396.11 from a recurring liability into a defensible, queryable record.
Related
Anchor link to "Related"- Severity Scoring Algorithms for DVIR Defects — the weighted 0–100 scoring model that feeds this pipeline.
- Dynamic Threshold Tuning for Fleet Types — adjusting severity boundaries per vehicle class and route profile.
- Critical vs Non-Critical Routing Logic — the branching that selects the maintenance pathway.
- Mechanic Assignment & Workload Balancing — certification-weighted, capacity-aware dispatch.
- Core DVIR Architecture & FMCSA Compliance Mapping — the upstream reference that defines the canonical record and gated state machine.
Frequently Asked Questions
Anchor link to "Frequently Asked Questions"What severity score triggers an out-of-service hold?
A severity score of 70–100 places a defect in the critical band, which deterministically produces an OOS_HOLD with an immobilization flag. This band aligns with CVSA North American Standard Out-of-Service criteria, and the transition is a hard gate: no role or manual action can route a critical defect to a lower-priority queue.
How does the routing layer prevent duplicate repair orders?
Work-order creation is keyed on a deterministic identifier derived from the VIN, component code, and inspection timestamp via SHA-256. The CMMS write is an idempotent upsert on that key, so a network retry, an offline replay, or a duplicate DVIR submission resolves to the same order instead of opening a second ticket.
Can a driver’s own severity flag lower the routed severity?
No. The driver’s § 396.11(a)(3) flag is treated as a floor, never a ceiling. A driver may under-call a defect, but the classifier will never route below the severity the component and wear context justify, so an under-reported critical brake defect cannot be returned to service.
How long must DVIR defect records be retained?
49 CFR § 396.11©(2)(iii) sets a floor of three months for the original report, the repair certification, and the reviewing-driver certification. The pipeline stores these in WORM storage with SHA-256 hashing and keys retention off the inspection timestamp; carriers commonly configure longer windows for insurance and litigation holds, but never below the federal minimum.
Back to Defect Classification & Repair Order Routing is the top of this section; from here, continue up to the Core DVIR Architecture & FMCSA Compliance Mapping reference that defines the invariants this layer enforces.