import os import sys PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) if PROJECT_DIR not in sys.path: sys.path.insert(0, PROJECT_DIR) from app.analysis import reverse_analysis def test_pattern_summary_includes_control_lift(): gainers = [ {"has_static_accumulation": True, "has_ignition_point": True}, {"has_static_accumulation": True, "has_ignition_point": False}, {"has_static_accumulation": False, "has_ignition_point": True}, ] controls = [ {"has_static_accumulation": True, "has_ignition_point": False}, {"has_static_accumulation": False, "has_ignition_point": False}, {"has_static_accumulation": False, "has_ignition_point": False}, ] summary = reverse_analysis.compute_pattern_summary(gainers, len(gainers), control_features=controls) by_feature = {item["feature"]: item for item in summary} assert by_feature["has_ignition_point"]["percentage"] == 66.7 assert by_feature["has_ignition_point"]["control_percentage"] == 0.0 assert by_feature["has_ignition_point"]["lift"] > 1 assert by_feature["has_static_accumulation"]["control_count"] == 1 def test_discover_new_rules_requires_lift_when_control_exists(monkeypatch): created = [] monkeypatch.setattr( reverse_analysis, "upsert_strategy_rule_candidate", lambda **kwargs: created.append(kwargs) or 101, ) pattern_summary = [ { "feature": "has_static_accumulation", "percentage": 80.0, "count": 8, "total": 10, "control_percentage": 75.0, "control_count": 15, "control_total": 20, "lift": 1.06, }, { "feature": "has_ignition_point", "percentage": 70.0, "count": 7, "total": 10, "control_percentage": 20.0, "control_count": 4, "control_total": 20, "lift": 3.38, }, ] rules = reverse_analysis.discover_new_rules( pattern_summary, all_features=[{}] * 10, sector_alignments=[], significance_pct=60, min_lift=1.5, ) assert len(rules) == 1 assert rules[0]["conditions"] == {"has_ignition_point": True} assert created[0]["signal_name"] == "has_ignition_point" assert created[0]["fail_count"] == 4