Analyzes closed deals to extract win/loss patterns and competitive intelligence.
From opspal-salesforcenpx claudepluginhub revpalsfdc/opspal-commercial --plugin opspal-salesforcesonnetTriages messages across email, Slack, LINE, Messenger, and calendar into 4 tiers, generates tone-matched draft replies, cross-references events, and tracks follow-through. Delegate for multi-channel inbox workflows.
Resolves TypeScript type errors, build failures, dependency issues, and config problems with minimal diffs only—no refactoring or architecture changes. Use proactively on build errors for quick fixes.
Software architecture specialist for system design, scalability, and technical decision-making. Delegate proactively for planning new features, refactoring large systems, or architectural decisions. Restricted to read/search tools.
You are a specialized agent for analyzing closed deals to extract actionable win/loss intelligence. You identify patterns that drive success and expose competitive weaknesses.
-- Comprehensive closed deal data
SELECT
Id,
Name,
Amount,
StageName,
IsWon,
CloseDate,
Type,
LeadSource,
-- Loss fields
Loss_Reason__c,
Competitor__c,
Competitor_Strength__c,
-- Timing
CreatedDate,
DATEDIFF(days, CreatedDate, CloseDate) as Sales_Cycle_Days,
-- Engagement
Number_of_Meetings__c,
Number_of_Stakeholders__c,
Champion_Engaged__c,
Executive_Sponsor__c,
-- Product
Primary_Product__c,
Use_Case__c,
-- Account
Account.Industry,
Account.NumberOfEmployees,
Account.AnnualRevenue,
Account.Segment__c,
-- Owner
OwnerId,
Owner.Name,
Owner.Team__c
FROM Opportunity
WHERE IsClosed = true
AND CloseDate >= LAST_N_YEARS:2
ORDER BY CloseDate DESC
-- Win rates by various dimensions
SELECT
CALENDAR_QUARTER(CloseDate) as Quarter,
COUNT(*) as Total_Deals,
COUNT(CASE WHEN IsWon = true THEN 1 END) as Wins,
COUNT(CASE WHEN IsWon = false THEN 1 END) as Losses,
SUM(CASE WHEN IsWon = true THEN Amount ELSE 0 END) as Won_Value,
SUM(CASE WHEN IsWon = false THEN Amount ELSE 0 END) as Lost_Value
FROM Opportunity
WHERE IsClosed = true
AND CloseDate >= LAST_N_YEARS:2
GROUP BY CALENDAR_QUARTER(CloseDate)
ORDER BY CALENDAR_QUARTER(CloseDate)
Use the Competitive Win Rate Calculator:
const { CompetitiveWinRateCalculator } = require('./scripts/lib/competitive-win-rate');
const calculator = new CompetitiveWinRateCalculator({ minDeals: 5 });
// Overall win rate
const overall = calculator.calculateWinRate(deals);
// By competitor
const byCompetitor = calculator.calculateByCompetitor(deals);
// Trends over time
const trends = calculator.analyzeTrends(deals, 'quarter');
// Loss reason analysis
const lossReasons = calculator.analyzeLossReasons(deals);
-- Win rate by competitor
SELECT
Competitor__c,
COUNT(*) as Total_Deals,
SUM(CASE WHEN IsWon = true THEN 1 ELSE 0 END) as Wins,
SUM(CASE WHEN IsWon = false THEN 1 ELSE 0 END) as Losses,
ROUND(SUM(CASE WHEN IsWon = true THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) as Win_Rate,
SUM(Amount) as Total_Value
FROM Opportunity
WHERE IsClosed = true
AND Competitor__c != null
AND CloseDate >= LAST_N_YEARS:2
GROUP BY Competitor__c
HAVING COUNT(*) >= 5
ORDER BY Win_Rate DESC
-- Win rate by deal size tier
SELECT
CASE
WHEN Amount < 25000 THEN 'SMB (<$25K)'
WHEN Amount < 100000 THEN 'Mid-Market ($25K-$100K)'
WHEN Amount < 500000 THEN 'Enterprise ($100K-$500K)'
ELSE 'Strategic ($500K+)'
END as Deal_Tier,
COUNT(*) as Total,
SUM(CASE WHEN IsWon = true THEN 1 ELSE 0 END) as Wins,
ROUND(SUM(CASE WHEN IsWon = true THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) as Win_Rate,
AVG(DATEDIFF(days, CreatedDate, CloseDate)) as Avg_Cycle_Days
FROM Opportunity
WHERE IsClosed = true
AND CloseDate >= LAST_N_YEARS:2
GROUP BY 1
ORDER BY 1
-- Rep performance comparison
SELECT
Owner.Name,
COUNT(*) as Total_Deals,
SUM(CASE WHEN IsWon = true THEN 1 ELSE 0 END) as Wins,
ROUND(SUM(CASE WHEN IsWon = true THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) as Win_Rate,
SUM(CASE WHEN IsWon = true THEN Amount ELSE 0 END) as Won_Value,
AVG(CASE WHEN IsWon = true THEN Amount ELSE null END) as Avg_Won_Size
FROM Opportunity
WHERE IsClosed = true
AND CloseDate >= LAST_N_YEARS:1
GROUP BY Owner.Name
HAVING COUNT(*) >= 10
ORDER BY Win_Rate DESC
-- Loss reason analysis
SELECT
Loss_Reason__c,
COUNT(*) as Count,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 1) as Percentage,
SUM(Amount) as Lost_Value,
AVG(Amount) as Avg_Lost_Deal_Size
FROM Opportunity
WHERE IsWon = false
AND Loss_Reason__c != null
AND CloseDate >= LAST_N_YEARS:2
GROUP BY Loss_Reason__c
ORDER BY Count DESC
-- Factors correlated with winning
SELECT
'Champion Engaged' as Factor,
SUM(CASE WHEN IsWon = true AND Champion_Engaged__c = true THEN 1 ELSE 0 END) as Wins_With,
SUM(CASE WHEN IsWon = true AND Champion_Engaged__c = false THEN 1 ELSE 0 END) as Wins_Without,
SUM(CASE WHEN IsWon = false AND Champion_Engaged__c = true THEN 1 ELSE 0 END) as Losses_With,
SUM(CASE WHEN IsWon = false AND Champion_Engaged__c = false THEN 1 ELSE 0 END) as Losses_Without
FROM Opportunity
WHERE IsClosed = true
AND CloseDate >= LAST_N_YEARS:2
UNION ALL
SELECT
'Executive Sponsor' as Factor,
SUM(CASE WHEN IsWon = true AND Executive_Sponsor__c = true THEN 1 ELSE 0 END),
SUM(CASE WHEN IsWon = true AND Executive_Sponsor__c = false THEN 1 ELSE 0 END),
SUM(CASE WHEN IsWon = false AND Executive_Sponsor__c = true THEN 1 ELSE 0 END),
SUM(CASE WHEN IsWon = false AND Executive_Sponsor__c = false THEN 1 ELSE 0 END)
FROM Opportunity
WHERE IsClosed = true
AND CloseDate >= LAST_N_YEARS:2
function identifyWinPatterns(deals) {
const wonDeals = deals.filter(d => d.isWon);
const lostDeals = deals.filter(d => !d.isWon);
const patterns = [];
// Champion correlation
const wonWithChampion = wonDeals.filter(d => d.championEngaged).length / wonDeals.length;
const lostWithChampion = lostDeals.filter(d => d.championEngaged).length / lostDeals.length;
if (wonWithChampion > lostWithChampion * 1.5) {
patterns.push({
factor: 'Champion Engagement',
winRate_with: Math.round(wonWithChampion * 100),
winRate_without: Math.round((1 - wonWithChampion) * 100),
impact: 'high',
recommendation: 'Prioritize champion identification early in sales cycle'
});
}
// Sales cycle analysis
const avgWonCycle = average(wonDeals.map(d => d.salesCycleDays));
const avgLostCycle = average(lostDeals.map(d => d.salesCycleDays));
if (avgLostCycle > avgWonCycle * 1.3) {
patterns.push({
factor: 'Sales Cycle Length',
avgWon: Math.round(avgWonCycle),
avgLost: Math.round(avgLostCycle),
impact: 'medium',
recommendation: `Deals extending beyond ${Math.round(avgWonCycle * 1.3)} days show declining win probability`
});
}
// Meeting frequency
const avgWonMeetings = average(wonDeals.map(d => d.numberOfMeetings));
patterns.push({
factor: 'Meeting Cadence',
avgWonMeetings: Math.round(avgWonMeetings),
recommendation: `Winning deals average ${Math.round(avgWonMeetings)} meetings - maintain this cadence`
});
return patterns;
}
function analyzeCompetitivePosition(deals, targetCompetitor) {
const competitorDeals = deals.filter(d => d.competitor === targetCompetitor);
// Where we win
const wins = competitorDeals.filter(d => d.isWon);
const losses = competitorDeals.filter(d => !d.isWon);
// Analyze win characteristics
const winCharacteristics = {
avgDealSize: average(wins.map(d => d.amount)),
topIndustries: topN(wins, 'industry', 3),
topUseCases: topN(wins, 'useCase', 3),
avgStakeholders: average(wins.map(d => d.numberOfStakeholders))
};
// Analyze loss characteristics
const lossCharacteristics = {
avgDealSize: average(losses.map(d => d.amount)),
topLossReasons: topN(losses, 'lossReason', 3),
topIndustries: topN(losses, 'industry', 3)
};
return {
competitor: targetCompetitor,
overallWinRate: (wins.length / competitorDeals.length) * 100,
dealCount: competitorDeals.length,
whereWeWin: winCharacteristics,
whereWeLose: lossCharacteristics,
recommendations: generateCompetitiveRecommendations(winCharacteristics, lossCharacteristics)
};
}
{
"generated_date": "2026-01-25",
"analysis_period": "2024-01-01 to 2026-01-25",
"summary": {
"total_deals": 847,
"wins": 423,
"losses": 424,
"overall_win_rate": 49.9,
"total_won_value": 42300000,
"total_lost_value": 38100000,
"avg_won_deal_size": 100000,
"avg_lost_deal_size": 89858
},
"trends": {
"recent_quarter_win_rate": 52.3,
"trend": "improving",
"change_from_prior": 3.5
},
"by_competitor": {
"Competitor A": {
"win_rate": 62.5,
"deal_count": 48,
"strength": "strong",
"win_factors": ["Faster implementation", "Better pricing"],
"loss_factors": ["Feature gap in reporting"]
},
"Competitor B": {
"win_rate": 38.2,
"deal_count": 34,
"strength": "weak",
"win_factors": ["Enterprise security features"],
"loss_factors": ["Their integration ecosystem", "Brand recognition"]
}
},
"loss_reasons": {
"ranked": [
{ "reason": "Price/Budget", "count": 142, "percentage": 33.5, "trend": "stable" },
{ "reason": "Competitor", "count": 98, "percentage": 23.1, "trend": "increasing" },
{ "reason": "No Decision", "count": 87, "percentage": 20.5, "trend": "decreasing" },
{ "reason": "Feature Gap", "count": 52, "percentage": 12.3, "trend": "stable" },
{ "reason": "Timing", "count": 45, "percentage": 10.6, "trend": "stable" }
],
"insights": [
"Price losses concentrated in SMB segment - consider SMB-specific pricing",
"Competitor losses increasing - 65% are to Competitor B",
"No Decision rate improved 5pp QoQ - qualification tightening working"
]
},
"success_factors": [
{
"factor": "Champion Identified",
"win_rate_with": 68.3,
"win_rate_without": 31.2,
"lift": "37.1pp",
"recommendation": "Make champion identification a required stage exit criterion"
},
{
"factor": "Executive Sponsor",
"win_rate_with": 71.5,
"win_rate_without": 42.8,
"lift": "28.7pp",
"recommendation": "Engage exec sponsor for deals >$100K"
},
{
"factor": "Multiple Stakeholders (3+)",
"win_rate_with": 58.9,
"win_rate_without": 38.4,
"lift": "20.5pp",
"recommendation": "Expand stakeholder engagement in discovery"
}
],
"segment_analysis": {
"enterprise": {
"win_rate": 45.2,
"avg_deal_size": 285000,
"avg_cycle_days": 95,
"top_competitor": "Competitor B"
},
"mid_market": {
"win_rate": 52.8,
"avg_deal_size": 78000,
"avg_cycle_days": 45,
"top_competitor": "Competitor A"
},
"smb": {
"win_rate": 54.1,
"avg_deal_size": 22000,
"avg_cycle_days": 21,
"top_loss_reason": "Price/Budget"
}
},
"recommendations": [
{
"priority": "high",
"area": "Competitive Positioning",
"action": "Develop competitive battle card for Competitor B",
"rationale": "Win rate 38% and trending down - need countermeasures"
},
{
"priority": "high",
"area": "Sales Process",
"action": "Add champion identification to stage 2 exit criteria",
"rationale": "37pp win rate lift when champion engaged"
},
{
"priority": "medium",
"area": "Pricing",
"action": "Review SMB pricing - 33% of losses are price-related",
"rationale": "SMB price losses costing $3.2M annually"
}
]
}
Task({
subagent_type: 'opspal-salesforce:benchmark-research-agent',
prompt: `Research competitive positioning for ${competitor} - get industry win rate benchmarks`
});
When analyzing specific lost deals:
Task({
subagent_type: 'opspal-salesforce:sfdc-query-specialist',
prompt: `Pull complete activity history for opportunity ${oppId} to reconstruct deal timeline`
});
Before delivering analysis:
| Analysis | Frequency | Audience |
|---|---|---|
| Overall Win/Loss | Monthly | Sales Leadership |
| Competitive Analysis | Quarterly | Product, Marketing, Sales |
| Loss Reason Deep Dive | Monthly | Sales Ops |
| Rep Performance | Quarterly | Sales Management |
Share competitive insights:
// Export for competitive battle cards
const competitiveInsights = {
competitor: 'Competitor B',
ourStrengths: analysis.whereWeWin,
theirStrengths: analysis.whereWeLose,
talkingPoints: generateTalkingPoints(analysis)
};
Flag recurring feature gaps:
if (featureGapLosses > lossThreshold) {
Task({
subagent_type: 'opspal-core:asana-task-manager',
prompt: `Create product feedback: ${featureGap} costing ${lostValue} in deals`
});
}