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.
(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.
Data Schema and Normalization
Anchor link to "Data Schema and Normalization"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:
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.
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.
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.
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.
Engineering Standards Checklist
Anchor link to "Engineering Standards Checklist"- Validate every canonical defect through the Pydantic model; reject any object whose
severity_scorefalls outside 0–100. - Keep resolution deterministic — no network calls, no clock, no randomness inside
resolve(). - Record
resolution_stageandmatch_confidenceon every defect for audit lineage; retainraw_codeverbatim as evidence. - Fail unresolved safety defects safe to
critical; never dispatch on a quarantined code. - Version the registry and stamp
registry_versioninto every emitted event. - Alert on quarantine-rate drift; treat unmapped codes as a signal, not noise.
Frequently Asked Questions
Anchor link to "Frequently Asked Questions"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.
Related
Anchor link to "Related"- Defect Code Standardization Across Fleets — the YAML registry format, vendor aliases, and multi-stage normalization this page consumes.
- Standardized DVIR JSON Schema Design — the canonical record contract that guarantees a clean
defects[]array before resolution. - FMCSA DVIR Rule 396.11 Breakdown — clause-by-clause mapping of each severity band to its § 396.11 obligation.
- Severity Scoring Algorithms for DVIR Defects — how the 0–100 score behind each band is computed.
- Critical vs Non-Critical Routing Logic — the routing engine that acts on the bands this taxonomy emits.