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

# Annual Planning Kickoff

> Chain personas, Focus Groups, Generate, Mave, and Speak into a complete annual marketing plan — the capstone Mavera playbook

## Mavera Surfaces Used

| Surface                                 | Role                                                                   |
| --------------------------------------- | ---------------------------------------------------------------------- |
| **Personas** (`POST /personas`)         | Create customer personas from CRM data                                 |
| **Focus Groups** (`POST /focus-groups`) | Validate next year's themes, priorities, and messaging with personas   |
| **Generate** (`POST /generate`)         | Draft the annual plan sections from validated insights                 |
| **Mave Agent** (`POST /mave/chat`)      | Research market trends, competitive landscape, and industry benchmarks |
| **Speak** (`POST /speak/conversations`) | Live voice conversations with personas for qualitative depth           |
| **Chat + `response_format`**            | Structure outputs and synthesize across stages                         |

<Info>
  This is the **capstone playbook** — it chains every major Mavera surface into one end-to-end workflow. Each stage feeds the next, producing a data-backed annual marketing plan that would normally take weeks of agency work, internal workshops, and executive reviews.
</Info>

***

## What Value Does Mavera Add?

| Value                 | How                                                                                                                                             |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **Insurance**         | Every plan element is validated against personas before it reaches the board deck. No "gut feel" positioning or unvalidated assumptions.        |
| **Opening new doors** | A complete annual plan — from CRM data to finished document — in a single day. Run multiple scenarios (aggressive vs conservative) in parallel. |
| **Saving time**       | Replaces 4-6 weeks of agency research, internal workshops, cross-functional reviews, and document drafting with a single automated pipeline.    |

***

## When to Use This

* It's Q4 and you need next year's marketing plan with data-backed priorities.
* You're a new CMO establishing your first-year strategy and need to move fast.
* You're presenting to the board and need a plan that's validated, not aspirational.
* You want to compare multiple strategic scenarios (aggressive growth vs efficiency vs expansion).

***

## What You Need

| Requirement                        | Details                                                                                                               |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| **Mavera API key**                 | Starts with `mvra_live_`. Get one at [Developer Settings](https://app.mavera.io/settings/developer).                  |
| **Workspace ID**                   | From your dashboard URL (`ws_...`).                                                                                   |
| **CRM export or customer data**    | Customer list with segment, ARR, tenure, and satisfaction data. CSV or JSON.                                          |
| **Current year metrics**           | Revenue, pipeline, win rate, churn, CAC, LTV — for benchmarking.                                                      |
| **Next year's candidate themes**   | 3-5 strategic themes you're considering (e.g. "Enterprise expansion", "Product-led growth", "International markets"). |
| **Credits**                        | \~500–1500 total. See [Credits Estimate](#credits-estimate).                                                          |
| **Python 3.8+** or **Node.js 18+** | `requests` / `openai` for Python; native `fetch` for Node.                                                            |

```
MAVERA_API_KEY=mvra_live_your_key_here
MAVERA_WORKSPACE_ID=ws_your_workspace_id
PLAN_YEAR=2027
```

***

## The Pipeline

This is a 6-stage pipeline where each stage feeds the next:

```mermaid theme={"dark"}
flowchart TD
    subgraph stage1["Stage 1: Data Collection"]
        crmExport[("CRM Export")] --> parseSegments["Parse Segments"]
        parseSegments --> segmentData[("Segment Profiles")]
        segmentData --> createPersonas["Personas: POST /personas"]
        createPersonas --> personaIds[("5 Custom Personas")]
    end

    subgraph stage2["Stage 2: Theme Validation"]
        candidateThemes[("3-5 Candidate Themes")] --> focusGroup["Focus Group: Ranking + Likert + Open-Ended"]
        personaIds --> focusGroup
        focusGroup --> scoreRank["Score and Rank Themes"]
        scoreRank --> themeDecision{"Themes validated?"}
        themeDecision -->|Yes| validatedThemes[("Validated Theme Priority")]
        themeDecision -->|No| candidateThemes
    end

    subgraph stage3["Stage 3: Market Research"]
        maveAgent["Mave Agent: POST /mave/chat"] --> compLandscape[("Competitive Landscape")]
        maveAgent --> industryTrends[("Industry Trends")]
        maveAgent --> benchmarkData[("Benchmark Data")]
    end

    subgraph stage4["Stage 4: Plan Draft"]
        validatedThemes --> generatePlan["Generate: POST /generate"]
        compLandscape --> generatePlan
        industryTrends --> generatePlan
        benchmarkData --> generatePlan
        generatePlan --> execSummary[("Executive Summary")]
        generatePlan --> channelStrategy[("Channel Strategy")]
        generatePlan --> budgetAlloc[("Budget Allocation")]
        generatePlan --> campaignCal[("Campaign Calendar")]
    end

    subgraph stage5["Stage 5: Voice Validation"]
        execSummary --> speakSessions["Speak: POST /speak/conversations"]
        personaIds --> speakSessions
        speakSessions --> qualFeedback[("Qualitative Feedback")]
    end

    subgraph stage6["Stage 6: Final Synthesis"]
        qualFeedback --> chatSynth["Chat: response_format"]
        channelStrategy --> chatSynth
        budgetAlloc --> chatSynth
        campaignCal --> chatSynth
        chatSynth --> annualPlan(("Consolidated Annual Plan"))
    end
```

***

## The Flow

<Steps>
  <Step title="Analyze CRM data">
    Parse customer data to identify segments, satisfaction patterns, churn indicators, and growth opportunities. This creates the factual foundation for personas and planning.
  </Step>

  <Step title="Create personas from data">
    Build 5 personas that represent your actual customer base — not hypothetical ICPs, but data-informed profiles of your best, newest, at-risk, churned, and prospect segments.
  </Step>

  <Step title="Run Focus Groups on next year's themes">
    Present 3-5 candidate strategic themes to all personas. Use Ranking, Likert, and Open-Ended questions to validate which themes resonate and which fall flat.
  </Step>

  <Step title="Mave researches market context">
    Use Mave Agent to research market trends, competitive moves, and industry benchmarks that inform the plan. Combines with Focus Group data for grounded strategy.
  </Step>

  <Step title="Generate the annual plan">
    Feed validated themes, Focus Group data, and market research into Generate to draft each section of the annual plan. Structured output ensures consistency.
  </Step>

  <Step title="Speak validation conversations">
    Run live voice conversations with key personas to stress-test the plan. Ask probing questions, handle objections, and refine based on verbal feedback.
  </Step>
</Steps>

***

## Code: Full Annual Planning Pipeline

### Setup and Configuration

<CodeGroup>
  ```python Python theme={"dark"}
  import os
  import json
  import time
  import csv
  import statistics
  import requests
  from openai import OpenAI

  MAVERA_API_KEY = os.environ["MAVERA_API_KEY"]
  WORKSPACE_ID = os.environ["MAVERA_WORKSPACE_ID"]
  PLAN_YEAR = os.environ.get("PLAN_YEAR", "2027")
  BASE = "https://app.mavera.io/api/v1"
  HEADERS = {
      "Authorization": f"Bearer {MAVERA_API_KEY}",
      "Content-Type": "application/json",
  }
  mavera = OpenAI(api_key=MAVERA_API_KEY, base_url=BASE)

  COMPANY = {
      "name": "Acme",
      "category": "AI-powered market research platform",
      "current_arr": "$4.2M",
      "growth_rate": "120% YoY",
      "team_size": 35,
      "key_metrics": {
          "customers": 280,
          "avg_contract": "$15K",
          "win_rate": "32%",
          "churn_rate": "8%",
          "cac": "$4,200",
          "ltv": "$42,000",
          "nps": 48,
      },
  }

  CANDIDATE_THEMES = [
      {
          "theme": "Enterprise Expansion",
          "description": (
              "Move upmarket: build enterprise features (SSO, SCIM, audit logs), "
              "hire enterprise AEs, target companies with 1000+ employees. "
              "Goal: 20 enterprise deals at $50K+ ACV."
          ),
      },
      {
          "theme": "Product-Led Growth",
          "description": (
              "Launch a self-serve tier with freemium entry. Invest in onboarding, "
              "in-app guides, and viral loops. Goal: 1000 self-serve accounts, "
              "15% conversion to paid."
          ),
      },
      {
          "theme": "Content & Thought Leadership",
          "description": (
              "Become the definitive voice in AI-powered market research. "
              "Weekly blog, monthly webinar, quarterly research report, conference presence. "
              "Goal: 3x organic traffic, 2x inbound pipeline."
          ),
      },
      {
          "theme": "Integration Ecosystem",
          "description": (
              "Build deep integrations with HubSpot, Salesforce, Slack, Notion. "
              "Launch a partner program. Goal: 10 integration partners, "
              "30% of customers using 2+ integrations."
          ),
      },
      {
          "theme": "International Markets",
          "description": (
              "Expand to EU and APAC. Localize product and content. "
              "Hire regional marketing leads. Goal: 15% of new ARR from international."
          ),
      },
  ]
  ```

  ```javascript JavaScript theme={"dark"}
  import OpenAI from "openai";
  import fs from "fs";

  const MAVERA_API_KEY = process.env.MAVERA_API_KEY;
  const WORKSPACE_ID = process.env.MAVERA_WORKSPACE_ID;
  const PLAN_YEAR = process.env.PLAN_YEAR || "2027";
  const BASE = "https://app.mavera.io/api/v1";
  const HEADERS = {
    Authorization: `Bearer ${MAVERA_API_KEY}`,
    "Content-Type": "application/json",
  };
  const mavera = new OpenAI({ apiKey: MAVERA_API_KEY, baseURL: BASE });

  const COMPANY = {
    name: "Acme",
    category: "AI-powered market research platform",
    current_arr: "$4.2M",
    growth_rate: "120% YoY",
    team_size: 35,
    key_metrics: {
      customers: 280, avg_contract: "$15K", win_rate: "32%",
      churn_rate: "8%", cac: "$4,200", ltv: "$42,000", nps: 48,
    },
  };

  const CANDIDATE_THEMES = [
    {
      theme: "Enterprise Expansion",
      description:
        "Move upmarket: build enterprise features (SSO, SCIM, audit logs), " +
        "hire enterprise AEs, target companies with 1000+ employees. " +
        "Goal: 20 enterprise deals at $50K+ ACV.",
    },
    {
      theme: "Product-Led Growth",
      description:
        "Launch a self-serve tier with freemium entry. Invest in onboarding, " +
        "in-app guides, and viral loops. Goal: 1000 self-serve accounts, 15% conversion.",
    },
    {
      theme: "Content & Thought Leadership",
      description:
        "Become the definitive voice in AI-powered market research. " +
        "Weekly blog, monthly webinar, quarterly research report.",
    },
    {
      theme: "Integration Ecosystem",
      description:
        "Build deep integrations with HubSpot, Salesforce, Slack, Notion. " +
        "Launch a partner program. Goal: 10 integration partners.",
    },
    {
      theme: "International Markets",
      description:
        "Expand to EU and APAC. Localize product and content. " +
        "Goal: 15% of new ARR from international.",
    },
  ];
  ```
</CodeGroup>

***

### Stage 1 — Analyze CRM Data

Parse customer data to identify segments and build persona-ready profiles.

<CodeGroup>
  ```python Python theme={"dark"}
  SAMPLE_CRM_DATA = [
      {"name": "TechCorp", "segment": "enterprise", "arr": 85000, "tenure_months": 18, "nps": 9, "usage": "heavy", "features_used": ["focus_groups", "chat", "mave", "video"]},
      {"name": "StartupX", "segment": "startup", "arr": 5000, "tenure_months": 4, "nps": 7, "usage": "moderate", "features_used": ["chat", "mave"]},
      {"name": "AgencyPro", "segment": "agency", "arr": 24000, "tenure_months": 12, "nps": 8, "usage": "heavy", "features_used": ["focus_groups", "chat", "generate", "brand_voice"]},
      {"name": "MidMarket Inc", "segment": "mid_market", "arr": 36000, "tenure_months": 8, "nps": 6, "usage": "light", "features_used": ["chat"]},
      {"name": "ChurnedCo", "segment": "churned", "arr": 0, "tenure_months": 6, "nps": 3, "usage": "none", "features_used": []},
  ]


  def analyze_crm_data(data: list[dict]) -> dict:
      """Analyze CRM data to identify segments and patterns."""
      segments = {}
      for customer in data:
          seg = customer["segment"]
          if seg not in segments:
              segments[seg] = {"customers": [], "total_arr": 0, "nps_scores": [], "tenures": []}
          segments[seg]["customers"].append(customer)
          segments[seg]["total_arr"] += customer["arr"]
          segments[seg]["nps_scores"].append(customer["nps"])
          segments[seg]["tenures"].append(customer["tenure_months"])

      analysis = {}
      for seg, data in segments.items():
          analysis[seg] = {
              "count": len(data["customers"]),
              "total_arr": data["total_arr"],
              "avg_arr": data["total_arr"] / len(data["customers"]) if data["customers"] else 0,
              "avg_nps": statistics.mean(data["nps_scores"]) if data["nps_scores"] else 0,
              "avg_tenure": statistics.mean(data["tenures"]) if data["tenures"] else 0,
              "top_features": _most_common_features(data["customers"]),
          }

      print(f"✓ Analyzed {sum(a['count'] for a in analysis.values())} customers across {len(analysis)} segments")
      for seg, info in analysis.items():
          print(f"  {seg}: {info['count']} customers, ${info['avg_arr']:.0f} avg ARR, NPS {info['avg_nps']:.1f}")

      return analysis


  def _most_common_features(customers: list[dict]) -> list[str]:
      """Find most commonly used features across customers."""
      counts = {}
      for c in customers:
          for f in c.get("features_used", []):
              counts[f] = counts.get(f, 0) + 1
      return sorted(counts, key=counts.get, reverse=True)[:5]
  ```

  ```javascript JavaScript theme={"dark"}
  const SAMPLE_CRM_DATA = [
    { name: "TechCorp", segment: "enterprise", arr: 85000, tenure_months: 18, nps: 9, usage: "heavy", features_used: ["focus_groups", "chat", "mave", "video"] },
    { name: "StartupX", segment: "startup", arr: 5000, tenure_months: 4, nps: 7, usage: "moderate", features_used: ["chat", "mave"] },
    { name: "AgencyPro", segment: "agency", arr: 24000, tenure_months: 12, nps: 8, usage: "heavy", features_used: ["focus_groups", "chat", "generate", "brand_voice"] },
    { name: "MidMarket Inc", segment: "mid_market", arr: 36000, tenure_months: 8, nps: 6, usage: "light", features_used: ["chat"] },
    { name: "ChurnedCo", segment: "churned", arr: 0, tenure_months: 6, nps: 3, usage: "none", features_used: [] },
  ];

  function analyzeCrmData(data) {
    const segments = {};
    for (const customer of data) {
      const seg = customer.segment;
      if (!segments[seg]) segments[seg] = { customers: [], total_arr: 0, nps_scores: [], tenures: [] };
      segments[seg].customers.push(customer);
      segments[seg].total_arr += customer.arr;
      segments[seg].nps_scores.push(customer.nps);
      segments[seg].tenures.push(customer.tenure_months);
    }

    const analysis = {};
    for (const [seg, d] of Object.entries(segments)) {
      const avg = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length;
      analysis[seg] = {
        count: d.customers.length,
        total_arr: d.total_arr,
        avg_arr: d.total_arr / d.customers.length,
        avg_nps: avg(d.nps_scores),
        avg_tenure: avg(d.tenures),
      };
    }
    console.log(`✓ Analyzed ${data.length} customers across ${Object.keys(analysis).length} segments`);
    return analysis;
  }
  ```
</CodeGroup>

***

### Stage 2 — Create Personas from CRM Data

Build 5 personas that represent real customer segments, not hypothetical ICPs.

<CodeGroup>
  ```python Python theme={"dark"}
  def generate_persona_descriptions(crm_analysis: dict) -> list[dict]:
      """Use Chat to generate rich persona descriptions from CRM segment data."""
      PERSONA_SCHEMA = {"type": "json_schema", "json_schema": {
          "name": "personas", "strict": True,
          "schema": {
              "type": "object",
              "properties": {
                  "personas": {
                      "type": "array",
                      "items": {
                          "type": "object",
                          "properties": {
                              "name": {"type": "string"},
                              "segment": {"type": "string"},
                              "title": {"type": "string"},
                              "description": {"type": "string"},
                          },
                          "required": ["name", "segment", "title", "description"],
                      },
                  },
              },
              "required": ["personas"],
          },
      }}

      prompt = (
          f"Based on this CRM analysis for {COMPANY['name']} ({COMPANY['category']}), "
          f"create 5 detailed personas — one per segment.\n\n"
          f"## CRM Analysis\n{json.dumps(crm_analysis, indent=2)}\n\n"
          f"## Company Context\n{json.dumps(COMPANY, indent=2)}\n\n"
          "For each persona, include:\n"
          "- Realistic name and title\n"
          "- Company size and budget context\n"
          "- How they use (or stopped using) the product\n"
          "- Their priorities for next year\n"
          "- What would make them increase/decrease their investment\n"
          "Make each description 3-5 sentences."
      )

      resp = mavera.responses.create(
          model="mavera-1",
          input=[{"role": "user", "content": prompt}],
          extra_body={"response_format": PERSONA_SCHEMA},
      )

      return json.loads(resp.output[0].content[0].text)["personas"]


  def create_personas_from_descriptions(persona_descs: list[dict]) -> list[str]:
      """Create personas in Mavera from generated descriptions."""
      persona_ids = []

      for p in persona_descs:
          resp = requests.post(
              f"{BASE}/personas",
              headers=HEADERS,
              json={
                  "name": p["name"],
                  "description": f"[{p['segment']}] {p['title']}\n\n{p['description']}",
                  "workspace_id": WORKSPACE_ID,
              },
          ).json()

          if "error" in resp:
              raise Exception(f"Failed: {p['name']}: {resp['error']['message']}")

          persona_ids.append(resp["id"])
          print(f"✓ Created: {p['name']} ({p['segment']}) — {resp['id']}")

      return persona_ids
  ```

  ```javascript JavaScript theme={"dark"}
  async function generatePersonaDescriptions(crmAnalysis) {
    const PERSONA_SCHEMA = { type: "json_schema", json_schema: {
      name: "personas", strict: true,
      schema: {
        type: "object",
        properties: {
          personas: {
            type: "array",
            items: {
              type: "object",
              properties: {
                name: { type: "string" }, segment: { type: "string" },
                title: { type: "string" }, description: { type: "string" },
              },
              required: ["name", "segment", "title", "description"],
            },
          },
        },
        required: ["personas"],
      },
    }};

    const resp = await mavera.responses.create({
      model: "mavera-1",
      input: [{
        role: "user",
        content:
          `Based on this CRM analysis for ${COMPANY.name}, create 5 detailed personas.\n\n` +
          `## CRM Analysis\n${JSON.stringify(crmAnalysis, null, 2)}\n\n` +
          `## Company\n${JSON.stringify(COMPANY, null, 2)}\n\n` +
          "Include title, company context, product usage, and next-year priorities.",
      }],
      response_format: PERSONA_SCHEMA,
    });

    return JSON.parse(resp.output[0].content[0].text).personas;
  }

  async function createPersonasFromDescriptions(personaDescs) {
    const ids = [];
    for (const p of personaDescs) {
      const resp = await fetch(`${BASE}/personas`, {
        method: "POST", headers: HEADERS,
        body: JSON.stringify({
          name: p.name,
          description: `[${p.segment}] ${p.title}\n\n${p.description}`,
          workspace_id: WORKSPACE_ID,
        }),
      }).then((r) => r.json());

      if (resp.error) throw new Error(`Failed: ${p.name}: ${resp.error.message}`);
      ids.push(resp.id);
      console.log(`✓ Created: ${p.name} (${p.segment}) — ${resp.id}`);
    }
    return ids;
  }
  ```
</CodeGroup>

***

### Stage 3 — Focus Groups on Next Year's Themes

<CodeGroup>
  ```python Python theme={"dark"}
  def run_theme_focus_group(persona_ids: list[str]) -> dict:
      """Run a focus group validating candidate themes for next year."""
      themes_block = "\n".join(
          f"{i+1}. **{t['theme']}**: {t['description']}"
          for i, t in enumerate(CANDIDATE_THEMES)
      )

      payload = {
          "name": f"{PLAN_YEAR} Annual Planning — Theme Validation",
          "sample_size": 30,
          "persona_ids": persona_ids,
          "workspace_id": WORKSPACE_ID,
          "questions": [
              {
                  "question": (
                      f"As a current or potential customer, rank these {PLAN_YEAR} strategic themes "
                      f"from most important to you (1st) to least (5th):\n\n{themes_block}"
                  ),
                  "type": "RANKING",
                  "options": [t["theme"] for t in CANDIDATE_THEMES],
                  "order": 1,
              },
              {
                  "question": (
                      f"For the theme you ranked #1, rate how much it would increase "
                      f"your likelihood to invest more in {COMPANY['name']}. (1-10)"
                  ),
                  "type": "LIKERT",
                  "scale": 10,
                  "order": 2,
              },
              {
                  "question": (
                      "Which theme would you be most disappointed if we did NOT pursue? "
                      "Why?"
                  ),
                  "type": "OPEN_ENDED",
                  "order": 3,
              },
              {
                  "question": (
                      f"Is there a strategic direction NOT listed above that you think "
                      f"{COMPANY['name']} should prioritize in {PLAN_YEAR}?"
                  ),
                  "type": "OPEN_ENDED",
                  "order": 4,
              },
              {
                  "question": (
                      f"Rate your overall confidence that {COMPANY['name']} is heading "
                      f"in the right direction. (0 = no confidence, 10 = very confident)"
                  ),
                  "type": "NPS",
                  "order": 5,
              },
          ],
      }

      resp = requests.post(f"{BASE}/focus-groups", headers=HEADERS, json=payload).json()
      if "error" in resp:
          raise Exception(resp["error"]["message"])

      print(f"✓ Theme focus group launched: {resp['id']}")
      return resp


  def poll_focus_group(fg_id: str, timeout_min: int = 15) -> dict:
      for _ in range(timeout_min * 6):
          resp = requests.get(f"{BASE}/focus-groups/{fg_id}", headers=HEADERS).json()
          if "error" in resp:
              raise Exception(resp["error"]["message"])
          if resp.get("status") == "COMPLETED":
              print("✓ Theme focus group completed")
              return resp
          if resp.get("status") == "FAILED":
              raise Exception("Focus group failed")
          time.sleep(10)
      raise TimeoutError("Focus group timed out")
  ```

  ```javascript JavaScript theme={"dark"}
  async function runThemeFocusGroup(personaIds) {
    const themesBlock = CANDIDATE_THEMES
      .map((t, i) => `${i + 1}. **${t.theme}**: ${t.description}`)
      .join("\n");

    const payload = {
      name: `${PLAN_YEAR} Annual Planning — Theme Validation`,
      sample_size: 30,
      persona_ids: personaIds,
      workspace_id: WORKSPACE_ID,
      questions: [
        {
          question: `Rank these ${PLAN_YEAR} strategic themes:\n\n${themesBlock}`,
          type: "RANKING",
          options: CANDIDATE_THEMES.map((t) => t.theme),
          order: 1,
        },
        {
          question: `For your #1 theme, rate how much it increases your likelihood to invest more. (1-10)`,
          type: "LIKERT", scale: 10, order: 2,
        },
        {
          question: "Which theme would you be most disappointed if we did NOT pursue? Why?",
          type: "OPEN_ENDED", order: 3,
        },
        {
          question: `Any strategic direction not listed that ${COMPANY.name} should prioritize?`,
          type: "OPEN_ENDED", order: 4,
        },
        {
          question: `Rate your confidence ${COMPANY.name} is heading in the right direction. (0-10)`,
          type: "NPS", order: 5,
        },
      ],
    };

    const resp = await fetch(`${BASE}/focus-groups`, {
      method: "POST", headers: HEADERS,
      body: JSON.stringify(payload),
    }).then((r) => r.json());

    if (resp.error) throw new Error(resp.error.message);
    console.log(`✓ Theme focus group launched: ${resp.id}`);
    return resp;
  }

  async function pollFocusGroup(fgId, timeoutMin = 15) {
    for (let i = 0; i < timeoutMin * 6; i++) {
      const resp = await fetch(`${BASE}/focus-groups/${fgId}`, { headers: HEADERS }).then((r) => r.json());
      if (resp.error) throw new Error(resp.error.message);
      if (resp.status === "COMPLETED") return resp;
      if (resp.status === "FAILED") throw new Error("Focus group failed");
      await new Promise((r) => setTimeout(r, 10000));
    }
    throw new Error("Focus group timed out");
  }
  ```
</CodeGroup>

***

### Stage 4 — Mave Researches Market Context

<CodeGroup>
  ```python Python theme={"dark"}
  def research_market_context() -> dict:
      """Use Mave to research market trends, competitive landscape, and benchmarks."""
      research_prompts = [
          {
              "label": "market_trends",
              "message": (
                  f"Research the top trends in the {COMPANY['category']} market for {PLAN_YEAR}. "
                  f"Focus on: technology shifts, buyer behavior changes, "
                  f"emerging competitors, and market size projections. Cite sources."
              ),
          },
          {
              "label": "competitive_landscape",
              "message": (
                  f"What are {COMPANY['name']}'s main competitors doing for {PLAN_YEAR}? "
                  f"Any recent funding, product launches, acquisitions, or pivots? "
                  f"How are they positioning for next year? Cite sources."
              ),
          },
          {
              "label": "industry_benchmarks",
              "message": (
                  f"What are typical SaaS benchmarks for a company at {COMPANY['current_arr']} ARR "
                  f"growing at {COMPANY['growth_rate']}? Include: ideal CAC:LTV ratio, "
                  f"net revenue retention targets, magic number, and marketing spend as % of revenue. "
                  f"Cite sources."
              ),
          },
      ]

      thread_id = None
      research = {}

      for prompt in research_prompts:
          payload = {"message": prompt["message"]}
          if thread_id:
              payload["thread_id"] = thread_id

          resp = requests.post(
              f"{BASE}/mave/chat",
              headers=HEADERS,
              json=payload,
              timeout=120,
          ).json()

          if "error" in resp:
              raise Exception(resp["error"]["message"])

          if not thread_id:
              thread_id = resp.get("thread_id")

          research[prompt["label"]] = {
              "content": resp.get("content", ""),
              "sources": resp.get("sources", []),
          }
          print(f"✓ Mave: {prompt['label']} ({len(resp.get('content', ''))} chars)")
          time.sleep(2)

      return {"thread_id": thread_id, "research": research}
  ```

  ```javascript JavaScript theme={"dark"}
  async function researchMarketContext() {
    const prompts = [
      {
        label: "market_trends",
        message: `Research the top trends in ${COMPANY.category} for ${PLAN_YEAR}. Focus on technology shifts, buyer behavior, emerging competitors.`,
      },
      {
        label: "competitive_landscape",
        message: `What are ${COMPANY.name}'s main competitors doing for ${PLAN_YEAR}? Recent funding, launches, acquisitions?`,
      },
      {
        label: "industry_benchmarks",
        message: `SaaS benchmarks for a company at ${COMPANY.current_arr} ARR growing at ${COMPANY.growth_rate}? CAC:LTV, NRR, marketing spend %.`,
      },
    ];

    let threadId = null;
    const research = {};

    for (const p of prompts) {
      const payload = { message: p.message };
      if (threadId) payload.thread_id = threadId;

      const resp = await fetch(`${BASE}/mave/chat`, {
        method: "POST", headers: HEADERS,
        body: JSON.stringify(payload),
        signal: AbortSignal.timeout(120000),
      }).then((r) => r.json());

      if (resp.error) throw new Error(resp.error.message);
      if (!threadId) threadId = resp.thread_id;
      research[p.label] = { content: resp.content || "", sources: resp.sources || [] };
      console.log(`✓ Mave: ${p.label}`);
      await new Promise((r) => setTimeout(r, 2000));
    }

    return { thread_id: threadId, research };
  }
  ```
</CodeGroup>

***

### Stage 5 — Generate the Annual Plan

<CodeGroup>
  ```python Python theme={"dark"}
  PLAN_SECTIONS = [
      "executive_summary",
      "market_context",
      "strategic_themes",
      "target_segments",
      "channel_strategy",
      "budget_allocation",
      "okrs_and_kpis",
      "quarterly_roadmap",
      "risks_and_mitigations",
  ]


  def generate_annual_plan(
      crm_analysis: dict, fg_results: dict, market_research: dict
  ) -> dict:
      """Generate each section of the annual plan using Generate API."""
      # Compile all inputs into context
      context = (
          f"# {PLAN_YEAR} Annual Marketing Plan Inputs\n\n"
          f"## Company\n{json.dumps(COMPANY, indent=2)}\n\n"
          f"## CRM Analysis\n{json.dumps(crm_analysis, indent=2)}\n\n"
          f"## Focus Group Results (Theme Validation)\n"
      )

      for result in fg_results.get("results", []):
          context += f"### {result.get('type')}: {result.get('question', '')[:80]}\n"
          if result.get("summary"):
              context += f"{result['summary']}\n"
          if result.get("ranking_distribution"):
              context += f"Rankings: {json.dumps(result['ranking_distribution'])}\n"
          if result.get("mean_score"):
              context += f"Mean: {result['mean_score']}\n"
          context += "\n"

      context += "## Market Research\n"
      for label, data in market_research["research"].items():
          context += f"### {label}\n{data['content'][:1000]}\n\n"

      plan_sections = {}

      for section in PLAN_SECTIONS:
          resp = requests.post(
              f"{BASE}/generate",
              headers=HEADERS,
              json={
                  "prompt": (
                      f"Write the '{section.replace('_', ' ').title()}' section of the "
                      f"{PLAN_YEAR} annual marketing plan for {COMPANY['name']}.\n\n"
                      f"Use the data and insights below. Be specific — include numbers, "
                      f"timelines, and owners where appropriate. "
                      f"Write in a professional tone suitable for a board presentation.\n\n"
                      f"{context}"
                  ),
                  "workspace_id": WORKSPACE_ID,
                  "max_tokens": 2000,
              },
          ).json()

          if "error" in resp:
              raise Exception(f"Generate failed for {section}: {resp['error']['message']}")

          plan_sections[section] = resp.get("content", resp.get("text", ""))
          print(f"✓ Generated: {section.replace('_', ' ').title()}")
          time.sleep(2)

      return plan_sections
  ```

  ```javascript JavaScript theme={"dark"}
  const PLAN_SECTIONS = [
    "executive_summary", "market_context", "strategic_themes",
    "target_segments", "channel_strategy", "budget_allocation",
    "okrs_and_kpis", "quarterly_roadmap", "risks_and_mitigations",
  ];

  async function generateAnnualPlan(crmAnalysis, fgResults, marketResearch) {
    let context = `# ${PLAN_YEAR} Annual Marketing Plan Inputs\n\n`;
    context += `## Company\n${JSON.stringify(COMPANY, null, 2)}\n\n`;
    context += `## CRM Analysis\n${JSON.stringify(crmAnalysis, null, 2)}\n\n`;
    context += "## Focus Group Results\n";

    for (const result of fgResults.results || []) {
      context += `### ${result.type}: ${(result.question || "").slice(0, 80)}\n`;
      if (result.summary) context += `${result.summary}\n`;
      context += "\n";
    }

    context += "## Market Research\n";
    for (const [label, data] of Object.entries(marketResearch.research)) {
      context += `### ${label}\n${data.content.slice(0, 1000)}\n\n`;
    }

    const planSections = {};

    for (const section of PLAN_SECTIONS) {
      const resp = await fetch(`${BASE}/generate`, {
        method: "POST", headers: HEADERS,
        body: JSON.stringify({
          prompt:
            `Write the '${section.replace(/_/g, " ")}' section of the ${PLAN_YEAR} ` +
            `annual marketing plan for ${COMPANY.name}.\n\n${context}`,
          workspace_id: WORKSPACE_ID,
          max_tokens: 2000,
        }),
      }).then((r) => r.json());

      if (resp.error) throw new Error(`Generate failed for ${section}: ${resp.error.message}`);
      planSections[section] = resp.content || resp.text || "";
      console.log(`✓ Generated: ${section.replace(/_/g, " ")}`);
      await new Promise((r) => setTimeout(r, 2000));
    }

    return planSections;
  }
  ```
</CodeGroup>

***

### Stage 6 — Speak Validation Conversations

Run live voice conversations with key personas to stress-test the plan.

<CodeGroup>
  ```python Python theme={"dark"}
  SPEAK_QUESTIONS = [
      f"I'm going to share our top strategic theme for {PLAN_YEAR}. Tell me honestly — does this resonate with your needs?",
      "What's missing from this plan? What would make you more excited about our direction?",
      "If you had to bet, would this plan make you increase your investment with us, keep it the same, or decrease it?",
      "What's the one thing we could do next year that would make the biggest difference for you?",
  ]


  def run_speak_conversations(persona_ids: list[str], plan_summary: str) -> list[dict]:
      """Run Speak conversations with each persona to validate the plan."""
      conversations = []

      for pid in persona_ids[:3]:  # Top 3 personas for voice validation
          resp = requests.post(
              f"{BASE}/speak/conversations",
              headers=HEADERS,
              json={
                  "persona_id": pid,
                  "workspace_id": WORKSPACE_ID,
                  "context": (
                      f"You are reviewing the {PLAN_YEAR} annual marketing plan for {COMPANY['name']}. "
                      f"Here is the plan summary:\n\n{plan_summary}"
                  ),
                  "initial_message": SPEAK_QUESTIONS[0],
              },
          ).json()

          if "error" in resp:
              print(f"  Warning: Speak failed for persona {pid}: {resp['error']['message']}")
              continue

          conversation_id = resp.get("id")
          conversations.append({
              "persona_id": pid,
              "conversation_id": conversation_id,
              "initial_response": resp.get("response", ""),
          })
          print(f"✓ Speak conversation started: {conversation_id}")

          # Follow-up questions
          for question in SPEAK_QUESTIONS[1:]:
              time.sleep(3)
              follow_up = requests.post(
                  f"{BASE}/speak/conversations/{conversation_id}/messages",
                  headers=HEADERS,
                  json={"message": question},
              ).json()

              if "error" not in follow_up:
                  conversations[-1][f"response_{question[:30]}"] = follow_up.get("response", "")

          time.sleep(2)

      print(f"✓ Completed {len(conversations)} Speak conversations")
      return conversations
  ```

  ```javascript JavaScript theme={"dark"}
  const SPEAK_QUESTIONS = [
    `I'm sharing our top strategic theme for ${PLAN_YEAR}. Does this resonate with your needs?`,
    "What's missing from this plan? What would make you more excited?",
    "Would this plan make you increase, maintain, or decrease your investment with us?",
    "What's the one thing we could do next year that would matter most to you?",
  ];

  async function runSpeakConversations(personaIds, planSummary) {
    const conversations = [];

    for (const pid of personaIds.slice(0, 3)) {
      const resp = await fetch(`${BASE}/speak/conversations`, {
        method: "POST", headers: HEADERS,
        body: JSON.stringify({
          persona_id: pid, workspace_id: WORKSPACE_ID,
          context:
            `Reviewing the ${PLAN_YEAR} annual plan for ${COMPANY.name}.\n\n${planSummary}`,
          initial_message: SPEAK_QUESTIONS[0],
        }),
      }).then((r) => r.json());

      if (resp.error) { console.warn(`Speak failed: ${resp.error.message}`); continue; }

      const conv = { persona_id: pid, conversation_id: resp.id, initial_response: resp.response || "" };

      for (const q of SPEAK_QUESTIONS.slice(1)) {
        await new Promise((r) => setTimeout(r, 3000));
        const followUp = await fetch(`${BASE}/speak/conversations/${resp.id}/messages`, {
          method: "POST", headers: HEADERS,
          body: JSON.stringify({ message: q }),
        }).then((r) => r.json());
        if (!followUp.error) conv[`response_${q.slice(0, 30)}`] = followUp.response || "";
      }

      conversations.push(conv);
      console.log(`✓ Speak conversation: ${resp.id}`);
      await new Promise((r) => setTimeout(r, 2000));
    }

    return conversations;
  }
  ```
</CodeGroup>

***

### Running the Full Pipeline

<CodeGroup>
  ```python Python theme={"dark"}
  def run_annual_planning():
      print("=" * 60)
      print(f"{PLAN_YEAR} ANNUAL PLANNING KICKOFF")
      print("=" * 60)

      # Stage 1: CRM Analysis
      print("\n--- Stage 1: CRM Data Analysis ---")
      crm_analysis = analyze_crm_data(SAMPLE_CRM_DATA)

      # Stage 2: Create Personas
      print("\n--- Stage 2: Creating Personas from CRM Data ---")
      persona_descs = generate_persona_descriptions(crm_analysis)
      persona_ids = create_personas_from_descriptions(persona_descs)

      # Stage 3: Theme Focus Group
      print("\n--- Stage 3: Theme Validation Focus Group ---")
      fg = run_theme_focus_group(persona_ids)
      fg_results = poll_focus_group(fg["id"])

      # Stage 4: Market Research
      print("\n--- Stage 4: Mave Market Research ---")
      market_research = research_market_context()

      # Stage 5: Generate Plan
      print("\n--- Stage 5: Generating Annual Plan ---")
      plan_sections = generate_annual_plan(crm_analysis, fg_results, market_research)

      # Compile plan document
      plan_doc = f"# {COMPANY['name']} — {PLAN_YEAR} Annual Marketing Plan\n\n"
      for section, content in plan_sections.items():
          plan_doc += f"## {section.replace('_', ' ').title()}\n\n{content}\n\n---\n\n"

      with open(f"annual_plan_{PLAN_YEAR}.md", "w") as f:
          f.write(plan_doc)
      print(f"\n✓ Draft plan saved to annual_plan_{PLAN_YEAR}.md")

      # Stage 6: Speak Validation
      print("\n--- Stage 6: Speak Validation Conversations ---")
      plan_summary = plan_sections.get("executive_summary", plan_doc[:2000])
      speak_results = run_speak_conversations(persona_ids, plan_summary)

      # Save everything
      output = {
          "crm_analysis": crm_analysis,
          "personas": persona_descs,
          "persona_ids": persona_ids,
          "theme_validation": {
              "results": fg_results.get("results", []),
          },
          "market_research": {
              label: data["content"][:500]
              for label, data in market_research["research"].items()
          },
          "plan_sections": list(plan_sections.keys()),
          "speak_conversations": len(speak_results),
      }

      with open(f"planning_data_{PLAN_YEAR}.json", "w") as f:
          json.dump(output, f, indent=2)

      print(f"\n{'='*60}")
      print(f"{PLAN_YEAR} ANNUAL PLAN — COMPLETE")
      print(f"{'='*60}")
      print(f"  Plan document: annual_plan_{PLAN_YEAR}.md")
      print(f"  Supporting data: planning_data_{PLAN_YEAR}.json")
      print(f"  Personas created: {len(persona_ids)}")
      print(f"  Themes validated: {len(CANDIDATE_THEMES)}")
      print(f"  Plan sections: {len(plan_sections)}")
      print(f"  Speak conversations: {len(speak_results)}")

      return plan_sections


  if __name__ == "__main__":
      run_annual_planning()
  ```

  ```javascript JavaScript theme={"dark"}
  async function runAnnualPlanning() {
    console.log(`${PLAN_YEAR} ANNUAL PLANNING KICKOFF`);

    // Stage 1
    console.log("\n--- Stage 1: CRM Data Analysis ---");
    const crmAnalysis = analyzeCrmData(SAMPLE_CRM_DATA);

    // Stage 2
    console.log("\n--- Stage 2: Creating Personas ---");
    const personaDescs = await generatePersonaDescriptions(crmAnalysis);
    const personaIds = await createPersonasFromDescriptions(personaDescs);

    // Stage 3
    console.log("\n--- Stage 3: Theme Validation ---");
    const fg = await runThemeFocusGroup(personaIds);
    const fgResults = await pollFocusGroup(fg.id);

    // Stage 4
    console.log("\n--- Stage 4: Market Research ---");
    const marketResearch = await researchMarketContext();

    // Stage 5
    console.log("\n--- Stage 5: Generating Plan ---");
    const planSections = await generateAnnualPlan(crmAnalysis, fgResults, marketResearch);

    let planDoc = `# ${COMPANY.name} — ${PLAN_YEAR} Annual Marketing Plan\n\n`;
    for (const [section, content] of Object.entries(planSections)) {
      planDoc += `## ${section.replace(/_/g, " ")}\n\n${content}\n\n---\n\n`;
    }
    fs.writeFileSync(`annual_plan_${PLAN_YEAR}.md`, planDoc);

    // Stage 6
    console.log("\n--- Stage 6: Speak Validation ---");
    const summary = planSections.executive_summary || planDoc.slice(0, 2000);
    const speakResults = await runSpeakConversations(personaIds, summary);

    console.log(`\n${PLAN_YEAR} ANNUAL PLAN — COMPLETE`);
    console.log(`  Plan: annual_plan_${PLAN_YEAR}.md`);
    console.log(`  Sections: ${Object.keys(planSections).length}`);
    console.log(`  Speak conversations: ${speakResults.length}`);

    return planSections;
  }

  runAnnualPlanning();
  ```
</CodeGroup>

***

## Example Output

The pipeline produces a full plan document. Here's a sample executive summary section:

```markdown theme={"dark"}
## Executive Summary

Acme's 2027 marketing plan is built on three validated strategic themes,
persona-validated by 5 customer segments representing 280 active accounts.

**Theme Priority (Focus Group Ranking, N=30):**
1. Integration Ecosystem (ranked #1 by 40% of respondents)
2. Product-Led Growth (ranked #1 by 27%)
3. Content & Thought Leadership (ranked #1 by 20%)

**Key Metrics Targets:**
- ARR: $4.2M → $9.5M (126% growth)
- Customers: 280 → 600
- Net Revenue Retention: 115% → 130%
- CAC Payback: 12 months → 9 months

**Budget Allocation:**
- Product-Led Growth: 35% ($1.2M)
- Integration Ecosystem: 25% ($875K)
- Content & Thought Leadership: 20% ($700K)
- Enterprise Expansion: 15% ($525K)
- International: 5% ($175K)

**Validated by:** 5 CRM-derived personas across enterprise, mid-market,
agency, startup, and churned segments. 3 Speak conversations confirmed
integration ecosystem as the top priority across all segments.
```

***

## Variations

<AccordionGroup>
  <Accordion title="Multiple scenarios">
    Run the pipeline 3 times with different theme priorities (aggressive, balanced, conservative):

    ```python theme={"dark"}
    scenarios = {
        "aggressive": {"budget_multiplier": 1.5, "growth_target": "150%"},
        "balanced": {"budget_multiplier": 1.0, "growth_target": "100%"},
        "conservative": {"budget_multiplier": 0.7, "growth_target": "60%"},
    }
    for name, params in scenarios.items():
        COMPANY["growth_target"] = params["growth_target"]
        plan = run_annual_planning()
    ```
  </Accordion>

  <Accordion title="Real CRM data instead of sample">
    Replace `SAMPLE_CRM_DATA` with a real CSV export:

    ```python theme={"dark"}
    def load_crm_csv(path: str) -> list[dict]:
        with open(path) as f:
            return list(csv.DictReader(f))

    crm_data = load_crm_csv("crm_export.csv")
    crm_analysis = analyze_crm_data(crm_data)
    ```
  </Accordion>

  <Accordion title="Skip Speak for faster execution">
    If you don't need voice validation, skip Stage 6. The plan is still validated by Focus Groups and market research:

    ```python theme={"dark"}
    # Comment out Stage 6
    # speak_results = run_speak_conversations(persona_ids, plan_summary)
    ```
  </Accordion>

  <Accordion title="Department-specific plans">
    After the company-level plan, generate department-specific versions:

    ```python theme={"dark"}
    departments = ["demand_gen", "product_marketing", "content", "brand"]
    for dept in departments:
        resp = requests.post(f"{BASE}/generate", headers=HEADERS, json={
            "prompt": f"Extract the {dept} team's plan from the company plan: {plan_doc[:3000]}",
            "workspace_id": WORKSPACE_ID,
        })
    ```
  </Accordion>
</AccordionGroup>

***

## Credits Estimate

| Stage                                  | Typical Cost          | Notes                         |
| -------------------------------------- | --------------------- | ----------------------------- |
| CRM analysis (local)                   | 0 credits             | Runs locally                  |
| Generate persona descriptions (1 chat) | 5–15 credits          |                               |
| Create 5 personas                      | 0                     | Persona creation is free      |
| Focus Group (N=30, 5 questions)        | 150–300 credits       | Theme validation              |
| Mave research (3 turns)                | 30–90 credits         | Market context                |
| Generate plan (9 sections)             | 90–180 credits        | One Generate call per section |
| Speak conversations (3 personas)       | 30–90 credits         | Optional voice validation     |
| **Total (full pipeline)**              | **\~305–675 credits** |                               |
| **Total (without Speak)**              | **\~275–585 credits** |                               |

<Warning>
  This is the most credit-intensive playbook. Run a quick version first (N=15 focus group, 3 plan sections, no Speak) to validate the pipeline before committing to the full run.
</Warning>

***

## See Also

<CardGroup cols={2}>
  <Card title="Market Entry Research" icon="compass" href="/playbooks/market-entry-research">
    Deep market research for new opportunities
  </Card>

  <Card title="Positioning Workshop" icon="bullseye" href="/playbooks/positioning-workshop">
    Validate positioning as part of planning
  </Card>

  <Card title="Pricing Research" icon="tags" href="/playbooks/pricing-research">
    Test pricing alongside strategic planning
  </Card>

  <Card title="Brand Perception Audit" icon="chart-pie" href="/playbooks/brand-perception-audit">
    Measure brand health before planning
  </Card>

  <Card title="News-Triggered Research" icon="bolt" href="/playbooks/news-triggered-research">
    Monitor market changes during plan execution
  </Card>

  <Card title="Focus Groups" icon="users" href="/features/focus-groups">
    Question types and configuration
  </Card>
</CardGroup>
