Files
signal-platform/tests/unit/test_fundamentals_peers.py
dennisthiessenandClaude Opus 4.8 979b4047dc fix(fundamentals): pure-core review — tie-aware percentile, null-safe reads
1. Peer percentile is now a tie-aware rank against the OTHER issuers
   ((worse + 0.5*tied)/(peers-1)): an all-equal group maps to 50 (not 100), the
   median maps to 50, a unique best to 100, a unique worst to 0.
2. Deterministic reads use the consecutive non-null suffix ending at the latest
   point (>=3 values): a null latest or an internal gap yields no read, so a read
   never reflects a period displayed as n/a.
3. Peer filtering excludes non-finite (NaN/±inf) as well as null, including an
   invalid subject.

Tests updated + added (all-equal, median rank, non-finite, latest-null history,
internal gap). 15 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 20:41:18 +02:00

59 lines
2.1 KiB
Python

"""Tests for pure peer statistics."""
from __future__ import annotations
import math
from app.services import fundamentals_peers as pr
def test_median_ranks_at_50_tie_aware():
s = pr.peer_stat(3, [1, 2, 3, 4, 5], higher_is_better=True)
assert s.median == 3
assert s.favorable_percentile == 50 # tie-aware rank of the median
assert s.peer_count == 5
def test_all_equal_peers_rank_at_50():
s = pr.peer_stat(3, [3, 3, 3, 3, 3], higher_is_better=True)
assert s.favorable_percentile == 50 # not 100 — ties don't get full credit
def test_peer_stat_lower_is_better_flips_direction():
assert pr.peer_stat(3, [1, 2, 3, 4, 5], higher_is_better=False).favorable_percentile == 50
def test_peer_stat_unique_top_and_bottom():
assert pr.peer_stat(5, [1, 2, 3, 4, 5], higher_is_better=True).favorable_percentile == 100
assert pr.peer_stat(1, [1, 2, 3, 4, 5], higher_is_better=True).favorable_percentile == 0
def test_peer_stat_requires_min_valid_peers():
assert pr.peer_stat(3, [1, 2, 3, None], higher_is_better=True) is None # 3 valid < 5
assert pr.peer_stat(None, [1, 2, 3, 4, 5], higher_is_better=True) is None # null subject
def test_peer_stat_excludes_null_and_non_finite():
s = pr.peer_stat(3, [1, 2, 3, 4, 5, None, math.nan, math.inf, -math.inf], higher_is_better=True)
assert s.peer_count == 5 # nulls + NaN/inf dropped
# a non-finite subject is invalid
assert pr.peer_stat(math.nan, [1, 2, 3, 4, 5], higher_is_better=True) is None
def test_net_debt_is_not_peer_eligible():
assert pr.peer_stat_for("net_debt", 100, [10, 20, 30, 40, 50]) is None # size-dependent
assert pr.peer_stat_for("net_debt_to_ebitda", 1.0, [1, 2, 3, 4, 5]) is not None
def test_peer_stat_for_uses_polarity():
# pe is lower-is-better: a low pe beats most peers
s = pr.peer_stat_for("pe", 10, [10, 20, 30, 40, 50])
assert s.favorable_percentile == 100
def test_two_digit_sic():
assert pr.two_digit_sic("7372") == "73"
assert pr.two_digit_sic("3571") == "35"
assert pr.two_digit_sic(None) is None
assert pr.two_digit_sic("x") is None