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 → MaveraPOST /mave/chat (categorize) → POST /focus-groups (Ranking) → Prioritized feature list
Code
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]}")
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)}`);
}
Example Output
{
"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
Feature request noise
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.
Ranking limit
Ranking limit
Focus Groups work best with 5-8 ranking options. More than 8 dilutes signal. Merge similar features before ranking.