Critical vs Non-Critical Routing Logic
Splitting Driver Vehicle Inspection Report (DVIR) defects into critical and non-critical streams is the single decision that determines whether an asset is legally allowed to move. Under 49 CFR § 396.11©(2), a driver-reported condition that affects safe operation must be corrected — and certified corrected — before the vehicle returns to service; the Federal Motor Carrier Safety Administration’s out-of-service (OOS) criteria then dictate which defects immobilize the unit outright. When that branch is made by hand, dispatchers clear held trucks under schedule pressure and carriers accrue Vehicle Maintenance BASIC violations. This page specifies the deterministic routing layer inside the Defect Classification & Repair Order Routing pipeline that consumes a scored defect, maps it to a compliance action in imperative terms — trigger an OOS hold or defer to a scheduled window — and emits the state transition to downstream maintenance and telematics systems without manual triage.
Prerequisites and Environment Setup
Anchor link to "Prerequisites and Environment Setup"The routing service is a thin, deterministic consumer that sits between the scoring stage and the maintenance execution layer. It does not compute severity itself — it receives a normalized score produced upstream by the Severity Scoring Algorithms for DVIR Defects and applies fleet-aware cut points supplied by Dynamic Threshold Tuning for Fleet Types. Target Python 3.10+ (the code below uses match statements and structural typing) with:
- Pydantic 2.x — schema validation and immutable routing payloads.
enum/datetime(stdlib) — severity tiers and UTC timestamps.- Celery 5.x or Prefect 2.x — asynchronous emission of alert and scheduling tasks.
- A message broker (Kafka, RabbitMQ, or AWS SNS/SQS) — the transport the routing decision is published to.
The routing service assumes the inbound defect already conforms to the canonical contract defined in the Standardized DVIR JSON Schema Design; any payload that fails that contract must be rejected before it reaches this layer, not silently defaulted to non-critical.
Data Schema and Normalization
Anchor link to "Data Schema and Normalization"The routing layer operates on two records: the scored input it consumes and the routing decision it emits. Field names and the 0–100 severity scale are held identical to the scoring and threshold pages so a single defect can be reconstructed across the pipeline during a DOT audit.
| Field | Type | Enumeration / Range | Compliance tag |
|---|---|---|---|
defect_id |
string (UUID) |
— | Immutable key for audit-trail reconstruction |
asset_id |
string (17-char VIN) |
— | Asset-level OOS and recall cross-reference |
component_code |
string |
SAE J1939 SPN / OEM fault tree | Maps to OOS component tables |
severity_score |
integer |
0–100 |
Output of the scoring engine |
fleet_type |
enum |
long_haul_tractor, regional_step_van, refrigerated_box, last_mile_sprinter |
Selects the threshold profile |
severity_tier |
enum |
minor, major, critical |
Routing decision label |
compliance_hold |
bool |
true / false |
true binds an OOS/dispatch freeze |
routing_lane |
enum |
oos_immediate, expedited_24h, deferred_pm |
Downstream queue selector |
decided_at |
ISO8601 |
UTC | State-transition timestamp for § 396.11 recordkeeping |
threshold_version |
string |
semver | Which cut-point matrix produced the decision |
The score-to-tier mapping is the same three-band table reproduced across the defect-classification pages; the routing layer only adds the lane and hold columns:
| Score | Tier | Compliance action | Routing lane |
|---|---|---|---|
0–34 |
Minor | Log for next scheduled PM | deferred_pm |
35–69 |
Major | Flag for pre-trip clearance within 24h | expedited_24h |
70–100 |
Critical / OOS | Trigger an OOS hold; freeze dispatch immediately | oos_immediate |
Core Routing Workflow
Anchor link to "Core Routing Workflow"The routing engine is a pure function over the scored defect plus its fleet-specific thresholds: given the same inputs it must always emit the same lane and hold flag. Fold no I/O, clocks, or random values into the branch itself — resolve those before or after, so the decision is reproducible from the audit log.
import logging
from enum import Enum
from datetime import datetime, timezone
from pydantic import BaseModel, Field
class SeverityTier(str, Enum):
MINOR = "minor"
MAJOR = "major"
CRITICAL = "critical"
class RoutingLane(str, Enum):
DEFERRED_PM = "deferred_pm"
EXPEDITED_24H = "expedited_24h"
OOS_IMMEDIATE = "oos_immediate"
class ScoredDefect(BaseModel):
defect_id: str
asset_id: str
component_code: str
severity_score: int = Field(ge=0, le=100)
fleet_type: str
threshold_version: str
class RoutingDecision(BaseModel, frozen=True):
defect_id: str
asset_id: str
severity_tier: SeverityTier
routing_lane: RoutingLane
compliance_hold: bool
threshold_version: str
decided_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
# Cut points arrive from Dynamic Threshold Tuning; defaults mirror the
# site-wide 0-34 / 35-69 / 70-100 bands and must never be hard-coded elsewhere.
def classify(score: int, major_floor: int = 35, critical_floor: int = 70) -> SeverityTier:
if score >= critical_floor:
return SeverityTier.CRITICAL
if score >= major_floor:
return SeverityTier.MAJOR
return SeverityTier.MINOR
def route(defect: ScoredDefect, major_floor: int = 35, critical_floor: int = 70) -> RoutingDecision:
tier = classify(defect.severity_score, major_floor, critical_floor)
match tier:
case SeverityTier.CRITICAL:
lane, hold = RoutingLane.OOS_IMMEDIATE, True # 49 CFR 396.11(c)(2): correct before return to service
case SeverityTier.MAJOR:
lane, hold = RoutingLane.EXPEDITED_24H, False
case SeverityTier.MINOR:
lane, hold = RoutingLane.DEFERRED_PM, False
return RoutingDecision(
defect_id=defect.defect_id,
asset_id=defect.asset_id,
severity_tier=tier,
routing_lane=lane,
compliance_hold=hold,
threshold_version=defect.threshold_version,
)
Because RoutingDecision is declared frozen=True, a decision cannot be mutated after it is emitted — an amended score must produce a new, separately logged decision rather than overwrite the original, which is exactly what an append-only audit trail requires.
Critical lane: atomic OOS enforcement
Anchor link to "Critical lane: atomic OOS enforcement"When route() returns compliance_hold=True, the system must execute one indivisible sequence: freeze the asset’s dispatch status, generate a high-priority repair order, and fan out notifications. The alerting fan-out is handled by Automating Critical Defect Alerts to Dispatchers, which pushes structured payloads to dispatch dashboards, ELD gates, and mobile technician apps.
async def enforce_critical(decision: RoutingDecision, publish) -> None:
if not decision.compliance_hold:
raise ValueError("enforce_critical called on a non-holding decision")
# Order matters: freeze dispatch BEFORE the repair order so no window exists
# in which the asset is both flagged critical and still dispatchable.
await publish("dispatch.freeze", {"asset_id": decision.asset_id, "reason": "OOS_396_11"})
await publish("repair_order.create", {"defect_id": decision.defect_id, "priority": "P1"})
await publish("alert.dispatcher", decision.model_dump(mode="json"))
logging.info("COMPLIANCE_LOCK asset=%s defect=%s", decision.asset_id, decision.defect_id)
The asset stays frozen until a certified mechanic signs the repair and uploads post-repair verification. Premature dispatch clearance, a missing signature, or a skipped state transition must raise a compliance exception and escalate to the safety director — never silently clear.
Non-critical lanes: deferred and batched work
Anchor link to "Non-critical lanes: deferred and batched work"Major and minor defects follow a different lifecycle. Minor findings are aggregated into batched work orders aligned to upcoming preventive-maintenance windows; major findings carry a hard 24-hour clearance clock but do not immobilize the unit mid-shift. The engine groups deferred items by component system (lighting, HVAC, cosmetic trim) so technician tooling and parts staging are prepared once per batch rather than per defect. High-volume deferral batches are handed to the same batching machinery described in Async Batching for High-Volume Ingestion.
Compliance Thresholding and Routing Obligations
Anchor link to "Compliance Thresholding and Routing Obligations"Every branch of the router maps to a concrete FMCSA obligation. The table below is the authoritative mapping compliance officers should reference when reconciling routing logs against inspection records:
| Computed value | Routing action | DOT / FMCSA obligation |
|---|---|---|
severity_score >= 70 |
oos_immediate, compliance_hold=true |
49 CFR § 396.11©(2): defect affecting safe operation must be corrected and certified before return to service; unit held per FMCSA OOS criteria |
35 <= severity_score < 70 |
expedited_24h, compliance_hold=false |
49 CFR § 396.11(a): documented defect requiring pre-trip correction within the carrier’s clearance window |
severity_score < 35 |
deferred_pm, compliance_hold=false |
49 CFR § 396.3(b): systematic maintenance record; defect logged for scheduled service |
| Missing or unparseable score | reject payload, raise exception | Never default to a non-holding lane — an unroutable defect is a compliance gap, not a minor one |
The imperative rule that governs the whole layer: when the score is ambiguous, missing, or the threshold version is unknown, reject the payload and escalate rather than deferring by default. A false negative here is an unsafe vehicle on the road.
Production Integration and Platform Synchronization
Anchor link to "Production Integration and Platform Synchronization"The routing decision is a fact that other systems subscribe to, not a call the router blocks on. Emit each RoutingDecision to the broker as a single event keyed by a deterministic identifier so retries during cellular dropouts or broker redelivery do not create duplicate repair orders. Derive the idempotency key from stable fields only:
import hashlib
def idempotency_key(decision: RoutingDecision) -> str:
# Deterministic across retries: same defect + same threshold version => same key.
material = f"{decision.defect_id}:{decision.threshold_version}:{decision.routing_lane.value}"
return hashlib.sha256(material.encode()).hexdigest()
The Computerized Maintenance Management System (CMMS) consumes repair_order.create events, ELD and telematics platforms (Samsara, Geotab) consume dispatch.freeze to enforce the OOS gate on the vehicle side, and the audit store consumes every decision. Chain audit entries cryptographically — each record includes the SHA-256 hash of the previous decision for the same asset — so a deleted or reordered clearance is detectable during a DOT audit. Reconciliation jobs periodically diff the routing ledger against CMMS and ELD state to catch assets cleared without a mechanic sign-off or major defects left open past their 24-hour clock. Assignment of the resulting repair orders to technicians is owned by Mechanic Assignment & Workload Balancing; the router only guarantees the correct lane and priority reach it.
Engineering Standards Checklist
Anchor link to "Engineering Standards Checklist"- Schema validation — reject any inbound payload that fails the canonical DVIR contract; never coerce a missing score to zero.
- Deterministic execution — the branch is a pure function of
severity_scoreand threshold version; no clocks or randomness insideroute(). - Immutable decisions — emit
frozenrecords; corrections create new logged decisions, never overwrites. - Idempotent emission — every published event carries a deterministic key derived from stable fields.
- Fail-closed defaults — ambiguous or unscorable defects escalate as exceptions, not silent deferrals.
- Versioned thresholds — persist
threshold_versionon every decision so a past routing call can be replayed exactly. - Cryptographic chaining — hash-link consecutive decisions per asset for tamper-evident audit packages.
Frequently Asked Questions
Anchor link to "Frequently Asked Questions"What severity score triggers an out-of-service hold?
A normalized severity_score of 70 or higher maps to the critical tier, which sets compliance_hold=true and routes to the oos_immediate lane. That band corresponds to defects meeting FMCSA out-of-service criteria under 49 CFR § 396.11©(2), so the asset’s dispatch status is frozen until a certified repair and post-repair verification are recorded.
How is a major defect different from a critical one in routing terms?
A major defect (score 35–69) carries a documented 24-hour pre-trip clearance obligation but does not immobilize the vehicle mid-shift, so it routes to the expedited_24h lane with compliance_hold=false. A critical defect immobilizes the unit immediately. Both are logged, but only the critical lane freezes dispatch.
What happens when a defect arrives without a valid severity score?
The router must reject the payload and raise a compliance exception rather than defaulting it to a non-critical lane. Defaulting an unscorable safety defect to deferred processing is the failure mode that puts an unsafe vehicle on the road, so unroutable defects escalate to the safety director for manual classification.
Why must routing decisions be immutable and cryptographically chained?
DOT audits require a verifiable, append-only record of every state transition. Freezing each RoutingDecision and hash-linking consecutive decisions per asset makes tampering — a deleted clearance or a reordered sign-off — detectable, which is what turns the routing log into a defensible audit package.
Related
Anchor link to "Related"- Severity Scoring Algorithms for DVIR Defects — produces the 0–100 score this layer consumes.
- Dynamic Threshold Tuning for Fleet Types — supplies the fleet-aware cut points that move the major and critical floors.
- Automating Critical Defect Alerts to Dispatchers — the alert fan-out invoked by the critical lane.
- Mechanic Assignment & Workload Balancing — assigns the repair orders this router emits.
- Standardized DVIR JSON Schema Design — the canonical contract every inbound defect must satisfy.