# `ALLM.TranscriptionEvent`
[🔗](https://github.com/cykod/ALLM/blob/v0.6.1/lib/allm/transcription_event.ex#L1)

Closed tagged-tuple union emitted by a streaming transcription call.
Layer A data.

Every event is `{tag, payload}`, one of:

- `{:transcription_started, started}`: the session opened. The payload
  carries `:request_id`, `:model`, `:provider` and the provider's
  `:session_id`, each possibly `nil`.
- `{:partial_transcript, %{text: text}}`: the current best guess for the
  segment being spoken. It **replaces** the previous partial of the same
  segment; it is not appended.
- `{:committed_transcript, %{text: text, language: language}}`: a final
  segment. Committed segments are appended. When the request set
  `timestamps: true` or `logprobs: true`, the payload also carries a
  `:spans` key: the segment's `t:ALLM.TranscriptSpan.t/0` list, or `nil`
  when the adapter has no trustworthy span data for that segment. With
  both flags off the key is absent, not `nil`.
- `{:transcription_completed, completed}`: the stream finished. The
  payload carries `:text`, `:language`, `:duration_seconds`,
  `:request_id`, `:usage` (an `t:ALLM.Usage.t/0`) and `:metadata`. When
  a flag was set it also carries an optional `:spans` key: the
  committed segments' spans concatenated in order, `[]` when none were
  committed.
- `{:error, %ALLM.Error.TranscriptionAdapterError{}}`: the stream failed.

## Stream grammar

A successful stream is one `:transcription_started`, then any number of
segments (zero or more `:partial_transcript` events followed by one
`:committed_transcript`), then zero or more trailing `:partial_transcript`
events, then one `:transcription_completed`. A failed stream ends with one
`:error`. Nothing follows a terminal event.

## The completed payload

`:text` is the whole transcript: each committed segment's text is
trimmed, empty segments are dropped, and the rest are joined with one
space. The adapter computes it, so a consumer does not need to fold the
segments itself.

`:spans`, when present, is likewise the adapter's concatenation of the
committed segments' spans. It is optional, so
`transcription_completed/1` does not require it.

`:duration_seconds` is **computed**, not reported by the provider: the
audio bytes sent divided by `sample_rate * 2` (16-bit mono PCM).

## Serializability

Events round-trip through `:erlang.term_to_binary/1`. They are not
JSON-encoded, matching `ALLM.SpeechEvent`.

## Closed union

Adding a variant is a breaking change for any reducer of this union, the
same rule as `ALLM.Event`. Adding a key to an existing payload map is not.
`event?/1` is `false` on every chat event. The reverse does not hold:
`ALLM.Event.event?/1` treats `:error` payloads as opaque, so it also returns
`true` on this union's `:error` event. Route by this module's `event?/1`, or
by the error struct.

Build events with the variant constructors, which check the payload's
required keys. `:error` has no constructor; its payload is the error
struct.

# `committed`

```elixir
@type committed() :: %{
  :text =&gt; String.t(),
  :language =&gt; String.t() | nil,
  optional(:spans) =&gt; [ALLM.TranscriptSpan.t()] | nil
}
```

Payload of `:committed_transcript`. `:spans` is present only when a span flag was set.

# `completed`

```elixir
@type completed() :: %{
  :text =&gt; String.t(),
  :language =&gt; String.t() | nil,
  :duration_seconds =&gt; number() | nil,
  :request_id =&gt; String.t() | nil,
  :usage =&gt; ALLM.Usage.t(),
  :metadata =&gt; map(),
  optional(:spans) =&gt; [ALLM.TranscriptSpan.t()]
}
```

Payload of `:transcription_completed`. `:spans` is present only when a span flag was set.

# `started`

```elixir
@type started() :: %{
  request_id: String.t() | nil,
  model: String.t() | nil,
  provider: atom() | nil,
  session_id: String.t() | nil
}
```

Payload of `:transcription_started`.

# `t`

```elixir
@type t() ::
  {:transcription_started, started()}
  | {:partial_transcript, %{text: String.t()}}
  | {:committed_transcript, committed()}
  | {:transcription_completed, completed()}
  | {:error, ALLM.Error.TranscriptionAdapterError.t()}
```

# `committed_transcript`

```elixir
@spec committed_transcript(String.t(), String.t() | nil) :: t()
```

Build a `:committed_transcript` event.

The payload has exactly `:text` and `:language`, and no `:spans` key.
`committed_transcript/3` is the form that carries spans.

## Examples

    iex> ALLM.TranscriptionEvent.committed_transcript("hello", "en")
    {:committed_transcript, %{text: "hello", language: "en"}}

# `committed_transcript`

```elixir
@spec committed_transcript(
  String.t(),
  String.t() | nil,
  [ALLM.TranscriptSpan.t()] | nil
) :: t()
```

Build a `:committed_transcript` event carrying the segment's spans.

Always writes the `:spans` key, even when it is `nil` (the adapter has no
trustworthy span data for this segment). An adapter uses this form for
every committed segment when the request set `timestamps: true` or
`logprobs: true`, and `committed_transcript/2` otherwise. Raises
`FunctionClauseError` when `spans` is neither a list nor `nil`.

## Examples

    iex> span = ALLM.TranscriptSpan.new(text: "hello", kind: :word, start_seconds: 0.0, end_seconds: 0.4)
    iex> {:committed_transcript, payload} = ALLM.TranscriptionEvent.committed_transcript("hello", "en", [span])
    iex> payload.spans == [span]
    true

# `event?`

```elixir
@spec event?(term()) :: boolean()
```

Return `true` when `value` is a well-shaped `ALLM.TranscriptionEvent`.

The tag must be one of the five variants. `:partial_transcript` and
`:committed_transcript` need a map payload with a binary `:text`, the
other structured variants a map, and `:error` an
`%ALLM.Error.TranscriptionAdapterError{}`.

## Examples

    iex> ALLM.TranscriptionEvent.event?({:partial_transcript, %{text: "hi"}})
    true

    iex> ALLM.TranscriptionEvent.event?({:text_delta, %{id: nil, delta: "hi"}})
    false

# `partial_transcript`

```elixir
@spec partial_transcript(String.t()) :: t()
```

Build a `:partial_transcript` event.

## Examples

    iex> ALLM.TranscriptionEvent.partial_transcript("hel")
    {:partial_transcript, %{text: "hel"}}

# `transcription_completed`

```elixir
@spec transcription_completed(completed()) :: t()
```

Build a `:transcription_completed` event.

Raises `ArgumentError` when `payload` lacks any of `:text`, `:language`,
`:duration_seconds`, `:request_id`, `:usage` or `:metadata`. `:spans` is
optional and passes through when present.

## Examples

    iex> {:transcription_completed, p} =
    ...>   ALLM.TranscriptionEvent.transcription_completed(%{
    ...>     text: "hello world", language: nil, duration_seconds: 1.5,
    ...>     request_id: nil, usage: %ALLM.Usage{}, metadata: %{}
    ...>   })
    iex> p.text
    "hello world"

# `transcription_started`

```elixir
@spec transcription_started(started()) :: t()
```

Build a `:transcription_started` event.

Raises `ArgumentError` when `payload` lacks any of `:request_id`,
`:model`, `:provider` or `:session_id` (a key may be present with a `nil`
value).

## Examples

    iex> ALLM.TranscriptionEvent.transcription_started(%{request_id: nil, model: "m", provider: :fake, session_id: nil})
    {:transcription_started, %{request_id: nil, model: "m", provider: :fake, session_id: nil}}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
