Skip to main content

Scoring Pipeline

Build a multi-factor scoring system with conditional logic - essential for lead scoring, risk assessment, and ranking systems.

Use Case

Business Applications

Multi-factor scoring is used across industries: lead scoring (sales), credit risk (finance), recommendation ranking (e-commerce), and content quality (social media).

You need to score entities based on multiple factors:

  • Combine numeric scores with weights
  • Apply conditional adjustments
  • Classify into tiers based on final score

This pattern is used in lead scoring, credit risk, recommendation ranking, and more.

The Pipeline

# scoring-pipeline.cst
# Multi-factor scoring with conditional tier assignment

use stdlib.math
use stdlib.comparison

type Candidate = {
id: String,
engagementScore: Int,
recencyScore: Int,
frequencyScore: Int
}

type Weights = {
engagementWeight: Int,
recencyWeight: Int,
frequencyWeight: Int,
threshold: Int
}

in candidate: Candidate
in weights: Weights

# Calculate weighted component scores
engagementContrib = multiply(candidate.engagementScore, weights.engagementWeight)
recencyContrib = multiply(candidate.recencyScore, weights.recencyWeight)
frequencyContrib = multiply(candidate.frequencyScore, weights.frequencyWeight)

# Combine into total score
step1 = add(engagementContrib, recencyContrib)
totalScore = add(step1, frequencyContrib)

# Determine if above threshold
isQualified = gte(totalScore, weights.threshold)

out totalScore
out isQualified

Explanation

ComponentDescription
Weighted scoresMultiply raw scores by configurable weights
Score combinationAdd weighted components together
Threshold checkCompare against configurable threshold

Running the Example

Input

{
"candidate": {
"id": "lead-123",
"engagementScore": 8,
"recencyScore": 6,
"frequencyScore": 4
},
"weights": {
"engagementWeight": 3,
"recencyWeight": 2,
"frequencyWeight": 1,
"threshold": 40
}
}

Calculation

  • Engagement: 8 * 3 = 24
  • Recency: 6 * 2 = 12
  • Frequency: 4 * 1 = 4
  • Total: 24 + 12 + 4 = 40
  • Qualified: 40 >= 40 = true

Expected Output

{
"totalScore": 40,
"isQualified": true
}

Variations

With Conditional Bonus

A/B Testing

Conditional adjustments are ideal for A/B testing different scoring strategies. Pass the variant as a boolean input and apply different bonuses accordingly.

Apply conditional score adjustments:

use stdlib.math
use stdlib.comparison

in baseScore: Int
in isPremium: Boolean
in bonusAmount: Int

# Apply bonus only if premium
bonus = if (isPremium) bonusAmount else 0
finalScore = add(baseScore, bonus)

out finalScore

Multi-tier Classification

Classify into multiple tiers:

use stdlib.comparison

in score: Int

# Tier thresholds
isHot = gte(score, 80)
isWarm = and(gte(score, 50), lt(score, 80))
isCold = lt(score, 50)

out isHot
out isWarm
out isCold

Risk Scoring

Calculate risk with multiple factors:

use stdlib.math
use stdlib.comparison

type RiskFactors = {
creditScore: Int,
debtRatio: Int,
paymentHistory: Int
}

in factors: RiskFactors
in maxRisk: Int

# Lower credit score = higher risk
creditRisk = subtract(100, factors.creditScore)

# Higher debt ratio = higher risk
debtRisk = factors.debtRatio

# Lower payment history = higher risk
historyRisk = subtract(100, factors.paymentHistory)

# Combine risks (simple average approximation)
combined = add(creditRisk, debtRisk)
totalRisk = add(combined, historyRisk)

# Check if exceeds threshold
isHighRisk = gt(totalRisk, maxRisk)

out totalRisk
out isHighRisk

Batch Scoring with Enrichment

Score batched candidates with context:

use stdlib.math

type Item = {
id: String,
relevanceScore: Int,
qualityScore: Int
}

type Context = {
boostFactor: Int
}

in items: Candidates<Item>
in context: Context

# Enrich each item with context
enriched = items + context

# Select for output (scoring would happen in custom module)
output = enriched[id, relevanceScore, qualityScore, boostFactor]

out output

Score Normalization

Boundary Conditions

Always clamp normalized scores to valid ranges. Raw calculations can produce values outside 0-100 due to outliers in the input data.

Normalize scores to a standard range:

use stdlib.math
use stdlib.comparison

in rawScore: Int
in minPossible: Int
in maxPossible: Int

# Calculate range
range = subtract(maxPossible, minPossible)

# Normalize to 0-100 scale
adjusted = subtract(rawScore, minPossible)
normalized = divide(multiply(adjusted, 100), range)

# Clamp to valid range
tooLow = lt(normalized, 0)
tooHigh = gt(normalized, 100)
clampedLow = if (tooLow) 0 else normalized
finalScore = if (tooHigh) 100 else clampedLow

out finalScore

Scoring Patterns

Linear Combination

score = w1*f1 + w2*f2 + w3*f3

Most common pattern. Each factor contributes proportionally.

Threshold Gates

qualified = (score1 >= t1) AND (score2 >= t2)

All conditions must be met.

Tiered Classification

tier = HOT if score >= 80
= WARM if score >= 50
= COLD otherwise

Mutually exclusive categories.

Conditional Adjustments

finalScore = baseScore + (isPremium ? bonus : 0)

Apply adjustments based on flags.

Real-World Applications

Lead Scoring

  • Engagement + recency + fit = lead quality
  • Classify into sales-ready, nurture, discard

Credit Risk

  • Payment history + debt ratio + credit age = risk score
  • Approve/review/decline decisions

Search Ranking

  • Relevance + freshness + authority = rank
  • Apply personalization boosts

Content Recommendations

  • User affinity + item quality + freshness = recommendation score
  • Filter by minimum threshold

Best Practices

Explainability

Log intermediate scores (each component contribution) to make the final score explainable. This is critical for debugging and for regulated industries requiring score justification.

  1. Externalize weights: Make weights configurable, not hardcoded
  2. Document thresholds: Explain what each threshold means
  3. Test edge cases: Zero scores, negative values, overflow
  4. Log intermediate scores: For debugging and explainability