Skip to main content

scientific_method/
observation.rs

1// Copyright © 2026 James Hunt. All rights reserved.
2
3use crate::Measurement;
4
5/// The address of one measurement under one experimental condition.
6#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct ObservationRef {
9    pub condition: String,
10    pub measurement: String,
11}
12
13impl ObservationRef {
14    pub fn new(condition: impl Into<String>, measurement: impl Into<String>) -> Self {
15        Self {
16            condition: condition.into(),
17            measurement: measurement.into(),
18        }
19    }
20}
21
22/// A measurement made under a named condition such as `baseline` or `candidate`.
23#[derive(Debug, Clone, PartialEq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Observation {
26    pub condition: String,
27    pub measurement: Measurement,
28}
29
30impl Observation {
31    pub fn new(condition: impl Into<String>, measurement: Measurement) -> Self {
32        Self {
33            condition: condition.into(),
34            measurement,
35        }
36    }
37
38    pub fn reference(&self) -> ObservationRef {
39        ObservationRef::new(&self.condition, &self.measurement.name)
40    }
41}
42
43/// The observations available when criteria are evaluated.
44///
45/// A vector is used intentionally so the public representation serializes
46/// cleanly in JSON, TOML, YAML, and other formats without special map-key rules.
47#[derive(Debug, Clone, Default, PartialEq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49#[cfg_attr(feature = "serde", serde(transparent))]
50pub struct Observations(Vec<Observation>);
51
52impl Observations {
53    pub fn new() -> Self {
54        Self::default()
55    }
56
57    pub fn insert(&mut self, observation: Observation) -> Option<Observation> {
58        let reference = observation.reference();
59        if let Some(index) = self
60            .0
61            .iter()
62            .position(|existing| existing.reference() == reference)
63        {
64            Some(std::mem::replace(&mut self.0[index], observation))
65        } else {
66            self.0.push(observation);
67            None
68        }
69    }
70
71    pub fn with(mut self, observation: Observation) -> Self {
72        self.insert(observation);
73        self
74    }
75
76    pub fn get(&self, reference: &ObservationRef) -> Option<&Measurement> {
77        self.0
78            .iter()
79            .find(|observation| observation.reference() == *reference)
80            .map(|observation| &observation.measurement)
81    }
82
83    pub fn len(&self) -> usize {
84        self.0.len()
85    }
86
87    pub fn is_empty(&self) -> bool {
88        self.0.is_empty()
89    }
90
91    pub fn iter(&self) -> impl Iterator<Item = &Observation> {
92        self.0.iter()
93    }
94}