Skip to main content

entrenar/ecosystem/ruchy/
session.rs

1//! Entrenar session representation and export functionality.
2
3use super::metrics::SessionMetrics;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Entrenar session representation (converted from Ruchy).
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct EntrenarSession {
10    /// Session identifier
11    pub id: String,
12    /// Session name/title
13    pub name: String,
14    /// User who created the session
15    pub user: Option<String>,
16    /// Session creation timestamp
17    pub created_at: chrono::DateTime<chrono::Utc>,
18    /// Session end timestamp (None if still active)
19    pub ended_at: Option<chrono::DateTime<chrono::Utc>>,
20    /// Model architecture used
21    pub model_architecture: Option<String>,
22    /// Dataset identifier
23    pub dataset_id: Option<String>,
24    /// Configuration parameters
25    pub config: HashMap<String, String>,
26    /// Training metrics
27    pub metrics: SessionMetrics,
28    /// Code cells/history from notebook
29    pub code_history: Vec<CodeCell>,
30    /// Session tags
31    pub tags: Vec<String>,
32    /// Notes/annotations
33    pub notes: Option<String>,
34}
35
36/// A code cell from the session history.
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct CodeCell {
39    /// Cell execution order
40    pub execution_order: u32,
41    /// Source code
42    pub source: String,
43    /// Output (if captured)
44    pub output: Option<String>,
45    /// Execution timestamp
46    pub timestamp: chrono::DateTime<chrono::Utc>,
47    /// Duration in milliseconds
48    pub duration_ms: Option<u64>,
49}
50
51impl EntrenarSession {
52    /// Create a new session.
53    pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
54        Self {
55            id: id.into(),
56            name: name.into(),
57            user: None,
58            created_at: chrono::Utc::now(),
59            ended_at: None,
60            model_architecture: None,
61            dataset_id: None,
62            config: HashMap::new(),
63            metrics: SessionMetrics::new(),
64            code_history: Vec::new(),
65            tags: Vec::new(),
66            notes: None,
67        }
68    }
69
70    /// Set user.
71    pub fn with_user(mut self, user: impl Into<String>) -> Self {
72        self.user = Some(user.into());
73        self
74    }
75
76    /// Set model architecture.
77    pub fn with_architecture(mut self, arch: impl Into<String>) -> Self {
78        self.model_architecture = Some(arch.into());
79        self
80    }
81
82    /// Set dataset.
83    pub fn with_dataset(mut self, dataset: impl Into<String>) -> Self {
84        self.dataset_id = Some(dataset.into());
85        self
86    }
87
88    /// Add configuration parameter.
89    pub fn with_config(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
90        self.config.insert(key.into(), value.into());
91        self
92    }
93
94    /// Add a tag.
95    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
96        self.tags.push(tag.into());
97        self
98    }
99
100    /// Set notes.
101    pub fn with_notes(mut self, notes: impl Into<String>) -> Self {
102        self.notes = Some(notes.into());
103        self
104    }
105
106    /// Add a code cell.
107    pub fn add_code_cell(&mut self, cell: CodeCell) {
108        self.code_history.push(cell);
109    }
110
111    /// Mark session as ended.
112    pub fn end(&mut self) {
113        self.ended_at = Some(chrono::Utc::now());
114    }
115
116    /// Calculate session duration.
117    pub fn duration(&self) -> Option<chrono::Duration> {
118        self.ended_at.map(|end| end - self.created_at)
119    }
120
121    /// Check if session has training data.
122    pub fn has_training_data(&self) -> bool {
123        !self.metrics.is_empty()
124    }
125
126    /// Export session to JSON for external tools (CLI, notebooks).
127    /// (Issue #75: Session Export API for ruchy integration)
128    ///
129    /// # Errors
130    /// Returns error if serialization fails.
131    pub fn export_json(&self) -> Result<serde_json::Value, serde_json::Error> {
132        let export = SessionExport {
133            session_id: self.id.clone(),
134            name: self.name.clone(),
135            user: self.user.clone(),
136            created_at: self.created_at.to_rfc3339(),
137            ended_at: self.ended_at.map(|t| t.to_rfc3339()),
138            duration_seconds: self.duration().map(|d| d.num_seconds()),
139            model_architecture: self.model_architecture.clone(),
140            dataset_id: self.dataset_id.clone(),
141            config: self.config.clone(),
142            metrics: MetricsExportSummary {
143                total_steps: self.metrics.total_steps(),
144                final_loss: self.metrics.final_loss(),
145                best_loss: self.metrics.best_loss(),
146                final_accuracy: self.metrics.final_accuracy(),
147                best_accuracy: self.metrics.best_accuracy(),
148                loss_history: self.metrics.loss_history.clone(),
149                accuracy_history: self.metrics.accuracy_history.clone(),
150                custom_metrics: self.metrics.custom.clone(),
151            },
152            code_cells_count: self.code_history.len(),
153            tags: self.tags.clone(),
154            notes: self.notes.clone(),
155        };
156        serde_json::to_value(export)
157    }
158
159    /// Export session to pretty-printed JSON string.
160    ///
161    /// # Errors
162    /// Returns error if serialization fails.
163    pub fn export_json_string(&self) -> Result<String, serde_json::Error> {
164        let value = self.export_json()?;
165        serde_json::to_string_pretty(&value)
166    }
167}
168
169/// Session export structure for JSON serialization (Issue #75).
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct SessionExport {
172    /// Session identifier
173    pub session_id: String,
174    /// Session name
175    pub name: String,
176    /// User who created the session
177    pub user: Option<String>,
178    /// Creation timestamp (RFC 3339)
179    pub created_at: String,
180    /// End timestamp (RFC 3339)
181    pub ended_at: Option<String>,
182    /// Duration in seconds
183    pub duration_seconds: Option<i64>,
184    /// Model architecture
185    pub model_architecture: Option<String>,
186    /// Dataset identifier
187    pub dataset_id: Option<String>,
188    /// Configuration parameters
189    pub config: HashMap<String, String>,
190    /// Training metrics summary
191    pub metrics: MetricsExportSummary,
192    /// Number of code cells
193    pub code_cells_count: usize,
194    /// Session tags
195    pub tags: Vec<String>,
196    /// Notes
197    pub notes: Option<String>,
198}
199
200/// Metrics export summary for JSON serialization.
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct MetricsExportSummary {
203    /// Total training steps
204    pub total_steps: usize,
205    /// Final loss value
206    pub final_loss: Option<f64>,
207    /// Best (minimum) loss value
208    pub best_loss: Option<f64>,
209    /// Final accuracy value
210    pub final_accuracy: Option<f64>,
211    /// Best (maximum) accuracy value
212    pub best_accuracy: Option<f64>,
213    /// Full loss history
214    pub loss_history: Vec<f64>,
215    /// Full accuracy history
216    pub accuracy_history: Vec<f64>,
217    /// Custom metrics
218    pub custom_metrics: HashMap<String, Vec<f64>>,
219}