//! Jury support: bias mitigations and inter-juror agreement (W7 v1.1).
//!
//! The two sub-modules are intentionally independent so a caller can pull
//! one without dragging the other through the type system.
//!
//! - [`bias`]: the five  mitigations (position swap, rubric
//!   calibration, chain-of-thought knob, self-preference detection,
//!   verbosity guard).
//! - [`agreement`]: Krippendorff's alpha plus simple variance/range
//!   diagnostics.
//! - [`calibration`]: per-verdict confidence calibration, Rogan-Gladen
//!   bias correction against a small trusted set, and the low-confidence
//!   routing decision.
//!
//! ## Combined warning
//!
//! [`combined_warning`] folds a bias signal block and an agreement result
//! into a single optional warning string. The W7 reporter wires that
//! string into the pretty output when both signals fire on the same
//! evaluation, per the  acceptance criteria.

pub mod agreement;
pub mod bias;
pub mod calibration;
pub mod engine;
pub mod listwise;
mod prompt;

use agreement::{Agreement, AgreementInterpretation};
use bias::BiasSignals;

/// Combine a bias signal block and an agreement result into one warning.
///
/// Returns `Some(message)` when the jury hit low agreement and at least
/// one bias signal fired on the same evaluation. Returns `None` otherwise
/// so the reporter can skip the line entirely. Wired into the
/// `jury.bias_warning` target via [`bias::combined_bias_warning`].
pub fn combined_warning(signals: &BiasSignals, agreement: &Agreement) -> Option<String> {
    if agreement.interpretation != AgreementInterpretation::LowAgreement {
        return None;
    }
    if !signals.any_warning() {
        return None;
    }
    Some(format!(
        "low agreement (alpha={:.3}) with bias signal: {}",
        agreement.krippendorff_alpha.unwrap_or(f64::NAN),
        signals.summary()
    ))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::eval::jury::agreement::interpret_alpha;
    use crate::eval::jury::bias::BiasSignals;

    #[test]
    fn combined_warning_fires_when_both_signals_present() {
        let signals = BiasSignals {
            same_family_warning: Some("juror and generator are both anthropic".into()),
            ..BiasSignals::default()
        };
        let agreement = Agreement {
            krippendorff_alpha: Some(0.42),
            score_variance: 0.18,
            score_range: 0.55,
            interpretation: interpret_alpha(0.42),
            low_agreement_threshold: 0.667,
        };
        assert!(combined_warning(&signals, &agreement).is_some());
    }

    #[test]
    fn combined_warning_skips_when_agreement_high() {
        let signals = BiasSignals {
            verbosity_warning: Some("longer response is 2.5x".into()),
            ..BiasSignals::default()
        };
        let agreement = Agreement {
            krippendorff_alpha: Some(0.91),
            score_variance: 0.01,
            score_range: 0.05,
            interpretation: interpret_alpha(0.91),
            low_agreement_threshold: 0.667,
        };
        assert!(combined_warning(&signals, &agreement).is_none());
    }

    #[test]
    fn combined_warning_skips_when_no_bias_signal() {
        let signals = BiasSignals::default();
        let agreement = Agreement {
            krippendorff_alpha: Some(0.42),
            score_variance: 0.20,
            score_range: 0.6,
            interpretation: interpret_alpha(0.42),
            low_agreement_threshold: 0.667,
        };
        assert!(combined_warning(&signals, &agreement).is_none());
    }
}
