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

# Local Business → Focus Group Stimulus

> Use Yelp competitor profiles as stimulus for a Mavera Focus Group with local consumer personas

## Scenario

You're a consumer brand deciding where to place your product, or a franchise evaluating which competitor model to emulate. You pull competitor business profiles from Yelp — their descriptions, photos, ratings, price tiers — and use them as stimulus material for a Mavera Focus Group. Synthetic consumer personas rank the competitors and explain their preferences, revealing which attributes drive consumer choice.

**Flow:** Yelp `GET /businesses/search` → Top competitors → Mavera `POST /personas` (local consumer archetypes) → `POST /focus-groups` (Ranking: "Which would you pick?") → Consumer preference data

## Code

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

  YELP = os.environ["YELP_API_KEY"]
  MV = os.environ["MAVERA_API_KEY"]
  YELP_H = {"Authorization": f"Bearer {YELP}"}
  MV_BASE = "https://app.mavera.io/api/v1"
  MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  # 1. Pull top competitors
  CATEGORY = "pizza"
  LOCATION = "Brooklyn, NY"

  r = requests.get("https://api.yelp.com/v3/businesses/search",
      headers=YELP_H,
      params={"term": CATEGORY, "location": LOCATION, "limit": 10,
              "sort_by": "review_count"})
  r.raise_for_status()
  competitors = r.json().get("businesses", [])

  # 2. Get detailed profiles
  profiles = []
  for biz in competitors[:6]:
      detail = requests.get(f"https://api.yelp.com/v3/businesses/{biz['id']}",
          headers=YELP_H).json()

      revs = requests.get(f"https://api.yelp.com/v3/businesses/{biz['id']}/reviews",
          headers=YELP_H).json().get("reviews", [])

      profiles.append({
          "name": detail.get("name", ""),
          "rating": detail.get("rating", 0),
          "reviews": detail.get("review_count", 0),
          "price": detail.get("price", "N/A"),
          "categories": ", ".join(c.get("title", "") for c in detail.get("categories", [])),
          "hours": "Open" if not detail.get("is_closed") else "Closed",
          "neighborhood": detail.get("location", {}).get("city", LOCATION),
          "excerpt": revs[0].get("text", "")[:200] if revs else "No reviews",
      })
      time.sleep(0.5)

  # 3. Create local consumer personas
  CONSUMER_ARCHETYPES = [
      {"name": "Busy Parent", "desc": "Family of 4, ordering takeout 3x/week. Values speed, portion size, kid-friendliness. Budget: $30-50/order."},
      {"name": "Foodie Millennial", "desc": "Late 20s, Instagram-active. Values quality, ambiance, unique menu items. Willing to pay premium."},
      {"name": "Budget College Student", "desc": "NYU student. Eats out daily. Values price, late hours, portion size. Budget: under $15."},
      {"name": "Local Regular", "desc": "Lives in the neighborhood 10+ years. Values consistency, knowing the staff, supporting local. Goes weekly."},
  ]

  persona_ids = []
  for arch in CONSUMER_ARCHETYPES:
      p = requests.post(f"{MV_BASE}/personas", headers=MV_H, json={
          "name": f"Yelp Consumer: {arch['name']}",
          "description": f"{arch['desc']} Location: {LOCATION}.",
          "demographic": {"location": LOCATION},
          "psychographic": {"dining_persona": arch["name"]},
      }).json()
      persona_ids.append({"id": p["id"], "name": arch["name"]})
      time.sleep(0.2)

  # 4. Build stimulus
  competitor_cards = "\n\n".join(
      f"({chr(65+i)}) {p['name']}\n"
      f"   Rating: {p['rating']}/5 ({p['reviews']} reviews) | Price: {p['price']}\n"
      f"   Categories: {p['categories']}\n"
      f"   Neighborhood: {p['neighborhood']}\n"
      f"   Recent review: \"{p['excerpt']}\""
      for i, p in enumerate(profiles)
  )

  # 5. Focus Group
  fg = requests.post(f"{MV_BASE}/focus-groups", headers=MV_H, json={
      "name": f"{CATEGORY.title()} Preference Study — {LOCATION}",
      "persona_ids": [p["id"] for p in persona_ids],
      "questions": [
          {"type": "ranking", "text": f"Rank these {CATEGORY} places from first choice to last:\n\n{competitor_cards}"},
          "Explain why you ranked your #1 choice first. What specific attribute sealed it?",
          "What would make you switch from your current favorite to a new option?",
          "Describe your ideal ordering experience for this category in 2 sentences.",
          "If a new place opened in your neighborhood, what ONE thing would make you try it?",
      ],
      "context": f"Consumer preference study: {CATEGORY} in {LOCATION}.\n\n{competitor_cards}",
      "responses_per_persona": 3,
  }).json()

  # 6. Poll
  for _ in range(20):
      time.sleep(5)
      data = requests.get(f"{MV_BASE}/focus-groups/{fg['id']}", headers=MV_H).json()
      if data.get("status") == "completed":
          break

  print(f"=== {CATEGORY.title()} Preference Study — {LOCATION} ===")
  for resp in data.get("responses", []):
      persona = next((p["name"] for p in persona_ids if p["id"] == resp.get("persona_id")), "?")
      print(f"\n[{persona}] {resp.get('question','')[:60]}")
      print(f"  → {resp.get('answer','')[:300]}")
  ```

  ```javascript JavaScript theme={"dark"}
  const YELP = process.env.YELP_API_KEY;
  const MV = process.env.MAVERA_API_KEY;
  const YELP_H = { Authorization: `Bearer ${YELP}` };
  const MV_BASE = "https://app.mavera.io/api/v1";
  const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };

  const CATEGORY = "pizza";
  const LOCATION = "Brooklyn, NY";

  // 1. Top competitors
  const competitors = (await fetch(
    `https://api.yelp.com/v3/businesses/search?term=${CATEGORY}&location=${encodeURIComponent(LOCATION)}&limit=10&sort_by=review_count`,
    { headers: YELP_H }
  ).then((r) => r.json())).businesses || [];

  // 2. Profiles
  const profiles = [];
  for (const biz of competitors.slice(0, 6)) {
    const detail = await fetch(`https://api.yelp.com/v3/businesses/${biz.id}`, { headers: YELP_H }).then((r) => r.json());
    const revs = await fetch(`https://api.yelp.com/v3/businesses/${biz.id}/reviews`, { headers: YELP_H })
      .then((r) => r.json()).then((d) => d.reviews || []);
    profiles.push({
      name: detail.name, rating: detail.rating, reviews: detail.review_count,
      price: detail.price || "N/A",
      categories: (detail.categories || []).map((c) => c.title).join(", "),
      excerpt: revs[0]?.text?.slice(0, 200) || "No reviews",
    });
    await new Promise((r) => setTimeout(r, 500));
  }

  // 3. Personas
  const archetypes = [
    { name: "Busy Parent", desc: "Family of 4. Values speed, portions, kid-friendly. $30-50." },
    { name: "Foodie Millennial", desc: "Late 20s, Instagram. Quality, ambiance, unique menu." },
    { name: "Budget Student", desc: "Eats out daily. Price, late hours, portions. Under $15." },
    { name: "Local Regular", desc: "10+ years in neighborhood. Consistency, community, weekly." },
  ];

  const personaIds = [];
  for (const arch of archetypes) {
    const p = await fetch(`${MV_BASE}/personas`, {
      method: "POST", headers: MV_H,
      body: JSON.stringify({
        name: `Yelp: ${arch.name}`, description: `${arch.desc} Location: ${LOCATION}.`,
        psychographic: { dining_persona: arch.name },
      }),
    }).then((r) => r.json());
    personaIds.push({ id: p.id, name: arch.name });
    await new Promise((r) => setTimeout(r, 200));
  }

  // 4. Stimulus + Focus Group
  const cards = profiles.map((p, i) =>
    `(${String.fromCharCode(65 + i)}) ${p.name}\n   ${p.rating}/5 (${p.reviews}) | ${p.price}\n   "${p.excerpt}"`
  ).join("\n\n");

  const fg = await fetch(`${MV_BASE}/focus-groups`, {
    method: "POST", headers: MV_H,
    body: JSON.stringify({
      name: `${CATEGORY} Preference — ${LOCATION}`,
      persona_ids: personaIds.map((p) => p.id),
      questions: [
        { type: "ranking", text: `Rank these:\n\n${cards}` },
        "Why is your #1 first?",
        "What would make you switch?",
        "Describe your ideal ordering experience.",
        "What ONE thing would make you try a new place?",
      ],
      context: cards,
      responses_per_persona: 3,
    }),
  }).then((r) => r.json());

  let data;
  for (let i = 0; i < 20; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    data = await fetch(`${MV_BASE}/focus-groups/${fg.id}`, { headers: MV_H }).then((r) => r.json());
    if (data.status === "completed") break;
  }

  for (const resp of data.responses || []) {
    const name = personaIds.find((p) => p.id === resp.persona_id)?.name || "?";
    console.log(`\n[${name}] ${(resp.question || "").slice(0, 60)}`);
    console.log(`  → ${(resp.answer || "").slice(0, 300)}`);
  }
  ```
</CodeGroup>

## Example Output

```json theme={"dark"}
{
  "study": "Pizza Preference — Brooklyn, NY",
  "rankings_by_persona": {
    "Busy Parent": ["Di Fara Pizza", "L&B Spumoni Gardens", "Juliana's"],
    "Foodie Millennial": ["Roberta's", "Juliana's", "Di Fara Pizza"],
    "Budget Student": ["L&B Spumoni Gardens", "Best Pizza", "Di Fara Pizza"],
    "Local Regular": ["Di Fara Pizza", "Totonno's", "L&B Spumoni Gardens"]
  },
  "key_insights": [
    {
      "persona": "Busy Parent",
      "why_first": "Di Fara — consistent quality, large slices kids love, and they don't mind if my 4-year-old makes a mess. The wait is the only downside."
    },
    {
      "persona": "Foodie Millennial",
      "why_first": "Roberta's — the wood-fired crust, the Bushwick vibe, the seasonal toppings. It photographs well and tastes even better."
    },
    {
      "persona": "Budget Student",
      "why_first": "L&B — a square slice is $3.50 and it's HUGE. Best dollar-to-pizza ratio in Brooklyn."
    },
    {
      "persona": "Local Regular",
      "switch_trigger": "If a new place opened within walking distance with consistent quality and the owner actually remembered my name? I'd give it a shot."
    }
  ]
}
```

## Error Handling

<AccordionGroup>
  <Accordion title="3 review excerpt limit">Yelp API returns at most 3 review excerpts. For richer stimulus, supplement with Yelp business description and category data. Never scrape additional reviews.</Accordion>
  <Accordion title="Business detail rate limits">Each `GET /businesses/{id}` counts against your quota. For 6 competitors with details + reviews, that's 12 calls. Cache results if running repeatedly.</Accordion>
  <Accordion title="Location ambiguity">Yelp's `location` param accepts city names, zip codes, or addresses. Use zip codes for precision in dense metro areas with overlapping neighborhood names.</Accordion>
  <Accordion title="Ranking too many options">Focus Groups work best with 4-6 ranking options. More than 8 creates decision fatigue. Filter to top competitors by review count before running.</Accordion>
</AccordionGroup>
