Scenario
Win/loss analysis usually happens after deals close — too late to course-correct. This job queries Opportunities at each stage (Negotiation, Closed Won, Closed Lost), extracts Contact Roles and notes, then runs a synthetic focus group with personas mapped to each stage. You get on-demand win/loss analysis instead of waiting for the quarterly retrospective.Architecture
Code
import os, requests
SF = os.environ["SALESFORCE_INSTANCE"]
SF_T = os.environ["SALESFORCE_ACCESS_TOKEN"]
MV_K = os.environ["MAVERA_API_KEY"]
SF_HEADERS = {"Authorization": f"Bearer {SF_T}"}
MV_HEADERS = {"Authorization": f"Bearer {MV_K}", "Content-Type": "application/json"}
STAGES = ["Negotiation", "Closed Won", "Closed Lost"]
persona_ids = []
for stage in STAGES:
soql = (
f"SELECT Id, Name, Amount, "
f"(SELECT Contact.Name, Contact.Title, Role FROM OpportunityContactRoles) "
f"FROM Opportunity WHERE StageName = '{stage}' LIMIT 20"
)
opps = requests.get(
f"https://{SF}/services/data/v66.0/query",
headers=SF_HEADERS, params={"q": soql},
).json()["records"]
titles = []
for opp in opps:
roles = opp.get("OpportunityContactRoles", {})
if roles and roles.get("records"):
titles.extend(r["Contact"]["Title"] or "Stakeholder" for r in roles["records"])
top_title = max(set(titles), key=titles.count) if titles else "Decision Maker"
pr = requests.post(
"https://app.mavera.io/api/v1/personas", headers=MV_HEADERS,
json={
"name": f"{stage} Buyer — {top_title}",
"description": f"Buyers in '{stage}' stage. Common role: {top_title}. Based on {len(opps)} opps.",
},
)
pr.raise_for_status()
persona_ids.append(pr.json()["id"])
fg = requests.post(
"https://app.mavera.io/api/v1/focus-groups", headers=MV_HEADERS,
json={
"persona_ids": persona_ids,
"questions": [
{"type": "nps", "text": "How likely are you to recommend our product to a peer?"},
{"type": "open_ended", "text": "What nearly stopped you from buying?"},
{"type": "open_ended", "text": "What was the single biggest factor in your decision?"},
{"type": "open_ended", "text": "If you could change one thing about our sales process, what would it be?"},
],
},
).json()
print(f"Focus Group ID: {fg['id']}")
for a in fg.get("responses", []):
print(f" [{a['persona_name']}] {a['question']}: {a['response']}")
const SF = process.env.SALESFORCE_INSTANCE;
const SF_T = process.env.SALESFORCE_ACCESS_TOKEN;
const MV_K = process.env.MAVERA_API_KEY;
const mvH = { Authorization: `Bearer ${MV_K}`, "Content-Type": "application/json" };
const STAGES = ["Negotiation", "Closed Won", "Closed Lost"];
const personaIds = [];
for (const stage of STAGES) {
const soql = encodeURIComponent(
`SELECT Id, Name, Amount, ` +
`(SELECT Contact.Name, Contact.Title, Role FROM OpportunityContactRoles) ` +
`FROM Opportunity WHERE StageName = '${stage}' LIMIT 20`
);
const { records: opps } = await fetch(
`https://${SF}/services/data/v66.0/query?q=${soql}`,
{ headers: { Authorization: `Bearer ${SF_T}` } }
).then((r) => r.json());
const titles = opps.flatMap((o) =>
(o.OpportunityContactRoles?.records || []).map((r) => r.Contact?.Title || "Stakeholder")
);
const topTitle = [...new Set(titles)].sort(
(a, b) => titles.filter((t) => t === b).length - titles.filter((t) => t === a).length
)[0] || "Decision Maker";
const p = await fetch("https://app.mavera.io/api/v1/personas", {
method: "POST", headers: mvH,
body: JSON.stringify({
name: `${stage} Buyer — ${topTitle}`,
description: `Buyers in '${stage}' stage. Common role: ${topTitle}. Based on ${opps.length} opps.`,
}),
}).then((r) => r.json());
personaIds.push(p.id);
}
const fg = await fetch("https://app.mavera.io/api/v1/focus-groups", {
method: "POST", headers: mvH,
body: JSON.stringify({
persona_ids: personaIds,
questions: [
{ type: "nps", text: "How likely are you to recommend our product to a peer?" },
{ type: "open_ended", text: "What nearly stopped you from buying?" },
{ type: "open_ended", text: "What was the single biggest factor in your decision?" },
{ type: "open_ended", text: "If you could change one thing about our sales process, what would it be?" },
],
}),
}).then((r) => r.json());
console.log(`Focus Group ID: ${fg.id}`);
(fg.responses || []).forEach((a) => console.log(` [${a.persona_name}] ${a.question}: ${a.response}`));
Example Output
{
"id": "fg_4kN8x",
"responses": [
{
"persona_name": "Closed Lost Buyer — VP Engineering",
"question": "What nearly stopped you from buying?",
"response": "Your pricing model penalizes teams that scale. We hit 50 seats and the per-seat cost made no sense compared to a platform license."
},
{
"persona_name": "Closed Won Buyer — Director of Product",
"question": "What was the single biggest factor in your decision?",
"response": "The POC was seamless — your SE built an integration in our sandbox within a day. Competitors needed two weeks."
},
{
"persona_name": "Negotiation Buyer — CTO",
"question": "How likely are you to recommend our product to a peer?",
"response": "7 — I'd recommend it once I see the first quarterly ROI. Right now I believe the promise but need proof."
}
]
}
Run this monthly to track sentiment drift across stages. Compare NPS scores over time to detect if your sales experience is improving or degrading.
Salesforce Overview
Back to all 8 Salesforce jobs
Account Intelligence Brief
Next: AI research briefs from Account data