Skip to content
Classification & Routing

Mapping Defect Severity Tiers to BASIC Violations

Turning a classified DVIR defect into its CSA exposure requires one concrete artifact: a crosswalk that, given a defect’s severity tier and standardized category, returns the CFR violation code an inspector would cite, the BASIC that code rolls up to, an illustrative severity weight, and whether the condition is out-of-service (OOS). Get that lookup wrong and the failure is asymmetric — a defect mapped to too low a weight understates the carrier’s Vehicle Maintenance liability, and a critical brake condition mapped to the wrong BASIC hides the single most damaging violation a mechanical inspection pipeline can produce. This page builds that crosswalk as a Python data structure plus a resolver function, and pins its behavior with tests. It is the runnable companion to CSA Score Impact and BASIC Category Mapping, which explains the weighting and percentile math this lookup feeds, and it consumes the 0–100 tiers produced by the Severity Scoring Algorithms for DVIR Defects engine.

The crosswalk is a reference table, not a scoring model. It does not compute a percentile and it does not decide dispatch; it answers a narrower question — if this defect reaches a roadside inspection, which BASIC violation does it become and how heavily is it weighted? Keep the weights framed as illustrative of FMCSA’s model. The authoritative severity weights live in the FMCSA Safety Measurement System methodology and change on FMCSA’s schedule, so the resolver reads them from a versioned table rather than treating the constants below as permanent.

  • Python 3.10+ — the resolver uses match/case and X | Y unions.
  • The canonical defect contract — every input carries defect_category, severity_score, and compliance_guardrail from the Standardized DVIR JSON Schema Design.
  • A resolved defect_category — the category must already map through the controlled vocabulary in Defect Code Standardization Across Fleets, so a brake node always keys the same crosswalk entry.
  • A pinned SMS weight tableversion and effective_date stamp the weights so a past mapping reproduces exactly.

The severity tiers are the canonical bands used everywhere in this section: 0–34 minor, 35–69 major, 70–100 critical, with any DOT_OOS_CRITERIA defect held to a critical floor of 70.

Step 1 — Model the crosswalk entry

Anchor link to "Step 1 — Model the crosswalk entry"

Each crosswalk row binds a defect category to the violation it becomes. Model it as a frozen dataclass so an entry cannot be mutated after the table loads, and carry is_oos as a fact independent of severity_weight — a defect can be high-weight without being OOS, and vice versa.

python
from dataclasses import dataclass


@dataclass(frozen=True)
class ViolationMapping:
    """One crosswalk row: a defect category's likely roadside violation."""
    defect_category: str
    cfr_code: str                 # e.g. "393.47"
    basic: str                    # e.g. "vehicle_maintenance"
    severity_weight: int          # 1-10, FMCSA-defined; illustrative here
    is_oos: bool                  # CVSA out-of-service eligibility
    description: str

Step 2 — Build the versioned lookup table

Anchor link to "Step 2 — Build the versioned lookup table"

The table is a dictionary keyed by defect_category, wrapped with the SMS-table version so a mapping can be reproduced. A single physical defect may correspond to more than one violation — a coupling failure can be cited under both a securement and a maintenance rule — so map each category to a list of mappings, not a single entry.

python
SMS_TABLE_VERSION = 7  # bump when the FMCSA severity-weight table is revised

VIOLATION_CROSSWALK: dict[str, list[ViolationMapping]] = {
    "brakes_out_of_adjustment": [
        ViolationMapping("brakes_out_of_adjustment", "393.47",
                         "vehicle_maintenance", 10, True,
                         "Brakes out of adjustment or defective"),
    ],
    "tire_tread_separation": [
        ViolationMapping("tire_tread_separation", "393.75",
                         "vehicle_maintenance", 8, True,
                         "Tire flat, exposed fabric, or tread separation"),
    ],
    "inspection_repair_lapse": [
        ViolationMapping("inspection_repair_lapse", "396.3(a)(1)",
                         "vehicle_maintenance", 4, False,
                         "Inspection, repair, and maintenance not performed"),
    ],
    "missing_dvir": [
        ViolationMapping("missing_dvir", "396.11",
                         "vehicle_maintenance", 4, False,
                         "No or false driver vehicle inspection report"),
    ],
    "inoperative_lamp": [
        ViolationMapping("inoperative_lamp", "393.9",
                         "vehicle_maintenance", 2, False,
                         "Inoperable required lamp"),
    ],
}

Step 3 — Resolve a classified defect to its BASIC exposure

Anchor link to "Step 3 — Resolve a classified defect to its BASIC exposure"

The resolver takes a classified defect and returns its likely BASIC exposure. Two rules make it safe. First, a defect tagged DOT_OOS_CRITERIA is OOS regardless of what the table says, so the guardrail is honored even if a table row is stale. Second, an unknown defect_category never falls through to a low default — it returns a conservative review sentinel that forces a human to classify it, because a silent default understates real exposure.

python
# Conservative fallback for an unmapped category: force review, never assume low.
REVIEW_DEFAULT = ViolationMapping(
    defect_category="unknown",
    cfr_code="UNMAPPED",
    basic="vehicle_maintenance",
    severity_weight=10,          # assume worst case until a human confirms
    is_oos=True,                 # hold the vehicle rather than release it
    description="Unmapped defect category — route to manual review",
)


def resolve_basic_exposure(
    defect_category: str,
    severity_score: int,
    compliance_guardrail: str,
) -> list[ViolationMapping]:
    """Return every likely BASIC violation for a classified defect.

    A DOT_OOS_CRITERIA guardrail forces is_oos True; an unknown category
    returns the conservative review default instead of an empty list.
    """
    mappings = VIOLATION_CROSSWALK.get(defect_category)
    if mappings is None:
        return [REVIEW_DEFAULT]

    resolved: list[ViolationMapping] = []
    for m in mappings:
        # The federal OOS tag overrides a stale table row — never release
        # an OOS-criteria defect because the crosswalk lagged a revision.
        oos = m.is_oos or compliance_guardrail == "OOS"
        resolved.append(ViolationMapping(
            m.defect_category, m.cfr_code, m.basic,
            m.severity_weight, oos, m.description,
        ))
    return resolved


def tier_for(severity_score: int) -> str:
    """Canonical band used across the pipeline."""
    match severity_score:
        case s if s >= 70:
            return "critical"     # 70-100 -> OOS hold
        case s if s >= 35:
            return "major"        # 35-69  -> 24-hour repair queue
        case _:
            return "minor"        # 0-34   -> scheduled maintenance

Step 4 — Summarize the Vehicle Maintenance exposure

Anchor link to "Step 4 — Summarize the Vehicle Maintenance exposure"

Because one defect can produce several violations, expose a small helper that reports the worst-case weight and whether any resulting violation is OOS. This is the value a dispatcher needs — not a point total, but the comparative severity a defect carries into the Vehicle Maintenance BASIC.

python
def worst_case_exposure(mappings: list[ViolationMapping]) -> dict:
    """Collapse a defect's violations into a single comparative summary."""
    return {
        "max_severity_weight": max(m.severity_weight for m in mappings),
        "any_oos": any(m.is_oos for m in mappings),
        "cfr_codes": sorted({m.cfr_code for m in mappings}),
        "basic": mappings[0].basic,
        "table_version": SMS_TABLE_VERSION,
    }

Test the behaviors that keep exposure honest: a critical brake defect maps to Vehicle Maintenance and OOS, an unknown code routes to conservative review rather than a low default, and a DOT_OOS_CRITERIA tag forces OOS even when the base row would not. Run these under pytest in CI and fail the build on any regression.

python
import pytest


def test_critical_brake_maps_to_vehicle_maintenance_oos():
    out = resolve_basic_exposure("brakes_out_of_adjustment", 95, "OOS")
    assert len(out) == 1
    m = out[0]
    assert m.basic == "vehicle_maintenance"
    assert m.cfr_code == "393.47"
    assert m.is_oos is True
    assert m.severity_weight == 10


def test_unknown_code_routes_to_conservative_review():
    out = resolve_basic_exposure("mystery_defect", 40, "conditional")
    assert len(out) == 1
    # Never assume low: the fallback is worst-case and OOS until reviewed.
    assert out[0].cfr_code == "UNMAPPED"
    assert out[0].is_oos is True
    assert out[0].severity_weight == 10


def test_oos_guardrail_overrides_a_non_oos_row():
    # A missing DVIR is not itself OOS, but a DOT_OOS_CRITERIA tag forces it.
    out = resolve_basic_exposure("missing_dvir", 72, "OOS")
    assert out[0].is_oos is True


def test_minor_lamp_stays_low_weight_and_not_oos():
    out = resolve_basic_exposure("inoperative_lamp", 20, "monitor_only")
    assert out[0].severity_weight == 2
    assert out[0].is_oos is False
    assert tier_for(20) == "minor"


def test_worst_case_exposure_is_deterministic():
    out = resolve_basic_exposure("tire_tread_separation", 80, "OOS")
    summary = worst_case_exposure(out)
    assert summary["max_severity_weight"] == 8
    assert summary["any_oos"] is True
    assert summary["table_version"] == SMS_TABLE_VERSION

Common Failure Modes and Gotchas

Anchor link to "Common Failure Modes and Gotchas"
  • One defect, multiple violations. A single physical condition can be cited under more than one CFR rule — a defective coupling can draw both a securement and a maintenance violation. A crosswalk that maps each category to exactly one code silently drops the second violation and understates exposure. Model the value as a list and let the resolver return every applicable mapping.
  • Treating illustrative weights as authoritative. The severity weights here teach the shape of FMCSA’s model; they are not a substitute for the published SMS table. FMCSA revises weights, so read them from a versioned table stamped with version and effective_date, and never hard-code a number a dispatcher might quote as fact.
  • Implying exact CSA points. The crosswalk returns a severity weight and an OOS flag, not points on a carrier’s score. The SMS converts weighted violations into a normalized measure and a peer-relative percentile, so there is no additive point value. Report max_severity_weight and any_oos as a comparative signal, not a running total.
  • Defaulting an unknown code low. The most dangerous bug is a fall-through that maps an unrecognized category to a low weight or an empty list, quietly releasing a vehicle that should have been held. The resolver returns a worst-case, OOS review sentinel instead, forcing a human to classify the defect before dispatch.
Why return a conservative default instead of skipping an unknown defect code?

Because skipping it releases the vehicle. An unmapped defect_category might be a genuinely new fault, an OCR misread, or a code the table has not caught up to, and any of those could be a serious mechanical condition. Returning a worst-case, OOS review sentinel forces a human to classify the defect before it can be dispatched, which is the safe direction to fail when a mapping is uncertain.

Can one DVIR defect map to more than one BASIC violation?

Yes, and the resolver models it. A single physical condition can be cited under multiple CFR rules at a roadside inspection, and although most Vehicle Maintenance defects stay within that one BASIC, some securement or hazardous-materials conditions cross into others. Mapping each category to a list of violations rather than a single code keeps the total exposure honest.

Do these weights match what FMCSA will actually apply?

They illustrate the model, not the current table. FMCSA defines and periodically revises the 1-to-10 severity weights in the Safety Measurement System methodology. Load the operative weights from a versioned table pinned by version and effective_date, and treat the constants in this page as a teaching reference rather than a live source.

Part of the CSA Score Impact and BASIC Category Mapping guide. Back to Defect Classification & Repair Order Routing.