scirs2_metrics/serialization/
mod.rs

1//! Metric serialization utilities
2//!
3//! This module provides tools for saving and loading metric calculations,
4//! comparing results between runs, and versioning metric results.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fs::File;
10use std::io::{Read, Write};
11use std::path::Path;
12
13use crate::error::{MetricsError, Result};
14
15// Re-export submodules
16pub mod comparison;
17pub mod format;
18
19/// Metric result with metadata
20///
21/// This struct represents a metric result with associated metadata.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct MetricResult {
24    /// Name of the metric
25    pub name: String,
26    /// Value of the metric
27    pub value: f64,
28    /// Optional additional values (e.g., for metrics with multiple outputs)
29    pub additional_values: Option<HashMap<String, f64>>,
30    /// When the metric was calculated
31    pub timestamp: DateTime<Utc>,
32    /// Optional metadata
33    pub metadata: Option<MetricMetadata>,
34}
35
36/// Metadata for metric results
37///
38/// This struct represents metadata associated with metric results.
39#[derive(Debug, Clone, Serialize, Deserialize, Default)]
40pub struct MetricMetadata {
41    /// ID of the dataset
42    pub dataset_id: Option<String>,
43    /// ID of the model
44    pub model_id: Option<String>,
45    /// Parameters used for the metric calculation
46    pub parameters: Option<HashMap<String, String>>,
47    /// Additional metadata
48    pub additional_metadata: Option<HashMap<String, String>>,
49}
50
51/// Named metric collection
52///
53/// This struct represents a collection of metric results with a common name.
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct MetricCollection {
56    /// Name of the collection
57    pub name: String,
58    /// Description of the collection
59    pub description: Option<String>,
60    /// Metrics in the collection
61    pub metrics: Vec<MetricResult>,
62    /// When the collection was created
63    pub created_at: DateTime<Utc>,
64    /// When the collection was last updated
65    pub updated_at: DateTime<Utc>,
66    /// Version of the collection
67    pub version: String,
68}
69
70impl MetricCollection {
71    /// Create a new metric collection
72    ///
73    /// # Arguments
74    ///
75    /// * `name` - Name of the collection
76    /// * `description` - Optional description of the collection
77    ///
78    /// # Returns
79    ///
80    /// * A new MetricCollection
81    pub fn new(name: &str, description: Option<&str>) -> Self {
82        let now = Utc::now();
83
84        MetricCollection {
85            name: name.to_string(),
86            description: description.map(|s| s.to_string()),
87            metrics: Vec::new(),
88            created_at: now,
89            updated_at: now,
90            version: "1.0.0".to_string(),
91        }
92    }
93
94    /// Add a metric result to the collection
95    ///
96    /// # Arguments
97    ///
98    /// * `metric` - Metric result to add
99    ///
100    /// # Returns
101    ///
102    /// * Self for method chaining
103    pub fn add_metric(&mut self, metric: MetricResult) -> &mut Self {
104        self.metrics.push(metric);
105        self.updated_at = Utc::now();
106        self
107    }
108
109    /// Set the version of the collection
110    ///
111    /// # Arguments
112    ///
113    /// * `version` - Version of the collection
114    ///
115    /// # Returns
116    ///
117    /// * Self for method chaining
118    pub fn with_version(&mut self, version: &str) -> &mut Self {
119        self.version = version.to_string();
120        self
121    }
122
123    /// Save the collection to a file
124    ///
125    /// # Arguments
126    ///
127    /// * `path` - Path to save the collection to
128    /// * `format` - Format to save the collection in
129    ///
130    /// # Returns
131    ///
132    /// * Result indicating success or error
133    pub fn save<P: AsRef<Path>>(&self, path: P, format: SerializationFormat) -> Result<()> {
134        let serialized = match format {
135            SerializationFormat::Json => serde_json::to_string_pretty(self)
136                .map_err(|e| MetricsError::SerializationError(e.to_string()))?,
137            SerializationFormat::Yaml => serde_yaml::to_string(self)
138                .map_err(|e| MetricsError::SerializationError(e.to_string()))?,
139            SerializationFormat::Toml => toml::to_string_pretty(self)
140                .map_err(|e| MetricsError::SerializationError(e.to_string()))?,
141            SerializationFormat::Cbor => {
142                let mut bytes = Vec::new();
143                ciborium::ser::into_writer(self, &mut bytes)
144                    .map_err(|e| MetricsError::SerializationError(e.to_string()))?;
145                return save_binary(path, &bytes);
146            }
147        };
148
149        savetext(path, &serialized)
150    }
151
152    /// Load a collection from a file
153    ///
154    /// # Arguments
155    ///
156    /// * `path` - Path to load the collection from
157    /// * `format` - Format to load the collection from
158    ///
159    /// # Returns
160    ///
161    /// * Result containing the loaded collection
162    pub fn load<P: AsRef<Path>>(path: P, format: SerializationFormat) -> Result<Self> {
163        match format {
164            SerializationFormat::Json => {
165                let text = loadtext(path)?;
166                serde_json::from_str(&text)
167                    .map_err(|e| MetricsError::SerializationError(e.to_string()))
168            }
169            SerializationFormat::Yaml => {
170                let text = loadtext(path)?;
171                serde_yaml::from_str(&text)
172                    .map_err(|e| MetricsError::SerializationError(e.to_string()))
173            }
174            SerializationFormat::Toml => {
175                let text = loadtext(path)?;
176                toml::from_str(&text).map_err(|e| MetricsError::SerializationError(e.to_string()))
177            }
178            SerializationFormat::Cbor => {
179                let bytes = load_binary(path)?;
180                ciborium::de::from_reader(&bytes[..])
181                    .map_err(|e| MetricsError::SerializationError(e.to_string()))
182            }
183        }
184    }
185}
186
187/// Create a new metric result
188///
189/// # Arguments
190///
191/// * `name` - Name of the metric
192/// * `value` - Value of the metric
193/// * `additional_values` - Optional additional values
194/// * `metadata` - Optional metadata
195///
196/// # Returns
197///
198/// * A new MetricResult
199#[allow(dead_code)]
200pub fn create_metric_result(
201    name: &str,
202    value: f64,
203    additional_values: Option<HashMap<String, f64>>,
204    metadata: Option<MetricMetadata>,
205) -> MetricResult {
206    MetricResult {
207        name: name.to_string(),
208        value,
209        additional_values,
210        timestamp: Utc::now(),
211        metadata,
212    }
213}
214
215/// Supported serialization formats
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum SerializationFormat {
218    /// JSON format
219    Json,
220    /// YAML format
221    Yaml,
222    /// TOML format
223    Toml,
224    /// CBOR format (binary)
225    Cbor,
226}
227
228/// Save text to a file
229///
230/// # Arguments
231///
232/// * `path` - Path to save to
233/// * `text` - Text to save
234///
235/// # Returns
236///
237/// * Result indicating success or error
238#[allow(dead_code)]
239fn savetext<P: AsRef<Path>>(path: P, text: &str) -> Result<()> {
240    let mut file = File::create(path).map_err(|e| MetricsError::IOError(e.to_string()))?;
241
242    file.write_all(text.as_bytes())
243        .map_err(|e| MetricsError::IOError(e.to_string()))?;
244
245    Ok(())
246}
247
248/// Load text from a file
249///
250/// # Arguments
251///
252/// * `path` - Path to load from
253///
254/// # Returns
255///
256/// * Result containing the loaded text
257#[allow(dead_code)]
258fn loadtext<P: AsRef<Path>>(path: P) -> Result<String> {
259    let mut file = File::open(path).map_err(|e| MetricsError::IOError(e.to_string()))?;
260
261    let mut contents = String::new();
262    file.read_to_string(&mut contents)
263        .map_err(|e| MetricsError::IOError(e.to_string()))?;
264
265    Ok(contents)
266}
267
268/// Save binary data to a file
269///
270/// # Arguments
271///
272/// * `path` - Path to save to
273/// * `data` - Data to save
274///
275/// # Returns
276///
277/// * Result indicating success or error
278#[allow(dead_code)]
279fn save_binary<P: AsRef<Path>>(path: P, data: &[u8]) -> Result<()> {
280    let mut file = File::create(path).map_err(|e| MetricsError::IOError(e.to_string()))?;
281
282    file.write_all(data)
283        .map_err(|e| MetricsError::IOError(e.to_string()))?;
284
285    Ok(())
286}
287
288/// Load binary data from a file
289///
290/// # Arguments
291///
292/// * `path` - Path to load from
293///
294/// # Returns
295///
296/// * Result containing the loaded data
297#[allow(dead_code)]
298fn load_binary<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
299    let mut file = File::open(path).map_err(|e| MetricsError::IOError(e.to_string()))?;
300
301    let mut contents = Vec::new();
302    file.read_to_end(&mut contents)
303        .map_err(|e| MetricsError::IOError(e.to_string()))?;
304
305    Ok(contents)
306}