Skip to content

mostlyright.weather.catalog.earnings

Earnings catalog adapter (Phase 27, 27-06).

The thin SDK CONSUMER of the earnings-audio Tier-2 feed (D-27.1 “satellites produce, SDKs consume”). Mirrors mostlyright.weather.catalog.awc exactly: SUPPORTED_SOURCES at class level, from_rows projecting parser-output rows to a canonical schema + overlay columns, bottom-of-file register_adapter.

Two delivery channels, ONE source identity (D-27.2/D-27.6). Both earnings.live (faster-whisper small on-device, keyless/offline BYO-audio — D-27.5) and earnings.hosted (fetch from EARNINGS_HOSTED_URL) produce byte-identical rows from the SAME public call audio. The per-row source overlay column is therefore always the shared identity "earnings_call" (so a live row and a hosted row reconcile — no SourceMismatchError), while the delivery column carries the channel lineage (live / hosted). This is exactly the satellite noaa_goes precedent.

knowledge_time = transcript availability (post-call), NOT event_time. The overlay knowledge_time is derived from each row’s transcript_available_at (when the transcript was published), never the call air time — the PIT leakage invariant (D-27.11) that keeps earnings facts backtest-safe. The PIT alignment + leakage assertions themselves live in mostlyright.markets.earnings.pit.

This adapter is a thin parser: it takes already-extracted satellite-payload or local-file rows (it does NOT run faster-whisper here — that is the satellite’s job; the live fetch path lazy-imports the STT engine guarded by the [earnings] extra and raises an install hint if missing).

EARNINGS_HOSTED_URLEnv-var name for the hosted earnings-feed seam (the CONSUMER side of the satellite serving layer — the serving layer itself is out of scope here).
EARNINGS_LIVE_STREAM_SOURCEThe live delivery-channel source id (D-27.16, 27-12).
EARNINGS_SOURCE_IDENTITYThe single shared source identity (D-27.2).
EarningsAdapter()Thin earnings-feed adapter (live + hosted).

mostlyright.weather.catalog.earnings.EARNINGS_HOSTED_URL = ‘EARNINGS_HOSTED_URL’

Section titled “mostlyright.weather.catalog.earnings.EARNINGS_HOSTED_URL = ‘EARNINGS_HOSTED_URL’”

Env-var name for the hosted earnings-feed seam (the CONSUMER side of the satellite serving layer — the serving layer itself is out of scope here).

mostlyright.weather.catalog.earnings.EARNINGS_LIVE_STREAM_SOURCE = ‘earnings.hosted.stream’

Section titled “mostlyright.weather.catalog.earnings.EARNINGS_LIVE_STREAM_SOURCE = ‘earnings.hosted.stream’”

The live delivery-channel source id (D-27.16, 27-12). Distinct from the post-call earnings.hosted ledger: rows tagged this way are PROVISIONAL and firewalled out of every backtest AND settlement read (see mostlyright.markets.earnings.pit). It is a per-row overlay value, NOT a registered adapter source — the live consumer is reached via EarningsAdapter.stream(), never research() / the merge join.

mostlyright.weather.catalog.earnings.EARNINGS_SOURCE_IDENTITY = ‘earnings_call’

Section titled “mostlyright.weather.catalog.earnings.EARNINGS_SOURCE_IDENTITY = ‘earnings_call’”

The single shared source identity (D-27.2). Live STT + the hosted feed are the SAME upstream public call audio, so both stamp this — they reconcile, they do not mismatch.

class mostlyright.weather.catalog.earnings.EarningsAdapter

Section titled “class mostlyright.weather.catalog.earnings.EarningsAdapter”

Bases: object

Thin earnings-feed adapter (live + hosted).

fetch(source, ticker, event_date, , client=None, retrieved_at=None)

Section titled “fetch(source, ticker, event_date, , client=None, retrieved_at=None)”

Fetch earnings rows from source.

earnings.hosted (the DEFAULT / our-infra path, D-27.6) httpx-fetches the served /facts feed at EARNINGS_HOSTED_URL, validates the payload shape, then projects through from_rows() — producing rows BYTE-IDENTICAL to the on-device earnings.live tier (same engine deployed centrally; only the delivery overlay differs, the source identity earnings_call is shared so live↔hosted reconcile).

earnings.live runs the on-device faster-whisper small STT path (lazy-imported, guarded by the [earnings] extra) — the actual capture/STT is the satellite engine’s job; the SDK live path here is the keyless/offline BYO-audio seam, exercised via from_rows().

client injects an httpx.Client (tests pass a MockTransport); retrieved_at overrides the overlay retrieval stamp (defaults to now). Raises EarningsError when EARNINGS_HOSTED_URL is unset (the seam is opt-in) or the fetch fails, and SchemaValidationError when the served payload is malformed (rejected BEFORE overlay-stamping).

  • Return type: DataFrame
  • Parameters:
    • source (str)
    • ticker (str)
    • event_date (str)
    • client (httpx.Client | None)
    • retrieved_at (datetime | None)

static from_rows(rows, , source=‘earnings.hosted’, retrieved_at=None)

Section titled “static from_rows(rows, , source=‘earnings.hosted’, retrieved_at=None)”

Project satellite-payload / local-file rows to a canonical DataFrame.

Emits the canonical schema.earnings_transcript.v1 / schema.earnings_fact.v1 fields present on the input rows plus the overlay columns source / retrieved_at / knowledge_time / event_time and the delivery lineage column.

Rows are BYTE-IDENTICAL across earnings.live and earnings.hosted (D-27.6); only delivery differs. source is always the shared identity "earnings_call" so live↔hosted reconcile.

async stream(ticker, call_id, , base_url, token=None, headers=None, client=None)

Section titled “async stream(ticker, call_id, , base_url, token=None, headers=None, client=None)”

Consume the live 27-11 SSE feed → incremental canonical rows. Async.

Mirrors the CWOP stream() async-generator precedent (mostlyright.weather.cwop.stream()): an async for over this yields incremental schema.earnings_transcript.v1 / schema.earnings_fact.v1 rows as the call airs. Every row is tagged source="earnings.hosted.stream" (EARNINGS_LIVE_STREAM_SOURCE) and carries:

  • spoken_at — the aired event time (from the payload’s spoken_at / event_time);
  • knowledge_time — the STT-finalization / publish wallclock (>= spoken_at, from the payload’s published_at / knowledge_time; NEVER spoken_at — codex P2 / D-27.16);
  • stream_seq — the SSE id: (monotonic; drives resume);
  • is_final — STT-segment finality ONLY (never settlement authority);
  • resolution_status"provisional" for a counted fact delta.

The consumer is a pure pass-through for kalshi_counted — it reads it straight from the fact-delta row and NEVER re-derives counts client-side (fail-closed stays at the satellite). A resume_incomplete frame is surfaced as a row with event="resume_incomplete" so a caller reconciles from the ledger rather than silently gapping.

  • Parameters:
    • ticker (str) – The issuer ticker (a query param on the /stream URL).
    • call_id (str) – The call id (a query param on the /stream URL).
    • base_url (str) – The /stream endpoint (the serving layer is the satellite + 27-08; the SDK ships no default).
    • token (str | None) – A short-lived signed-URL token (the browser EventSource path cannot set a header; the server-side httpx reader here can, but the token is passed as a query param for wire parity).
    • headers (Mapping[str, str] | None) – Extra request headers (e.g. an Authorization header for the header-auth reader variant).
    • client (object | None) – An optional httpx.AsyncClient (tests inject a mock transport). Passed straight to consume_sse().
  • Yields: Canonical row dicts tagged source="earnings.hosted.stream".
  • Return type: AsyncIterator[dict[str, object]]