Skip to content
Core Architecture

Defect Taxonomy Mapping for Heavy Trucks

A defect taxonomy is the deterministic lookup layer that turns fragmented inspection input — OEM diagnostic trouble codes (DTCs), legacy telematics payloads, and free-text driver notes — into a single controlled vocabulary that a compliance engine can act on. Getting this wrong is a Vehicle Maintenance BASIC problem: under 49 CFR § 396.11(a) every reported defect that affects safe operation must be documented and, under § 396.11(a)(3), a defect that would cause a breakdown must be recorded and resolved before dispatch. If the taxonomy silently drops or misclassifies a brake defect, the vehicle moves under an unremediated out-of-service (OOS) condition and the carrier absorbs the roadside violation. This guide specifies how to build that mapping layer for heavy trucks, and it sits directly under the Core DVIR Architecture & FMCSA Compliance Mapping reference architecture, consuming the canonical record it defines and feeding the classification stage that follows.

Problem Framing: One Vocabulary Across Heterogeneous Inputs

Anchor link to "Problem Framing: One Vocabulary Across Heterogeneous Inputs"

Heavy-truck fleets rarely operate a single ELD or a single OEM. A mixed fleet emits Freightliner fault strings, Volvo diagnostic exports, PACCAR J1939 SPN/FMI pairs, and hand-typed notes from a driver’s phone — all describing the same physical defect with different tokens. The taxonomy’s job is to collapse that variance into a stable (system, component, severity) triple that maps to an exact regulatory obligation. The severity band, not the raw string, is what triggers a compliance action: a score of 70–100 forces an immediate OOS hold, 35–69 opens the regulated repair window, and 0–34 routes to scheduled maintenance. Those bands are the same ones the Severity Scoring Algorithms for DVIR Defects reference computes and the Critical vs Non-Critical Routing Logic engine acts on, so the taxonomy must emit them identically.

Three-tier defect taxonomy resolution A left-to-right pipeline. Three heterogeneous raw inputs — an OEM diagnostic trouble code, a legacy telematics payload, and a free-text driver note — converge on a single normalization step. Normalization feeds Tier 1, the closed system-domain enum (brakes, steering, lighting, tires, coupling, frame, emergency equipment). Tier 1 feeds Tier 2, the canonical snake_case component identifier. Tier 2 feeds Tier 3, the severity band derived from a 0 to 100 score. The band splits into three compliance actions: a minor score of 0 to 34 routes to scheduled maintenance, a major score of 35 to 69 opens the regulated repair window, and a critical score of 70 to 100 triggers an out-of-service hold under 49 CFR 396.11(c)(2). RAW INPUTS OEM DTC J1939 SPN/FMI Telematics payload legacy vendor string Free-text note driver-entered Normalize NFC · lowercase collapse ws TIER 1 · SYSTEM system_domain brakes steering lighting tires coupling frame emergency_equipment closed by § 396.11(a) TIER 2 · COMPONENT component canonical token snake_case TIER 3 · SEVERITY → ACTION Critical · 70–100 OOS hold · block dispatch Major · 35–69 regulated repair window Minor · 0–34 scheduled maintenance Unresolved below the fuzzy floor → quarantine, scored critical (fail safe) so the unit is held, never dispatched under § 396.11(a)(3).
The taxonomy collapses heterogeneous inputs into a (system, component, severity) triple, then maps each severity band to its exact § 396.11 obligation.

Prerequisites and Environment Setup

Anchor link to "Prerequisites and Environment Setup"

Target Python 3.10+ so the taxonomy models can use match statements and union (X | Y) type syntax. The reference stack is:

  • pydantic>=2.0 — runtime validation of the canonical defect object and enum enforcement.
  • pyyaml — loading the versioned mapping registry (canonical codes, aliases, severity weights).
  • rapidfuzz — bounded fuzzy matching for typo recovery on driver-entered strings.
  • redis (optional) — in-memory cache for the compiled lookup table under high telematics burst.

The taxonomy does not validate the whole DVIR; it assumes the payload already conforms to the contract defined in the Standardized DVIR JSON Schema Design, which guarantees a well-formed defects[] array with timestamped discovery windows and driver certification flags before any code resolution runs. Field-level normalization of the surrounding record (VIN casing, timestamp timezone, unit-number formatting) is handled upstream by Automated Field Mapping & Data Normalization, so this layer receives clean strings and concerns itself only with the defect vocabulary.

The taxonomy resolves each raw defect into a canonical object. The input is whatever the client submitted; the output is the controlled triple plus a resolution audit trail.

Field Type Enumeration / Constraint Compliance tag
raw_code str free-form, 1–128 chars evidence — retained verbatim
system_domain enum brakes, steering, lighting, tires, coupling, frame, emergency_equipment maps to § 396.11(a) inspection items
component str canonical token, snake_case maintenance routing key
severity_band enum minor (0–34), major (35–69), critical (70–100) drives OOS decision
severity_score int 0–100 audit-defensible numeric
oos_trigger bool true when band == critical § 396.11©(2) hold
resolution_stage enum exact, regex, fuzzy, quarantine data-lineage flag
match_confidence float 0.0–1.0 quarantine gate

The three system-domain tiers map one-to-one onto the § 396.11(a) inspection list, so the enum is closed by regulation rather than by convention. Model the canonical object in Pydantic so malformed severity data cannot enter the pipeline:

python
from enum import Enum
from pydantic import BaseModel, Field, field_validator


class SeverityBand(str, Enum):
    MINOR = "minor"        # 0-34   -> scheduled maintenance
    MAJOR = "major"        # 35-69  -> regulated repair window
    CRITICAL = "critical"  # 70-100 -> immediate OOS hold, 49 CFR § 396.11(c)(2)


class SystemDomain(str, Enum):
    BRAKES = "brakes"
    STEERING = "steering"
    LIGHTING = "lighting"
    TIRES = "tires"
    COUPLING = "coupling"
    FRAME = "frame"
    EMERGENCY_EQUIPMENT = "emergency_equipment"


class CanonicalDefect(BaseModel):
    raw_code: str = Field(min_length=1, max_length=128)
    system_domain: SystemDomain
    component: str
    severity_score: int = Field(ge=0, le=100)
    resolution_stage: str
    match_confidence: float = Field(ge=0.0, le=1.0)

    @property
    def severity_band(self) -> SeverityBand:
        if self.severity_score >= 70:
            return SeverityBand.CRITICAL
        if self.severity_score >= 35:
            return SeverityBand.MAJOR
        return SeverityBand.MINOR

    @property
    def oos_trigger(self) -> bool:
        return self.severity_band is SeverityBand.CRITICAL

    @field_validator("component")
    @classmethod
    def snake_case(cls, v: str) -> str:
        return v.strip().lower().replace(" ", "_").replace("-", "_")

The mapping registry itself is configuration, not code, so compliance staff can amend it without a deploy. The child page Defect Code Standardization Across Fleets specifies the full YAML registry format — canonical codes, prioritized vendor aliases, regex extraction patterns, and per-code severity weights — and is the authoritative source for the resolution rules this page consumes.

Core Workflow: Deterministic Resolution

Anchor link to "Core Workflow: Deterministic Resolution"

Resolution runs as an ordered cascade. Each stage is deterministic and records which stage produced the match, so every canonical code is traceable back to its raw input during a DOT audit. Never let a low-confidence guess silently become a compliance signal — route it to quarantine instead.

python
import unicodedata
from rapidfuzz import process, fuzz


def normalize(raw: str) -> str:
    """NFC-normalize, strip noise, collapse whitespace, lowercase."""
    text = unicodedata.normalize("NFC", raw).strip().lower()
    return " ".join(text.split())


def resolve(raw: str, registry: "Registry") -> CanonicalDefect:
    norm = normalize(raw)

    # Stage 1: exact canonical or alias lookup.
    if (entry := registry.exact(norm)) is not None:
        return entry.to_canonical(raw, stage="exact", confidence=1.0)

    # Stage 2: compiled vendor regex (e.g. ^frt-(\d{3})$, ^volvo_brake_(\w+)$).
    if (entry := registry.match_regex(norm)) is not None:
        return entry.to_canonical(raw, stage="regex", confidence=0.95)

    # Stage 3: bounded fuzzy match for driver typos; strict floor.
    best = process.extractOne(norm, registry.alias_index, scorer=fuzz.ratio)
    if best and best[1] >= 88:  # >= 0.88 confidence
        entry = registry.by_alias(best[0])
        return entry.to_canonical(raw, stage="fuzzy", confidence=best[1] / 100)

    # Below the floor: DO NOT guess. Quarantine for human review so an
    # unmapped safety defect is never dispatched under § 396.11(a)(3).
    return CanonicalDefect(
        raw_code=raw,
        system_domain=SystemDomain.FRAME,  # conservative placeholder
        component="unresolved",
        severity_score=70,                  # fail safe: treat as critical
        resolution_stage="quarantine",
        match_confidence=(best[1] / 100) if best else 0.0,
    )

Two rules make this safe. First, an unresolved defect fails safe to a critical score so the vehicle is held rather than released. Second, composite strings such as LIGHTS_BRAKES_TIRES or STEERING+EXHAUST must be tokenized on delimiters and resolved per token, with the record inheriting the highest severity across all resolved tokens — a truck with a minor light and a critical brake defect is a critical hold. The routing state machine below is the same shape the parent architecture publishes.

Deterministic resolution cascade A state diagram of the ordered resolution cascade. A normalized string enters an exact canonical-or-alias lookup at confidence 1.0; a miss falls through to a compiled vendor-regex stage at confidence 0.95; a miss there falls through to a bounded fuzzy match. If the fuzzy score is at least 0.88 the defect resolves to a canonical triple; below 0.88 it is sent to quarantine for human review and scored critical so it is never dispatched. A match at any stage produces the canonical defect, whose severity band then splits three ways: minor to scheduled maintenance, major to the regulated repair window, and critical to an out-of-service hold. Normalize input string Exact lookup canonical / alias · 1.0 Vendor regex compiled · 0.95 Fuzzy match floor ≥ 0.88 Quarantine score 70 · human review Canonical defect (system, comp, score) OOS hold critical 70–100 Repair window major 35–69 Scheduled minor 0–34 miss miss exact match regex match ≥ 0.88 below floor
Every stage is deterministic and stamps its resolution_stage, so each canonical code is traceable back to its raw input during a DOT audit.

Compliance Thresholding and Routing

Anchor link to "Compliance Thresholding and Routing"

Once a defect carries a canonical (system, component, severity_score), the band determines the exact obligation. Reproduce these thresholds identically wherever this taxonomy is referenced:

Severity band Score Compliance action Regulatory basis
critical 70–100 Trigger an OOS hold; block dispatch; require mechanic certification before return-to-service 49 CFR § 396.11©(2), § 396.11(a)(3)
major 35–69 Open a repair order inside the regulated repair window; certify repair before next dispatch 49 CFR § 396.11©(2)
minor 0–34 Route to scheduled maintenance queue; document, no dispatch hold 49 CFR § 396.11(a)

State compliance actions in the imperative: when oos_trigger is true, reject the dispatch request and place a hard hold — do not emit an advisory that an operator can override. The mechanic certification gate that clears a critical hold, and the finite-state transitions that enforce it, are specified in Compliance Boundary Enforcement in Cloud Workflows, and the clause-by-clause obligation each band satisfies is detailed in the FMCSA DVIR Rule 396.11 Breakdown. Where a fleet needs the numeric band boundaries themselves to differ by vehicle class, apply Dynamic Threshold Tuning for Fleet Types rather than editing the taxonomy in place.

Production Integration and Platform Synchronization

Anchor link to "Production Integration and Platform Synchronization"

The taxonomy is a pure function over (raw_code, registry_version), which makes it idempotent: the same input and the same registry version always produce the same canonical triple. Emit that triple as an event so downstream systems stay in sync without re-resolving.

python
import hashlib
import json


def emit_defect_event(defect: CanonicalDefect, dvir_id: str, registry_version: str) -> dict:
    payload = {
        "dvir_id": dvir_id,
        "registry_version": registry_version,
        "system_domain": defect.system_domain.value,
        "component": defect.component,
        "severity_band": defect.severity_band.value,
        "severity_score": defect.severity_score,
        "oos_trigger": defect.oos_trigger,
    }
    # Deterministic idempotency key: same defect + registry -> same key.
    key_src = f"{dvir_id}:{defect.component}:{registry_version}".encode()
    payload["event_key"] = hashlib.sha256(key_src).hexdigest()
    return payload

A critical event feeds the CMMS as an immediate work order and the telematics/ELD platform as a unit status change, keyed on event_key so a redelivered message never creates a duplicate hold. Pin registry_version into every event: when the registry is amended, previously resolved defects remain reproducible against the version that produced them, which is what a DOT auditor needs to reconstruct a decision. Cache the compiled lookup table in-memory (functools.lru_cache) or in Redis, and fire a pipeline alert when the rate of quarantine-stage resolutions crosses a configured floor — a spike means a new OEM format has appeared and the registry is drifting behind the fleet.

  • Validate every canonical defect through the Pydantic model; reject any object whose severity_score falls outside 0–100.
  • Keep resolution deterministic — no network calls, no clock, no randomness inside resolve().
  • Record resolution_stage and match_confidence on every defect for audit lineage; retain raw_code verbatim as evidence.
  • Fail unresolved safety defects safe to critical; never dispatch on a quarantined code.
  • Version the registry and stamp registry_version into every emitted event.
  • Alert on quarantine-rate drift; treat unmapped codes as a signal, not noise.
How do I map an OEM J1939 SPN/FMI pair to the taxonomy?

Treat the SPN-FMI string as a raw_code and add its canonical mapping as an exact registry entry, because SPN/FMI pairs are stable and should never fall through to fuzzy matching. The SPN identifies the suspect component and the FMI the failure mode; encode both into the (system_domain, component, severity_score) triple so the same physical fault from any J1939 source resolves identically.

Can severity scoring use a machine-learning model instead of the registry?

The band that maps a defect to an OOS action must be deterministic and reproducible to survive a DOT audit, so the final score and oos_trigger must come from the versioned registry rules. A model may pre-normalize free text or suggest a canonical code, but it cannot be the authority that sets the compliance band under 49 CFR § 396.11©(2).

What happens when a defect string names two systems at once?

Tokenize the string on underscores, plus signs, hyphens, and semicolons, resolve each token independently, and assign the record the highest severity across all resolved tokens. A composite such as LIGHTS_BRAKES inherits the critical brake band, so the vehicle is held rather than routed to scheduled maintenance.

Back to Core DVIR Architecture & FMCSA Compliance Mapping.