ai-internet-diagnostic-model / tests /test_features.py
WolfDavid's picture
feat(02-01): feature matrix assembly + calibrated LightGBM classifier
10cb234
Raw
History Blame Contribute Delete
2.08 kB
"""Feature matrix assembly tests (RESEARCH Pattern 0)."""
from pathlib import Path
import numpy as np
import pytest
from model.features import (
ANOMALY_FEATURES,
CATEGORICAL_FEATURES,
CLASSES,
CLASSIFIER_FEATURES,
load_anomaly_features,
load_split,
)
from model.synth.state_machines import GENERATORS
def test_classes_match_generators_order() -> None:
"""CLASSES MUST mirror GENERATORS insertion order (byte-identicality anchor)."""
assert CLASSES == list(GENERATORS.keys())
assert len(CLASSES) == 10
def test_classifier_features_shape() -> None:
"""20 features = 9 numerics + 8 categoricals + 3 misc."""
assert len(CLASSIFIER_FEATURES) == 20
assert len(ANOMALY_FEATURES) == 9
assert len(CATEGORICAL_FEATURES) == 8
def test_classifier_features_no_leakage_columns() -> None:
"""`bssid` and `timestamp` MUST NOT be in CLASSIFIER_FEATURES (Pitfall 2)."""
assert "bssid" not in CLASSIFIER_FEATURES
assert "timestamp" not in CLASSIFIER_FEATURES
@pytest.mark.skipif(
not Path("data/train.parquet").exists(),
reason="data/train.parquet not generated (run `make synth` first)",
)
def test_load_split_train_shape() -> None:
X, y, names = load_split(Path("data/train.parquet"))
assert X.dtype == np.float64
assert y.dtype == np.int64
assert X.shape[0] == 3_000_000 # 10k samples × 10 classes × 30 frames
assert X.shape[1] == len(CLASSIFIER_FEATURES)
assert names == list(CLASSIFIER_FEATURES)
assert y.min() >= 0 and y.max() < len(CLASSES)
@pytest.mark.skipif(
not Path("data/train.parquet").exists(),
reason="data/train.parquet not generated (run `make synth` first)",
)
def test_load_anomaly_features_shape() -> None:
X_anom, y, ts = load_anomaly_features(Path("data/train.parquet"))
assert X_anom.shape[0] == 3_000_000
assert X_anom.shape[1] == len(ANOMALY_FEATURES)
assert X_anom.dtype == np.float64
assert ts.shape[0] == 3_000_000
# All real classes (not -1 baseline) on train.parquet
assert (y >= 0).all() and (y < len(CLASSES)).all()