use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SourceId(String);
impl SourceId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MeasurementId(String);
impl MeasurementId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct BoundedFraction(f64);
impl BoundedFraction {
pub fn new(value: f64) -> Result<Self, String> {
if value.is_nan() {
return Err("BoundedFraction cannot be NaN".to_string());
}
if !(0.0..=1.0).contains(&value) {
return Err(format!(
"BoundedFraction must be in [0.0, 1.0], got {}",
value
));
}
Ok(Self(value))
}
pub fn value(&self) -> f64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct NonNegativeHours(f64);
impl NonNegativeHours {
pub fn new(value: f64) -> Result<Self, String> {
if value.is_nan() {
return Err("NonNegativeHours cannot be NaN".to_string());
}
if value < 0.0 {
return Err(format!("NonNegativeHours must be >= 0.0, got {}", value));
}
Ok(Self(value))
}
pub fn value(&self) -> f64 {
self.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SourceValue {
Fraction(BoundedFraction),
Hours(NonNegativeHours),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Provenance {
pub source_url: Url,
pub fetch_timestamp: DateTime<Utc>,
pub source_version: Option<String>,
pub raw_value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Evidence {
pub source: SourceId,
pub measurement: MeasurementId,
pub value: SourceValue,
pub reliability_percentile: u8,
pub provenance: Provenance,
}