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,
LatentTruth,
}
#[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,
pub score_sign: f64,
pub gradient_sign: f64,
pub update_cosine: f64,
pub teacher_residual: f64,
pub shuffle_seed: f64,
pub loss_recipe: 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,
score_sign: 0.0,
gradient_sign: 0.0,
update_cosine: 0.0,
teacher_residual: 0.0,
shuffle_seed: 0.0,
loss_recipe: 0.0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct LatentTruthSafety {
pub demote_on_correlated_warning: bool,
pub min_evaluator_effective_n: Option<f64>,
pub demote_on_non_convergence: bool,
}
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,
pub latent_truth_safety: LatentTruthSafety,
}
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,
latent_truth_safety: LatentTruthSafety::default(),
}
}
}
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),
("score_sign", w.score_sign),
("gradient_sign", w.gradient_sign),
("update_cosine", w.update_cosine),
("teacher_residual", w.teacher_residual),
("shuffle_seed", w.shuffle_seed),
("loss_recipe", w.loss_recipe),
] {
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
+ w.score_sign
+ w.gradient_sign
+ w.update_cosine
+ w.teacher_residual
+ w.shuffle_seed
+ w.loss_recipe
== 0.0
{
return Err(crate::error::Error::AllWeightsZero);
}
if let Some(min_n) = self.latent_truth_safety.min_evaluator_effective_n
&& (!min_n.is_finite() || min_n <= 0.0)
{
return Err(crate::error::Error::InvalidThreshold(format!(
"min_evaluator_effective_n ({min_n}) must be > 0.0 and finite"
)));
}
Ok(())
}
}