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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user