use crate::decision::Thresholds;
#[derive(Debug, Clone, PartialEq, Default)]
pub enum ScoreDispersion {
#[default]
Std,
Mad,
Iqr,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub enum DecisionScore {
#[default]
Raw,
Adjusted,
LowerConfidenceBound,
GoldWeighted,
}
#[derive(Debug, Clone)]
pub struct MinRequirements {
pub observations: usize,
pub evaluators: usize,
pub seeds: usize,
pub budgets: usize,
pub models: usize,
}
impl Default for MinRequirements {
fn default() -> Self {
Self {
observations: 1,
evaluators: 0,
seeds: 0,
budgets: 0,
models: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct ScoreWeights {
pub label_agreement: f64,
pub score_stability: f64,
pub budget_stability: f64,
pub seed_stability: f64,
pub model_agreement: f64,
pub evaluator_agreement: f64,
}
impl Default for ScoreWeights {
fn default() -> Self {
Self {
label_agreement: 1.0,
score_stability: 1.0,
budget_stability: 1.0,
seed_stability: 1.0,
model_agreement: 1.0,
evaluator_agreement: 1.0,
}
}
}
pub struct ScoreConfig {
pub score_scale: f64,
pub thresholds: Thresholds,
pub weights: ScoreWeights,
pub confidence_k: f64,
pub min_requirements: MinRequirements,
pub decision_score: DecisionScore,
pub score_dispersion: ScoreDispersion,
pub confidence_level: f64,
}
impl Default for ScoreConfig {
fn default() -> Self {
Self {
score_scale: 1.0,
thresholds: Thresholds::default(),
weights: ScoreWeights::default(),
confidence_k: 3.0,
min_requirements: MinRequirements::default(),
decision_score: DecisionScore::Raw,
score_dispersion: ScoreDispersion::Std,
confidence_level: 0.95,
}
}
}
impl ScoreConfig {
pub fn validate(&self) -> crate::error::Result<()> {
if !self.score_scale.is_finite() || self.score_scale <= 0.0 {
return Err(crate::error::Error::InvalidScoreScale(self.score_scale));
}
if !self.confidence_k.is_finite() || self.confidence_k < 0.0 {
return Err(crate::error::Error::InvalidConfidenceK(self.confidence_k));
}
let t = &self.thresholds;
if !t.keep.is_finite() || t.keep < 0.0 || t.keep > 1.0 {
return Err(crate::error::Error::InvalidThreshold(format!(
"keep_threshold ({}) must be in [0.0, 1.0]",
t.keep
)));
}
if !t.drop.is_finite() || t.drop < 0.0 || t.drop > 1.0 {
return Err(crate::error::Error::InvalidThreshold(format!(
"drop_threshold ({}) must be in [0.0, 1.0]",
t.drop
)));
}
if !self.confidence_level.is_finite() || !(0.0..=1.0).contains(&self.confidence_level) {
return Err(crate::error::Error::InvalidThreshold(format!(
"confidence_level ({}) must be in [0.0, 1.0]",
self.confidence_level
)));
}
if t.drop > t.keep {
return Err(crate::error::Error::InvalidThreshold(format!(
"drop_threshold ({}) must be <= keep_threshold ({})",
t.drop, t.keep
)));
}
let w = &self.weights;
for (name, value) in [
("label_agreement", w.label_agreement),
("score_stability", w.score_stability),
("budget_stability", w.budget_stability),
("seed_stability", w.seed_stability),
("model_agreement", w.model_agreement),
("evaluator_agreement", w.evaluator_agreement),
] {
if !value.is_finite() || value < 0.0 {
return Err(crate::error::Error::InvalidWeight { name, value });
}
}
if w.label_agreement
+ w.score_stability
+ w.budget_stability
+ w.seed_stability
+ w.model_agreement
+ w.evaluator_agreement
== 0.0
{
return Err(crate::error::Error::AllWeightsZero);
}
Ok(())
}
}