use crate::domain::metrics::{
core_metrics::{ConfidenceLevel, FScore, Precision, Recall},
performance::PerformanceAssessment,
sample_count::SampleCount,
timestamp::{Timestamp, TimestampAge},
MetricsError,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FScoreDataPoint {
timestamp: Timestamp,
f_score: FScore,
precision: Option<Precision>,
recall: Option<Recall>,
sample_count: SampleCount,
confidence_level: Option<ConfidenceLevel>,
}
impl FScoreDataPoint {
pub fn new(timestamp: Timestamp, f_score: FScore, sample_count: SampleCount) -> Self {
Self {
timestamp,
f_score,
precision: None,
recall: None,
sample_count,
confidence_level: None,
}
}
pub fn with_precision_recall(
timestamp: Timestamp,
precision: Precision,
recall: Recall,
sample_count: SampleCount,
) -> Result<Self, MetricsError> {
let f_score = FScore::from_precision_recall(precision, recall)?;
Ok(Self {
timestamp,
f_score,
precision: Some(precision),
recall: Some(recall),
sample_count,
confidence_level: None,
})
}
pub fn with_confidence(mut self, confidence_level: ConfidenceLevel) -> Self {
self.confidence_level = Some(confidence_level);
self
}
pub fn performance_assessment(&self) -> PerformanceAssessment {
PerformanceAssessment::from_components(self.f_score, self.precision, self.recall)
}
pub fn is_recent(&self) -> bool {
self.timestamp.is_recent()
}
pub fn age_category(&self) -> TimestampAge {
self.timestamp.age_category()
}
pub fn timestamp(&self) -> Timestamp {
self.timestamp
}
pub fn f_score(&self) -> FScore {
self.f_score
}
pub fn precision(&self) -> Option<Precision> {
self.precision
}
pub fn recall(&self) -> Option<Recall> {
self.recall
}
pub fn sample_count(&self) -> SampleCount {
self.sample_count
}
pub fn confidence_level(&self) -> Option<ConfidenceLevel> {
self.confidence_level
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::metrics::{constants, performance::PerformanceLevel};
#[test]
fn test_f_score_data_point() {
let timestamp = Timestamp::now();
let precision = Precision::try_new(0.8).unwrap();
let recall = Recall::try_new(0.7).unwrap();
let sample_count = SampleCount::try_new(100).unwrap();
let data_point =
FScoreDataPoint::with_precision_recall(timestamp, precision, recall, sample_count)
.unwrap();
assert_eq!(data_point.timestamp(), timestamp);
assert_eq!(data_point.precision(), Some(precision));
assert_eq!(data_point.recall(), Some(recall));
assert_eq!(data_point.sample_count(), sample_count);
let expected_f_score = constants::calculation::F1_MULTIPLIER * (0.8 * 0.7) / (0.8 + 0.7);
assert!((data_point.f_score().into_inner() - expected_f_score).abs() < 1e-10);
assert!(data_point.is_recent());
let assessment = data_point.performance_assessment();
assert_eq!(assessment.f_score_level(), PerformanceLevel::Good);
}
#[test]
fn test_data_point_with_confidence() {
let timestamp = Timestamp::now();
let f_score = FScore::try_new(0.85).unwrap();
let sample_count = SampleCount::try_new(500).unwrap();
let confidence = ConfidenceLevel::ninety_five_percent();
let data_point =
FScoreDataPoint::new(timestamp, f_score, sample_count).with_confidence(confidence);
assert_eq!(data_point.confidence_level(), Some(confidence));
}
}