mcpField
W
win-loss-reason-extraction

win-loss-reason-extraction

github.com/louisblythe营销与增长190412 次安装40 周前更新
代码仓库

关于 win-loss-reason-extraction

用于构建销售 Bot 的 Skill,可提取并分类交易赢单或丢单的原因,涵盖对话与模式或大模型提取、置信度检查、人工审核和分组趋势分析。

主要功能

  1. 01分类赢单与丢单原因
  2. 02从对话中提取原因
  3. 03使用模式或大模型分析
  4. 04评估置信度并标记审核项
  5. 05按细分市场或竞争对手分析趋势
  6. 06生成可执行洞察

适用场景

  1. 01归类丢单背后的原因
  2. 02将不确定的提取结果交由人工审核
  3. 03比较不同客户群体的丢单模式
  4. 04用交易原因更新 CRM 记录

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/louisblythe/sales-skills --skill win-loss-reason-extraction

安装后在 Claude Code 中输入/win-loss-reason-extraction确认已加载。

详细介绍

赢单/丢单原因提取

你是构建销售机器人的专家,能够自动提取并分类交易赢单或丢单的原因。你的目标是帮助开发者创建能够从结果中学习、提升未来业绩的系统。

为什么赢单/丢单原因提取很重要

知识鸿沟
Without extraction:
- "Why did we lose?" "I don't know"
- Same mistakes repeated
- No pattern visibility
- Intuition-based strategy

With extraction:
- Categorized loss reasons
- Trend identification
- Data-driven improvement
- Actionable insights

赢单原因类别

常见赢单原因
WIN_REASONS = {
    "product_fit": {
        "keywords": ["exactly what we need", "perfect fit", "solves our problem"],
        "indicators": ["feature_match_high", "use_case_alignment"]
    },
    "price_value": {
        "keywords": ["fair price", "good value", "worth it", "roi makes sense"],
        "indicators": ["price_accepted", "roi_discussed_positively"]
    },
    "trust_relationship": {
        "keywords": ["trust you", "great to work with", "understood us"],
        "indicators": ["high_engagement", "personal_connection"]
    },
    "competitive_advantage": {
        "keywords": ["better than", "chose you over", "differentiated"],
        "indicators": ["competitor_comparison_won", "unique_feature_mentioned"]
    },
    "timing": {
        "keywords": ["right time", "urgent need", "perfect timing"],
        "indicators": ["fast_sales_cycle", "urgency_expressed"]
    },
    "champion_advocacy": {
        "keywords": ["fought for", "convinced the team", "my recommendation"],
        "indicators": ["strong_champion", "internal_selling"]
    }
}

丢单原因类别

常见丢单原因
LOSS_REASONS = {
    "price": {
        "keywords": ["too expensive", "over budget", "cheaper option", "can't afford"],
        "indicators": ["price_objection", "budget_constraints"],
        "severity": "high"
    },
    "timing": {
        "keywords": ["bad timing", "not now", "maybe later", "next year"],
        "indicators": ["timing_objection", "delayed_decision"],
        "severity": "medium"
    },
    "competitor": {
        "keywords": ["went with", "chose", "competitor won", "using [competitor]"],
        "indicators": ["competitor_mentioned", "comparison_lost"],
        "severity": "high"
    },
    "no_decision": {
        "keywords": ["doing nothing", "staying with current", "not a priority"],
        "indicators": ["stalled", "no_urgency"],
        "severity": "medium"
    },
    "feature_gap": {
        "keywords": ["missing feature", "doesn't do", "need X that you don't have"],
        "indicators": ["feature_request_unmet", "requirement_gap"],
        "severity": "high"
    },
    "bad_fit": {
        "keywords": ["not right for us", "doesn't fit", "not what we need"],
        "indicators": ["poor_icp_match", "misaligned_use_case"],
        "severity": "medium"
    },
    "internal_issues": {
        "keywords": ["reorganizing", "merger", "budget freeze", "leadership change"],
        "indicators": ["external_factors", "company_change"],
        "severity": "low"
    },
    "lost_champion": {
        "keywords": ["contact left", "no longer there", "reporting changed"],
        "indicators": ["champion_departed", "stakeholder_change"],
        "severity": "medium"
    }
}

提取方法

对话分析
def extract_reasons_from_conversation(conversation, outcome):
    reasons = []

    # Analyze final messages
    final_messages = conversation.messages[-5:]

    for message in final_messages:
        if message.sender == "prospect":
            # Check against reason keywords
            if outcome == "lost":
                matched_reasons = match_loss_reasons(message.text)
            else:
                matched_reasons = match_win_reasons(message.text)

            reasons.extend(matched_reasons)

    # Analyze conversation patterns
    pattern_reasons = extract_pattern_based_reasons(conversation, outcome)
    reasons.extend(pattern_reasons)

    # Dedupe and rank
    return rank_reasons(reasons)

def match_loss_reasons(text):
    matches = []
    for reason_code, config in LOSS_REASONS.items():
        for keyword in config["keywords"]:
            if keyword.lower() in text.lower():
                matches.append({
                    "reason": reason_code,
                    "confidence": 0.8,
                    "source": "keyword_match",
                    "evidence": text
                })
    return matches
基于模式的提取
def extract_pattern_based_reasons(conversation, outcome):
    reasons = []

    if outcome == "lost":
        # Price patterns
        if had_price_objection(conversation) and not resolved_price_objection(conversation):
            reasons.append({
                "reason": "price",
                "confidence": 0.7,
                "source": "pattern",
                "evidence": "Unresolved price objection"
            })

        # Competitor patterns
        competitor = detect_competitor_mention(conversation)
        if competitor and conversation.last_stage == "evaluation":
            reasons.append({
                "reason": "competitor",
                "confidence": 0.6,
                "source": "pattern",
                "evidence": f"Competitor {competitor} mentioned during evaluation"
            })

        # No decision patterns
        if conversation.days_stalled > 30:
            reasons.append({
                "reason": "no_decision",
                "confidence": 0.5,
                "source": "pattern",
                "evidence": f"Deal stalled {conversation.days_stalled} days"
            })

    return reasons
基于大模型的提取
def extract_reasons_with_llm(conversation, outcome):
    prompt = f"""
    Analyze this {outcome} sales conversation and identify the primary
    reason for the outcome.

    Conversation:
    {format_conversation(conversation)}

    Provide:
    1. Primary reason (select from: {list_reasons(outcome)})
    2. Secondary reason (if applicable)
    3. Confidence level (high/medium/low)
    4. Evidence from conversation

    Format as JSON.
    """

    response = llm.generate(prompt)
    return parse_reason_response(response)

原因验证

置信度评分
def calculate_reason_confidence(reason, conversation):
    confidence = 0.5  # Base confidence

    # Direct statement bonus
    if reason["source"] == "explicit_statement":
        confidence += 0.3

    # Multiple signals bonus
    signal_count = count_supporting_signals(reason, conversation)
    confidence += min(signal_count * 0.1, 0.3)

    # Recency bonus (recent statements more reliable)
    if reason.get("message_index") and reason["message_index"] >= len(conversation.messages) - 3:
        confidence += 0.1

    # Cross-reference with outcome survey
    if matches_survey_response(reason, conversation.deal_id):
        confidence += 0.2

    return min(confidence, 1.0)
人工验证循环
def queue_for_validation(deal_id, extracted_reasons):
    """Flag uncertain extractions for human review"""

    needs_review = []
    for reason in extracted_reasons:
        if reason["confidence"] < 0.7:
            needs_review.append(reason)

    if needs_review:
        create_review_task(
            deal_id=deal_id,
            reasons=needs_review,
            priority="medium" if len(needs_review) == 1 else "high"
        )

汇总与分析

趋势分析
def analyze_loss_trends(time_period):
    losses = get_lost_deals(time_period)

    # Count by reason
    reason_counts = Counter()
    for deal in losses:
        for reason in deal.loss_reasons:
            reason_counts[reason["reason"]] += 1

    # Calculate percentages
    total = len(losses)
    reason_pcts = {r: c/total for r, c in reason_counts.items()}

    # Compare to previous period
    prev_period = get_previous_period(time_period)
    prev_reason_pcts = analyze_loss_trends(prev_period)

    # Identify significant changes
    trends = {}
    for reason, pct in reason_pcts.items():
        prev_pct = prev_reason_pcts.get(reason, 0)
        change = pct - prev_pct
        if abs(change) > 0.05:  # >5% change
            trends[reason] = {
                "current": pct,
                "previous": prev_pct,
                "change": change,
                "direction": "increasing" if change > 0 else "decreasing"
            }

    return {
        "distribution": reason_pcts,
        "trends": trends,
        "top_reasons": sorted(reason_pcts.items(), key=lambda x: -x[1])[:5]
    }
分段分析
def analyze_by_segment(time_period):
    """Analyze win/loss reasons by segment"""

    segments = ["smb", "mid_market", "enterprise"]
    analysis = {}

    for segment in segments:
        deals = get_deals(time_period, segment=segment)
        won = [d for d in deals if d.outcome == "won"]
        lost = [d for d in deals if d.outcome == "lost"]

        analysis[segment] = {
            "win_rate": len(won) / len(deals) if deals else 0,
            "top_win_reasons": get_top_reasons(won, "win"),
            "top_loss_reasons": get_top_reasons(lost, "loss"),
            "deal_count": len(deals)
        }

    return analysis
竞争对手分析
def analyze_competitor_losses(time_period):
    """Analyze losses to specific competitors"""

    competitor_losses = get_deals(
        time_period,
        outcome="lost",
        reason="competitor"
    )

    by_competitor = {}
    for deal in competitor_losses:
        competitor = deal.competitor_name
        if competitor not in by_competitor:
            by_competitor[competitor] = {
                "count": 0,
                "reasons": [],
                "deal_sizes": []
            }

        by_competitor[competitor]["count"] += 1
        by_competitor[competitor]["reasons"].extend(deal.loss_details)
        by_competitor[competitor]["deal_sizes"].append(deal.deal_size)

    # Summarize
    for competitor in by_competitor:
        by_competitor[competitor]["avg_deal_size"] = mean(
            by_competitor[competitor]["deal_sizes"]
        )
        by_competitor[competitor]["common_reasons"] = Counter(
            by_competitor[competitor]["reasons"]
        ).most_common(3)

    return by_competitor

可执行的洞察

洞察生成
def generate_win_loss_insights(analysis):
    insights = []

    # Price insight
    if analysis["top_loss_reasons"][0][0] == "price":
        insights.append({
            "type": "alert",
            "topic": "pricing",
            "insight": f"Price is top loss reason at {analysis['top_loss_reasons'][0][1]:.0%}",
            "recommendation": "Review pricing strategy or value communication"
        })

    # Competitor insight
    competitor_losses = [r for r in analysis["distribution"] if r.startswith("competitor_")]
    if sum(analysis["distribution"].get(r, 0) for r in competitor_losses) > 0.3:
        insights.append({
            "type": "alert",
            "topic": "competitive",
            "insight": "Over 30% of losses to competitors",
            "recommendation": "Update competitive positioning and battlecards"
        })

    # Win insight
    if analysis.get("win_analysis", {}).get("top_win_reasons", []):
        top_win = analysis["win_analysis"]["top_win_reasons"][0]
        insights.append({
            "type": "positive",
            "topic": "winning",
            "insight": f"Primary win driver: {top_win[0]} ({top_win[1]:.0%})",
            "recommendation": f"Emphasize {top_win[0]} in messaging"
        })

    return insights

集成

CRM 更新
def update_crm_with_reasons(deal_id, reasons):
    """Update CRM with extracted reasons"""

    primary_reason = reasons[0] if reasons else None
    secondary_reason = reasons[1] if len(reasons) > 1 else None

    crm_update = {
        "Loss_Reason__c": primary_reason["reason"] if primary_reason else None,
        "Loss_Reason_Secondary__c": secondary_reason["reason"] if secondary_reason else None,
        "Loss_Details__c": "; ".join([r["evidence"] for r in reasons]),
        "Competitor_Lost_To__c": extract_competitor(reasons),
        "Reason_Confidence__c": primary_reason["confidence"] if primary_reason else None
    }

    crm_client.update_opportunity(deal_id, crm_update)

评论

支持 Markdown