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

Feature requests in #feature-requests or #ideas channels are gold — but they’re unstructured and difficult to prioritize. This job extracts and clusters requests, creates user personas based on the types of requestors, then runs a Mavera Focus Group to pressure-test each feature’s appeal across user segments and generate a ranked priority list. Flow: Discord messages → Extract feature requests → Mavera POST /mave/chat (cluster) → POST /personasPOST /focus-groups → Ranked priorities

Code

import os, requests, time

DC_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
DC_BASE = "https://discord.com/api/v10"
DC_H = {"Authorization": f"Bot {DC_TOKEN}"}
MV = os.environ["MAVERA_API_KEY"]
MV_BASE = "https://app.mavera.io/api/v1"
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

FEATURE_CHANNEL = "CHANNEL_ID_FEATURES"

# 1. Fetch feature request messages
msgs = []
after = "0"
while len(msgs) < 300:
    r = requests.get(f"{DC_BASE}/channels/{FEATURE_CHANNEL}/messages", headers=DC_H,
        params={"limit": 100, "after": after})
    if r.status_code == 429:
        time.sleep(r.json().get("retry_after", 1))
        continue
    if not r.ok:
        break
    batch = r.json()
    if not batch:
        break
    for m in batch:
        if not m.get("author",{}).get("bot",False) and len(m.get("content","")) > 15:
            reactions = sum(r.get("count",0) for r in m.get("reactions", []))
            msgs.append({"text": m["content"][:400], "reactions": reactions,
                         "author_roles": [r.get("name","") for r in m.get("member",{}).get("roles",[])]})
    after = batch[0]["id"]
    time.sleep(0.5)

print(f"Feature requests: {len(msgs)}")

# 2. Cluster via Mave
corpus = "\n".join(
    f"- (reactions: {m['reactions']}) {m['text'][:250]}"
    for m in sorted(msgs, key=lambda x: -x["reactions"])[:80]
)

clustering = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
    "message": f"Product analyst. Cluster {len(msgs)} Discord feature requests into top 8 themes.\n\n"
        f"{corpus[:6000]}\n\n"
        "For each:\n- Feature name (3-5 words)\n- Request count\n- Total reactions (proxy for community votes)\n"
        "- Representative request (exact quote)\n- Complexity estimate (low/medium/high)\n"
        "Rank by reactions × frequency."
}).json()
clusters = clustering.get("content", "")
print(f"\nClusters:\n{clusters[:1200]}")

# 3. Create community-segment personas
SEGMENTS = [
    {"name": "Free Tier Power User", "desc": "Uses daily, hits limits constantly. Won't pay but evangelizes to others. Cares about functionality over polish."},
    {"name": "Pro Subscriber", "desc": "Paying $15/month. Expects reliability and premium features. Compares to competitors. Values ROI."},
    {"name": "Community Moderator", "desc": "Manages a 500+ member server using the product. Cares about admin tools, permissions, analytics. Loyal but demanding."},
    {"name": "Developer / API User", "desc": "Builds integrations. Cares about API quality, documentation, rate limits, webhooks. Will leave for better DX."},
]
persona_ids = []
for seg in SEGMENTS:
    p = requests.post(f"{MV_BASE}/personas", headers=MV_H, json={
        "name": f"Discord: {seg['name']}", "description": seg["desc"],
    }).json()
    persona_ids.append(p["id"])
    time.sleep(0.3)

# 4. Focus Group
fg = requests.post(f"{MV_BASE}/focus-groups", headers=MV_H, json={
    "name": "Discord Feature Request Validation",
    "persona_ids": persona_ids,
    "questions": [
        f"Here are the top community feature requests:\n{clusters[:2000]}\n\nRank your top 3 and explain why.",
        "Which feature would make you upgrade (or stay on your current plan)?",
        "Is there a request NOT on this list that matters more to you?",
        "If we shipped your #1 request but it was behind a paywall, would you pay?",
    ],
    "responses_per_persona": 2,
}).json()

for _ in range(25):
    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"\n{'='*60}\nFEATURE REQUEST FOCUS GROUP\n{'='*60}")
for resp in data.get("responses", []):
    idx = persona_ids.index(resp.get("persona_id")) if resp.get("persona_id") in persona_ids else -1
    name = SEGMENTS[idx]["name"] if 0 <= idx < len(SEGMENTS) else "Unknown"
    print(f"\n[{name}] Q: {resp.get('question','')[:80]}")
    print(f"  A: {resp.get('answer','')[:350]}")

Example Output

Feature requests: 243

Clusters:
1. Dark Mode (48 requests, 312 reactions) — "dark mode please my eyes"
2. API Webhooks (35 requests, 287 reactions) — "webhook support for events"
3. Mobile App Rebuild (29 requests, 245 reactions) — "mobile app is unusable"
4. Custom Dashboards (22 requests, 198 reactions) — "let us build our own dashboards"
5. Keyboard Shortcuts (19 requests, 156 reactions) — "vim keybindings when?"

FEATURE REQUEST FOCUS GROUP
============================================================

[Free Power User] Q: Rank top 3
  A: 1. Dark mode — I use this at night, it's actually painful.
     2. Keyboard shortcuts — efficiency matters when you're in it 8 hours.
     3. Mobile app — would use on commute but it's too broken.

[Developer / API] Q: Which feature would make you upgrade?
  A: Webhooks, easily. I'm polling your API every 30 seconds which is
     wasteful for both of us. Real-time webhooks = I'd upgrade to Pro
     immediately because my integration quality jumps dramatically.

[Pro Subscriber] Q: Pay for #1 behind a paywall?
  A: Depends. Dark mode behind a paywall would feel insulting — it's
     a basic accessibility feature. Custom dashboards behind Pro? Sure,
     that's clearly a power feature. Know the difference.

Error Handling

Reactions serve as community votes. Some servers use custom emojis for upvoting — check emoji.name to identify vote reactions vs. casual emoji use.
Users often post the same request in different channels. The clustering step in Mave handles deduplication by theme, but consider cross-referencing channel IDs.
Personas may over-index on vocal segments. Balance by including a “silent majority” persona who uses the product but never posts in Discord.