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

# Pipeline Stage Voice Analysis

> Analyze deal notes by Pipedrive pipeline stage to find messaging patterns that correlate with wins — and coach reps on what to use or avoid

## Scenario

Your sales team writes deal notes at every pipeline stage. Each stage has a different conversational texture. **You want to know which messaging patterns correlate with wins at each stage** so you can coach reps.

## Architecture

```mermaid theme={"dark"}
flowchart LR
    A[Pipedrive Deals by pipeline] --> B[Group by stage] --> C[Pull notes per deal] --> D["Mavera Chat (per stage)"] --> E[Voice analysis report]
```

## Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests
  from collections import defaultdict
  from openai import OpenAI

  DOMAIN = os.environ["PIPEDRIVE_DOMAIN"]
  PD_TOKEN = os.environ["PIPEDRIVE_API_TOKEN"]
  PD_BASE = f"https://{DOMAIN}.pipedrive.com"

  def pd_get(path, params=None):
      params = params or {}
      params["api_token"] = PD_TOKEN
      r = requests.get(f"{PD_BASE}{path}", params=params)
      r.raise_for_status()
      return r.json()

  # 1. Pull deals for a pipeline, group by stage
  deals = pd_get("/api/v2/deals", {"pipeline_id": 1, "limit": 100}).get("data", [])
  stage_deals = defaultdict(list)
  for d in deals:
      stage_deals[d["stage_id"]].append(d["id"])

  # 2. Pull notes per deal, bucket by stage
  stage_notes = defaultdict(list)
  for stage_id, deal_ids in stage_deals.items():
      for deal_id in deal_ids:
          notes = pd_get("/api/v1/notes", {"deal_id": deal_id, "limit": 50}).get("data", []) or []
          for note in notes:
              if note.get("content"):
                  stage_notes[stage_id].append(note["content"][:500])

  # 3. Analyze each stage with Mavera Chat
  mavera = OpenAI(api_key=os.environ["MAVERA_API_KEY"], base_url="https://app.mavera.io/api/v1")
  stage_names = {1: "Prospecting", 2: "Proposal", 3: "Negotiation", 4: "Closed Won"}

  for stage_id, notes in stage_notes.items():
      combined = "\n---\n".join(notes[:30])
      response = mavera.responses.create(
          model="mavera-1",
          input=[{
              "role": "user",
              "content": (
                  f"You are a senior sales coach. Below are deal notes from the "
                  f"'{stage_names.get(stage_id, stage_id)}' stage.\n\n"
                  f"Analyze language patterns. What messaging resonates at this stage? "
                  f"What should reps use or avoid?\n\n{combined}"
              ),
          }],
      )
      print(f"\n=== {stage_names.get(stage_id, stage_id)} ===")
      print(response.output[0].content[0].text[:600])
  ```

  ```javascript JavaScript theme={"dark"}
  const OpenAI = require("openai");
  const DOMAIN = process.env.PIPEDRIVE_DOMAIN;
  const PD_TOKEN = process.env.PIPEDRIVE_API_TOKEN;
  const PD_BASE = `https://${DOMAIN}.pipedrive.com`;

  async function pdGet(path, params = {}) {
    const url = new URL(`${PD_BASE}${path}`);
    url.searchParams.set("api_token", PD_TOKEN);
    for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
    const res = await fetch(url);
    if (!res.ok) throw new Error(`Pipedrive ${res.status}: ${await res.text()}`);
    return res.json();
  }

  // 1. Pull deals, group by stage
  const deals = (await pdGet("/api/v2/deals", { pipeline_id: 1, limit: 100 })).data || [];
  const stageDeals = {};
  for (const d of deals) (stageDeals[d.stage_id] ||= []).push(d.id);

  // 2. Pull notes per deal, bucket by stage
  const stageNotes = {};
  for (const [stageId, dealIds] of Object.entries(stageDeals)) {
    stageNotes[stageId] = [];
    for (const dealId of dealIds) {
      const notes = (await pdGet("/api/v1/notes", { deal_id: dealId, limit: 50 })).data || [];
      for (const n of notes) if (n.content) stageNotes[stageId].push(n.content.slice(0, 500));
    }
  }

  // 3. Analyze each stage
  const mavera = new OpenAI({ apiKey: process.env.MAVERA_API_KEY, baseURL: "https://app.mavera.io/api/v1" });
  const stageNames = { 1: "Prospecting", 2: "Proposal", 3: "Negotiation", 4: "Closed Won" };

  for (const [stageId, notes] of Object.entries(stageNotes)) {
    const combined = notes.slice(0, 30).join("\n---\n");
    const response = await mavera.responses.create({
      model: "mavera-1",
      input: [{ role: "user", content:
        `You are a senior sales coach. Below are deal notes from '${stageNames[stageId] || stageId}'.\n\n` +
        `Analyze language patterns. What messaging resonates? What should reps avoid?\n\n${combined}` }],
    });
    console.log(`\n=== ${stageNames[stageId] || stageId} ===`);
    console.log(response.output[0].content[0].text.slice(0, 600));
  }
  ```
</CodeGroup>

## Example Output

```text theme={"dark"}
=== Prospecting ===
Notes that led to advancement use open-ended discovery questions
("What does your current workflow look like?") rather than feature-dumps.
Avoid: "Just checking in" and "circling back" — these correlate with stalls.

=== Negotiation ===
Winning notes reference the prospect's own language ("as you mentioned,
speed-to-deploy is critical") rather than internal jargon. Avoid premature
discounting. Use value-anchoring ("the ROI model we built shows…").
```

## Error Handling

| Error                   | Cause                        | Fix                                                     |
| ----------------------- | ---------------------------- | ------------------------------------------------------- |
| `401 Unauthorized`      | Expired API token            | Regenerate in Pipedrive settings or refresh OAuth token |
| `429 Too Many Requests` | Exceeded 30,000 daily tokens | Batch requests across days; implement backoff           |
| Empty notes list        | Deals with no notes          | Filter out stages with \< 3 notes before analysis       |
