The A5 cutover has been on and observed in production, so SEC Company Facts + DoltHub earnings are already the live source for `fundamental_data`. This removes everything the legacy path still occupied. Gone: the three providers and their config/env keys; the weekly `fundamental_collector` job; the cutover toggle (SEC + Dolt is now the unconditional path, so `off` can no longer silently freeze scoring inputs); the A5 parity report, whose deltas became structurally zero once the candidate builder started writing the table it compared against; and the FMP tier of universe bootstrap. Two behavioral notes: - Disabling **SEC Fundamentals Import** now stops the SEC network fetch only. The local cache refresh moved outside the job-enable check, because candidates also derive from daily closes and earnings events — freezing those on an ingestion pause would stale scoring with no fallback left to recover from. - `/ingestion/fetch?sources=fundamentals` still accepts the key and reports `skipped`; there is no per-ticker fetch any more. Migration 029 does not blanket-delete the leftover settings rows. Migrations run before the service restart, and pre-A6 code reads an absent `job_*_enabled` row as *enabled* — so the two behavior-bearing keys become tombstones pinned to safe values (hidden in Admin) and only the inert three are deleted. Removing the provider keys from the production `.env` is the matching rollout step. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
2.1 KiB
Python
72 lines
2.1 KiB
Python
"""Provider protocols and lightweight data transfer objects.
|
|
|
|
Protocols define the interface for external data providers.
|
|
DTOs are simple dataclasses — NOT SQLAlchemy models — used to
|
|
transfer data between providers and the service layer.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import date, datetime
|
|
from typing import Protocol
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Data Transfer Objects
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class OHLCVData:
|
|
"""Lightweight OHLCV record returned by market data providers."""
|
|
|
|
ticker: str
|
|
date: date
|
|
open: float
|
|
high: float
|
|
low: float
|
|
close: float
|
|
volume: int
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class SentimentData:
|
|
"""Sentiment analysis result returned by sentiment providers."""
|
|
|
|
ticker: str
|
|
classification: str # "bullish" | "bearish" | "neutral"
|
|
confidence: int # 0-100
|
|
source: str
|
|
timestamp: datetime
|
|
reasoning: str = ""
|
|
citations: list[dict[str, str]] = field(default_factory=list) # [{"url": ..., "title": ...}]
|
|
recommendation: str | None = None # "buy" | "hold" | "avoid" — actionable LLM view
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Provider Protocols
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class MarketDataProvider(Protocol):
|
|
"""Protocol for OHLCV market data providers."""
|
|
|
|
async def fetch_ohlcv(
|
|
self, ticker: str, start_date: date, end_date: date
|
|
) -> list[OHLCVData]:
|
|
"""Fetch OHLCV data for a ticker in a date range."""
|
|
...
|
|
|
|
|
|
class SentimentProvider(Protocol):
|
|
"""Protocol for sentiment analysis providers."""
|
|
|
|
async def fetch_sentiment(self, ticker: str) -> SentimentData:
|
|
"""Fetch current sentiment analysis for a ticker."""
|
|
...
|
|
|
|
|
|
# No fundamentals provider protocol: since A6 fundamentals come only from the
|
|
# batch SEC/Dolt imports, never from a request-time provider call.
|