Files
signal-platform/app/routers/fundamentals.py
T
dennisthiessen f0b92a9718
Deploy / lint (push) Successful in 5s
Deploy / test (push) Successful in 36s
Deploy / deploy (push) Successful in 25s
add earnings-date guard — warn when a report falls in the target horizon
Finnhub's earnings calendar now supplies next_earnings_date through the
fundamentals chain; persisted on fundamental_data (migration 006) and exposed in
the fundamentals API. The recommendation panel warns when earnings fall within
the ~30-day target horizon (a report can gap price through stop/target) and
otherwise shows the next date. Informational only.

Deploy: run alembic upgrade (new fundamental_data.next_earnings_date column).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 12:44:08 +02:00

51 lines
1.7 KiB
Python

"""Fundamentals router — fundamental data endpoints."""
import json
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_db, require_access
from app.schemas.common import APIEnvelope
from app.schemas.fundamental import FundamentalResponse
from app.services.fundamental_service import get_fundamental
router = APIRouter(tags=["fundamentals"])
def _parse_unavailable_fields(raw_json: str) -> dict[str, str]:
"""Deserialize unavailable_fields_json, defaulting to {} on invalid JSON."""
try:
parsed = json.loads(raw_json)
except (json.JSONDecodeError, TypeError):
return {}
if not isinstance(parsed, dict):
return {}
return {k: v for k, v in parsed.items() if isinstance(k, str) and isinstance(v, str)}
@router.get("/fundamentals/{symbol}", response_model=APIEnvelope)
async def read_fundamentals(
symbol: str,
_user=Depends(require_access),
db: AsyncSession = Depends(get_db),
) -> APIEnvelope:
"""Get latest fundamental data for a symbol."""
record = await get_fundamental(db, symbol)
if record is None:
data = FundamentalResponse(symbol=symbol.strip().upper())
else:
data = FundamentalResponse(
symbol=symbol.strip().upper(),
pe_ratio=record.pe_ratio,
revenue_growth=record.revenue_growth,
earnings_surprise=record.earnings_surprise,
market_cap=record.market_cap,
next_earnings_date=record.next_earnings_date,
fetched_at=record.fetched_at,
unavailable_fields=_parse_unavailable_fields(record.unavailable_fields_json),
)
return APIEnvelope(status="success", data=data.model_dump())