Scenario
Deals marked “lost” carry structured data:lost_reason, amount, stage at loss. You create persona archetypes for each lost-deal pattern and run a Focus Group to surface qualitative reasoning behind each loss category.
Architecture
Code
import os, requests
from collections import defaultdict
DOMAIN = os.environ["PIPEDRIVE_DOMAIN"]
PD_TOKEN = os.environ["PIPEDRIVE_API_TOKEN"]
PD_BASE = f"https://{DOMAIN}.pipedrive.com"
MAVERA_KEY = os.environ["MAVERA_API_KEY"]
def pd_get(path, params=None):
params = params or {}
params["api_token"] = PD_TOKEN
r = requests.get(f"{PD_BASE}{path}", params=params)
r.raise_for_status()
return r.json()
# 1. Pull lost deals and cluster by reason
lost_deals = pd_get("/api/v2/deals", {"status": "lost", "limit": 200}).get("data", []) or []
clusters = defaultdict(list)
for deal in lost_deals:
reason = deal.get("lost_reason") or "No reason given"
clusters[reason].append({"title": deal.get("title"), "value": deal.get("value", 0), "stage_id": deal.get("stage_id")})
# 2. Build personas per archetype
personas = []
for reason, deals in clusters.items():
avg_val = sum(d["value"] or 0 for d in deals) / max(len(deals), 1)
personas.append({
"name": f"Lost: {reason[:40]}",
"description": f"Represents {len(deals)} deals lost because: '{reason}'. Avg value: ${avg_val:,.0f}.",
})
# 3. Run Focus Group
fg_resp = requests.post(
"https://app.mavera.io/api/v1/focus-groups",
headers={"Authorization": f"Bearer {MAVERA_KEY}"},
json={
"title": "Pipedrive Lost Deal Analysis",
"personas": personas[:8],
"questions": [
"Why did you ultimately decide not to buy?",
"Was pricing the primary factor, or were there deeper concerns?",
"How did competitors position themselves against us?",
"What product capability would have changed your decision?",
"If we returned in 6 months with improvements, what would you need to see?",
],
},
)
fg_resp.raise_for_status()
fg = fg_resp.json()
for resp in fg.get("responses", []):
print(f"\n--- {resp['persona_name']} ---")
for a in resp.get("answers", []):
print(f" Q: {a['question']}\n A: {a['answer'][:400]}\n")
const DOMAIN = process.env.PIPEDRIVE_DOMAIN;
const PD_TOKEN = process.env.PIPEDRIVE_API_TOKEN;
const PD_BASE = `https://${DOMAIN}.pipedrive.com`;
async function pdGet(path, params = {}) {
const url = new URL(`${PD_BASE}${path}`);
url.searchParams.set("api_token", PD_TOKEN);
for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
const res = await fetch(url);
if (!res.ok) throw new Error(`Pipedrive ${res.status}`);
return res.json();
}
// 1. Pull lost deals, cluster by reason
const lostDeals = (await pdGet("/api/v2/deals", { status: "lost", limit: 200 })).data || [];
const clusters = {};
for (const deal of lostDeals) {
const reason = deal.lost_reason || "No reason given";
(clusters[reason] ||= []).push({ title: deal.title, value: deal.value || 0, stage_id: deal.stage_id });
}
// 2. Build personas
const personas = Object.entries(clusters).map(([reason, deals]) => {
const avgVal = deals.reduce((s, d) => s + d.value, 0) / deals.length;
return { name: `Lost: ${reason.slice(0, 40)}`, description:
`Represents ${deals.length} deals lost because: '${reason}'. Avg value: $${avgVal.toLocaleString()}.` };
});
// 3. Focus Group
const fgRes = await fetch("https://app.mavera.io/api/v1/focus-groups", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.MAVERA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
title: "Pipedrive Lost Deal Analysis",
personas: personas.slice(0, 8),
questions: [
"Why did you decide not to buy?",
"Was pricing primary, or deeper concerns?",
"How did competitors position against us?",
"What capability would have changed your decision?",
"If we returned in 6 months, what would you need?",
],
}),
});
const fg = await fgRes.json();
for (const resp of fg.responses || []) {
console.log(`\n--- ${resp.persona_name} ---`);
for (const a of resp.answers || []) console.log(` Q: ${a.question}\n A: ${a.answer.slice(0, 400)}\n`);
}
Example Output
--- Lost: Too expensive ---
Q: Why did you decide not to buy?
A: Per-seat pricing didn't work for our 200-person team. We needed
a volume discount or usage-based model. The competitor offered
flat-rate enterprise pricing that made budgeting simpler.
--- Lost: Went with competitor ---
Q: How did competitors position against us?
A: They led with integration depth — their Salesforce connector was
native, not API-based. They also offered a free pilot with
dedicated onboarding. We never got past the PDF proposal stage.
Error Handling
| Error | Cause | Fix |
|---|---|---|
lost_reason is null | No reason logged | Map to “No reason given”; prompt sales ops to require reasons |
| Too many clusters | Freetext reasons | Normalize reasons (lowercase, group synonyms) before clustering |
| Focus Group rate limit | Too many concurrent calls | Queue with 2-3s delay between batches |