Scenario
How accurate are Mavera’s synthetic personas compared to real humans? This job creates the same set of questions in both Typeform (for real respondents) and a Mavera Focus Group (for synthetic personas), then compares the results. The output is calibration data: where synthetic matches reality (and where it diverges), so you know which research questions you can confidently delegate to Mavera and which still need human validation. Flow: Design questions → Deploy as Typeform survey + Mavera Focus Group → Collect real + synthetic responses → Compare themes, sentiment, rankings → Calibration reportArchitecture
Code
import os, json, requests, time
from collections import Counter
TF = os.environ["TYPEFORM_TOKEN"]
MV = os.environ["MAVERA_API_KEY"]
TF_BASE = "https://api.typeform.com"
MB = "https://app.mavera.io/api/v1"
TF_H = {"Authorization": f"Bearer {TF}"}
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
TYPEFORM_FORM_ID = os.environ.get("CALIBRATION_FORM_ID", "your_form_id")
PERSONA_IDS = os.environ.get("PERSONA_IDS", "").split(",")
SHARED_QUESTIONS = [
"What is the biggest challenge you face in your role today?",
"How would you describe our product to a colleague in one sentence?",
"What nearly stopped you from becoming a customer?",
"Rate your onboarding experience (1-10) and explain why.",
"If you could change one thing about our product, what would it be?",
]
# 1. Pull Typeform responses (assumes form already deployed with these questions)
form = requests.get(f"{TF_BASE}/forms/{TYPEFORM_FORM_ID}", headers=TF_H).json()
field_map = {}
for f in form.get("fields", []):
for q in SHARED_QUESTIONS:
if q.lower()[:30] in f.get("title", "").lower():
field_map[f["id"]] = q
break
responses_raw = []
params = {"page_size": 1000}
while True:
r = requests.get(f"{TF_BASE}/forms/{TYPEFORM_FORM_ID}/responses",
headers=TF_H, params=params)
if r.status_code == 429:
time.sleep(1); continue
r.raise_for_status()
data = r.json()
responses_raw.extend(data.get("items", []))
if len(data.get("items", [])) < 1000: break
params["before"] = data["items"][-1]["token"]
time.sleep(0.6)
# Parse real responses
real_answers = {q: [] for q in SHARED_QUESTIONS}
for resp in responses_raw:
for ans in resp.get("answers", []):
fid = ans.get("field", {}).get("id", "")
if fid in field_map:
text = ans.get("text", "") or str(ans.get("number", ""))
if text.strip():
real_answers[field_map[fid]].append(text.strip())
print(f"Real responses: {len(responses_raw)}")
for q, answers in real_answers.items():
print(f" {q[:50]}... → {len(answers)} answers")
# 2. Run Mavera Focus Group with same questions
if not PERSONA_IDS or PERSONA_IDS == [""]:
p = requests.post(f"{MB}/personas", headers=MV_H, json={
"name": "Calibration Persona",
"description": "General target customer for A/B calibration study.",
}).json()
PERSONA_IDS = [p["id"]]
fg = requests.post(f"{MB}/focus-groups", headers=MV_H, json={
"name": "Calibration: Typeform vs Mavera",
"persona_ids": PERSONA_IDS,
"questions": SHARED_QUESTIONS,
"context": "Answer these questions as you genuinely would. Be specific and honest.",
"responses_per_persona": 5,
}).json()
for _ in range(20):
time.sleep(5)
fg_result = requests.get(f"{MB}/focus-groups/{fg['id']}",
headers=MV_H).json()
if fg_result.get("status") == "completed":
break
# Parse synthetic responses
synthetic_answers = {q: [] for q in SHARED_QUESTIONS}
for resp in fg_result.get("responses", []):
q_text = resp.get("question", "")
for q in SHARED_QUESTIONS:
if q.lower()[:30] in q_text.lower():
synthetic_answers[q].append(resp.get("answer", ""))
break
# 3. Compare with Mave
comparison_prompt = "Compare real human survey responses vs synthetic persona responses.\n\n"
for q in SHARED_QUESTIONS:
real = real_answers.get(q, [])[:10]
synth = synthetic_answers.get(q, [])[:5]
comparison_prompt += f"\n**Question:** {q}\n"
comparison_prompt += f"REAL ({len(real_answers.get(q, []))} total): {'; '.join(r[:100] for r in real[:5])}\n"
comparison_prompt += f"SYNTHETIC ({len(synth)}): {'; '.join(s[:100] for s in synth[:3])}\n"
comparison_prompt += """
Analyze:
1) Theme overlap: What % of real themes appear in synthetic responses?
2) Sentiment alignment: Do synthetic responses match the emotional tone?
3) Specificity gap: Are real responses more/less specific?
4) Blind spots: What did real humans say that synthetics completely missed?
5) Synthetic advantages: Where did synthetics provide insight humans didn't?
6) Calibration score (0-100): How reliable is synthetic for this question set?
7) Recommendations: Which question types are safe to delegate to synthetic?"""
comparison = requests.post(f"{MB}/mave/chat", headers=MV_H,
json={"message": comparison_prompt}).json()
print(f"\n{'='*60}")
print("CALIBRATION REPORT: Typeform (Real) vs Mavera (Synthetic)")
print(f"{'='*60}")
print(comparison.get("content", "")[:3000])
const TF = process.env.TYPEFORM_TOKEN;
const MV = process.env.MAVERA_API_KEY;
const TF_BASE = "https://api.typeform.com";
const MB = "https://app.mavera.io/api/v1";
const tfH = { Authorization: `Bearer ${TF}` };
const mvH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const FORM_ID = process.env.CALIBRATION_FORM_ID || "your_form_id";
let personaIds = (process.env.PERSONA_IDS || "").split(",").filter(Boolean);
const SHARED_QUESTIONS = [
"What is the biggest challenge you face in your role today?",
"How would you describe our product to a colleague in one sentence?",
"What nearly stopped you from becoming a customer?",
"Rate your onboarding experience (1-10) and explain why.",
"If you could change one thing about our product, what would it be?",
];
// 1. Pull Typeform responses
const form = await fetch(`${TF_BASE}/forms/${FORM_ID}`, { headers: tfH }).then(r => r.json());
const fieldMap = {};
for (const f of form.fields || []) {
for (const q of SHARED_QUESTIONS) {
if ((f.title || "").toLowerCase().includes(q.toLowerCase().slice(0, 30))) {
fieldMap[f.id] = q; break;
}
}
}
const responsesRaw = [];
const params = new URLSearchParams({ page_size: "1000" });
while (true) {
let res = await fetch(`${TF_BASE}/forms/${FORM_ID}/responses?${params}`, { headers: tfH });
if (res.status === 429) { await new Promise(r => setTimeout(r, 1000)); continue; }
const data = await res.json();
responsesRaw.push(...(data.items || []));
if ((data.items || []).length < 1000) break;
params.set("before", data.items[data.items.length - 1].token);
await new Promise(r => setTimeout(r, 600));
}
const realAnswers = Object.fromEntries(SHARED_QUESTIONS.map(q => [q, []]));
for (const resp of responsesRaw) {
for (const ans of resp.answers || []) {
const q = fieldMap[ans.field?.id];
if (q) { const t = ans.text || String(ans.number ?? ""); if (t.trim()) realAnswers[q].push(t.trim()); }
}
}
console.log(`Real responses: ${responsesRaw.length}`);
// 2. Mavera Focus Group
if (!personaIds.length) {
const p = await fetch(`${MB}/personas`, { method: "POST", headers: mvH,
body: JSON.stringify({ name: "Calibration Persona",
description: "General target customer for calibration." }),
}).then(r => r.json());
personaIds = [p.id];
}
const fg = await fetch(`${MB}/focus-groups`, { method: "POST", headers: mvH,
body: JSON.stringify({ name: "Calibration: TF vs Mavera", persona_ids: personaIds,
questions: SHARED_QUESTIONS,
context: "Answer genuinely. Be specific and honest.", responses_per_persona: 5 }),
}).then(r => r.json());
let fgResult;
for (let i = 0; i < 20; i++) {
await new Promise(r => setTimeout(r, 5000));
fgResult = await fetch(`${MB}/focus-groups/${fg.id}`, { headers: mvH }).then(r => r.json());
if (fgResult.status === "completed") break;
}
const synthAnswers = Object.fromEntries(SHARED_QUESTIONS.map(q => [q, []]));
for (const resp of fgResult.responses || []) {
for (const q of SHARED_QUESTIONS) {
if ((resp.question || "").toLowerCase().includes(q.toLowerCase().slice(0, 30))) {
synthAnswers[q].push(resp.answer || ""); break;
}
}
}
// 3. Compare
let prompt = "Compare real vs synthetic survey responses.\n\n";
for (const q of SHARED_QUESTIONS) {
prompt += `\n**Q:** ${q}\nREAL (${realAnswers[q].length}): ${realAnswers[q].slice(0, 5).map(r => r.slice(0, 100)).join("; ")}\nSYNTH (${synthAnswers[q].length}): ${synthAnswers[q].slice(0, 3).map(s => s.slice(0, 100)).join("; ")}\n`;
}
prompt += `\nAnalyze: 1) Theme overlap % 2) Sentiment alignment 3) Specificity gap 4) Blind spots 5) Synthetic advantages 6) Calibration score (0-100) 7) Recommendations`;
const comparison = await fetch(`${MB}/mave/chat`, { method: "POST", headers: mvH,
body: JSON.stringify({ message: prompt }) }).then(r => r.json());
console.log(`\n${"=".repeat(60)}\nCALIBRATION REPORT\n${"=".repeat(60)}`);
console.log((comparison.content || "").slice(0, 3000));
Example Output
============================================================
CALIBRATION REPORT: Typeform (Real) vs Mavera (Synthetic)
============================================================