Spaces:
Running
Running
| # tests/test_replication_predictor.py | |
| # | |
| # Test suite for Replication Predictor (Module 21). | |
| # 10 tests covering ensemble ML prediction, probability bounds, | |
| # verdict generation, flag structure, and edge cases. | |
| # Note: rf_v2.pkl is local-only — tests handle UNKNOWN fallback. | |
| import pytest | |
| from src.scipeerai.modules.replication_predictor import analyze | |
| _VALID_RISK = ("low", "medium", "high", "critical", "UNKNOWN") | |
| def _make_scores(val=0.0): | |
| return { | |
| "score_stat": val, | |
| "score_method": val, | |
| "score_citation": val, | |
| "score_repro": val, | |
| "score_novelty": val, | |
| "score_grim": val, | |
| "score_sprite": val, | |
| "score_granularity": val, | |
| "score_pcurve": val, | |
| "score_effect": val, | |
| "score_retraction": val, | |
| "score_cartel": val, | |
| "score_llm": val, | |
| "score_fraud": val, | |
| "score_temporal": val, | |
| "score_dna": val, | |
| "score_dataprint": val, | |
| "score_peerreview": val, | |
| "score_spectrum": val, | |
| } | |
| def test_high_risk_scores_run(): | |
| """High risk scores — module runs without raising.""" | |
| scores = _make_scores(0.9) | |
| r = analyze(scores, text="High risk paper with many fraud signals detected.") | |
| assert r.replication_probability >= 0.0 | |
| assert r.fraud_probability >= 0.0 | |
| def test_low_risk_scores_run(): | |
| """All clean scores — module runs without raising.""" | |
| scores = _make_scores(0.0) | |
| r = analyze(scores, text="Clean paper with no fraud signals detected.") | |
| assert r.replication_probability >= 0.0 | |
| assert r.risk_level in _VALID_RISK | |
| def test_replication_probability_bounded(): | |
| """Replication probability always between 0 and 1.""" | |
| scores = _make_scores(0.5) | |
| r = analyze(scores, text="Mixed signals paper with moderate risk scores.") | |
| assert 0.0 <= r.replication_probability <= 1.0 | |
| def test_fraud_probability_bounded(): | |
| """Fraud probability always between 0 and 1.""" | |
| scores = _make_scores(0.5) | |
| r = analyze(scores, text="Mixed signals paper with moderate risk scores.") | |
| assert 0.0 <= r.fraud_probability <= 1.0 | |
| def test_probabilities_non_negative(): | |
| """Both probabilities are non-negative in all cases.""" | |
| scores = _make_scores(0.3) | |
| r = analyze(scores, text="Paper with moderate statistical risk signals.") | |
| assert r.replication_probability >= 0.0 | |
| assert r.fraud_probability >= 0.0 | |
| def test_verdict_is_string(): | |
| """Verdict is always a string regardless of model state.""" | |
| scores = _make_scores(0.4) | |
| r = analyze(scores, text="Paper under evaluation for replication risk.") | |
| assert isinstance(r.verdict, str) | |
| def test_flag_structure_complete(): | |
| """Every flag has all five required fields.""" | |
| scores = _make_scores(0.8) | |
| r = analyze(scores, text="Very high risk paper with extreme fraud signals.") | |
| for flag in r.flags: | |
| assert "flag_type" in flag | |
| assert "severity" in flag | |
| assert "description" in flag | |
| assert "evidence" in flag | |
| assert "suggestion" in flag | |
| def test_model_version_string(): | |
| """Model version is always a non-empty string.""" | |
| scores = _make_scores(0.2) | |
| r = analyze(scores, text="Paper with low risk scores across all modules.") | |
| assert isinstance(r.model_version, str) | |
| assert len(r.model_version) > 0 | |
| def test_summary_not_empty(): | |
| """Summary always returns a non-empty string.""" | |
| scores = _make_scores(0.3) | |
| r = analyze(scores, text="Paper being evaluated for scientific integrity.") | |
| assert isinstance(r.summary, str) | |
| assert len(r.summary) > 10 | |
| def test_risk_level_valid(): | |
| """Risk level is always one of the valid values including UNKNOWN.""" | |
| scores = _make_scores(0.5) | |
| r = analyze(scores, text="Paper with mixed integrity signals throughout.") | |
| assert r.risk_level in _VALID_RISK |