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

# Feature Request Aggregation → Product Focus Group

> Aggregate G2 feature requests, categorize with Mave, and run a Focus Group to prioritize for product roadmap

## Scenario

G2 reviewers often describe features they wish your product had. These requests are scattered across hundreds of reviews. You aggregate feature request mentions, create personas representing the requesters, then run a Focus Group where personas rank the features by importance. The output feeds directly into product roadmap prioritization.

**Flow:** G2 reviews → Extract feature requests → Mavera `POST /mave/chat` (categorize) → `POST /focus-groups` (Ranking) → Prioritized feature list

## Code

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

  G2 = os.environ["G2_API_KEY"]
  MV = os.environ["MAVERA_API_KEY"]
  G2_H = {"Authorization": f"Token token={G2}", "Content-Type": "application/vnd.api+json"}
  MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  # 1. Pull reviews
  reviews = []
  page = 1
  while len(reviews) < 200:
      r = requests.get(f"https://data.g2.com/api/v1/survey-responses",
          headers=G2_H, params={"page[size]": 50, "page[number]": page})
      if r.status_code == 429: time.sleep(1); continue
      r.raise_for_status()
      data = r.json().get("data", [])
      if not data: break
      reviews.extend(data)
      page += 1
      time.sleep(0.1)

  # 2. Extract "dislike" and "recommendation" text
  feature_texts = []
  for rev in reviews:
      attrs = rev.get("attributes", {})
      for key, val in attrs.get("comment_answers", {}).items():
          text = val if isinstance(val, str) else val.get("text", "")
          k = key.lower()
          if ("dislike" in k or "wish" in k or "improve" in k or "recommend" in k) and text.strip():
              feature_texts.append(text[:300])

  # 3. Use Mave to categorize feature requests
  from openai import OpenAI
  mavera = OpenAI(api_key=MV, base_url="https://app.mavera.io/api/v1")

  feature_block = "\n".join(f"{i+1}. {t}" for i, t in enumerate(feature_texts[:50]))

  categorization = mavera.responses.create(model="mavera-1",
      input=[{"role": "user", "content": f"""Categorize these G2 review comments into feature request categories.

  {feature_block}

  Return JSON with: feature_name, description, mention_count, representative_quotes (2 each).
  Group similar requests. Minimum 3 mentions to include."""}],
      extra_body={"response_format": {"type": "json_object"}})

  features = json.loads(categorization.output[0].content[0].text)
  feature_list = features.get("features", features.get("feature_requests", []))
  print(f"Identified {len(feature_list)} feature request categories")

  # 4. Create product user personas
  PRODUCT_PERSONAS = [
      {"name": "Power User PM", "desc": "Uses product daily. Deeply invested in feature depth."},
      {"name": "Executive Sponsor", "desc": "Bought the product. Cares about ROI and team adoption."},
      {"name": "New User", "desc": "Onboarding. Wants simplicity and quick wins."},
  ]

  persona_ids = []
  for pp in PRODUCT_PERSONAS:
      p = requests.post(f"https://app.mavera.io/api/v1/personas", headers=MV_H, json={
          "name": f"G2: {pp['name']}", "description": pp["desc"],
      }).json()
      persona_ids.append(p["id"])
      time.sleep(0.2)

  # 5. Focus Group: rank features
  feature_options = "\n".join(
      f"({chr(65+i)}) {f.get('feature_name', f.get('name', 'Unknown'))}: {f.get('description','')[:80]}"
      for i, f in enumerate(feature_list[:8])
  )

  fg = requests.post(f"https://app.mavera.io/api/v1/focus-groups", headers=MV_H, json={
      "name": "G2 Feature Request Prioritization",
      "persona_ids": persona_ids,
      "questions": [
          {"type": "ranking", "text": f"Rank these features by impact on YOUR workflow:\n{feature_options}"},
          "Which feature would make you upgrade to a higher tier?",
          "Which feature, if missing, would make you consider switching products?",
          "Describe a workflow you currently can't do that these features would enable.",
      ],
      "responses_per_persona": 3,
  }).json()

  for _ in range(20):
      time.sleep(5)
      data = requests.get(f"https://app.mavera.io/api/v1/focus-groups/{fg['id']}", headers=MV_H).json()
      if data.get("status") == "completed": break

  print(f"\nFocus Group: {fg['id']}")
  for resp in data.get("responses", []):
      print(f"\n{resp.get('question','')[:60]}")
      print(f"  → {resp.get('answer','')[:300]}")
  ```

  ```javascript JavaScript theme={"dark"}
  const G2 = process.env.G2_API_KEY;
  const MV = process.env.MAVERA_API_KEY;
  const G2_H = { Authorization: `Token token=${G2}`, "Content-Type": "application/vnd.api+json" };
  const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
  const OpenAI = require("openai").default;

  // 1. Pull reviews
  const reviews = [];
  let page = 1;
  while (reviews.length < 200) {
    const res = await fetch(`https://data.g2.com/api/v1/survey-responses?page[size]=50&page[number]=${page}`, { headers: G2_H });
    if (res.status === 429) { await new Promise((r) => setTimeout(r, 1000)); continue; }
    const data = (await res.json()).data || [];
    if (!data.length) break;
    reviews.push(...data);
    page++;
    await new Promise((r) => setTimeout(r, 100));
  }

  // 2. Extract feature requests
  const featureTexts = [];
  for (const rev of reviews) {
    for (const [key, val] of Object.entries(rev.attributes?.comment_answers || {})) {
      const text = typeof val === "string" ? val : val?.text || "";
      const k = key.toLowerCase();
      if ((k.includes("dislike") || k.includes("wish") || k.includes("improve")) && text.trim())
        featureTexts.push(text.slice(0, 300));
    }
  }

  // 3. Categorize
  const mavera = new OpenAI({ apiKey: MV, baseURL: "https://app.mavera.io/api/v1" });
  const cat = await mavera.responses.create({
    model: "mavera-1",
    input: [{ role: "user", content: `Categorize into feature requests:\n\n${featureTexts.slice(0, 50).map((t, i) => `${i + 1}. ${t}`).join("\n")}\n\nReturn JSON: features[{feature_name, description, mention_count}].` }],
    extra_body: { response_format: { type: "json_object" } },
  });
  const features = JSON.parse(cat.output[0].content[0].text).features || [];

  // 4. Personas + Focus Group
  const personaIds = [];
  for (const pp of [
    { name: "Power User PM", desc: "Daily user. Wants depth." },
    { name: "Executive Sponsor", desc: "Bought it. Cares about ROI." },
    { name: "New User", desc: "Onboarding. Wants simplicity." },
  ]) {
    const p = await fetch("https://app.mavera.io/api/v1/personas", {
      method: "POST", headers: MV_H,
      body: JSON.stringify({ name: `G2: ${pp.name}`, description: pp.desc }),
    }).then((r) => r.json());
    personaIds.push(p.id);
    await new Promise((r) => setTimeout(r, 200));
  }

  const options = features.slice(0, 8).map((f, i) =>
    `(${String.fromCharCode(65 + i)}) ${f.feature_name || f.name}: ${(f.description || "").slice(0, 80)}`
  ).join("\n");

  const fg = await fetch("https://app.mavera.io/api/v1/focus-groups", {
    method: "POST", headers: MV_H,
    body: JSON.stringify({
      name: "G2 Feature Prioritization",
      persona_ids: personaIds,
      questions: [
        { type: "ranking", text: `Rank by workflow impact:\n${options}` },
        "Which feature would make you upgrade?",
        "Which missing feature would make you switch?",
        "What workflow can't you currently do?",
      ],
      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(`https://app.mavera.io/api/v1/focus-groups/${fg.id}`, { headers: MV_H }).then((r) => r.json());
    if (data.status === "completed") break;
  }

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

## Example Output

```json theme={"dark"}
{
  "feature_categories": 7,
  "top_ranked": [
    { "feature": "Custom Dashboard Builder", "mentions": 23, "rank_avg": 1.8 },
    { "feature": "API Webhook Support", "mentions": 15, "rank_avg": 2.1 },
    { "feature": "Multi-language Support", "mentions": 12, "rank_avg": 3.4 },
    { "feature": "Slack Integration", "mentions": 11, "rank_avg": 2.9 }
  ],
  "persona_insights": {
    "Power User PM": "Custom dashboards ranked #1. 'I export to Sheets weekly because your reporting doesn't slice by persona × campaign.'",
    "Executive Sponsor": "API webhooks ranked #1. 'Need real-time data flow to our BI tool. Manual exports kill adoption.'",
    "New User": "Slack integration ranked #1. 'I live in Slack. If I can't get notifications there, I'll forget this tool exists.'"
  }
}
```

## Error Handling

<AccordionGroup>
  <Accordion title="Feature request noise">Not all 'dislike' comments are feature requests — some are complaints. Mave's categorization filters noise, but verify the output categories manually before sharing with product.</Accordion>
  <Accordion title="Ranking limit">Focus Groups work best with 5-8 ranking options. More than 8 dilutes signal. Merge similar features before ranking.</Accordion>
</AccordionGroup>
