3. Pass-fail decision logic design
In occupant protection assessment, a single crash test produces dozens of injury metrics across multiple body regions, dummies, and seating positions. The pass-fail decision is therefore never a single comparison — it is a structured evaluation of many criteria whose results must be combined into one defensible verdict. This section explains how to design that decision layer: how individual thresholds are compared, how boolean results are aggregated, and when a rule engine is warranted over hand-coded logic.
From Raw Signals to Criterion Results
Before any comparison happens, each injury metric must be reduced to a scalar that the threshold logic can consume. Typical inputs include Head Injury Criterion (HIC15), 3 ms chest acceleration clip, chest deflection, neck Nij, femur axial force, and tibia index. Each has a defined evaluation window, filter class (e.g., CFC 600 or CFC 1000 per SAE J211), and a specified side (left/right, driver/passenger). The decision layer should treat these as typed inputs rather than loose numbers, because a threshold applied to the wrong channel or the wrong filter is a silent, catastrophic error.
A robust pattern is to separate measurement from judgement. The measurement stage outputs a normalized record such as {metric: "HIC15", value: 812.4, unit: "g", location: "driver_head"}. The judgement stage then applies the threshold. This separation lets you re-run the decision logic against stored results without re-processing raw signals, which is essential for regression testing when limits change.
Threshold Comparison: Equality, Margins, and Direction
Every criterion has a limit and a direction. Most injury criteria are "lower is better," so the pass condition is value <= limit. Some, like certain restraint performance indicators, may be "higher is better." Encode the direction explicitly rather than assuming it. A comparison function should return not just a boolean but a margin, because the margin drives reporting, risk classification, and engineering triage:
def compare(value, limit, direction="max"):
# direction="max": pass when value <= limit
# direction="min": pass when value >= limit
if direction == "max":
margin = limit - value # positive = pass headroom
passed = value <= limit
else:
margin = value - limit
passed = value >= limit
return {
"passed": passed,
"margin": round(margin, 3),
"ratio": round(value / limit, 4) # utilization, 1.0 = at limit
}
The ratio field (utilization) is more useful than raw margin when comparing criteria with different units. A HIC of 812 against a limit of 1000 is 0.812 utilization; a chest deflection of 58 mm against 63 mm is 0.921. The second criterion is closer to failure even though its absolute margin looks smaller in its own units. Reporting utilization lets reviewers rank risk consistently.
<=, not <.Boolean Aggregation: AND, OR, and the Structure of a Verdict
Individual criterion results combine into a verdict through a tree of boolean operators. The most common structure is a conjunction of per-region requirements, where each region itself may be a disjunction of alternative criteria. For example, a head protection requirement might pass if either HIC15 or the 3 ms clip is within limits, while the overall occupant verdict requires all of head, neck, chest, and lower extremity to pass.
Represent this as an explicit expression tree rather than nested if statements. The tree is inspectable, serializable, and can be rendered in a report showing exactly which branch failed:
verdict = AND(
region("head", OR(crit("HIC15"), crit("a3ms_head"))),
region("neck", AND(crit("Nij"), crit("neck_tension"))),
region("chest", AND(crit("a3ms_chest"), crit("deflection"))),
region("femur", OR(crit("femur_left"), crit("femur_right")))
)
Two aggregation pitfalls deserve attention. First, short-circuit evaluation hides information: if the head fails, an AND that stops early never evaluates the chest, so the report cannot show chest status. For assessment reports, evaluate all leaves and aggregate afterward — the cost is negligible and the diagnostic value is high. Second, mixing AND/OR without grouping produces ambiguous precedence. Always parenthesize explicitly in the data model, even if the language's operator precedence would happen to agree.
When a Rule Engine Earns Its Place
Hand-coded boolean trees work well when criteria are stable and few. They become fragile when limits vary by market, dummy type, seating row, or test configuration, and when non-engineers need to review or amend rules. A rule engine — typically a decision table or a declarative ruleset — externalizes the logic so it can be versioned, audited, and changed without redeploying code.
A decision table is often the most readable form for multi-criteria assessment. Each row is a rule; columns are conditions and the resulting action. The table below is an illustrative example with assumed limits, not regulatory values:
| Rule | Region | Metric | Condition | Outcome |
|---|---|---|---|---|
| R1 | Head | HIC15 | ≤ 1000 | PASS |
| R2 | Head | HIC15 | > 1000 and ≤ 1100 | REVIEW |
| R3 | Head | HIC15 | > 1100 | FAIL |
| R4 | Chest | Deflection | ≤ 63 mm | PASS |
| R5 | Chest | Deflection | > 63 mm | FAIL |
Note the three-valued outcome. Real assessment rarely collapses to binary at the criterion level; a "review" band captures near-limit results that need engineering judgement before a formal verdict. The aggregation layer then maps criterion outcomes to a final verdict: any FAIL yields overall FAIL; otherwise any REVIEW yields CONDITIONAL; otherwise PASS. This is a small state lattice, and encoding it explicitly prevents the common bug where a near-limit value is silently rounded into a pass.
Putting the Layers Together
The figure below shows the full decision pipeline for one occupant: typed metric inputs enter a comparison stage that emits pass/fail plus utilization, results flow into a grouped boolean tree, and the tree resolves to a three-valued verdict with a traceable failure path.
The trace on the right of the figure is the practical payoff of evaluating all leaves: when a verdict fails, the report can name the exact criterion, its value, and its utilization, rather than merely stating "occupant failed." That trace is what engineers use to decide whether to adjust a restraint, re-examine a sensor channel, or accept the result.
Implementation Notes and Failure Symptoms
Three implementation details separate a trustworthy decision layer from a brittle one. First, version the ruleset alongside the results; a verdict is only meaningful relative to the limits and logic that produced it. Store a hash of the ruleset with each result record. Second, validate the input schema before comparison — a missing chest deflection should raise an explicit "incomplete data" outcome, not default to zero and pass. Third, test the boundaries: unit tests should cover values exactly at the limit, one epsilon above, and one epsilon below, for every criterion and both directions.
Common failure symptoms map to specific causes. A verdict that flips between runs on identical data usually indicates unordered aggregation or floating-point boundary handling. A report that shows PASS while a reviewer's manual check shows a near-limit value often means short-circuit evaluation skipped a leaf, or a REVIEW band was collapsed to PASS. A criterion that never fails across many tests may be wired to the wrong channel or the wrong filter class — verify the metric's evaluation window and CFC against the test specification. Treating these symptoms as diagnostic signals, rather than rerunning until the result looks right, keeps the decision layer auditable and defensible.