fix(fundamentals): compute eps_growth_yoy read; cover same-day + price guards

- The eps_growth_yoy read was never computed, leaving that fixed by_key entry
  null even with sufficient EPS history; now growth_read() is applied to EPS
  history just like revenue.
- Tests: same-day earnings returns as next with days_until 0 (and not in
  recent); zero close guards valuation to null; eps read populated. Fixture
  seeds three fiscal years so YoY growth reads have a >=3 run. 9 API tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 22:01:24 +02:00
co-authored by Claude Opus 4.8
parent b3dcf356a6
commit fb6d39c68b
2 changed files with 43 additions and 2 deletions
+41 -2
View File
@@ -48,8 +48,9 @@ async def _seed_issuer(s, symbol, cik, sic, rev_base, price, *, eps_base=1.0, sn
s.add(t)
await s.flush()
if snapshots:
for fy, mult in [(2025, 1.0), (2026, 1.1)]:
shares = 1000 if fy == 2025 else 950 # buyback
# three fiscal years so YoY growth reads have a >=3 consecutive run
for fy, mult in [(2024, 0.9), (2025, 1.0), (2026, 1.1)]:
shares = {2024: 1050, 2025: 1000, 2026: 950}[fy] # steady buyback
rev = [rev_base * mult * x for x in (1.0, 1.05, 1.1, 1.15)]
eps = [eps_base * mult * x for x in (1.0, 1.05, 1.1, 1.15)]
for i, fp in enumerate(_FP):
@@ -116,6 +117,44 @@ async def test_full_assembly(factory):
assert dumped["metrics"][0]["key"] == "revenue_growth_yoy"
async def test_same_day_earnings_is_next_with_zero_days(factory):
async with factory() as s:
t = Ticker(symbol="TDY", cik=None)
s.add(t)
await s.flush()
s.add(EarningsEvent(ticker_id=t.id, announce_date=TODAY, session="bmo", source="dolt_earnings"))
s.add(EarningsEvent(ticker_id=t.id, announce_date=date(2026, 9, 1), session="amc",
eps_estimate=1.0, eps_actual=1.1, source="dolt_earnings"))
await s.commit()
async with factory() as s:
v1 = await build_fundamentals_v1(s, "TDY", today=TODAY)
assert v1["earnings"]["next"] == {"date": TODAY.isoformat(), "session": "bmo", "days_until": 0}
# the same-day event is upcoming, not in recent
assert all(r["announce_date"] != TODAY.isoformat() for r in v1["earnings"]["recent"])
async def test_eps_growth_read_is_populated(factory):
await _seed_group(factory)
async with factory() as s:
v1 = await build_fundamentals_v1(s, "AAPL", today=TODAY)
assert v1["reads"]["by_key"]["eps_growth_yoy"] is not None # EPS read now computed
async def test_non_positive_price_guards_valuation(factory):
async with factory() as s:
t = Ticker(symbol="ZERO", cik="0000000055", sic="3571")
s.add(t)
await s.flush()
s.add(FundamentalSnapshot(cik="0000000055", accession="z", form="10-K", filed_date=date(2026, 1, 1),
accepted_at=datetime(2026, 1, 1, tzinfo=UTC), period_end=date(2025, 12, 31),
fiscal_year=2025, fiscal_period="FY", diluted_eps=5.0, shares_outstanding=1000))
s.add(OHLCVRecord(ticker_id=t.id, date=date(2026, 1, 2), open=0, high=0, low=0, close=0, volume=1))
await s.commit()
async with factory() as s:
v1 = await build_fundamentals_v1(s, "ZERO", today=TODAY)
assert v1["valuation"] is None # close of 0 is not a usable price
async def test_no_cik_ticker_yields_null_metrics(factory):
async with factory() as s:
s.add(Ticker(symbol="ADR", cik=None)) # no SEC identity