fix(fundamentals): A4a review — stricter null semantics in derivation
1. net_debt requires BOTH cash and total_debt; a missing side is null, not treated as zero (which would be a partial, misleading value). 2. net_debt_to_ebitda is null when TTM EBITDA <= 0 — a negative denominator would otherwise rank a distressed issuer as favorably low-leverage. 3. The quarter tape is the CONSECUTIVE run ending at the latest period (stops at a gap), so trend text never compares non-adjacent quarters as if consecutive. 4. YoY growth is null when the prior-year TTM is <= 0 (e.g. loss->profit), which is not a meaningful percentage. Also corrected the plan's net-debt formula to total debt − (cash + ST) matching the positive-means-net-debt implementation. +4 tests. 10 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,43 @@ def test_missing_period_yields_null_never_partial():
|
||||
assert d.ttm_diluted_eps is None
|
||||
|
||||
|
||||
def test_net_debt_requires_both_components():
|
||||
rows = _two_years()
|
||||
for r in rows: # drop debt on the latest year -> can't form net debt
|
||||
if r.fiscal_year == 2026:
|
||||
r.total_debt = None
|
||||
d = fd.derive(rows)
|
||||
assert d.metrics["net_debt"].value is None
|
||||
assert d.metrics["net_debt_to_ebitda"].value is None # net debt null -> leverage null
|
||||
|
||||
|
||||
def test_leverage_null_when_ebitda_nonpositive():
|
||||
rows = _two_years()
|
||||
for r in rows: # negative operating income -> TTM EBITDA <= 0
|
||||
r.operating_income = -abs(r.revenue)
|
||||
r.depreciation_amortization = 1
|
||||
d = fd.derive(rows)
|
||||
assert d.metrics["net_debt"].value == pytest.approx(100.0) # net debt still valid
|
||||
assert d.metrics["net_debt_to_ebitda"].value is None # but leverage nulled
|
||||
|
||||
|
||||
def test_tape_stops_at_a_gap():
|
||||
rows = [r for r in _two_years() if not (r.fiscal_year == 2026 and r.fiscal_period == "Q1")]
|
||||
d = fd.derive(rows)
|
||||
hist = d.metrics["operating_margin"].history
|
||||
# consecutive suffix ending at FY2026: Q2, Q3, FY (not compressed across the Q1 gap)
|
||||
assert [p.period_end for p in hist] == [date(2026, 3, 31), date(2026, 6, 30), date(2026, 9, 30)]
|
||||
|
||||
|
||||
def test_yoy_growth_null_when_prior_nonpositive():
|
||||
rows = _two_years()
|
||||
for r in rows: # prior-year TTM EPS becomes negative
|
||||
if r.fiscal_year == 2025:
|
||||
r.diluted_eps = -abs(r.diluted_eps)
|
||||
d = fd.derive(rows)
|
||||
assert d.metrics["eps_growth_yoy"].value is None # loss->profit is not a %
|
||||
|
||||
|
||||
def test_amendment_selection_newest_accepted_wins():
|
||||
rows = _two_years()
|
||||
# an amendment to FY2026 FY restates revenue YTD higher, accepted later
|
||||
|
||||
Reference in New Issue
Block a user