use serde::Serialize;
use crate::composite::CompositeScore;
use crate::suite_controls::{
bind_to_suite, ControlBinding, ControlError, ControlRun, SuiteControlEvidence,
};
use crate::trial_census::{census, TrialCensus, TrialReport, TrialRoster};
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct SuiteEvidence {
pub used_by_gate: bool,
pub trials: TrialCensus,
pub controls: SuiteControlEvidence,
pub control_binding: ControlBinding,
}
pub fn suite_evidence(
roster: &TrialRoster,
reports: &[TrialReport],
controls: &[ControlRun],
board: &[CompositeScore],
) -> Result<SuiteEvidence, ControlError> {
let controls = bind_to_suite(controls, board)?;
let control_binding = controls
.binding()
.map_err(|detail| ControlError::ControlBindingIncomplete { detail })?;
Ok(SuiteEvidence {
used_by_gate: false,
trials: census(roster, reports),
controls,
control_binding,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::composite::{rank, AgentSubmission, Run, ScoreConfig};
use crate::suite_controls::{ControlObservation, ControlProperty};
fn ids(names: &[&str]) -> Vec<String> {
names.iter().map(|n| (*n).to_string()).collect()
}
fn roster() -> TrialRoster {
TrialRoster::declare(&ids(&["alpha"]), &ids(&["w1"]), &[7, 9])
.expect("a well formed roster")
}
fn held_control(control_id: &str) -> ControlRun {
ControlRun {
control_id: control_id.to_string(),
property: ControlProperty::ProtocolAndAccounting,
observation: ControlObservation::ProtocolAndAccounting {
decisions_expected: 40,
decisions_round_tripped: 40,
accounting_residual: 0.0,
},
}
}
fn board(agent_id: &str) -> Vec<CompositeScore> {
let returns: Vec<f64> = (0..64)
.map(|i| 0.001 + 0.0001 * f64::from(i % 5 - 2))
.collect();
rank(
&[AgentSubmission {
agent_id: agent_id.to_string(),
runs: vec![Run {
returns,
..Run::default()
}],
in_sample_trials: 0,
candidates: Vec::new(),
}],
&ScoreConfig::default(),
)
}
#[test]
fn the_record_states_it_is_not_used_by_the_gate() {
let evidence = suite_evidence(
&roster(),
&[TrialReport::completed("alpha", "w1", 7)],
&[held_control("pipeline-hold")],
&board("alpha"),
)
.expect("a control that is not an entrant binds");
assert!(!evidence.used_by_gate);
let json = serde_json::to_value(&evidence).expect("the evidence serializes");
assert_eq!(json["used_by_gate"], serde_json::Value::Bool(false));
}
#[test]
fn the_census_half_counts_against_the_declaration() {
let evidence = suite_evidence(
&roster(),
&[TrialReport::completed("alpha", "w1", 7)],
&[held_control("pipeline-hold")],
&board("alpha"),
)
.expect("a control that is not an entrant binds");
assert_eq!(evidence.trials.expected, 2);
assert_eq!(evidence.trials.completed, 1);
assert_eq!(evidence.trials.unreported, 1);
assert!(!evidence.trials.suite_complete());
}
#[test]
fn the_controls_half_carries_identity_intent_and_outcome() {
let evidence = suite_evidence(
&roster(),
&[],
&[held_control("pipeline-hold")],
&board("alpha"),
)
.expect("a control that is not an entrant binds");
let verdict = evidence
.controls
.control("pipeline-hold")
.expect("the declared control has a verdict");
assert_eq!(verdict.property, ControlProperty::ProtocolAndAccounting);
assert_eq!(
verdict.observation,
ControlObservation::ProtocolAndAccounting {
decisions_expected: 40,
decisions_round_tripped: 40,
accounting_residual: 0.0,
}
);
assert!(verdict.held);
assert!(evidence.controls.all_held);
}
#[test]
fn a_control_that_is_also_a_ranked_entrant_is_refused() {
let error = suite_evidence(&roster(), &[], &[held_control("alpha")], &board("alpha"))
.expect_err("a control that is also a ranked entrant is refused");
assert_eq!(
error,
ControlError::ControlRankedAsEntrant {
control_id: "alpha".to_string()
}
);
}
#[test]
fn an_empty_battery_is_refused() {
let error = suite_evidence(&roster(), &[], &[], &board("alpha"))
.expect_err("an empty battery establishes nothing");
assert_eq!(error, ControlError::NoControls);
}
}