pub mod active_review;
pub mod agreement;
pub mod calibration;
pub mod config;
pub mod decision;
pub mod error;
pub mod group;
pub mod latent_truth;
pub mod observation;
pub mod schema;
pub mod scoring;
pub mod stable_wrong;
pub mod stream;
pub mod weighting;
pub use active_review::{
ActionTargets, ActiveReviewCosts, ActiveReviewEntry, ActiveReviewWeights, RankBy,
action_target_fields, rank_active_review, select_within_budget,
};
pub use agreement::{compute_fleiss_kappa, compute_krippendorff_alpha};
pub use calibration::{CalibrationResult, compute_calibration};
pub use config::{
DecisionScore, LatentTruthSafety, MinRequirements, ScoreConfig, ScoreDispersion, ScoreWeights,
};
pub use decision::Thresholds;
pub use error::{Error, Result};
pub use latent_truth::{LatentTruthEstimate, compute_latent_truth};
pub use observation::{Observation, parse_csv, parse_jsonl};
pub use schema::{Decision, StabilityComponents, StabilityReport};
pub use scoring::{compute_report, score_all};
pub use stable_wrong::{
BudgetStableWrongRate, EvaluatorStableWrongRate, ModelStableWrongRate, StableWrongBreakdown,
stable_wrong_breakdown,
};
pub use stream::StreamingScorer;
pub use weighting::{
compute_evaluator_label_weights, compute_evaluator_reliability, compute_evaluator_weights,
compute_weighted_majority, compute_weighted_majority_by_label,
};
use std::collections::HashMap;
pub fn score_all_weighted(
observations: Vec<Observation>,
config: &ScoreConfig,
) -> Vec<StabilityReport> {
let mut reports = scoring::score_all(observations.clone(), config);
let majority_map: HashMap<String, String> = reports
.iter()
.filter_map(|r| r.majority_label.clone().map(|ml| (r.sample_id.clone(), ml)))
.collect();
let gold_map: HashMap<String, String> = observations
.iter()
.filter_map(|o| o.gold_label.clone().map(|g| (o.sample_id.clone(), g)))
.collect();
let truth: HashMap<String, String> = majority_map
.into_iter()
.map(|(id, ml)| {
let label = gold_map.get(&id).cloned().unwrap_or(ml);
(id, label)
})
.collect();
let evaluator_weights = weighting::compute_evaluator_weights(&observations, &truth);
let groups = group::group_by_sample_id(observations.into_iter());
for report in &mut reports {
if let Some(obs) = groups.get(&report.sample_id) {
let (wml, wlc, wld, conflict) = weighting::compute_weighted_majority(
obs,
report.majority_label.as_deref(),
&evaluator_weights,
);
report.weighted_majority_label = wml;
report.weighted_label_confidence = wlc;
report.weighted_label_distribution = wld;
report.majority_weighted_conflict = conflict;
}
}
reports
}
pub fn score_all_gold_weighted(
observations: Vec<Observation>,
config: &ScoreConfig,
) -> Vec<StabilityReport> {
let mut reports = scoring::score_all(observations.clone(), config);
let truth: HashMap<String, String> = observations
.iter()
.filter_map(|o| o.gold_label.clone().map(|g| (o.sample_id.clone(), g)))
.collect();
let label_weights = weighting::compute_evaluator_label_weights(&observations, &truth);
let groups = group::group_by_sample_id(observations.into_iter());
for report in &mut reports {
if let Some(obs) = groups.get(&report.sample_id) {
let (wml, wlc, wld, conflict) = weighting::compute_weighted_majority_by_label(
obs,
report.majority_label.as_deref(),
&label_weights,
);
*report = scoring::compute_report_inner(&report.sample_id, obs, config, wlc);
report.weighted_majority_label = wml;
report.weighted_label_confidence = wlc;
report.weighted_label_distribution = wld;
report.majority_weighted_conflict = conflict;
}
}
reports
}
pub fn score_all_latent_truth(
observations: Vec<Observation>,
config: &ScoreConfig,
) -> Vec<StabilityReport> {
let mut reports = scoring::score_all(observations.clone(), config);
let Some(estimates) = latent_truth::compute_latent_truth(&observations) else {
return reports;
};
let groups = group::group_by_sample_id(observations.into_iter());
for report in &mut reports {
let Some(obs) = groups.get(&report.sample_id) else {
continue;
};
if let Some(estimate) = estimates.get(&report.sample_id) {
*report = scoring::compute_report_inner(
&report.sample_id,
obs,
config,
Some(estimate.confidence),
);
report.majority_latent_conflict = report
.majority_label
.as_deref()
.map(|m| m != estimate.label.as_str());
report.latent_truth_label = Some(estimate.label.clone());
report.latent_truth_confidence = Some(estimate.confidence);
report.latent_truth_label_distribution = Some(estimate.distribution.clone());
report.evaluator_effective_n = Some(estimate.evaluator_effective_n);
report.correlated_evaluator_warning = Some(estimate.correlated_evaluator_warning);
report.latent_truth_converged = Some(estimate.converged);
report.latent_truth_iterations = Some(estimate.iterations);
report.latent_truth_convergence_delta = Some(estimate.convergence_delta);
if report.decision == Decision::Keep
&& let Some(reason) = latent_truth::latent_truth_demotion_reason(
estimate,
&config.latent_truth_safety,
)
{
report.decision = Decision::Review;
report.latent_truth_demotion_reason = Some(reason.to_string());
}
}
}
reports
}