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

# Pricing Research

> Run Van Westendorp-style price sensitivity analysis with persona segments using Focus Group Slider questions

## Mavera Surfaces Used

| Surface                                          | Role                                                                        |
| ------------------------------------------------ | --------------------------------------------------------------------------- |
| **Personas** (`POST /personas`, `GET /personas`) | Create budget-segment personas (price-sensitive, mid-market, enterprise)    |
| **Focus Groups** (`POST /focus-groups`)          | Run pricing sensitivity study with Slider, Likert, and Open-Ended questions |
| **Chat + `response_format`**                     | Synthesize results into a price sensitivity report with optimal price range |

<Info>
  The Van Westendorp Price Sensitivity Meter uses four questions to find optimal pricing. This playbook adapts those questions to Mavera's Slider question type, running them across budget-segment personas to produce a synthetic price sensitivity analysis.
</Info>

***

## What Value Does Mavera Add?

| Value                 | How                                                                                                               |
| --------------------- | ----------------------------------------------------------------------------------------------------------------- |
| **Insurance**         | Test pricing with 4 distinct budget segments before announcing. Catch price-perception mismatches early.          |
| **Opening new doors** | Run pricing studies for new features, bundles, or market segments in an afternoon — no recruiting, no incentives. |
| **Saving time**       | Traditional Van Westendorp studies take 2-4 weeks and \$10K+. This runs in 30 minutes for \~300 credits.          |

***

## When to Use This

* You're setting launch pricing for a new product or feature and need segment-specific sensitivity data.
* You're considering a price increase and want to estimate the elasticity before announcing.
* You're designing tiered pricing and need to validate where tier boundaries should fall.
* You're entering a new market and need a pricing anchor based on local buyer expectations.

***

## 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_...`).                                                                  |
| **Product description**            | Clear description of what's being priced (features, value prop, competitive context).                |
| **Price range**                    | Reasonable min/max for the Slider questions (e.g. $0-$500/month).                                    |
| **Credits**                        | \~200–500 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
```

***

## The Van Westendorp Framework

The original Van Westendorp asks four questions. Mavera adapts each to a Slider question type:

| Van Westendorp Question                                                         | Mavera Slider     | What It Finds             |
| ------------------------------------------------------------------------------- | ----------------- | ------------------------- |
| "At what price would this be **too expensive** — you wouldn't consider buying?" | Slider: $0 – $max | Upper bound (ceiling)     |
| "At what price would this be **expensive but worth considering**?"              | Slider: $0 – $max | High-end acceptable range |
| "At what price would this be a **great deal** — a bargain?"                     | Slider: $0 – $max | Low-end sweet spot        |
| "At what price would this be **so cheap you'd question the quality**?"          | Slider: $0 – $max | Lower bound (floor)       |

The intersections of these four curves define:

* **Point of Marginal Cheapness (PMC):** Where "too cheap" meets "bargain"
* **Point of Marginal Expensiveness (PME):** Where "too expensive" meets "expensive but worth it"
* **Optimal Price Point (OPP):** Where "too cheap" meets "too expensive"
* **Acceptable Price Range:** Between PMC and PME

***

## Architecture

```mermaid theme={"dark"}
flowchart TD
    subgraph personaSetup["Persona Setup"]
        bootstrapped["Bootstrapped Startup"] --> personaIds[("4 Budget-Segment Personas")]
        growthStage["Growth-Stage Lead"] --> personaIds
        midMarket["Mid-Market VP"] --> personaIds
        enterprise["Enterprise CMO"] --> personaIds
    end

    subgraph vwFocusGroup["Van Westendorp Focus Group"]
        personaIds --> focusGroup["Focus Group: N=40, 6 Questions"]
        focusGroup --> tooExpensive["Slider: Too Expensive"]
        focusGroup --> expensiveWorth["Slider: Expensive but Worth It"]
        focusGroup --> bargainSlider["Slider: Bargain"]
        focusGroup --> tooCheap["Slider: Too Cheap"]
    end

    subgraph analysis["Price Sensitivity Analysis"]
        tooExpensive --> parseResponses["Parse Slider Responses"]
        expensiveWorth --> parseResponses
        bargainSlider --> parseResponses
        tooCheap --> parseResponses
        parseResponses --> calcIntersections["Calculate VW Intersection Points"]
        calcIntersections --> pmc[("PMC: Marginal Cheapness")]
        calcIntersections --> opp[("OPP: Optimal Price Point")]
        calcIntersections --> pme[("PME: Marginal Expensiveness")]
    end

    subgraph pricingSynthesis["Synthesis"]
        pmc --> chatSynth["Chat: response_format"]
        opp --> chatSynth
        pme --> chatSynth
        chatSynth --> report(("Price Sensitivity Report with Optimal Range"))
    end
```

***

## The Flow

<Steps>
  <Step title="Define your product and price range">
    Write a clear product description and set a realistic price range for the Slider min/max. Too narrow misses the range; too wide reduces precision.
  </Step>

  <Step title="Create 4 budget-segment personas">
    Build personas representing distinct budget tiers: bootstrapped startup, growth-stage, mid-market, and enterprise. Each brings a different price sensitivity.
  </Step>

  <Step title="Run the pricing Focus Group">
    40 respondents across 4 personas (10 per persona). 4 Van Westendorp Slider questions + 1 Likert (value-for-money) + 1 Open-Ended (pricing model preference).
  </Step>

  <Step title="Poll for completion">
    Pricing focus groups with 40 respondents and 6 questions typically complete in 5-10 minutes.
  </Step>

  <Step title="Calculate price sensitivity curves">
    Extract Slider medians and distributions. Compute the four intersection points.
  </Step>

  <Step title="Generate the pricing report">
    Use Chat with structured output to synthesize findings into a pricing recommendation with segment breakdowns.
  </Step>
</Steps>

***

## Code: Full Pricing Research Pipeline

### Setup

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

  MAVERA_API_KEY = os.environ["MAVERA_API_KEY"]
  WORKSPACE_ID = os.environ["MAVERA_WORKSPACE_ID"]
  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)

  PRODUCT_DESCRIPTION = (
      "An AI-powered market research platform that lets you create synthetic audience "
      "personas and run focus groups, pricing studies, and message testing in minutes. "
      "Replaces traditional research agencies for positioning, pricing, and campaign validation. "
      "Includes: unlimited personas, focus groups (up to N=100), AI research agent, "
      "video analysis, and content generation."
  )

  PRICE_RANGE = {"min": 0, "max": 500, "unit": "$/month"}

  BUDGET_PERSONAS = [
      {
          "name": "Bootstrapped Startup Founder",
          "description": (
              "Solo founder or 2-person team at a pre-revenue startup. "
              "Total marketing budget: $500-$2K/month. Every dollar is scrutinized. "
              "Uses free tools wherever possible. Will pay for tools that directly "
              "generate revenue or save significant time. Compares against doing it manually."
          ),
      },
      {
          "name": "Growth-Stage Marketing Lead",
          "description": (
              "Head of Marketing at a Series A/B startup (20-80 employees). "
              "Marketing budget: $10K-$50K/month. Has budget flexibility but needs to justify "
              "ROI for each tool. Currently spending $5-15K/quarter on ad hoc research. "
              "Evaluates based on cost-per-insight vs traditional agencies."
          ),
      },
      {
          "name": "Mid-Market VP Marketing",
          "description": (
              "VP of Marketing at a $10M-$50M ARR company (100-500 employees). "
              "Marketing budget: $50K-$200K/month. Has a research budget line item. "
              "Currently uses a mix of agencies ($20-50K/project) and DIY surveys. "
              "Values speed and always-on availability over cost savings."
          ),
      },
      {
          "name": "Enterprise CMO",
          "description": (
              "CMO at a $100M+ company with a dedicated insights team. "
              "Research budget: $500K-$2M/year. Uses top-tier agencies for major projects. "
              "Evaluates platforms on capability depth, security, and integration with "
              "existing insights stack. Price is secondary to reliability and scalability."
          ),
      },
  ]
  ```

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

  const MAVERA_API_KEY = process.env.MAVERA_API_KEY;
  const WORKSPACE_ID = process.env.MAVERA_WORKSPACE_ID;
  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 PRODUCT_DESCRIPTION =
    "An AI-powered market research platform that lets you create synthetic audience " +
    "personas and run focus groups, pricing studies, and message testing in minutes. " +
    "Replaces traditional research agencies for positioning, pricing, and campaign validation. " +
    "Includes: unlimited personas, focus groups (up to N=100), AI research agent, " +
    "video analysis, and content generation.";

  const PRICE_RANGE = { min: 0, max: 500, unit: "$/month" };

  const BUDGET_PERSONAS = [
    {
      name: "Bootstrapped Startup Founder",
      description:
        "Solo founder or 2-person team at a pre-revenue startup. " +
        "Total marketing budget: $500-$2K/month. Every dollar is scrutinized. " +
        "Uses free tools wherever possible. Compares against doing it manually.",
    },
    {
      name: "Growth-Stage Marketing Lead",
      description:
        "Head of Marketing at a Series A/B startup (20-80 employees). " +
        "Marketing budget: $10K-$50K/month. Has budget flexibility but needs to justify " +
        "ROI for each tool. Currently spending $5-15K/quarter on ad hoc research.",
    },
    {
      name: "Mid-Market VP Marketing",
      description:
        "VP of Marketing at a $10M-$50M ARR company (100-500 employees). " +
        "Marketing budget: $50K-$200K/month. Has a research budget line item. " +
        "Values speed and always-on availability over cost savings.",
    },
    {
      name: "Enterprise CMO",
      description:
        "CMO at a $100M+ company with a dedicated insights team. " +
        "Research budget: $500K-$2M/year. Uses top-tier agencies for major projects. " +
        "Price is secondary to reliability and scalability.",
    },
  ];
  ```
</CodeGroup>

***

### Stage 1 — Create Budget-Segment Personas

<CodeGroup>
  ```python Python theme={"dark"}
  def create_budget_personas() -> list[str]:
      """Create 4 budget-segment personas and return their IDs."""
      persona_ids = []

      for persona in BUDGET_PERSONAS:
          resp = requests.post(
              f"{BASE}/personas",
              headers=HEADERS,
              json={
                  "name": persona["name"],
                  "description": persona["description"],
                  "workspace_id": WORKSPACE_ID,
              },
          ).json()

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

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

      return persona_ids
  ```

  ```javascript JavaScript theme={"dark"}
  async function createBudgetPersonas() {
    const personaIds = [];

    for (const persona of BUDGET_PERSONAS) {
      const resp = await fetch(`${BASE}/personas`, {
        method: "POST",
        headers: HEADERS,
        body: JSON.stringify({
          name: persona.name,
          description: persona.description,
          workspace_id: WORKSPACE_ID,
        }),
      }).then((r) => r.json());

      if (resp.error)
        throw new Error(`Failed to create persona '${persona.name}': ${resp.error.message}`);

      personaIds.push(resp.id);
      console.log(`✓ Created persona: ${persona.name} (${resp.id})`);
    }

    return personaIds;
  }
  ```
</CodeGroup>

***

### Stage 2 — Run Pricing Focus Group

Six questions: 4 Van Westendorp Sliders, 1 Likert (value-for-money), 1 Open-Ended (pricing model).

<CodeGroup>
  ```python Python theme={"dark"}
  def run_pricing_focus_group(persona_ids: list[str]) -> dict:
      """Run the Van Westendorp pricing focus group."""
      product_context = (
          f"Product: {PRODUCT_DESCRIPTION}\n\n"
          f"Pricing is per month ({PRICE_RANGE['unit']}). "
          f"Consider the full feature set when answering."
      )

      payload = {
          "name": "Pricing Research — Van Westendorp × 4 Segments",
          "sample_size": 40,
          "persona_ids": persona_ids,
          "workspace_id": WORKSPACE_ID,
          "context": product_context,
          "questions": [
              {
                  "question": (
                      f"At what price per month would this product be SO EXPENSIVE "
                      f"that you would not consider buying it, regardless of quality?\n\n"
                      f"Product: {PRODUCT_DESCRIPTION}"
                  ),
                  "type": "SLIDER",
                  "min_value": PRICE_RANGE["min"],
                  "max_value": PRICE_RANGE["max"],
                  "min_label": f"${PRICE_RANGE['min']}/mo",
                  "max_label": f"${PRICE_RANGE['max']}/mo",
                  "order": 1,
              },
              {
                  "question": (
                      f"At what price per month would this product be EXPENSIVE "
                      f"but you would still consider buying it because of its value?"
                  ),
                  "type": "SLIDER",
                  "min_value": PRICE_RANGE["min"],
                  "max_value": PRICE_RANGE["max"],
                  "min_label": f"${PRICE_RANGE['min']}/mo",
                  "max_label": f"${PRICE_RANGE['max']}/mo",
                  "order": 2,
              },
              {
                  "question": (
                      f"At what price per month would this product be a BARGAIN — "
                      f"a great deal for what you get?"
                  ),
                  "type": "SLIDER",
                  "min_value": PRICE_RANGE["min"],
                  "max_value": PRICE_RANGE["max"],
                  "min_label": f"${PRICE_RANGE['min']}/mo",
                  "max_label": f"${PRICE_RANGE['max']}/mo",
                  "order": 3,
              },
              {
                  "question": (
                      f"At what price per month would this product be SO CHEAP that "
                      f"you would question its quality and reliability?"
                  ),
                  "type": "SLIDER",
                  "min_value": PRICE_RANGE["min"],
                  "max_value": PRICE_RANGE["max"],
                  "min_label": f"${PRICE_RANGE['min']}/mo",
                  "max_label": f"${PRICE_RANGE['max']}/mo",
                  "order": 4,
              },
              {
                  "question": (
                      "If this product were priced at $149/month, would it represent "
                      "good value for money? Rate 1-10."
                  ),
                  "type": "LIKERT",
                  "scale": 10,
                  "order": 5,
              },
              {
                  "question": (
                      "Which pricing model would you prefer for this type of product?\n"
                      "- Flat monthly fee (unlimited usage)\n"
                      "- Usage-based (pay per focus group / research query)\n"
                      "- Tiered plans (starter / pro / enterprise)\n"
                      "- Annual contract with monthly billing\n\n"
                      "Explain your reasoning."
                  ),
                  "type": "OPEN_ENDED",
                  "order": 6,
              },
          ],
      }

      resp = requests.post(
          f"{BASE}/focus-groups",
          headers=HEADERS,
          json=payload,
      ).json()

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

      print(f"✓ Pricing focus group created: {resp['id']}")
      print(f"  Sample size: 40 (10 per segment)")
      print(f"  Questions: 6 (4 sliders + 1 likert + 1 open-ended)")
      return resp


  def poll_focus_group(fg_id: str, timeout_min: int = 15) -> dict:
      """Poll until the focus group completes."""
      for attempt 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(f"✓ Pricing focus group completed")
              return resp
          if resp.get("status") == "FAILED":
              raise Exception(f"Focus group failed: {resp.get('error', 'Unknown')}")

          time.sleep(10)

      raise TimeoutError(f"Focus group {fg_id} timed out")
  ```

  ```javascript JavaScript theme={"dark"}
  async function runPricingFocusGroup(personaIds) {
    const productContext =
      `Product: ${PRODUCT_DESCRIPTION}\n\n` +
      `Pricing is per month (${PRICE_RANGE.unit}). Consider the full feature set.`;

    const payload = {
      name: "Pricing Research — Van Westendorp × 4 Segments",
      sample_size: 40,
      persona_ids: personaIds,
      workspace_id: WORKSPACE_ID,
      context: productContext,
      questions: [
        {
          question:
            `At what price per month would this product be SO EXPENSIVE ` +
            `that you would not consider buying it, regardless of quality?\n\n` +
            `Product: ${PRODUCT_DESCRIPTION}`,
          type: "SLIDER",
          min_value: PRICE_RANGE.min, max_value: PRICE_RANGE.max,
          min_label: `$${PRICE_RANGE.min}/mo`, max_label: `$${PRICE_RANGE.max}/mo`,
          order: 1,
        },
        {
          question:
            `At what price per month would this product be EXPENSIVE ` +
            `but you would still consider buying it because of its value?`,
          type: "SLIDER",
          min_value: PRICE_RANGE.min, max_value: PRICE_RANGE.max,
          min_label: `$${PRICE_RANGE.min}/mo`, max_label: `$${PRICE_RANGE.max}/mo`,
          order: 2,
        },
        {
          question:
            `At what price per month would this product be a BARGAIN — ` +
            `a great deal for what you get?`,
          type: "SLIDER",
          min_value: PRICE_RANGE.min, max_value: PRICE_RANGE.max,
          min_label: `$${PRICE_RANGE.min}/mo`, max_label: `$${PRICE_RANGE.max}/mo`,
          order: 3,
        },
        {
          question:
            `At what price per month would this product be SO CHEAP that ` +
            `you would question its quality and reliability?`,
          type: "SLIDER",
          min_value: PRICE_RANGE.min, max_value: PRICE_RANGE.max,
          min_label: `$${PRICE_RANGE.min}/mo`, max_label: `$${PRICE_RANGE.max}/mo`,
          order: 4,
        },
        {
          question:
            "If this product were priced at $149/month, would it represent " +
            "good value for money? Rate 1-10.",
          type: "LIKERT", scale: 10, order: 5,
        },
        {
          question:
            "Which pricing model would you prefer for this type of product?\n" +
            "- Flat monthly fee (unlimited usage)\n" +
            "- Usage-based (pay per focus group / research query)\n" +
            "- Tiered plans (starter / pro / enterprise)\n" +
            "- Annual contract with monthly billing\n\n" +
            "Explain your reasoning.",
          type: "OPEN_ENDED", order: 6,
        },
      ],
    };

    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(`✓ Pricing focus group created: ${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") {
        console.log("✓ Pricing focus group completed");
        return resp;
      }
      if (resp.status === "FAILED")
        throw new Error(`Focus group failed: ${resp.error || "Unknown"}`);

      await new Promise((r) => setTimeout(r, 10000));
    }
    throw new Error(`Focus group ${fgId} timed out`);
  }
  ```
</CodeGroup>

<Warning>
  Set Slider `max_value` high enough to capture the true ceiling. If most "too expensive" responses cluster at the max, your range is too narrow and you'll miss price points.
</Warning>

***

### Stage 3 — Calculate Price Sensitivity

Extract Slider values and compute the Van Westendorp intersection points.

<CodeGroup>
  ```python Python theme={"dark"}
  VW_QUESTION_MAP = {
      "too_expensive": "SO EXPENSIVE",
      "expensive_worth_it": "EXPENSIVE but you would still consider",
      "bargain": "BARGAIN",
      "too_cheap": "SO CHEAP",
  }


  def extract_slider_data(fg_results: dict) -> dict:
      """Extract Van Westendorp slider responses from focus group results."""
      slider_data = {}

      for result in fg_results.get("results", []):
          if result.get("type") != "SLIDER":
              continue

          question = result.get("question", "")
          for key, marker in VW_QUESTION_MAP.items():
              if marker in question:
                  slider_data[key] = {
                      "values": result.get("values", []),
                      "mean": result.get("mean_value", 0),
                      "median": result.get("median_value", 0),
                      "std_dev": result.get("std_dev", 0),
                      "distribution": result.get("distribution", {}),
                      "per_persona": result.get("per_persona", []),
                  }
                  break

      return slider_data


  def calculate_vw_points(slider_data: dict) -> dict:
      """Calculate Van Westendorp intersection points from slider medians."""
      too_expensive = slider_data.get("too_expensive", {}).get("median", 0)
      expensive_ok = slider_data.get("expensive_worth_it", {}).get("median", 0)
      bargain = slider_data.get("bargain", {}).get("median", 0)
      too_cheap = slider_data.get("too_cheap", {}).get("median", 0)

      # Approximate intersection points
      pmc = (too_cheap + bargain) / 2       # Point of Marginal Cheapness
      pme = (too_expensive + expensive_ok) / 2  # Point of Marginal Expensiveness
      opp = (too_cheap + too_expensive) / 2  # Optimal Price Point
      idp = (bargain + expensive_ok) / 2     # Indifference Price Point

      return {
          "point_of_marginal_cheapness": round(pmc, 2),
          "point_of_marginal_expensiveness": round(pme, 2),
          "optimal_price_point": round(opp, 2),
          "indifference_price_point": round(idp, 2),
          "acceptable_range": {
              "low": round(pmc, 2),
              "high": round(pme, 2),
          },
          "raw_medians": {
              "too_expensive": too_expensive,
              "expensive_worth_it": expensive_ok,
              "bargain": bargain,
              "too_cheap": too_cheap,
          },
      }


  def segment_analysis(slider_data: dict) -> dict:
      """Break down pricing by persona segment."""
      segments = {}

      for key, data in slider_data.items():
          for persona_result in data.get("per_persona", []):
              pid = persona_result.get("persona_id", "unknown")
              if pid not in segments:
                  segments[pid] = {}
              segments[pid][key] = {
                  "mean": persona_result.get("mean_value", 0),
                  "median": persona_result.get("median_value", 0),
              }

      # Calculate per-segment VW points
      for pid, data in segments.items():
          te = data.get("too_expensive", {}).get("median", 0)
          eo = data.get("expensive_worth_it", {}).get("median", 0)
          bg = data.get("bargain", {}).get("median", 0)
          tc = data.get("too_cheap", {}).get("median", 0)

          segments[pid]["optimal_price"] = round((tc + te) / 2, 2)
          segments[pid]["acceptable_range"] = {
              "low": round((tc + bg) / 2, 2),
              "high": round((te + eo) / 2, 2),
          }

      return segments
  ```

  ```javascript JavaScript theme={"dark"}
  const VW_QUESTION_MAP = {
    too_expensive: "SO EXPENSIVE",
    expensive_worth_it: "EXPENSIVE but you would still consider",
    bargain: "BARGAIN",
    too_cheap: "SO CHEAP",
  };

  function extractSliderData(fgResults) {
    const sliderData = {};

    for (const result of fgResults.results || []) {
      if (result.type !== "SLIDER") continue;

      for (const [key, marker] of Object.entries(VW_QUESTION_MAP)) {
        if (result.question?.includes(marker)) {
          sliderData[key] = {
            values: result.values || [],
            mean: result.mean_value || 0,
            median: result.median_value || 0,
            std_dev: result.std_dev || 0,
            distribution: result.distribution || {},
            per_persona: result.per_persona || [],
          };
          break;
        }
      }
    }

    return sliderData;
  }

  function calculateVWPoints(sliderData) {
    const te = sliderData.too_expensive?.median || 0;
    const eo = sliderData.expensive_worth_it?.median || 0;
    const bg = sliderData.bargain?.median || 0;
    const tc = sliderData.too_cheap?.median || 0;

    const pmc = (tc + bg) / 2;
    const pme = (te + eo) / 2;
    const opp = (tc + te) / 2;
    const idp = (bg + eo) / 2;

    return {
      point_of_marginal_cheapness: Math.round(pmc * 100) / 100,
      point_of_marginal_expensiveness: Math.round(pme * 100) / 100,
      optimal_price_point: Math.round(opp * 100) / 100,
      indifference_price_point: Math.round(idp * 100) / 100,
      acceptable_range: {
        low: Math.round(pmc * 100) / 100,
        high: Math.round(pme * 100) / 100,
      },
      raw_medians: { too_expensive: te, expensive_worth_it: eo, bargain: bg, too_cheap: tc },
    };
  }

  function segmentAnalysis(sliderData) {
    const segments = {};

    for (const [key, data] of Object.entries(sliderData)) {
      for (const pr of data.per_persona || []) {
        const pid = pr.persona_id || "unknown";
        if (!segments[pid]) segments[pid] = {};
        segments[pid][key] = { mean: pr.mean_value || 0, median: pr.median_value || 0 };
      }
    }

    for (const [pid, data] of Object.entries(segments)) {
      const te = data.too_expensive?.median || 0;
      const eo = data.expensive_worth_it?.median || 0;
      const bg = data.bargain?.median || 0;
      const tc = data.too_cheap?.median || 0;

      segments[pid].optimal_price = Math.round(((tc + te) / 2) * 100) / 100;
      segments[pid].acceptable_range = {
        low: Math.round(((tc + bg) / 2) * 100) / 100,
        high: Math.round(((te + eo) / 2) * 100) / 100,
      };
    }

    return segments;
  }
  ```
</CodeGroup>

***

### Stage 4 — Generate Pricing Report

<CodeGroup>
  ```python Python theme={"dark"}
  PRICING_REPORT_SCHEMA = {"type": "json_schema", "json_schema": {
      "name": "pricing_report", "strict": True,
      "schema": {
          "type": "object",
          "properties": {
              "executive_summary": {"type": "string"},
              "optimal_price_point": {"type": "number"},
              "acceptable_range_low": {"type": "number"},
              "acceptable_range_high": {"type": "number"},
              "recommended_price": {"type": "number"},
              "recommended_pricing_model": {"type": "string"},
              "segment_recommendations": {
                  "type": "array",
                  "items": {
                      "type": "object",
                      "properties": {
                          "segment": {"type": "string"},
                          "optimal_price": {"type": "number"},
                          "willingness_to_pay": {"type": "string"},
                          "pricing_model_preference": {"type": "string"},
                      },
                      "required": ["segment", "optimal_price", "willingness_to_pay", "pricing_model_preference"],
                  },
              },
              "tier_suggestions": {
                  "type": "array",
                  "items": {
                      "type": "object",
                      "properties": {
                          "tier_name": {"type": "string"},
                          "price_point": {"type": "number"},
                          "target_segment": {"type": "string"},
                          "key_features": {"type": "array", "items": {"type": "string"}},
                      },
                      "required": ["tier_name", "price_point", "target_segment", "key_features"],
                  },
              },
              "risks": {
                  "type": "array",
                  "items": {"type": "string"},
              },
              "next_steps": {
                  "type": "array",
                  "items": {"type": "string"},
              },
          },
          "required": [
              "executive_summary", "optimal_price_point", "acceptable_range_low",
              "acceptable_range_high", "recommended_price", "recommended_pricing_model",
              "segment_recommendations", "tier_suggestions", "risks", "next_steps",
          ],
      },
  }}


  def generate_pricing_report(
      vw_points: dict, segments: dict, fg_results: dict
  ) -> dict:
      """Synthesize pricing data into a recommendation report."""
      # Extract Likert and open-ended results
      likert_result = None
      open_ended_result = None
      for result in fg_results.get("results", []):
          if result.get("type") == "LIKERT":
              likert_result = result
          elif result.get("type") == "OPEN_ENDED":
              open_ended_result = result

      prompt = (
          "You are a pricing strategist. Analyze this Van Westendorp pricing data "
          "and produce a pricing recommendation.\n\n"
          f"## Product\n{PRODUCT_DESCRIPTION}\n\n"
          f"## Van Westendorp Results (All Segments)\n{json.dumps(vw_points, indent=2)}\n\n"
          f"## Segment-Level Analysis\n{json.dumps(segments, indent=2)}\n\n"
      )

      if likert_result:
          prompt += f"## Value-for-Money at $149/mo\nMean score: {likert_result.get('mean_score', 'N/A')}/10\n\n"

      if open_ended_result:
          prompt += f"## Pricing Model Preferences\n{open_ended_result.get('summary', 'N/A')}\n\n"

      prompt += (
          "Produce a recommendation with:\n"
          "1. Optimal price point and acceptable range\n"
          "2. Per-segment pricing sensitivity\n"
          "3. Suggested pricing tiers\n"
          "4. Risks of pricing too high or too low\n"
          "5. Next steps for validation"
      )

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

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

  ```javascript JavaScript theme={"dark"}
  const PRICING_REPORT_SCHEMA = { type: "json_schema", json_schema: {
    name: "pricing_report", strict: true,
    schema: {
      type: "object",
      properties: {
        executive_summary: { type: "string" },
        optimal_price_point: { type: "number" },
        acceptable_range_low: { type: "number" },
        acceptable_range_high: { type: "number" },
        recommended_price: { type: "number" },
        recommended_pricing_model: { type: "string" },
        segment_recommendations: {
          type: "array",
          items: {
            type: "object",
            properties: {
              segment: { type: "string" },
              optimal_price: { type: "number" },
              willingness_to_pay: { type: "string" },
              pricing_model_preference: { type: "string" },
            },
            required: ["segment", "optimal_price", "willingness_to_pay", "pricing_model_preference"],
          },
        },
        tier_suggestions: {
          type: "array",
          items: {
            type: "object",
            properties: {
              tier_name: { type: "string" },
              price_point: { type: "number" },
              target_segment: { type: "string" },
              key_features: { type: "array", items: { type: "string" } },
            },
            required: ["tier_name", "price_point", "target_segment", "key_features"],
          },
        },
        risks: { type: "array", items: { type: "string" } },
        next_steps: { type: "array", items: { type: "string" } },
      },
      required: [
        "executive_summary", "optimal_price_point", "acceptable_range_low",
        "acceptable_range_high", "recommended_price", "recommended_pricing_model",
        "segment_recommendations", "tier_suggestions", "risks", "next_steps",
      ],
    },
  }};

  async function generatePricingReport(vwPoints, segments, fgResults) {
    let likertResult = null, openEndedResult = null;
    for (const r of fgResults.results || []) {
      if (r.type === "LIKERT") likertResult = r;
      if (r.type === "OPEN_ENDED") openEndedResult = r;
    }

    let prompt =
      "You are a pricing strategist. Analyze this Van Westendorp pricing data " +
      "and produce a pricing recommendation.\n\n" +
      `## Product\n${PRODUCT_DESCRIPTION}\n\n` +
      `## Van Westendorp Results\n${JSON.stringify(vwPoints, null, 2)}\n\n` +
      `## Segment-Level Analysis\n${JSON.stringify(segments, null, 2)}\n\n`;

    if (likertResult) prompt += `## Value-for-Money at $149/mo\nMean: ${likertResult.mean_score}/10\n\n`;
    if (openEndedResult) prompt += `## Pricing Model Preferences\n${openEndedResult.summary}\n\n`;

    prompt +=
      "Produce a recommendation with optimal price, per-segment sensitivity, " +
      "suggested tiers, risks, and next steps.";

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

    return JSON.parse(resp.output[0].content[0].text);
  }
  ```
</CodeGroup>

***

### Running the Full Pipeline

<CodeGroup>
  ```python Python theme={"dark"}
  def run_pricing_research():
      print("=" * 60)
      print("PRICING RESEARCH — VAN WESTENDORP")
      print("=" * 60)

      # Stage 1: Create personas
      print("\n--- Stage 1: Creating Budget-Segment Personas ---")
      persona_ids = create_budget_personas()

      # Stage 2: Run focus group
      print("\n--- Stage 2: Running Pricing Focus Group ---")
      fg = run_pricing_focus_group(persona_ids)
      fg_results = poll_focus_group(fg["id"])

      # Stage 3: Calculate price sensitivity
      print("\n--- Stage 3: Calculating Price Sensitivity ---")
      slider_data = extract_slider_data(fg_results)
      vw_points = calculate_vw_points(slider_data)
      segments = segment_analysis(slider_data)

      print(f"\nVan Westendorp Results:")
      print(f"  Too cheap (floor):    ${vw_points['raw_medians']['too_cheap']}/mo")
      print(f"  Bargain:              ${vw_points['raw_medians']['bargain']}/mo")
      print(f"  Expensive but OK:     ${vw_points['raw_medians']['expensive_worth_it']}/mo")
      print(f"  Too expensive (ceil): ${vw_points['raw_medians']['too_expensive']}/mo")
      print(f"\n  Optimal Price Point:  ${vw_points['optimal_price_point']}/mo")
      print(f"  Acceptable Range:     ${vw_points['acceptable_range']['low']} – ${vw_points['acceptable_range']['high']}/mo")

      # Stage 4: Generate report
      print("\n--- Stage 4: Generating Pricing Report ---")
      report = generate_pricing_report(vw_points, segments, fg_results)

      print(f"\nRecommended price: ${report['recommended_price']}/mo")
      print(f"Pricing model: {report['recommended_pricing_model']}")
      print(f"\nTier suggestions:")
      for tier in report.get("tier_suggestions", []):
          print(f"  {tier['tier_name']}: ${tier['price_point']}/mo → {tier['target_segment']}")

      # Save outputs
      output = {
          "van_westendorp": vw_points,
          "segments": segments,
          "report": report,
      }
      with open("pricing_research.json", "w") as f:
          json.dump(output, f, indent=2)

      print("\n✓ Saved pricing_research.json")
      return report


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

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

  async function runPricingResearch() {
    console.log("PRICING RESEARCH — VAN WESTENDORP");

    // Stage 1
    console.log("\n--- Stage 1: Creating Budget-Segment Personas ---");
    const personaIds = await createBudgetPersonas();

    // Stage 2
    console.log("\n--- Stage 2: Running Pricing Focus Group ---");
    const fg = await runPricingFocusGroup(personaIds);
    const fgResults = await pollFocusGroup(fg.id);

    // Stage 3
    console.log("\n--- Stage 3: Calculating Price Sensitivity ---");
    const sliderData = extractSliderData(fgResults);
    const vwPoints = calculateVWPoints(sliderData);
    const segments = segmentAnalysis(sliderData);

    console.log(`\nOptimal Price Point: $${vwPoints.optimal_price_point}/mo`);
    console.log(`Acceptable Range: $${vwPoints.acceptable_range.low} – $${vwPoints.acceptable_range.high}/mo`);

    // Stage 4
    console.log("\n--- Stage 4: Generating Pricing Report ---");
    const report = await generatePricingReport(vwPoints, segments, fgResults);

    console.log(`\nRecommended price: $${report.recommended_price}/mo`);
    for (const tier of report.tier_suggestions || []) {
      console.log(`  ${tier.tier_name}: $${tier.price_point}/mo → ${tier.target_segment}`);
    }

    fs.writeFileSync("pricing_research.json", JSON.stringify({ van_westendorp: vwPoints, segments, report }, null, 2));
    console.log("\n✓ Saved pricing_research.json");
    return report;
  }

  runPricingResearch();
  ```
</CodeGroup>

***

## Example Output

```json theme={"dark"}
{
  "executive_summary": "Van Westendorp analysis across 4 budget segments reveals an optimal price point of $129/mo with an acceptable range of $79-$229/mo. Strong segment divergence: bootstrapped founders cap at $49/mo while enterprise CMOs find anything under $99/mo suspiciously cheap. Tiered pricing is the clear winner in model preference.",
  "optimal_price_point": 129,
  "acceptable_range_low": 79,
  "acceptable_range_high": 229,
  "recommended_price": 149,
  "recommended_pricing_model": "Tiered plans with usage-based overage",
  "segment_recommendations": [
    {
      "segment": "Bootstrapped Startup Founder",
      "optimal_price": 39,
      "willingness_to_pay": "Low — caps at $49/mo, highly price sensitive",
      "pricing_model_preference": "Usage-based or freemium"
    },
    {
      "segment": "Growth-Stage Marketing Lead",
      "optimal_price": 129,
      "willingness_to_pay": "Moderate — willing to pay $99-$199/mo with clear ROI",
      "pricing_model_preference": "Tiered plans"
    },
    {
      "segment": "Enterprise CMO",
      "optimal_price": 349,
      "willingness_to_pay": "High — price is secondary to capability and reliability",
      "pricing_model_preference": "Annual contract with dedicated support"
    }
  ],
  "tier_suggestions": [
    {
      "tier_name": "Starter",
      "price_point": 49,
      "target_segment": "Bootstrapped startups",
      "key_features": ["5 personas", "3 focus groups/mo", "Basic chat"]
    },
    {
      "tier_name": "Growth",
      "price_point": 149,
      "target_segment": "Growth-stage companies",
      "key_features": ["25 personas", "15 focus groups/mo", "Mave agent", "Video analysis"]
    },
    {
      "tier_name": "Enterprise",
      "price_point": 399,
      "target_segment": "Mid-market and enterprise",
      "key_features": ["Unlimited personas", "Unlimited focus groups", "Priority support", "SSO", "API access"]
    }
  ],
  "risks": [
    "Pricing below $79/mo signals low quality to enterprise buyers",
    "Pricing above $229/mo loses growth-stage segment entirely",
    "Usage-based pricing creates unpredictable bills — monitor NPS"
  ],
  "next_steps": [
    "A/B test $129 vs $149 on pricing page with real traffic",
    "Run a follow-up focus group testing tier feature bundles",
    "Validate enterprise pricing with 5 real enterprise prospects"
  ]
}
```

***

## Variations

<AccordionGroup>
  <Accordion title="Test price increases on existing customers">
    Create personas representing your current customer base. Test the proposed increase against their current price anchor:

    ```python theme={"dark"}
    {
        "question": (
            "You currently pay $99/mo for this product. "
            "At what price would you cancel your subscription?"
        ),
        "type": "SLIDER",
        "min_value": 99,
        "max_value": 500,
        "order": 1,
    }
    ```
  </Accordion>

  <Accordion title="Feature-value analysis">
    Add questions that price individual features to find which drive willingness-to-pay:

    ```python theme={"dark"}
    features = ["AI research agent", "Video analysis", "Focus groups (N=100)"]
    for i, feature in enumerate(features):
        questions.append({
            "question": f"How much would you pay monthly for {feature} alone?",
            "type": "SLIDER",
            "min_value": 0, "max_value": 200,
            "order": 7 + i,
        })
    ```
  </Accordion>

  <Accordion title="Geographic pricing">
    Run the same study with region-specific personas (US, EU, APAC, LATAM) to set geographic pricing:

    ```python theme={"dark"}
    regions = {
        "US": ["us_startup_id", "us_enterprise_id"],
        "EU": ["eu_startup_id", "eu_enterprise_id"],
        "APAC": ["apac_startup_id", "apac_enterprise_id"],
    }
    for region, ids in regions.items():
        fg = run_pricing_focus_group(ids)
        # Compare VW points across regions
    ```
  </Accordion>

  <Accordion title="Competitive price anchoring">
    Include competitor pricing in the context so personas anchor relative to known alternatives:

    ```python theme={"dark"}
    PRODUCT_DESCRIPTION += (
        "\n\nFor reference, similar products are priced at:\n"
        "- Competitor A: $99/mo (basic plan)\n"
        "- Competitor B: $249/mo (all features)\n"
        "- Competitor C: Usage-based, typically $150-$300/mo"
    )
    ```
  </Accordion>
</AccordionGroup>

***

## Credits Estimate

| Stage                           | Typical Cost          | Notes                               |
| ------------------------------- | --------------------- | ----------------------------------- |
| Create 4 personas               | 0                     | Persona creation is free            |
| Focus Group (N=40, 6 questions) | 200–400 credits       | 4 sliders + 1 likert + 1 open-ended |
| Pricing report (1 chat call)    | 5–15 credits          | Single structured output            |
| **Total**                       | **\~205–415 credits** |                                     |

<Tip>
  For a quick directional read, start with N=20 (5 per segment) at roughly half the cost. Scale to N=40+ for tier-worthy data you'd put in a pricing deck.
</Tip>

***

## See Also

<CardGroup cols={2}>
  <Card title="Focus Groups" icon="users" href="/features/focus-groups">
    Slider, Likert, and all 12 question types
  </Card>

  <Card title="Positioning Workshop" icon="bullseye" href="/playbooks/positioning-workshop">
    Test positioning alongside pricing
  </Card>

  <Card title="Market Entry Research" icon="compass" href="/playbooks/market-entry-research">
    Research the market before pricing
  </Card>

  <Card title="Brand Perception Audit" icon="chart-pie" href="/playbooks/brand-perception-audit">
    Understand brand perception context for pricing
  </Card>

  <Card title="Persona Debate" icon="scale-balanced" href="/playbooks/persona-debate">
    Opposing personas evaluate pricing at different points
  </Card>

  <Card title="Credits & Budget" icon="coins" href="/cookbooks/credits-budget-alerts">
    Track and manage credit usage
  </Card>
</CardGroup>
