///|
/// A stable score vector used by dashboards and downstream decision rules.
pub struct ScoreVector {
recency : Int
frequency : Int
monetary : Int
composite : Int
}
///|
pub struct CustomerInsight {
customer_id : String
scores : ScoreVector
segment : Segment
churn : ChurnScore
lifetime : ValueEstimate
priority : Int
actions : Array[String]
}
///|
fn clamp(value : Int, low : Int, high : Int) -> Int {
if value < low {
low
} else if value > high {
high
} else {
value
}
}
///|
fn priority_for(
segment : Segment,
churn : ChurnScore,
value : ValueEstimate,
) -> Int {
let segment_weight = match segment {
Champions => 40
Loyal => 30
AtRisk => 35
Dormant => 20
New => 15
}
clamp(segment_weight + churn.score / 2 + value.estimated_value / 100, 0, 100)
}
///|
fn actions_for(segment : Segment, churn : ChurnScore) -> Array[String] {
let actions : Array[String] = []
match segment {
Champions => {
actions.push("invite to referral program")
actions.push("offer early access")
}
Loyal => {
actions.push("recommend a complementary product")
actions.push("start a loyalty reminder")
}
AtRisk => {
actions.push("send a win-back message")
actions.push("review recent service issues")
}
Dormant => actions.push("use a low-cost reactivation test")
New => actions.push("complete onboarding sequence")
}
if churn.score >= 70 {
actions.push("assign retention owner")
}
actions
}
///|
/// Combine existing deterministic analyses into one operational insight.
pub fn build_insight(
metrics : CustomerMetrics,
config : ScoringConfig,
) -> CustomerInsight {
let lifetime = estimate_lifetime_value(metrics)
let churn = score_churn(metrics, config)
let scores = {
recency: metrics.r_score,
frequency: metrics.f_score,
monetary: metrics.m_score,
composite: (metrics.r_score + metrics.f_score + metrics.m_score) / 3,
}
{
customer_id: metrics.customer_id,
scores,
segment: metrics.segment,
churn,
lifetime,
priority: priority_for(metrics.segment, churn, lifetime),
actions: actions_for(metrics.segment, churn),
}
}
///|
pub fn insights(
report : Report,
config : ScoringConfig,
) -> Array[CustomerInsight] {
report.customers.map(customer => build_insight(customer, config))
}
///|
pub fn rank_insights(items : Array[CustomerInsight]) -> Array[CustomerInsight] {
let result = items.copy()
result.sort_by((left, right) => right.priority - left.priority)
result
}
///|
pub fn select_priority(
items : Array[CustomerInsight],
minimum : Int,
) -> Array[CustomerInsight] {
items.filter(item => item.priority >= minimum)
}