Metadata-Version: 2.4
Name: live-earnings
Version: 0.4.1
Summary: Live Earnings — live, attributed earnings-call transcripts.
Author: Live Earnings
License: Proprietary
Project-URL: Homepage, https://live-earnings.com/live/docs
Project-URL: Documentation, https://live-earnings.com/live/docs
Project-URL: Source, https://live-earnings.com/live/docs
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.31

# live-earnings

Python client for the Live Earnings API: live earnings-call transcripts, each
turn's text as it is spoken and then who said it. Python 3.9+.

## Install

```bash
pip install live-earnings
```

## Quickstart

Your API key (`ek_…`) covers one call; copy it from the call's **API Access**
tab on https://live-earnings.com/live. Run with `LE_KEY=ek_… SLUG=<call-slug>`:

```python
import os
import time

from live_earnings import LiveEarningsClient, LiveEarningsError, Transcript

client = LiveEarningsClient(token=os.environ["LE_KEY"])
slug = os.environ["SLUG"]
transcript = Transcript()


def show(turn):
    if turn:
        print(f"{turn.name or '…'}: {turn.text or ''}")


# Everything so far…
last = None  # the last seq seen; None = start the stream from now
ended = False
for env in client.events(slug):
    show(transcript.apply(env))
    last = env.seq
    ended = ended or env.event.type == "done"
# …then live until the call ends. Connections drop (and are cut after an
# hour), so reconnect from the last seq seen; the server replays the gap.
while not ended:
    try:
        for env in client.stream(slug, last_event_id=last):
            if env.event.type == "done":
                ended = True
                break
            show(transcript.apply(env))
            last = env.seq
    except LiveEarningsError as err:
        # A 4xx (bad key, wrong call) will not fix itself; anything else might.
        if 400 <= err.status < 500 and err.status != 429:
            raise
    if not ended:
        time.sleep(2)
```

A turn prints once when its text arrives (`…: text`) and again when its
speaker is named. `Transcript` applies the rules: a person's correction beats
the model's name, `new_name == "unknown"` leaves a turn unnamed,
`basis == "early"` marks a provisional name, and a repeated envelope is
ignored. `apply()` returns the turn itself, updated in place as later
events arrive.

## The client

| | |
|---|---|
| `meta(slug)` | the call: ticker, fiscal period, status, roster |
| `events(slug, from_=None, to=None, limit=None)` | every stored envelope, following pagination |
| `stream(slug, last_event_id=None)` | live envelopes; raises if the connection goes silent for 90 s — resume with the last `seq` you saw |
| `session_token(slug)` | a `SessionToken` (`session_token`, `expires_in`) for a browser `EventSource` (`?t=`) — call it on your server |

Hosts default to `https://live-earnings.com` (REST) and
`https://stream.live-earnings.com` (streams); override with `base_url` and
`stream_url`. Both must be `https://` unless you pass `allow_insecure=True` for
local development. Errors raise `LiveEarningsError` (`status`, `body`).

Full reference: https://live-earnings.com/live/docs

## Changes

**0.4.1** — No API change. The quickstart reconnects until the call ends
(streams are cut after an hour) and never resumes from seq 0.

**0.4.0** — Default hosts (`base_url` is optional; streams go to the stream
host). `Transcript` folds events into turns. `session_token()`. `events()`
takes an optional range. Streams deliver each message as it arrives (they
were buffered in 512-byte reads). `AiInsightEvent` is deprecated — the API
does not currently send it.

**0.3.0** — `UnknownEvent.naming == "pending"` (the name follows as a
`TurnSpeakerAmendEvent`); amends carry `by` and may carry `basis == "early"`.

**0.2.0** — `stt_speaker_change` on `AttributionEvent` and `UnknownEvent`;
`ai_insight` and `turn_speaker_amend` decode to their own classes.
