Skip to main content

entrenar/ecosystem/ruchy/
artifact.rs

1//! Artifact conversion for session data.
2
3use super::error::RuchyBridgeError;
4use super::session::EntrenarSession;
5use crate::research::{ArtifactType, Author, ContributorRole, License, ResearchArtifact};
6
7/// Convert a Ruchy session to a research artifact.
8///
9/// Preserves training history and metadata in the artifact.
10pub fn session_to_artifact(
11    session: &EntrenarSession,
12) -> Result<ResearchArtifact, RuchyBridgeError> {
13    if !session.has_training_data() && session.code_history.is_empty() {
14        return Err(RuchyBridgeError::NoTrainingHistory);
15    }
16
17    let mut artifact =
18        ResearchArtifact::new(&session.id, &session.name, ArtifactType::Notebook, License::Mit);
19
20    // Add user as author if available
21    if let Some(ref user) = session.user {
22        let author = Author::new(user)
23            .with_role(ContributorRole::Software)
24            .with_role(ContributorRole::Investigation);
25        artifact = artifact.with_author(author);
26    }
27
28    // Add description with metrics summary
29    let description = build_session_description(session);
30    artifact = artifact.with_description(description);
31
32    // Add keywords from tags
33    if session.tags.is_empty() {
34        artifact = artifact.with_keywords(["training", "experiment", "entrenar"]);
35    } else {
36        artifact = artifact.with_keywords(session.tags.iter().map(String::as_str));
37    }
38
39    // Set version based on training steps
40    let steps = session.metrics.total_steps();
41    artifact = artifact.with_version(format!("1.0.0+steps{steps}"));
42
43    Ok(artifact)
44}
45
46/// Build a description from session data.
47pub(crate) fn build_session_description(session: &EntrenarSession) -> String {
48    let mut parts = Vec::new();
49
50    if let Some(ref arch) = session.model_architecture {
51        parts.push(format!("Model: {arch}"));
52    }
53
54    if let Some(ref dataset) = session.dataset_id {
55        parts.push(format!("Dataset: {dataset}"));
56    }
57
58    let steps = session.metrics.total_steps();
59    if steps > 0 {
60        parts.push(format!("Training steps: {steps}"));
61    }
62
63    if let Some(loss) = session.metrics.final_loss() {
64        parts.push(format!("Final loss: {loss:.4}"));
65    }
66
67    if let Some(acc) = session.metrics.final_accuracy() {
68        parts.push(format!("Final accuracy: {acc:.2}%"));
69    }
70
71    if let Some(duration) = session.duration() {
72        let hours = duration.num_hours();
73        let minutes = duration.num_minutes() % 60;
74        parts.push(format!("Duration: {hours}h {minutes}m"));
75    }
76
77    if parts.is_empty() {
78        format!("Training session from Ruchy ({})", session.id)
79    } else {
80        parts.join(". ")
81    }
82}