> ## 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.

# Win/Loss Notes → Brand Voice

### Scenario

Your `#wins` and `#losses` channels (or `#deal-updates`) capture the language of victory and defeat. This job combines both, feeds them to Mavera's Brand Voice endpoint, and extracts two complementary voices: a "winning voice" (confidence, proof, momentum) and a "recovery voice" (empathy, resilience, learning). Both are useful for marketing — wins for case studies and sales enablement, losses for retention and re-engagement campaigns.

**Flow:** Slack `conversations.history` (#wins + #losses) → Concatenate → Mavera `POST /brand-voices` → Two brand voices + test generation

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, time

  SL_TOKEN = os.environ["SLACK_BOT_TOKEN"]
  SL_BASE = "https://slack.com/api"
  SL_H = {"Authorization": f"Bearer {SL_TOKEN}"}
  MV = os.environ["MAVERA_API_KEY"]
  MV_BASE = "https://app.mavera.io/api/v1"
  MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  WINS_CHANNEL = "C0123WINS"
  LOSSES_CHANNEL = "C0123LOSSES"
  DAYS_BACK = 90

  def fetch_channel(channel_id):
      oldest = str(int(time.time()) - DAYS_BACK * 86400)
      msgs = []
      cursor = None
      while True:
          params = {"channel": channel_id, "limit": 200, "oldest": oldest}
          if cursor:
              params["cursor"] = cursor
          r = requests.get(f"{SL_BASE}/conversations.history", headers=SL_H, params=params)
          data = r.json()
          if not data.get("ok"):
              break
          msgs.extend(data.get("messages", []))
          cursor = data.get("response_metadata", {}).get("next_cursor")
          if not cursor:
              break
          time.sleep(1)
      return [m for m in msgs if m.get("type") == "message"
              and not m.get("bot_id") and len(m.get("text","")) > 20]

  wins = fetch_channel(WINS_CHANNEL)
  losses = fetch_channel(LOSSES_CHANNEL)
  print(f"Wins: {len(wins)} messages | Losses: {len(losses)} messages")

  # 2. Build corpora
  win_text = "\n\n---\n\n".join(m.get("text","")[:500] for m in wins[-50:])
  loss_text = "\n\n---\n\n".join(m.get("text","")[:500] for m in losses[-50:])

  # 3. Create two brand voices
  win_voice = requests.post(f"{MV_BASE}/brand-voices", headers=MV_H, json={
      "name": "Win Voice: Sales Victories",
      "extracted_content": win_text,
      "description": f"Voice from {len(wins)} deal-win announcements. Captures celebration, proof language, competitive positioning, and momentum.",
  }).json()
  print(f"Win Voice: {win_voice.get('id','')} — {win_voice.get('traits', win_voice.get('voice_summary',''))}")

  loss_voice = requests.post(f"{MV_BASE}/brand-voices", headers=MV_H, json={
      "name": "Recovery Voice: Deal Losses",
      "extracted_content": loss_text,
      "description": f"Voice from {len(losses)} deal-loss notes. Captures empathy, learning, resilience, and honest reflection.",
  }).json()
  print(f"Loss Voice: {loss_voice.get('id','')} — {loss_voice.get('traits', loss_voice.get('voice_summary',''))}")

  # 4. Test generation with each voice
  for voice, label, prompt in [
      (win_voice, "WIN", "Write a case study opening paragraph (100 words) celebrating a customer's success with our platform. Confident, proof-heavy, momentum-driven."),
      (loss_voice, "RECOVERY", "Write a re-engagement email (100 words) to a churned customer. Empathetic, honest, learning-focused. Invite them back with a new feature."),
  ]:
      gen = requests.post(f"{MV_BASE}/generations", headers=MV_H, json={
          "brand_voice_id": voice["id"],
          "prompt": prompt,
      }).json()
      print(f"\n[{label} VOICE TEST]\n{gen.get('output', gen.get('content',''))[:400]}")
  ```

  ```javascript JavaScript theme={"dark"}
  const SL_TOKEN = process.env.SLACK_BOT_TOKEN;
  const SL_BASE = "https://slack.com/api";
  const SL_H = { Authorization: `Bearer ${SL_TOKEN}` };
  const MV = process.env.MAVERA_API_KEY;
  const MV_BASE = "https://app.mavera.io/api/v1";
  const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };

  const WINS_CHANNEL = "C0123WINS";
  const LOSSES_CHANNEL = "C0123LOSSES";
  const DAYS_BACK = 90;

  async function fetchChannel(channelId) {
    const oldest = String(Math.floor(Date.now() / 1000) - DAYS_BACK * 86400);
    const msgs = [];
    let cursor;
    do {
      const params = new URLSearchParams({ channel: channelId, limit: "200", oldest });
      if (cursor) params.set("cursor", cursor);
      const data = await (await fetch(`${SL_BASE}/conversations.history?${params}`, { headers: SL_H })).json();
      if (!data.ok) break;
      msgs.push(...(data.messages || []));
      cursor = data.response_metadata?.next_cursor;
      await new Promise(r => setTimeout(r, 1000));
    } while (cursor);
    return msgs.filter(m => m.type === "message" && !m.bot_id && (m.text||"").length > 20);
  }

  const wins = await fetchChannel(WINS_CHANNEL);
  const losses = await fetchChannel(LOSSES_CHANNEL);
  console.log(`Wins: ${wins.length} | Losses: ${losses.length}`);

  // 2. Corpora
  const winText = wins.slice(-50).map(m => (m.text||"").slice(0,500)).join("\n\n---\n\n");
  const lossText = losses.slice(-50).map(m => (m.text||"").slice(0,500)).join("\n\n---\n\n");

  // 3. Two brand voices
  const winVoice = await fetch(`${MV_BASE}/brand-voices`, { method: "POST", headers: MV_H,
    body: JSON.stringify({ name: "Win Voice: Sales Victories", extracted_content: winText,
      description: `Voice from ${wins.length} win announcements. Celebration, proof, momentum.` }),
  }).then(r => r.json());
  console.log(`Win Voice: ${winVoice.id} — ${winVoice.traits || winVoice.voice_summary || ""}`);

  const lossVoice = await fetch(`${MV_BASE}/brand-voices`, { method: "POST", headers: MV_H,
    body: JSON.stringify({ name: "Recovery Voice: Deal Losses", extracted_content: lossText,
      description: `Voice from ${losses.length} loss notes. Empathy, learning, resilience.` }),
  }).then(r => r.json());
  console.log(`Loss Voice: ${lossVoice.id} — ${lossVoice.traits || lossVoice.voice_summary || ""}`);

  // 4. Test
  for (const [voice, label, prompt] of [
    [winVoice, "WIN", "Case study opener (100 words). Confident, proof-heavy, momentum."],
    [lossVoice, "RECOVERY", "Re-engagement email (100 words). Empathetic, honest. Invite back."],
  ]) {
    const gen = await fetch(`${MV_BASE}/generations`, { method: "POST", headers: MV_H,
      body: JSON.stringify({ brand_voice_id: voice.id, prompt }),
    }).then(r => r.json());
    console.log(`\n[${label} VOICE TEST]\n${(gen.output || gen.content || "").slice(0, 400)}`);
  }
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
Wins: 156 messages | Losses: 43 messages
Win Voice: bv_slack_wins_8j3k — Confident, celebratory, data-backed.
  Uses "crushed it," "landed," and specific revenue figures.
Loss Voice: bv_slack_loss_2m4n — Reflective, honest, growth-oriented.
  Uses "learned," "next time," and process language.

[WIN VOICE TEST]
When Acme Corp needed to cut campaign launch time from 6 weeks to 6
days, they didn't hedge — they went all in on our platform. Within 30
days, their team shipped 14 campaigns. Pipeline grew 340%. Their VP of
Marketing called it "the best tool decision we've made in five years."

[RECOVERY VOICE TEST]
We noticed you haven't logged in since February, and honestly, we've
been thinking about why. Since you left, we shipped the dashboard
customization you asked about — and rebuilt our mobile experience from
scratch. We'd love 15 minutes to show you what changed.
```

### Error Handling

<AccordionGroup>
  <Accordion title="Unequal volumes">Win channels often have 3-5x more messages than loss channels. The code takes the last 50 from each to balance the voice extraction.</Accordion>
  <Accordion title="Duplicate celebrations">Wins often get cross-posted. Deduplicate by matching the first 100 chars of message text before building the corpus.</Accordion>
  <Accordion title="Sensitive deal data">Win/loss messages may contain deal sizes, customer names, and competitive details. The Brand Voice endpoint extracts patterns, not specifics — but review the voice traits to confirm no PII leaked through.</Accordion>
</AccordionGroup>
