1pub mod active_review;
17pub mod agreement;
18pub mod block;
19pub mod calibration;
20pub mod config;
21pub mod decision;
22pub mod error;
23pub mod group;
24pub mod latent_truth;
25pub mod observation;
26pub mod preflight;
27pub mod schema;
28pub mod scoring;
29pub mod stable_wrong;
30pub mod stream;
31pub mod trajectory_audit;
32pub mod weighting;
33
34pub use active_review::{
35 ActionTargets, ActiveReviewCosts, ActiveReviewEntry, ActiveReviewWeights, RankBy,
36 action_target_fields, rank_active_review, select_within_budget,
37};
38pub use agreement::{
39 compute_fleiss_kappa, compute_krippendorff_alpha, compute_spearman_rank_correlation,
40};
41pub use block::{
42 BlockClass, BlockStabilityReport, BlockThresholds, LossRecipeIssue, compute_block_report,
43 loss_recipe_comparable, loss_recipe_issues, score_all_blocks,
44};
45pub use calibration::{
46 CalibrationOptions, CalibrationResult, GroupKey, GroupLeakage, compute_calibration,
47 compute_calibration_with_options, group_leakage, leaked_group_keys,
48};
49pub use config::{
50 DecisionScore, LatentTruthSafety, MinRequirements, ScoreConfig, ScoreDispersion, ScoreWeights,
51};
52pub use decision::Thresholds;
53pub use error::{Error, Result};
54pub use latent_truth::{LatentTruthEstimate, compute_latent_truth};
55pub use observation::{Observation, parse_csv, parse_jsonl};
56pub use preflight::{
57 BlockRunIdIssue, CheckpointCorrespondence, FieldCoverage, PreflightReport, PreflightTarget,
58 SeedCoverage, SidePreflight, compute_preflight, required_fields,
59};
60pub use schema::{Decision, StabilityComponents, StabilityReport};
61pub use scoring::{compute_report, score_all};
62pub use stable_wrong::{
63 BudgetStableWrongRate, EvaluatorStableWrongRate, ModelStableWrongRate, StableWrongBreakdown,
64 stable_wrong_breakdown,
65};
66pub use stream::StreamingScorer;
67pub use trajectory_audit::{
68 ComparisonIssue, TrajectoryAuditEntry, compute_trajectory_audit, group_size_mismatches,
69};
70pub use weighting::{
71 compute_evaluator_label_weights, compute_evaluator_reliability, compute_evaluator_weights,
72 compute_weighted_majority, compute_weighted_majority_by_label,
73};
74
75use std::collections::HashMap;
76
77pub fn score_all_weighted(
83 observations: Vec<Observation>,
84 config: &ScoreConfig,
85) -> Vec<StabilityReport> {
86 let mut reports = scoring::score_all(observations.clone(), config);
88
89 let majority_map: HashMap<String, String> = reports
91 .iter()
92 .filter_map(|r| r.majority_label.clone().map(|ml| (r.sample_id.clone(), ml)))
93 .collect();
94 let gold_map: HashMap<String, String> = observations
95 .iter()
96 .filter_map(|o| o.gold_label.clone().map(|g| (o.sample_id.clone(), g)))
97 .collect();
98 let truth: HashMap<String, String> = majority_map
99 .into_iter()
100 .map(|(id, ml)| {
101 let label = gold_map.get(&id).cloned().unwrap_or(ml);
102 (id, label)
103 })
104 .collect();
105
106 let evaluator_weights = weighting::compute_evaluator_weights(&observations, &truth);
107 let groups = group::group_by_sample_id(observations.into_iter());
108
109 for report in &mut reports {
111 if let Some(obs) = groups.get(&report.sample_id) {
112 let (wml, wlc, wld, conflict) = weighting::compute_weighted_majority(
113 obs,
114 report.majority_label.as_deref(),
115 &evaluator_weights,
116 );
117 report.weighted_majority_label = wml;
118 report.weighted_label_confidence = wlc;
119 report.weighted_label_distribution = wld;
120 report.majority_weighted_conflict = conflict;
121 }
122 }
123 reports
124}
125
126pub fn score_all_gold_weighted(
138 observations: Vec<Observation>,
139 config: &ScoreConfig,
140) -> Vec<StabilityReport> {
141 let mut reports = scoring::score_all(observations.clone(), config);
142
143 let truth: HashMap<String, String> = observations
144 .iter()
145 .filter_map(|o| o.gold_label.clone().map(|g| (o.sample_id.clone(), g)))
146 .collect();
147 let label_weights = weighting::compute_evaluator_label_weights(&observations, &truth);
148 let groups = group::group_by_sample_id(observations.into_iter());
149
150 for report in &mut reports {
151 if let Some(obs) = groups.get(&report.sample_id) {
152 let (wml, wlc, wld, conflict) = weighting::compute_weighted_majority_by_label(
153 obs,
154 report.majority_label.as_deref(),
155 &label_weights,
156 );
157 *report = scoring::compute_report_inner(&report.sample_id, obs, config, wlc);
158 report.weighted_majority_label = wml;
159 report.weighted_label_confidence = wlc;
160 report.weighted_label_distribution = wld;
161 report.majority_weighted_conflict = conflict;
162 }
163 }
164 reports
165}
166
167pub fn score_all_latent_truth(
177 observations: Vec<Observation>,
178 config: &ScoreConfig,
179) -> Vec<StabilityReport> {
180 let mut reports = scoring::score_all(observations.clone(), config);
181
182 let Some(estimates) = latent_truth::compute_latent_truth(&observations) else {
183 return reports;
184 };
185 let groups = group::group_by_sample_id(observations.into_iter());
186
187 for report in &mut reports {
188 let Some(obs) = groups.get(&report.sample_id) else {
189 continue;
190 };
191 if let Some(estimate) = estimates.get(&report.sample_id) {
192 *report = scoring::compute_report_inner(
193 &report.sample_id,
194 obs,
195 config,
196 Some(estimate.confidence),
197 );
198 report.majority_latent_conflict = report
199 .majority_label
200 .as_deref()
201 .map(|m| m != estimate.label.as_str());
202 report.latent_truth_label = Some(estimate.label.clone());
203 report.latent_truth_confidence = Some(estimate.confidence);
204 report.latent_truth_label_distribution = Some(estimate.distribution.clone());
205 report.evaluator_effective_n = Some(estimate.evaluator_effective_n);
206 report.correlated_evaluator_warning = Some(estimate.correlated_evaluator_warning);
207 report.latent_truth_converged = Some(estimate.converged);
208 report.latent_truth_iterations = Some(estimate.iterations);
209 report.latent_truth_convergence_delta = Some(estimate.convergence_delta);
210 if report.decision == Decision::Keep
217 && let Some(reason) = latent_truth::latent_truth_demotion_reason(
218 estimate,
219 &config.latent_truth_safety,
220 )
221 {
222 report.decision = Decision::Review;
223 report.latent_truth_demotion_reason = Some(reason.to_string());
224 }
225 }
226 }
227 reports
228}