///|
/// A bounded audience allocation opportunity.
pub struct AllocationRequest {
campaign : String
audience : Int
cost_per_customer : Int
expected_value_per_customer : Int
priority : Int
}
///|
pub fn AllocationRequest::new(
campaign : String,
audience : Int,
cost_per_customer : Int,
expected_value_per_customer : Int,
priority : Int,
) -> AllocationRequest {
{
campaign,
audience,
cost_per_customer,
expected_value_per_customer,
priority,
}
}
///|
pub struct AllocationLine {
campaign : String
customers : Int
spend : Int
expected_value : Int
expected_roi_percent : Int
}
///|
pub struct AllocationPlan {
lines : Array[AllocationLine]
customers : Int
spend : Int
expected_value : Int
remaining_budget : Int
}
///|
fn request_roi(request : AllocationRequest) -> Int {
if request.cost_per_customer <= 0 {
0
} else {
(request.expected_value_per_customer - request.cost_per_customer) *
100 /
request.cost_per_customer
}
}
///|
/// Greedily allocate a finite budget to the best positive-return opportunities.
pub fn allocate_budget(
requests : Array[AllocationRequest],
budget : Int,
max_customers : Int,
) -> AllocationPlan {
let ranked = requests.filter(request => {
request.audience > 0 &&
request.cost_per_customer > 0 &&
request.expected_value_per_customer > 0
})
ranked.sort_by((left, right) => {
let roi_delta = request_roi(right) - request_roi(left)
if roi_delta == 0 {
right.priority - left.priority
} else {
roi_delta
}
})
let lines : Array[AllocationLine] = []
let mut remaining = if budget < 0 { 0 } else { budget }
let mut capacity = if max_customers < 0 { 0 } else { max_customers }
let mut total_customers = 0
let mut total_spend = 0
let mut total_value = 0
for request in ranked {
if remaining <= 0 || capacity <= 0 {
break
}
let by_budget = remaining / request.cost_per_customer
let customers = if request.audience < by_budget {
request.audience
} else {
by_budget
}
let customers = if capacity < customers { capacity } else { customers }
if customers > 0 {
let spend = customers * request.cost_per_customer
let expected_value = customers * request.expected_value_per_customer
lines.push({
campaign: request.campaign,
customers,
spend,
expected_value,
expected_roi_percent: request_roi(request),
})
remaining -= spend
capacity -= customers
total_customers += customers
total_spend += spend
total_value += expected_value
}
}
{
lines,
customers: total_customers,
spend: total_spend,
expected_value: total_value,
remaining_budget: remaining,
}
}