use std::fmt;
use rustev_contract::eval_report::{DatasetProvenance, Split};
use rustev_contract::ids::{ContentDigest, DatasetId, EvaluatorConfigId, PlanId};
use rustev_contract::limits::REPLAY_V1;
use rustev_contract::{Document, Identified};
use serde::{Deserialize, Serialize};
use crate::config::AdapterRules;
use crate::repeat::{
BandDoc, CohortStatus, FigureDoc, HeldOut, IntervalDoc, Observation, RepeatInputs,
RepeatReport, RunRef, Source, SplitRole, repeat,
};
use crate::repeat_config::RepeatConfig;
pub const BASELINE: &str = "rustev.baseline/1";
pub const MAX_NAME: usize = 64;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Baseline {
pub schema: String,
pub name: String,
pub repeat_report: ContentDigest,
pub runs: Vec<RunRef>,
pub dataset: DatasetId,
pub split: Split,
pub split_role: SplitRole,
pub provenance: DatasetProvenance,
pub evaluator_config: EvaluatorConfigId,
pub evaluator_config_version: String,
pub adapter_rules: Option<AdapterRules>,
pub repeat_config: ContentDigest,
pub plan: PlanId,
pub source: Source,
pub n: u64,
pub recorded_at_ms: i64,
pub held_out: Option<HeldOut>,
pub cohorts: Vec<BaselineCohort>,
}
crate::doc::eval_document!(Baseline, BASELINE, REPLAY_V1);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BaselineCohort {
pub name: String,
pub status: CohortStatus,
pub metrics: Vec<BaselineMetric>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BaselineMetric {
pub name: String,
pub observation: Option<Observation>,
pub pooled: FigureDoc,
pub interval: IntervalDoc,
pub band: BandDoc,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BaselineError {
Name,
Binding(String),
}
impl fmt::Display for BaselineError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BaselineError::Name => write!(
f,
"a baseline name is 1 to 64 bytes of [A-Za-z0-9._-], starting alphanumeric"
),
BaselineError::Binding(m) => write!(f, "binding: {m}"),
}
}
}
impl std::error::Error for BaselineError {}
pub fn valid_name(name: &str) -> bool {
!name.is_empty()
&& name.len() <= MAX_NAME
&& name.as_bytes()[0].is_ascii_alphanumeric()
&& name
.bytes()
.all(|b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'_' | b'-'))
}
pub fn record(
name: &str,
inputs: &RepeatInputs<'_>,
report: &RepeatReport,
recorded_at_ms: i64,
) -> Result<Baseline, BaselineError> {
if !valid_name(name) {
return Err(BaselineError::Name);
}
let again = repeat(inputs).map_err(|e| BaselineError::Binding(e.to_string()))?;
let mut masked = report.clone();
masked.held_out = again.held_out.clone();
if masked != again {
return Err(BaselineError::Binding(
"the supplied runs and configurations do not reproduce the repeat report".into(),
));
}
if let Some(HeldOut::Counted(r)) = &report.held_out
&& (r.count == 0
|| r.plan_count == 0
|| r.count > r.dataset_count
|| r.plan_count > r.dataset_count)
{
return Err(BaselineError::Binding(
"held-out count or plan count is zero or exceeds the dataset count".into(),
));
}
let first = inputs
.runs
.first()
.ok_or_else(|| BaselineError::Binding("no run".into()))?;
Ok(Baseline {
schema: BASELINE.into(),
name: name.into(),
repeat_report: report
.record_digest()
.map_err(|_| BaselineError::Binding("the repeat report has no record digest".into()))?,
runs: report.runs.clone(),
dataset: report.dataset.clone(),
split: report.split,
split_role: report.split_role,
provenance: report.provenance.clone(),
evaluator_config: report.evaluator_config.clone(),
evaluator_config_version: first.evaluated.config.schema.clone(),
adapter_rules: report.adapter_rules.clone(),
repeat_config: report.repeat_config.clone(),
plan: report.plan.clone(),
source: report.source,
n: report.runs.len() as u64,
recorded_at_ms,
held_out: report.held_out.clone(),
cohorts: report
.cohorts
.iter()
.map(|c| BaselineCohort {
name: c.name.clone(),
status: c.status,
metrics: c
.metrics
.iter()
.map(|m| BaselineMetric {
name: m.name.clone(),
observation: m.observation,
pooled: m.pooled.clone(),
interval: m.interval.clone(),
band: m.band.clone(),
})
.collect(),
})
.collect(),
})
}
#[derive(Debug, Clone, PartialEq)]
pub struct Exceedance {
pub cohort: String,
pub metric: String,
pub delta: f64,
pub band: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Reproducibility {
Reproducible,
NotReproducible {
exceedances: Vec<Exceedance>,
},
Unknown {
reasons: Vec<String>,
},
}
pub fn reproducibility(
baseline: &Baseline,
run: &RepeatReport,
config: &RepeatConfig,
) -> Reproducibility {
let mut reasons = vec![];
if config.id().ok().as_ref() != Some(&baseline.repeat_config) {
reasons.push("the repeat configuration is not the baseline's".to_string());
}
if run.dataset != baseline.dataset
|| run.split != baseline.split
|| run.evaluator_config != baseline.evaluator_config
|| run.repeat_config != baseline.repeat_config
|| run.plan != baseline.plan
{
reasons.push("the repeat set's dataset, split, evaluator configuration, repeat configuration or plan is not the baseline's".into());
}
let mut exceedances = vec![];
for bc in &baseline.cohorts {
let rc = run.cohorts.iter().find(|c| c.name == bc.name);
for metric in &config.stability_metrics {
let base = bc.metrics.iter().find(|m| &m.name == metric);
let now = rc.and_then(|c| c.metrics.iter().find(|m| &m.name == metric));
let at = format!("{} in {}", metric, bc.name);
let (Some(base), Some(now)) = (base, now) else {
reasons.push(format!("{at}: not reported on both sides"));
continue;
};
match (&base.pooled, &now.pooled, &base.band) {
(FigureDoc::Value(b), FigureDoc::Value(r), BandDoc::Value { value: band, .. }) => {
let delta = r - b;
if delta.abs() > *band {
exceedances.push(Exceedance {
cohort: bc.name.clone(),
metric: metric.clone(),
delta,
band: *band,
});
}
}
_ => reasons.push(format!("{at}: a value or the baseline band is unknown")),
}
}
}
if !exceedances.is_empty() {
Reproducibility::NotReproducible { exceedances }
} else if !reasons.is_empty() {
Reproducibility::Unknown { reasons }
} else {
Reproducibility::Reproducible
}
}