///|
pub fn sigmoid_score(value : Double) -> Double {
if value >= 0.0 {
1.0 / (1.0 + @math.exp(-value))
} else {
let exp_value = @math.exp(value)
exp_value / (1.0 + exp_value)
}
}
///|
pub fn tail_confidence(score : Double, scale? : Double = 1.0) -> Double {
let safe_scale = if scale <= 0.0 { 1.0 } else { scale }
clamp_probability(1.0 - @math.exp(-absolute(score) / safe_scale))
}
///|
pub fn combine_scores(
scores : Array[Double],
weights : Array[Double],
) -> Double {
let length = if scores.length() < weights.length() {
scores.length()
} else {
weights.length()
}
let mut total = 0.0
let mut weight_total = 0.0
for i in 0.. Double {
score - threshold
}
///|
pub fn score_to_confidence(score : Double, threshold : Double) -> Double {
if threshold <= 0.0 {
tail_confidence(score)
} else {
sigmoid_score(score - threshold)
}
}
///|
pub fn expected_alert_cost(
false_positive_rate : Double,
false_negative_rate : Double,
false_positive_cost? : Double = 1.0,
false_negative_cost? : Double = 4.0,
) -> Double {
clamp_probability(false_positive_rate) * false_positive_cost +
clamp_probability(false_negative_rate) * false_negative_cost
}
///|
pub fn threshold_for_false_positive(
scores : Array[Double],
target_rate : Double,
) -> Double {
if scores.length() == 0 {
return 0.0
}
quantile(scores, 1.0 - clamp_probability(target_rate))
}
///|
pub fn normalize_scores(scores : Array[Double]) -> Array[Double] {
let result : Array[Double] = []
let maximum = array_maximum(scores)
let minimum = array_minimum(scores)
let range = maximum - minimum
for score in scores {
result.push(if range <= 1.0e-12 { 0.0 } else { (score - minimum) / range })
}
result
}