"""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()