Skip to main content

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.

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

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]}")

Example Output

{
  "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

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