propensity-scoring-realtime
Installation
SKILL.md
Propensity Scoring in Real-Time
You are an expert in building sales bots that dynamically update lead scores as conversations progress. Your goal is to help developers create systems that re-evaluate prospect quality with every interaction.
Why Real-Time Scoring Matters
Static Scoring Limitations
Traditional lead scoring:
- Scored once at creation
- Based on firmographics only
- Doesn't reflect engagement
- Score stale by first conversation
Lead created at score 75 (based on company size).
Three weeks later, still 75, even after:
- Ignored 5 emails
- Bounced from website
- Never responded
Should be much lower.
Real-Time Scoring
Dynamic scoring:
- Updates with every signal
- Reflects actual engagement
- Predicts current likelihood
- Enables smart prioritization
Lead starts at 75.
After engagement signals:
- Opened 3 emails: +10
- Clicked pricing: +15
- Asked about timeline: +20
- Mentioned budget: +25
New score: 145 (high priority)
Scoring Components
Firmographic Baseline
Starting score based on fit:
Company size:
- <10 employees: +10
- 10-50: +20
- 51-200: +30
- 201-1000: +40
- 1000+: +50
Industry fit:
- Ideal industry: +30
- Good fit: +20
- Neutral: +10
- Poor fit: 0
Title/Role:
- Decision maker: +30
- Influencer: +20
- User: +10
- Unknown: +5
Engagement Signals
Real-time adjustments:
Email engagement:
- Opened: +5 per open
- Clicked: +10 per click
- Replied: +25
- Positive reply: +40
Website behavior:
- Page visit: +3
- Pricing page: +15
- Demo page: +20
- Multiple pages: +5 per page
- Return visit: +10
Content engagement:
- Downloaded: +15
- Video watched: +10 per 25% completion
- Webinar registered: +20
- Webinar attended: +35
Conversation Signals
From bot conversations:
Questions asked:
- Feature question: +5
- Pricing question: +20
- Timeline question: +25
- Implementation: +20
- Competitor mention: +15
Buying signals:
- "We need": +15
- "When can we": +20
- "Who else uses": +10
- "What's the process": +25
- Budget mentioned: +30
Objection signals:
- Price objection: -5 (but engaged)
- Timing objection: -10
- "Not interested": -30
- Unsubscribe request: -50
Real-Time Implementation
Score Calculation Engine
class PropensityScorer:
def __init__(self, prospect):
self.prospect = prospect
self.base_score = self.calculate_base_score()
self.engagement_score = 0
self.conversation_score = 0
self.decay_factor = 0
def calculate_base_score(self):
score = 0
score += COMPANY_SIZE_SCORES.get(self.prospect.company_size_bucket, 10)
score += INDUSTRY_SCORES.get(self.prospect.industry, 10)
score += TITLE_SCORES.get(self.prospect.title_level, 5)
return score
def update_on_event(self, event):
"""Called in real-time when events occur"""
# Get score delta for event type
delta = EVENT_SCORES.get(event.type, 0)
# Apply context multipliers
if event.recency < timedelta(hours=1):
delta *= 1.5 # Recent activity bonus
if event.context.get("high_intent_page"):
delta *= 1.3
# Update appropriate component
if event.category == "engagement":
self.engagement_score += delta
elif event.category == "conversation":
self.conversation_score += delta
# Apply decay to old scores
self.apply_decay()
# Emit score update event
self.emit_score_change()
def get_current_score(self):
return (
self.base_score +
self.engagement_score +
self.conversation_score -
self.decay_factor
)
def get_score_tier(self):
score = self.get_current_score()
if score >= 100:
return "hot"
elif score >= 60:
return "warm"
elif score >= 30:
return "cool"
else:
return "cold"
Event Processing Pipeline
class ScoreEventProcessor:
def __init__(self):
self.scorers = {}
def process_event(self, event):
prospect_id = event.prospect_id
# Get or create scorer
if prospect_id not in self.scorers:
prospect = load_prospect(prospect_id)
self.scorers[prospect_id] = PropensityScorer(prospect)
scorer = self.scorers[prospect_id]
# Update score
old_score = scorer.get_current_score()
scorer.update_on_event(event)
new_score = scorer.get_current_score()
# Check for tier changes
old_tier = get_tier(old_score)
new_tier = get_tier(new_score)
if new_tier != old_tier:
self.handle_tier_change(prospect_id, old_tier, new_tier)
# Persist score
update_prospect_score(prospect_id, new_score, new_tier)
return {
"prospect_id": prospect_id,
"old_score": old_score,
"new_score": new_score,
"tier_change": new_tier != old_tier
}
def handle_tier_change(self, prospect_id, old_tier, new_tier):
if new_tier == "hot" and old_tier != "hot":
# Alert rep
notify_rep(prospect_id, "Lead became hot")
# Accelerate sequence
accelerate_outreach(prospect_id)
elif new_tier == "cold" and old_tier != "cold":
# Slow down outreach
decelerate_outreach(prospect_id)
Conversation Score Updates
def update_score_from_message(conversation_id, message):
"""Update score based on conversation content"""
prospect_id = get_prospect_id(conversation_id)
scorer = get_scorer(prospect_id)
# Analyze message for signals
signals = analyze_message_signals(message)
for signal in signals:
event = ConversationEvent(
type=signal.type,
category="conversation",
value=signal.value,
context=signal.context
)
scorer.update_on_event(event)
return scorer.get_current_score()
def analyze_message_signals(message):
signals = []
# Buying signals
buying_patterns = [
(r"what('s| is) the (price|cost|pricing)", "pricing_question", 20),
(r"when can we (start|begin|implement)", "timeline_question", 25),
(r"(we need|we're looking for)", "stated_need", 15),
(r"who else (uses|is using)", "social_proof_request", 10),
(r"(budget|allocated|set aside)", "budget_mention", 30)
]
for pattern, signal_type, value in buying_patterns:
if re.search(pattern, message.lower()):
signals.append(Signal(type=signal_type, value=value))
# Negative signals
negative_patterns = [
(r"not interested", "not_interested", -30),
(r"stop (emailing|contacting)", "opt_out_request", -50),
(r"too expensive", "price_objection", -5),
(r"maybe (later|next year)", "timing_objection", -10)
]
for pattern, signal_type, value in negative_patterns:
if re.search(pattern, message.lower()):
signals.append(Signal(type=signal_type, value=value))
return signals
Score Decay
Time-Based Decay
def calculate_decay(last_activity, base_decay_rate=0.02):
"""Score decays over time without activity"""
days_since_activity = (now() - last_activity).days
# No decay for recent activity
if days_since_activity < 7:
return 0
# Gradual decay
decay = (days_since_activity - 7) * base_decay_rate
# Cap decay
return min(decay, 0.5) # Max 50% decay
Engagement Decay
def decay_engagement_score(scorer):
"""Recent engagement matters more"""
events = scorer.get_engagement_events()
decayed_score = 0
for event in events:
age_days = (now() - event.timestamp).days
if age_days < 7:
weight = 1.0
elif age_days < 30:
weight = 0.7
elif age_days < 90:
weight = 0.4
else:
weight = 0.1
decayed_score += event.score_delta * weight
return decayed_score
Score-Based Actions
Routing by Score
def route_prospect(prospect):
score = prospect.current_score
tier = prospect.score_tier
if tier == "hot":
return {
"queue": "immediate_follow_up",
"assignee": "senior_ae",
"urgency": "high",
"action": "call_within_1_hour"
}
elif tier == "warm":
return {
"queue": "standard_follow_up",
"assignee": "sdr",
"urgency": "medium",
"action": "email_within_24_hours"
}
elif tier == "cool":
return {
"queue": "nurture_sequence",
"assignee": "bot",
"urgency": "low",
"action": "automated_nurture"
}
else: # cold
return {
"queue": "re_engagement",
"assignee": "bot",
"urgency": "lowest",
"action": "monthly_check_in"
}
Dynamic Sequence Adjustment
def adjust_sequence_for_score(prospect, sequence):
score = prospect.current_score
if score >= 100:
# High intent - accelerate
return modify_sequence(sequence,
interval_multiplier=0.5, # Faster
add_phone_touches=True,
urgency_messaging=True
)
elif score >= 60:
# Warm - standard pace
return sequence
elif score >= 30:
# Cool - slow down
return modify_sequence(sequence,
interval_multiplier=1.5, # Slower
value_focused_messaging=True
)
else:
# Cold - minimal contact
return modify_sequence(sequence,
interval_multiplier=3.0, # Much slower
re_engagement_messaging=True
)
Visualization & Reporting
Score Timeline
def get_score_timeline(prospect_id, days=30):
"""Visualize score changes over time"""
events = get_score_events(prospect_id, days=days)
timeline = []
running_score = get_initial_score(prospect_id, days)
for event in events:
running_score += event.delta
timeline.append({
"timestamp": event.timestamp,
"score": running_score,
"event": event.type,
"delta": event.delta
})
return timeline
# Output for charting:
# [
# {"timestamp": "2024-01-01", "score": 50, "event": "created", "delta": 50},
# {"timestamp": "2024-01-05", "score": 65, "event": "email_opened", "delta": 15},
# {"timestamp": "2024-01-06", "score": 90, "event": "pricing_question", "delta": 25},
# ...
# ]
Score Distribution Report
def generate_score_report():
all_prospects = get_all_active_prospects()
return {
"distribution": {
"hot": len([p for p in all_prospects if p.tier == "hot"]),
"warm": len([p for p in all_prospects if p.tier == "warm"]),
"cool": len([p for p in all_prospects if p.tier == "cool"]),
"cold": len([p for p in all_prospects if p.tier == "cold"])
},
"average_score": mean([p.score for p in all_prospects]),
"score_changes_today": count_tier_changes(today()),
"top_movers": get_biggest_score_increases(limit=10),
"at_risk": get_biggest_score_decreases(limit=10)
}
Model Calibration
Score Validation
def validate_score_model():
"""Check if scores predict outcomes"""
# Get closed deals
won = get_closed_won_last_90_days()
lost = get_closed_lost_last_90_days()
# Analyze scores at various stages
won_scores_at_qualification = [d.score_at_stage("qualified") for d in won]
lost_scores_at_qualification = [d.score_at_stage("qualified") for d in lost]
# Scores should differentiate winners from losers
avg_won = mean(won_scores_at_qualification)
avg_lost = mean(lost_scores_at_qualification)
if avg_won <= avg_lost:
alert("Score model not predictive - won deals don't score higher")
# Check tier conversion rates
for tier in ["hot", "warm", "cool", "cold"]:
conversion = conversion_rate_by_tier(tier)
expected = EXPECTED_CONVERSION_BY_TIER[tier]
if abs(conversion - expected) > 0.1:
alert(f"Tier {tier} conversion {conversion} differs from expected {expected}")
Weekly Installs
6
Repository
louisblythe/salesskillsGitHub Stars
11
First Seen
Mar 18, 2026
Security Audits