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

# Star Distribution → Persona Segmentation

> Segment Trustpilot reviews by star tier, create personas per tier, and run a Focus Group to find what moves users to 5 stars

## Scenario

Not all reviewers are the same. 1-star reviewers have different concerns than 3-star reviewers. You segment reviews by star rating, create a persona for each tier (1-2 star detractors, 3-star neutrals, 4-5 star promoters), then run a Focus Group asking the pivotal question: "What would move you to 5 stars?" The answers reveal the exact interventions needed for each segment.

**Flow:** Trustpilot `GET /reviews` → Group by star tier → Mavera `POST /personas` (per tier) → `POST /focus-groups` ("What moves you to 5?")

## Code

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

  TP_KEY = os.environ["TRUSTPILOT_API_KEY"]
  MV = os.environ["MAVERA_API_KEY"]
  BU_ID = os.environ["TRUSTPILOT_BU_ID"]
  TP_BASE = "https://api.trustpilot.com/v1"
  MV_BASE = "https://app.mavera.io/api/v1"
  MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  TIERS = {
      "Detractor (1-2★)": [1, 2],
      "Neutral (3★)": [3],
      "Promoter (4-5★)": [4, 5],
  }

  # 1. Pull reviews per tier
  tier_reviews = {}
  for tier_name, star_list in TIERS.items():
      all_revs = []
      for stars in star_list:
          r = requests.get(f"{TP_BASE}/business-units/{BU_ID}/reviews",
              params={"apikey": TP_KEY, "stars": stars, "perPage": 50,
                       "orderBy": "createdat.desc"})
          r.raise_for_status()
          all_revs.extend(r.json().get("reviews", []))
          time.sleep(0.2)
      tier_reviews[tier_name] = all_revs

  # 2. Extract themes per tier
  tier_themes = {}
  for tier_name, revs in tier_reviews.items():
      texts = [r.get("text", "")[:300] for r in revs if r.get("text")]
      themes = defaultdict(int)
      keywords = {"shipping": 0, "quality": 0, "support": 0, "price": 0, "easy": 0,
                  "fast": 0, "broken": 0, "refund": 0, "love": 0, "recommend": 0}
      for t in texts:
          lower = t.lower()
          for kw in keywords:
              if kw in lower:
                  themes[kw] += 1
      tier_themes[tier_name] = {"texts": texts, "themes": dict(themes), "count": len(revs)}

  # 3. Create personas per tier
  persona_ids = []
  for tier_name, data in tier_themes.items():
      top_themes = sorted(data["themes"].items(), key=lambda x: -x[1])[:5]
      theme_str = ", ".join(f"{k} ({v})" for k, v in top_themes)
      sample_texts = data["texts"][:3]

      p = requests.post(f"{MV_BASE}/personas", headers=MV_H, json={
          "name": f"TP: {tier_name}",
          "description": (
              f"Trustpilot reviewer in {tier_name} segment. N={data['count']}. "
              f"Key themes: {theme_str}. "
              f"Sample: \"{sample_texts[0][:100]}...\""
          ),
          "psychographic": {
              "satisfaction_tier": tier_name,
              "top_themes": [k for k, _ in top_themes],
          },
      }).json()
      persona_ids.append({"id": p["id"], "tier": tier_name, "n": data["count"]})
      print(f"Persona: {p['id']} — {tier_name} ({data['count']} reviews)")
      time.sleep(0.3)

  # 4. Focus Group: "What moves you to 5 stars?"
  fg = requests.post(f"{MV_BASE}/focus-groups", headers=MV_H, json={
      "name": "TrustScore Improvement Strategy",
      "persona_ids": [p["id"] for p in persona_ids],
      "questions": [
          "What would it take for you to give this company a 5-star review?",
          "What's the #1 thing this company does well that they should never change?",
          "If you could fix one thing about your experience, what would it be?",
          {"type": "likert", "text": "How likely are you to update your review if the company resolves your issue? (1-5)", "scale": 5},
          "Describe your ideal post-purchase experience in 2 sentences.",
      ],
      "responses_per_persona": 3,
  }).json()

  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

  for resp in data.get("responses", []):
      tier = next((p["tier"] for p in persona_ids if p["id"] == resp.get("persona_id")), "?")
      print(f"\n[{tier}] {resp.get('question','')[:60]}")
      print(f"  → {resp.get('answer','')[:250]}")
  ```

  ```javascript JavaScript theme={"dark"}
  const TP_KEY = process.env.TRUSTPILOT_API_KEY;
  const MV = process.env.MAVERA_API_KEY;
  const BU_ID = process.env.TRUSTPILOT_BU_ID;
  const TP_BASE = "https://api.trustpilot.com/v1";
  const MV_BASE = "https://app.mavera.io/api/v1";
  const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };

  const TIERS = { "Detractor (1-2★)": [1, 2], "Neutral (3★)": [3], "Promoter (4-5★)": [4, 5] };

  // 1. Pull per tier
  const tierReviews = {};
  for (const [tierName, starList] of Object.entries(TIERS)) {
    const allRevs = [];
    for (const stars of starList) {
      const res = await fetch(
        `${TP_BASE}/business-units/${BU_ID}/reviews?apikey=${TP_KEY}&stars=${stars}&perPage=50&orderBy=createdat.desc`
      );
      allRevs.push(...((await res.json()).reviews || []));
      await new Promise((r) => setTimeout(r, 200));
    }
    tierReviews[tierName] = allRevs;
  }

  // 2. Personas
  const personaIds = [];
  for (const [tierName, revs] of Object.entries(tierReviews)) {
    const texts = revs.filter((r) => r.text).map((r) => r.text.slice(0, 300));
    const p = await fetch(`${MV_BASE}/personas`, {
      method: "POST", headers: MV_H,
      body: JSON.stringify({
        name: `TP: ${tierName}`,
        description: `Trustpilot ${tierName}. N=${revs.length}. Sample: "${(texts[0] || "").slice(0, 100)}..."`,
        psychographic: { satisfaction_tier: tierName },
      }),
    }).then((r) => r.json());
    personaIds.push({ id: p.id, tier: tierName, n: revs.length });
    await new Promise((r) => setTimeout(r, 300));
  }

  // 3. Focus Group
  const fg = await fetch(`${MV_BASE}/focus-groups`, {
    method: "POST", headers: MV_H,
    body: JSON.stringify({
      name: "TrustScore Improvement",
      persona_ids: personaIds.map((p) => p.id),
      questions: [
        "What would it take for you to give 5 stars?",
        "What's the #1 thing they do well?",
        "If you could fix one thing, what?",
        { type: "likert", text: "How likely to update review if issue resolved? (1-5)", scale: 5 },
        "Describe your ideal post-purchase experience.",
      ],
      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 tier = personaIds.find((p) => p.id === resp.persona_id)?.tier || "?";
    console.log(`\n[${tier}] ${(resp.question || "").slice(0, 60)}`);
    console.log(`  → ${(resp.answer || "").slice(0, 250)}`);
  }
  ```
</CodeGroup>

## Example Output

```json theme={"dark"}
{
  "focus_group": "fg_trust_tier_9k2",
  "insights": [
    {
      "tier": "Detractor (1-2★)",
      "to_5_stars": "Ship when you say you'll ship. That's it. I ordered with 2-day shipping, waited 9 days. Fix the logistics and I'd give you 5 stars — the product itself is great.",
      "update_likelihood": 3.8
    },
    {
      "tier": "Neutral (3★)",
      "to_5_stars": "The product does what it says. But the website is clunky, checkout had errors, and I had to email support for a tracking number. Smooth that out.",
      "update_likelihood": 4.1
    },
    {
      "tier": "Promoter (4-5★)",
      "to_5_stars": "Already gave 5. But proactive shipping updates instead of me checking the tracking page would be nice. And a loyalty program.",
      "update_likelihood": 4.7
    }
  ],
  "strategy": "Shipping fixes would recover Detractors (highest ROI). UX polish converts Neutrals. Proactive communication retains Promoters."
}
```

## Error Handling

<AccordionGroup>
  <Accordion title="Star-specific pagination">Trustpilot requires separate API calls per star rating. The code loops through stars individually. For large volumes, paginate each star level independently.</Accordion>
  <Accordion title="Review text language">Trustpilot reviews can be in any language. For non-English dominant businesses, filter by `language` param or use Mave for translation.</Accordion>
</AccordionGroup>
