from __future__ import annotations import json import pytest from django.core.management import call_command from control_plane.trading_studio.indicators.definitions import IndicatorVariant, Role from control_plane.trading_studio.indicators.feature_catalog import ( FEATURE_CATALOG_V1, FEATURE_CATALOG_V1_PATH, FEATURE_SAFETY_AUDIT_V1_PATH, CausalClassification, FeatureSupportState, FeatureUnavailableError, feature_catalog_artifact_bytes, feature_definition, feature_safety_audit_artifact_bytes, require_validated_features, ) def test_catalog_explicitly_records_validated_coverage_and_source_only_scope(): assert len(FEATURE_CATALOG_V1) == 1107 validated = { item.key for item in FEATURE_CATALOG_V1 if item.support_state == FeatureSupportState.VALIDATED } assert len(validated) == 80 artifact = json.loads(feature_catalog_artifact_bytes()) assert artifact["implementation_coverage"]["coverage"] == "80 of 1107" assert artifact["evaluation"] == "not evaluated via reference runtime; no strategy search" def test_safety_audit_derives_causal_classification_from_source_semantics(): trailing = feature_definition(IndicatorVariant(30, 14, 0.0, Role.OSC)) index_based = feature_definition(IndicatorVariant(117, 1, 0.0, Role.TREND)) alias = feature_definition(IndicatorVariant(25, 14, 0.0, Role.LEVEL)) assert trailing.causal_classification == CausalClassification.TRAILING_WINDOW_CAUSAL assert index_based.causal_classification == CausalClassification.INDEX_POSITION_CAUSAL assert alias.support_state == FeatureSupportState.ALIAS_REFUSED policy = json.loads(feature_safety_audit_artifact_bytes())["fail_closed_policy"] assert policy.startswith("Any requested") def test_non_validated_historical_variants_fail_closed_without_baseline_expansion(): validated = IndicatorVariant(30, 14, 0.0, Role.OSC) unvalidated = IndicatorVariant(30, 12, 0.0, Role.OSC) with pytest.raises(FeatureUnavailableError, match="fails closed"): require_validated_features([validated, unvalidated]) def test_generator_writes_machine_readable_catalog_and_audit(tmp_path): call_command("tradingstudio_generate_feature_catalog", "--output-dir", str(tmp_path)) catalog = tmp_path / FEATURE_CATALOG_V1_PATH.name audit = tmp_path / FEATURE_SAFETY_AUDIT_V1_PATH.name assert json.loads(catalog.read_text())["catalog"] == "FEATURE_CATALOG_V1" assert json.loads(audit.read_text())["audit"] == "FEATURE_SAFETY_AUDIT_V1"