Update botmodels

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-12-21 23:40:41 -03:00
parent 178a088902
commit f7690e1d81

View file

@ -27,11 +27,6 @@ logger = get_logger("scoring")
router = APIRouter(prefix="/scoring", tags=["Lead Scoring"]) router = APIRouter(prefix="/scoring", tags=["Lead Scoring"])
# ============================================================================
# Request/Response Models
# ============================================================================
class LeadProfile(BaseModel): class LeadProfile(BaseModel):
"""Lead profile information for scoring""" """Lead profile information for scoring"""
@ -118,42 +113,31 @@ class ModelInfoResponse(BaseModel):
accuracy_metrics: Dict[str, float] accuracy_metrics: Dict[str, float]
# ============================================================================
# Scoring Configuration
# ============================================================================
class ScoringWeights: class ScoringWeights:
"""Default weights for scoring components""" """Default weights for scoring components"""
# Demographic factors
COMPANY_SIZE_WEIGHT = 10.0 COMPANY_SIZE_WEIGHT = 10.0
INDUSTRY_MATCH_WEIGHT = 15.0 INDUSTRY_MATCH_WEIGHT = 15.0
LOCATION_MATCH_WEIGHT = 5.0 LOCATION_MATCH_WEIGHT = 5.0
JOB_TITLE_WEIGHT = 15.0 JOB_TITLE_WEIGHT = 15.0
# Behavioral factors
EMAIL_OPENS_WEIGHT = 5.0 EMAIL_OPENS_WEIGHT = 5.0
EMAIL_CLICKS_WEIGHT = 10.0 EMAIL_CLICKS_WEIGHT = 10.0
PAGE_VISITS_WEIGHT = 5.0 PAGE_VISITS_WEIGHT = 5.0
FORM_SUBMISSIONS_WEIGHT = 15.0 FORM_SUBMISSIONS_WEIGHT = 15.0
CONTENT_DOWNLOADS_WEIGHT = 10.0 CONTENT_DOWNLOADS_WEIGHT = 10.0
# Engagement factors
RESPONSE_TIME_WEIGHT = 10.0 RESPONSE_TIME_WEIGHT = 10.0
INTERACTION_FREQUENCY_WEIGHT = 10.0 INTERACTION_FREQUENCY_WEIGHT = 10.0
SESSION_DURATION_WEIGHT = 5.0 SESSION_DURATION_WEIGHT = 5.0
# Intent signals
PRICING_PAGE_WEIGHT = 20.0 PRICING_PAGE_WEIGHT = 20.0
DEMO_REQUEST_WEIGHT = 25.0 DEMO_REQUEST_WEIGHT = 25.0
TRIAL_SIGNUP_WEIGHT = 30.0 TRIAL_SIGNUP_WEIGHT = 30.0
# Penalties
INACTIVITY_PENALTY = -15.0 INACTIVITY_PENALTY = -15.0
# Target industries and titles for scoring
TARGET_INDUSTRIES = { TARGET_INDUSTRIES = {
"technology": 1.0, "technology": 1.0,
"software": 1.0, "software": 1.0,
@ -202,17 +186,11 @@ COMPANY_SIZE_SCORES = {
} }
# ============================================================================
# Scoring Logic
# ============================================================================
def calculate_demographic_score(profile: LeadProfile) -> float: def calculate_demographic_score(profile: LeadProfile) -> float:
"""Calculate demographic component of lead score""" """Calculate demographic component of lead score"""
score = 0.0 score = 0.0
weights = ScoringWeights() weights = ScoringWeights()
# Company size scoring
if profile.company_size: if profile.company_size:
size_lower = profile.company_size.lower() size_lower = profile.company_size.lower()
for key, value in COMPANY_SIZE_SCORES.items(): for key, value in COMPANY_SIZE_SCORES.items():
@ -222,7 +200,6 @@ def calculate_demographic_score(profile: LeadProfile) -> float:
else: else:
score += 0.3 * weights.COMPANY_SIZE_WEIGHT score += 0.3 * weights.COMPANY_SIZE_WEIGHT
# Industry scoring
if profile.industry: if profile.industry:
industry_lower = profile.industry.lower() industry_lower = profile.industry.lower()
for key, value in TARGET_INDUSTRIES.items(): for key, value in TARGET_INDUSTRIES.items():
@ -232,16 +209,14 @@ def calculate_demographic_score(profile: LeadProfile) -> float:
else: else:
score += 0.4 * weights.INDUSTRY_MATCH_WEIGHT score += 0.4 * weights.INDUSTRY_MATCH_WEIGHT
# Job title scoring
if profile.job_title: if profile.job_title:
title_lower = profile.job_title.lower() title_lower = profile.job_title.lower()
title_score = 0.3 # default title_score = 0.3
for key, value in TITLE_SCORES.items(): for key, value in TITLE_SCORES.items():
if key in title_lower: if key in title_lower:
title_score = max(title_score, value) title_score = max(title_score, value)
score += title_score * weights.JOB_TITLE_WEIGHT score += title_score * weights.JOB_TITLE_WEIGHT
# Location scoring (simplified)
if profile.location: if profile.location:
score += 0.5 * weights.LOCATION_MATCH_WEIGHT score += 0.5 * weights.LOCATION_MATCH_WEIGHT
@ -253,22 +228,18 @@ def calculate_behavioral_score(behavior: LeadBehavior) -> float:
score = 0.0 score = 0.0
weights = ScoringWeights() weights = ScoringWeights()
# Email engagement
email_open_score = min(behavior.email_opens / 10.0, 1.0) email_open_score = min(behavior.email_opens / 10.0, 1.0)
score += email_open_score * weights.EMAIL_OPENS_WEIGHT score += email_open_score * weights.EMAIL_OPENS_WEIGHT
email_click_score = min(behavior.email_clicks / 5.0, 1.0) email_click_score = min(behavior.email_clicks / 5.0, 1.0)
score += email_click_score * weights.EMAIL_CLICKS_WEIGHT score += email_click_score * weights.EMAIL_CLICKS_WEIGHT
# Page visits
visit_score = min(behavior.page_visits / 20.0, 1.0) visit_score = min(behavior.page_visits / 20.0, 1.0)
score += visit_score * weights.PAGE_VISITS_WEIGHT score += visit_score * weights.PAGE_VISITS_WEIGHT
# Form submissions
form_score = min(behavior.form_submissions / 3.0, 1.0) form_score = min(behavior.form_submissions / 3.0, 1.0)
score += form_score * weights.FORM_SUBMISSIONS_WEIGHT score += form_score * weights.FORM_SUBMISSIONS_WEIGHT
# Content downloads
download_score = min(behavior.content_downloads / 5.0, 1.0) download_score = min(behavior.content_downloads / 5.0, 1.0)
score += download_score * weights.CONTENT_DOWNLOADS_WEIGHT score += download_score * weights.CONTENT_DOWNLOADS_WEIGHT
@ -280,15 +251,12 @@ def calculate_engagement_score(behavior: LeadBehavior) -> float:
score = 0.0 score = 0.0
weights = ScoringWeights() weights = ScoringWeights()
# Interaction frequency
frequency_score = min(behavior.total_sessions / 10.0, 1.0) frequency_score = min(behavior.total_sessions / 10.0, 1.0)
score += frequency_score * weights.INTERACTION_FREQUENCY_WEIGHT score += frequency_score * weights.INTERACTION_FREQUENCY_WEIGHT
# Session duration (5 min = max score)
duration_score = min(behavior.avg_session_duration / 300.0, 1.0) duration_score = min(behavior.avg_session_duration / 300.0, 1.0)
score += duration_score * weights.SESSION_DURATION_WEIGHT score += duration_score * weights.SESSION_DURATION_WEIGHT
# Recency scoring
if behavior.days_since_last_activity is not None: if behavior.days_since_last_activity is not None:
days = behavior.days_since_last_activity days = behavior.days_since_last_activity
if days <= 1: if days <= 1:
@ -313,16 +281,13 @@ def calculate_intent_score(behavior: LeadBehavior) -> float:
score = 0.0 score = 0.0
weights = ScoringWeights() weights = ScoringWeights()
# Pricing page visits
if behavior.pricing_page_visits > 0: if behavior.pricing_page_visits > 0:
pricing_score = min(behavior.pricing_page_visits / 3.0, 1.0) pricing_score = min(behavior.pricing_page_visits / 3.0, 1.0)
score += pricing_score * weights.PRICING_PAGE_WEIGHT score += pricing_score * weights.PRICING_PAGE_WEIGHT
# Demo requests
if behavior.demo_requests > 0: if behavior.demo_requests > 0:
score += weights.DEMO_REQUEST_WEIGHT score += weights.DEMO_REQUEST_WEIGHT
# Trial signups
if behavior.trial_signups > 0: if behavior.trial_signups > 0:
score += weights.TRIAL_SIGNUP_WEIGHT score += weights.TRIAL_SIGNUP_WEIGHT
@ -334,7 +299,6 @@ def calculate_penalty_score(behavior: LeadBehavior) -> float:
penalty = 0.0 penalty = 0.0
weights = ScoringWeights() weights = ScoringWeights()
# Inactivity penalty
if behavior.days_since_last_activity is not None: if behavior.days_since_last_activity is not None:
if behavior.days_since_last_activity > 60: if behavior.days_since_last_activity > 60:
penalty += weights.INACTIVITY_PENALTY penalty += weights.INACTIVITY_PENALTY
@ -365,9 +329,9 @@ def get_qualification_status(
) -> str: ) -> str:
"""Determine qualification status""" """Determine qualification status"""
if has_trial or score >= 90: if has_trial or score >= 90:
return "sql" # Sales Qualified Lead return "sql"
elif has_demo or score >= 70: elif has_demo or score >= 70:
return "mql" # Marketing Qualified Lead return "mql"
else: else:
return "unqualified" return "unqualified"
@ -378,7 +342,6 @@ def generate_recommendations(
"""Generate actionable recommendations for the lead""" """Generate actionable recommendations for the lead"""
recommendations = [] recommendations = []
# Score-based recommendations
if score >= 80: if score >= 80:
recommendations.append("Hot lead! Prioritize immediate sales outreach.") recommendations.append("Hot lead! Prioritize immediate sales outreach.")
elif score >= 60: elif score >= 60:
@ -388,7 +351,6 @@ def generate_recommendations(
else: else:
recommendations.append("Low priority - add to nurturing campaign.") recommendations.append("Low priority - add to nurturing campaign.")
# Behavior-based recommendations
if behavior.pricing_page_visits > 0 and behavior.demo_requests == 0: if behavior.pricing_page_visits > 0 and behavior.demo_requests == 0:
recommendations.append("Visited pricing page - send personalized demo invite.") recommendations.append("Visited pricing page - send personalized demo invite.")
@ -400,14 +362,12 @@ def generate_recommendations(
if behavior.email_opens > 5 and behavior.email_clicks < 2: if behavior.email_opens > 5 and behavior.email_clicks < 2:
recommendations.append("Opens emails but doesn't click - try different CTAs.") recommendations.append("Opens emails but doesn't click - try different CTAs.")
# Profile-based recommendations
if not profile.company: if not profile.company:
recommendations.append("Missing company info - enrich profile data.") recommendations.append("Missing company info - enrich profile data.")
if not profile.job_title: if not profile.job_title:
recommendations.append("Unknown job title - request more information.") recommendations.append("Unknown job title - request more information.")
# Engagement recommendations
if behavior.days_since_last_activity and behavior.days_since_last_activity > 14: if behavior.days_since_last_activity and behavior.days_since_last_activity > 14:
recommendations.append("Inactive for 2+ weeks - send re-engagement email.") recommendations.append("Inactive for 2+ weeks - send re-engagement email.")
@ -419,14 +379,12 @@ def score_lead(request: ScoreLeadRequest) -> LeadScoreResponse:
profile = request.profile profile = request.profile
behavior = request.behavior or LeadBehavior() behavior = request.behavior or LeadBehavior()
# Calculate component scores
demographic_score = calculate_demographic_score(profile) demographic_score = calculate_demographic_score(profile)
behavioral_score = calculate_behavioral_score(behavior) behavioral_score = calculate_behavioral_score(behavior)
engagement_score = calculate_engagement_score(behavior) engagement_score = calculate_engagement_score(behavior)
intent_score = calculate_intent_score(behavior) intent_score = calculate_intent_score(behavior)
penalty_score = calculate_penalty_score(behavior) penalty_score = calculate_penalty_score(behavior)
# Calculate total score
raw_score = ( raw_score = (
demographic_score demographic_score
+ behavioral_score + behavioral_score
@ -436,7 +394,6 @@ def score_lead(request: ScoreLeadRequest) -> LeadScoreResponse:
) )
total_score = max(0, min(100, raw_score)) total_score = max(0, min(100, raw_score))
# Determine grade and status
grade = get_grade(total_score) grade = get_grade(total_score)
qualification_status = get_qualification_status( qualification_status = get_qualification_status(
total_score, total_score,
@ -444,12 +401,10 @@ def score_lead(request: ScoreLeadRequest) -> LeadScoreResponse:
has_trial=behavior.trial_signups > 0, has_trial=behavior.trial_signups > 0,
) )
# Generate recommendations
recommendations = [] recommendations = []
if request.include_recommendations: if request.include_recommendations:
recommendations = generate_recommendations(profile, behavior, total_score) recommendations = generate_recommendations(profile, behavior, total_score)
# Calculate confidence based on data completeness
data_points = sum( data_points = sum(
[ [
1 if profile.email else 0, 1 if profile.email else 0,
@ -482,11 +437,6 @@ def score_lead(request: ScoreLeadRequest) -> LeadScoreResponse:
) )
# ============================================================================
# API Endpoints
# ============================================================================
@router.post("/score", response_model=LeadScoreResponse) @router.post("/score", response_model=LeadScoreResponse)
async def calculate_lead_score( async def calculate_lead_score(
request: ScoreLeadRequest, request: ScoreLeadRequest,
@ -551,7 +501,6 @@ async def batch_score_leads(
scores = [score_lead(lead_request) for lead_request in request.leads] scores = [score_lead(lead_request) for lead_request in request.leads]
# Calculate statistics
total_score = sum(s.total_score for s in scores) total_score = sum(s.total_score for s in scores)
avg_score = total_score / len(scores) if scores else 0 avg_score = total_score / len(scores) if scores else 0