use serde_json::{Value, json};
use super::TrialError;
use crate::error::{Error, Result as CrateResult};
use crate::judge::Answer;
#[derive(Debug, Clone, PartialEq)]
pub enum Measurement {
Verdict(bool),
Answer(Answer),
}
impl Measurement {
fn to_h(&self) -> Value {
match self {
Measurement::Verdict(b) => Value::Bool(*b),
Measurement::Answer(a) => a.to_value(),
}
}
fn inspect(&self) -> String {
match self {
Measurement::Verdict(b) => b.to_string(),
Measurement::Answer(a) => format!("{a:?}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Status {
Passed,
Failed,
Measured,
Unassessed,
Error,
}
impl Status {
pub const ALL: [Status; 5] = [
Status::Passed,
Status::Failed,
Status::Measured,
Status::Unassessed,
Status::Error,
];
pub fn as_str(&self) -> &'static str {
match self {
Status::Passed => "passed",
Status::Failed => "failed",
Status::Measured => "measured",
Status::Unassessed => "unassessed",
Status::Error => "error",
}
}
}
impl std::fmt::Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Result {
name: String,
value: Option<Measurement>,
reason: Option<String>,
minimum: Option<f64>,
error: Option<TrialError>,
model: Option<String>,
evidence: Option<Value>,
}
impl Result {
pub fn new(
name: impl Into<String>,
value: Option<Measurement>,
minimum: Option<f64>,
) -> CrateResult<Result> {
let numeric = matches!(
value,
Some(Measurement::Answer(
Answer::Probability { .. } | Answer::Score { .. }
))
);
if minimum.is_some() && !numeric {
return Err(Error::Argument(
"Minimum applies only to a probability or score".into(),
));
}
Ok(Result {
name: name.into(),
value,
reason: None,
minimum,
error: None,
model: None,
evidence: None,
})
}
pub fn failed(name: impl Into<String>, error: TrialError) -> Result {
Result {
name: name.into(),
value: None,
reason: None,
minimum: None,
error: Some(error),
model: None,
evidence: None,
}
}
pub(crate) fn with_details(
mut self,
reason: Option<String>,
model: Option<String>,
evidence: Option<Value>,
) -> Result {
self.reason = reason;
self.model = model;
self.evidence = evidence;
self
}
pub fn name(&self) -> &str {
&self.name
}
pub fn value(&self) -> Option<&Measurement> {
self.value.as_ref()
}
pub fn reason(&self) -> Option<&str> {
self.reason.as_deref()
}
pub fn error(&self) -> Option<&TrialError> {
self.error.as_ref()
}
pub fn model(&self) -> Option<&str> {
self.model.as_deref()
}
pub fn evidence(&self) -> Option<&Value> {
self.evidence.as_ref()
}
pub fn is_passed(&self) -> bool {
self.status() == Status::Passed
}
fn decision(&self) -> Option<bool> {
if self.error.is_some() {
return None;
}
match &self.value {
Some(Measurement::Verdict(b)) => return Some(*b),
None => return None,
_ => {}
}
let minimum = self.minimum?;
let number = match &self.value {
Some(Measurement::Answer(Answer::Probability { probability })) => *probability,
Some(Measurement::Answer(Answer::Score { score, .. })) => *score,
_ => return None,
};
Some(number >= minimum)
}
pub fn status(&self) -> Status {
if self.error.is_some() {
return Status::Error;
}
if self.value.is_none() {
return Status::Unassessed;
}
match self.decision() {
None => Status::Measured,
Some(true) => Status::Passed,
Some(false) => Status::Failed,
}
}
pub fn to_h(&self) -> Value {
json!({
"name": self.name,
"status": self.status().as_str(),
"value": self.value.as_ref().map_or(Value::Null, Measurement::to_h),
"reason": self.reason,
"minimum": self.minimum,
"model": self.model,
"evidence": self.evidence,
"error": self.error.as_ref().map(TrialError::to_h),
})
}
pub(crate) fn failure_message(&self) -> String {
if let Some(e) = &self.error {
return e.message.clone();
}
if let Some(r) = &self.reason {
return r.clone();
}
self.value
.as_ref()
.map_or_else(|| "nil".to_string(), Measurement::inspect)
}
}