import os, json, requests, time
from openai import OpenAI
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"}
FORM_ID = os.environ.get("TYPEFORM_FORM_ID", "your_form_id")
PERSONA_IDS = os.environ.get("PERSONA_IDS", "").split(",")
# 1. Get form and identify open-ended fields
form = requests.get(f"{TF_BASE}/forms/{FORM_ID}", headers=TF_H).json()
open_fields = [f for f in form.get("fields", [])
if f.get("type") in ("long_text", "short_text")]
print(f"Open-ended fields: {len(open_fields)}")
# 2. Pull responses
responses = []
params = {"page_size": 1000}
while True:
r = requests.get(f"{TF_BASE}/forms/{FORM_ID}/responses",
headers=TF_H, params=params)
if r.status_code == 429:
time.sleep(1)
else:
r.raise_for_status()
data = r.json()
responses.extend(data.get("items", []))
if len(data.get("items", [])) < 1000:
break
params["before"] = data["items"][-1]["token"]
time.sleep(0.6)
# 3. Extract open-ended answers grouped by field
open_field_ids = {f["id"] for f in open_fields}
field_titles = {f["id"]: f.get("title", f["id"]) for f in open_fields}
answers_by_field = {fid: [] for fid in open_field_ids}
for resp in responses:
for ans in resp.get("answers", []):
fid = ans.get("field", {}).get("id", "")
if fid in open_field_ids and ans.get("type") == "text":
text = ans.get("text", "").strip()
if text and len(text) > 10:
answers_by_field[fid].append(text)
# 4. Identify themes with Mavera Chat
mavera = OpenAI(api_key=MV, base_url=MB)
all_text = []
for fid, answers in answers_by_field.items():
title = field_titles[fid]
for ans in answers[:50]:
all_text.append(f"[{title}] {ans[:200]}")
theme_result = mavera.responses.create(
model="mavera-1",
input=[{"role": "user", "content": f"""Analyze these {len(all_text)} open-ended survey responses.
Identify 5-7 distinct themes. For each theme:
- Theme name (2-4 words)
- Frequency estimate (what % of responses mention it)
- Representative quotes (3 examples)
- Underlying sentiment (positive, negative, mixed)
- Key insight
RESPONSES:
{chr(10).join(all_text[:100])}
Return as JSON: {{"themes": [...]}}"""}],
)
theme_content = theme_result.output[0].content[0].text
print("=== Discovered Themes ===")
print(theme_content[:1500])
# 5. Parse themes and create focus group questions
try:
json_str = theme_content[theme_content.find("{"):theme_content.rfind("}")+1]
themes = json.loads(json_str).get("themes", [])
except (json.JSONDecodeError, ValueError):
themes = []
focus_questions = []
for theme in themes[:5]:
name = theme.get("name", "Unknown")
sentiment = theme.get("sentiment", "mixed")
quotes = theme.get("representative_quotes", theme.get("quotes", []))
quote_sample = quotes[0] if quotes else "N/A"
focus_questions.append(
f'Survey respondents mentioned "{name}" — e.g., "{quote_sample[:100]}". '
f"How does this resonate with your experience? What would you add?"
)
focus_questions.append("Which of these themes matters most to you? Why?")
focus_questions.append("What's missing from these themes? What topic should we have asked about?")
# 6. Run Focus Group
if not PERSONA_IDS or PERSONA_IDS == [""]:
p = requests.post(f"{MB}/personas", headers=MV_H, json={
"name": "TF Survey Respondent",
"description": "Represents the typical respondent of this Typeform survey.",
}).json()
PERSONA_IDS = [p["id"]]
fg = requests.post(f"{MB}/focus-groups", headers=MV_H, json={
"name": f"Theme Deep-Dive: {form.get('title', 'Survey')}",
"persona_ids": PERSONA_IDS,
"questions": focus_questions,
"context": f"Based on analysis of {len(responses)} survey responses. Themes discovered: {', '.join(t.get('name','') for t in themes[:5])}",
"responses_per_persona": 3,
}).json()
# 7. Poll for results
for _ in range(20):
time.sleep(5)
result = requests.get(f"{MB}/focus-groups/{fg['id']}",
headers=MV_H).json()
if result.get("status") == "completed":
break
print(f"\nFocus Group: {fg['id']}")
for resp in result.get("responses", []):
print(f"\n[{resp.get('persona_name', '?')}] {resp.get('question', '')[:70]}...")
print(f" → {resp.get('answer', '')[:250]}")