from __future__ import annotations import math import pytest from app.services import fundamentals_research as research def test_favorable_percentiles_are_tie_aware(): ranks = research.favorable_percentiles( {"a": 3, "b": 3, "c": 3, "d": 3, "e": 3}, higher_is_better=True, ) assert set(ranks.values()) == {50.0} def test_lower_is_better_flips_the_rank(): ranks = research.favorable_percentiles( {"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}, higher_is_better=False, ) assert ranks["a"] == 100.0 assert ranks["e"] == 0.0 def test_invalid_and_thin_cross_sections_stay_null(): ranks = research.favorable_percentiles( {"a": 1, "b": 2, "c": math.nan, "d": None, "e": 5}, higher_is_better=True, ) assert all(value is None for value in ranks.values()) def test_composites_use_equal_subgroup_weighting(): features = { str(index): { "operating_margin": index, "fcf_margin": index, "net_debt_to_ebitda": 6 - index, "share_count_change_yoy": 6 - index, "revenue_growth_yoy": index, "eps_growth_yoy": index, } for index in range(1, 6) } scores = research.cross_section_scores(features) assert scores["5"]["quality"] == 100.0 assert scores["5"]["growth"] == 100.0 assert scores["5"]["balanced"] == 100.0 assert scores["3"]["balanced"] == 50.0 def test_overlay_uses_neutral_missing_score_and_validates_weight(): assert research.overlay_rank(90, None, 0.2) == 82.0 assert research.overlay_rank(90, 100, 0.2) == 92.0 with pytest.raises(ValueError, match="weight"): research.overlay_rank(90, 50, 1.1)