Files
impakt/tests/test_integration.py
2026-04-10 14:37:34 -04:00

184 lines
6.9 KiB
Python

"""Integration tests using the synthetic MME fixture.
Tests the full pipeline: read MME -> transform -> criteria -> protocol scoring.
"""
from pathlib import Path
import numpy as np
import pytest
from impakt.criteria import chest_deflection, clip_3ms, femur_load, hic15, nij, tibia_index
from impakt.io.mme import MMEReader
from impakt.protocol import euro_ncap, iihs, us_ncap
from impakt.transform.cfc import cfc_filter
FIXTURE_DIR = Path(__file__).parent / "fixtures" / "sample_mme"
@pytest.fixture(scope="module")
def test_data():
"""Load the synthetic MME fixture."""
reader = MMEReader()
assert reader.supports(FIXTURE_DIR), f"MME reader should support {FIXTURE_DIR}"
return reader.read(FIXTURE_DIR)
class TestMMEFixtureLoading:
def test_metadata(self, test_data):
assert test_data.test_id == "IMPAKT_SYNTH_001"
assert test_data.metadata.vehicle.make == "Synthetic"
assert test_data.metadata.vehicle.model == "TestCar"
assert test_data.metadata.dummy.dummy_type == "Hybrid III 50th Percentile Male"
assert test_data.metadata.impact.test_type == "Full Frontal Rigid Barrier"
assert test_data.metadata.impact.speed_kmh == 56.3
def test_channel_count(self, test_data):
assert len(test_data) == 26
def test_head_channels_found(self, test_data):
head_accel = test_data.find("11HEAD0000AC*")
assert len(head_accel) == 3 # X, Y, Z
def test_channel_properties(self, test_data):
ch = test_data.get("11HEAD0000ACXA")
assert ch.unit == "g"
assert ch.sample_rate == 20000.0
assert ch.n_samples == 4200
assert ch.cfc_class == 1000
def test_auto_grouping(self, test_data):
groups = test_data.groups()
# Should have groups for: head accel, head ang vel, chest accel, pelvis accel
group_keys = list(groups.keys())
head_groups = [k for k in group_keys if "HEAD" in k and "AC" in k]
assert len(head_groups) == 1
def test_channel_tree(self, test_data):
tree = test_data.channel_tree()
assert "Driver (1st Row Left)" in tree
assert "Vehicle Structure" in tree
class TestTransformPipeline:
def test_cfc_filter_on_fixture(self, test_data):
ch = test_data.get("11HEAD0000ACXA")
filtered = cfc_filter(ch, 600)
assert filtered.cfc_class == 600
assert filtered.peak <= ch.peak # Filter reduces peak
assert filtered.peak > 30.0 # But retains signal
def test_resultant_from_group(self, test_data):
group = test_data.group("HEAD0000AC")
resultant = group.resultant()
assert resultant.code.direction == "R"
# Resultant should be >= any individual component
assert resultant.peak >= test_data.get("11HEAD0000ACXA").peak
class TestInjuryCriteriaPipeline:
def test_hic15(self, test_data):
group = test_data.group("HEAD0000AC")
result = hic15(group)
assert result.criterion == "HIC15"
assert result.value > 0
assert result.body_region == "Head"
# With a ~45g half-sine pulse, HIC15 should be meaningful
assert result.value > 50
assert result.value < 5000
def test_3ms_clip(self, test_data):
group = test_data.group("CHST0000AC")
result = clip_3ms(group)
assert result.criterion == "3ms Clip"
assert result.value > 0
assert result.value < 100 # Reasonable for ~38g chest pulse
def test_nij(self, test_data):
fz = test_data.get("11NECKUP00FOZA")
my = test_data.get("11NECKUP00MOYA")
result = nij(fz_channel=fz, my_channel=my, dummy=test_data.metadata.dummy)
assert result.criterion == "Nij"
assert result.value > 0
assert result.value < 5.0 # Should be reasonable
def test_chest_deflection(self, test_data):
ch = test_data.get("11CHST0000DCXA")
result = chest_deflection(channel=ch)
assert result.criterion == "Chest Deflection"
assert result.unit == "mm"
assert 30.0 < result.value < 45.0 # ~36mm peak in fixture
def test_femur_load(self, test_data):
ch_left = test_data.get("11FEMRLE00FOZA")
result = femur_load(channel=ch_left, side="left")
assert result.criterion == "Femur Load Left"
assert result.unit == "kN"
assert 3.0 < result.value < 7.0 # ~4.8kN peak in fixture
def test_tibia_index(self, test_data):
fz = test_data.get("11TIBILEUOFOZA")
mx = test_data.get("11TIBILEUOMOXA")
my = test_data.get("11TIBILEUOMOYA")
result = tibia_index(fz_channel=fz, mx_channel=mx, my_channel=my)
assert result.value > 0
class TestProtocolScoringPipeline:
@pytest.fixture
def all_criteria(self, test_data):
"""Compute all criteria from the fixture data."""
head_group = test_data.group("HEAD0000AC")
chest_group = test_data.group("CHST0000AC")
results = {}
results["HIC15"] = hic15(head_group)
results["3ms Clip"] = clip_3ms(chest_group)
results["Nij"] = nij(
fz_channel=test_data.get("11NECKUP00FOZA"),
my_channel=test_data.get("11NECKUP00MOYA"),
dummy=test_data.metadata.dummy,
)
results["Chest Deflection"] = chest_deflection(channel=test_data.get("11CHST0000DCXA"))
results["Femur Load Left"] = femur_load(
channel=test_data.get("11FEMRLE00FOZA"), side="left"
)
results["Femur Load Right"] = femur_load(
channel=test_data.get("11FEMRRI00FOZA"), side="right"
)
results["Tibia Index"] = tibia_index(
fz_channel=test_data.get("11TIBILEUOFOZA"),
mx_channel=test_data.get("11TIBILEUOMOXA"),
my_channel=test_data.get("11TIBILEUOMOYA"),
)
return results
def test_euro_ncap_scoring(self, all_criteria):
result = euro_ncap.evaluate(all_criteria)
assert result.protocol == "Euro NCAP"
assert result.stars is not None
assert 0 <= result.stars <= 5
assert len(result.region_scores) > 0
# Check that all region scores have colors
for rs in result.region_scores:
assert rs.color is not None
def test_us_ncap_scoring(self, all_criteria):
result = us_ncap.evaluate(all_criteria)
assert result.protocol == "US NCAP"
assert result.stars is not None
assert 1 <= result.stars <= 5
def test_iihs_scoring(self, all_criteria):
result = iihs.evaluate(all_criteria)
assert result.protocol == "IIHS"
assert result.overall_rating in ("GOOD", "ACCEPTABLE", "MARGINAL", "POOR")
assert len(result.region_scores) > 0
def test_protocol_summary_printable(self, all_criteria):
for scorer in [euro_ncap, us_ncap, iihs]:
result = scorer.evaluate(all_criteria)
summary = result.summary()
assert len(summary) > 0
assert result.protocol in summary