> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tazolabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Post call analytics

> Review recorded calls with Tazo: flag low-quality audio, and find out whether the customer accepted the offer.

After a call ends, a company would probably want to know two things:

1. **Was the caller audio clear and noise free?** This is a useful filter to have for
   downstream analytics tasks.
2. **What was the outcome of the call?** If it was a sales call, that is very important
   information to gather.

Tazo can answer both from the **recording itself**, with no transcript,
and returns a probability for every option so you can decide how much to trust each answer.

In this cookbook we attempt to build a call-review tool for a bank that offers a credit
card by phone. It uses the [Tazo Python SDK](https://docs.tazolabs.com/sdk/python/overview.md) and asks two questions
about each recorded call:

| Question | Options | Used for |
| - | - | - |
| **Overall quality** | usable, too noisy, clipping, reverberant | Filtering low quality data for downstream analytics |
| **Offer accepted** | agreed, declined | Sales understanding |

```mermaid theme={null}
flowchart LR
    rec["recorded call<br/>WAV"] --> q1["Tazo: Overall quality"]
    rec --> q2["Tazo: Offer accepted"]
    q1 --> g{{"confidence ≥ 80%<br/>and lead ≥ 20 points?"}}
    g -->|"too noisy or clipping"| bad["Noisy or distorted audio"]
    g -->|"reverberant"| echo["Echo"]
    g -->|"usable"| ok["Clear audio"]
    q2 --> out["agreed / declined"]
```

Tazo Large answers one `choice` question per request, so each question is its own call on the
same recording. The two calls run in parallel.

## Setup

```bash theme={null}
python -m pip install tazo-sdk
export TAZO_API_KEY='your-key'
```

Each call is a WAV recording of at most 30 seconds. Put the recordings in
a `calls/` folder.

## Define the questions

Each question has a short context that tells Tazo what kind of recording it is hearing,
the question, and a `criteria` map from option ID to description. Descriptions are what Tazo
reads, so write them in plain language.

```python theme={null}
import asyncio
from pathlib import Path

from tazo import AsyncTazo
from tazo.types import ChoiceAnswer

MODEL = "tazo-large"
QUALITY_MIN_CONFIDENCE = 0.8  # a quality verdict needs a clear winner...
QUALITY_MIN_MARGIN = 0.2  # ...and a wide lead over the runner-up

QUALITY = {
    "context": "Judge the main audible issue in this recorded customer service call: speech quality only, not words, identity or equipment.",
    "instructions": "Overall quality: listen for noise or distortion behind the speech. Which issue is audible?",
    "criteria": {
        "usable": "Clean, clear speech with no audible hiss, noise or distortion.",
        "too_noisy": "Audible background noise or hiss behind the speech.",
        "clipping": "Harsh, crackly distortion like clipping.",
        "reverberant": "Echo or room reverb that blurs the speech.",
    },
}
OFFER = {
    "context": "Recorded sales call where an agent offers a credit card to a customer. Judge whether the customer accepts the offer.",
    "instructions": "Did the customer like the offer and agree to it, for example by saying they are interested or asking for the details to be sent?",
    "criteria": {
        "agreed": "Customer says they like the offer, are interested, or asks for the details to be sent or to go ahead.",
        "declined": "Customer says they are not interested or declines the offer.",
    },
}

# What each quality answer means for the call.
VERDICTS = {
    "usable": ("clear", "Clear audio"),
    "too_noisy": ("low_quality", "Low quality · noisy"),
    "clipping": ("low_quality", "Low quality · distorted"),
    "reverberant": ("low_quality", "Low quality · echo"),
}
```

## Ask Tazo

```python theme={null}
async def ask(client: AsyncTazo, audio: Path, spec: dict) -> ChoiceAnswer:
    result = await client.decisions.create(
        audio=audio,
        model=MODEL,
        state=spec["context"],
        questions={
            "answer": {"type": "choice", "instructions": spec["instructions"], "criteria": spec["criteria"]}
        },
    )
    answer = result.answers["answer"]
    assert isinstance(answer, ChoiceAnswer)
    return answer
```

## Turn the quality answer into a verdict

Flagging a call as low quality drops it from downstream analytics, so the quality verdict needs a high bar: at
least 80% confidence and a 20-point lead over the runner-up. If Tazo is not sure, the call for simplicity sake is reported as clear.

```python theme={null}
def quality_verdict(answer: ChoiceAnswer) -> tuple[str, str]:
    ranked = sorted(answer.probabilities.values(), reverse=True)
    runner_up = ranked[1] if len(ranked) > 1 else 0.0
    sure = answer.confidence >= QUALITY_MIN_CONFIDENCE and answer.confidence - runner_up >= QUALITY_MIN_MARGIN
    # An unsure answer never flags a call; it is reported as clear.
    return VERDICTS[answer.choice] if sure else ("clear", "Clear audio")


async def analyze_call(audio: Path) -> dict:
    """Two independent questions about one recorded call, asked in parallel."""
    async with AsyncTazo() as client:
        quality, offer = await asyncio.gather(ask(client, audio, QUALITY), ask(client, audio, OFFER))
    outcome, title = quality_verdict(quality)
    return {
        "call": audio.name,
        "outcome": outcome,
        "title": title,
        "quality": {"choice": quality.choice, "confidence": round(quality.confidence, 2)},
        "offer": {"choice": offer.choice, "confidence": round(offer.confidence, 2)},
    }
```

## Analyze the calls

```python theme={null}
async def main() -> None:
    import json

    for wav in sorted(Path("calls").glob("*.wav")):
        print(json.dumps(await analyze_call(wav)))
```

Each line is a JSON object like this. The shape is real; the numbers are illustrative.

```json theme={null}
{"call": "call1.wav", "outcome": "clear", "title": "Clear audio", "quality": {"choice": "usable", "confidence": 0.97}, "offer": {"choice": "declined", "confidence": 0.95}}
```

## Build on it

* **More checks.** Add a question with its own context and options, and ask it in the same `gather`. For example, whether the customer was satisfied at the end of the call.
* **Follow-up questions.** Ask a second question only when the first finds something. For example, ask how many times a distracting sound happens only after a distracting sound was heard.
* **Batch review.** Run `analyze_call` over a folder and send low-quality and uncertain calls to a person.

## Limitations

* The thresholds are not calibrated quality guarantees.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.