1#![allow(clippy::doc_markdown)]
5#![allow(clippy::uninlined_format_args)]
6#![allow(clippy::missing_panics_doc)]
7#![allow(clippy::missing_errors_doc)]
8#![allow(clippy::redundant_closure_for_method_calls)]
9
10use crate::data_card::{DataCard, DataColumn};
11use crate::model_card::{ModelCard, ModelMetric, ModelStatus};
12use presentar_yaml::formats::{AldDataset, AprModel, DType};
13
14pub trait AprModelExt {
16 fn to_model_card(&self) -> ModelCard;
18}
19
20impl AprModelExt for AprModel {
21 fn to_model_card(&self) -> ModelCard {
22 let total_params: u64 = self
24 .layers
25 .iter()
26 .flat_map(|l| l.parameters.iter())
27 .map(|t| t.numel() as u64)
28 .sum();
29
30 let layer_summary: String = self
32 .layers
33 .iter()
34 .map(|l| format!("{}({})", l.layer_type, l.parameters.len()))
35 .collect::<Vec<_>>()
36 .join(" → ");
37
38 let mut card = ModelCard::new(&self.model_type)
39 .version(format!("v{}", self.version))
40 .framework("Aprender")
41 .parameters(total_params)
42 .status(ModelStatus::Published);
43
44 if !layer_summary.is_empty() {
45 card = card.description(format!("Architecture: {}", layer_summary));
46 }
47
48 if let Some(acc) = self.metadata.get("accuracy") {
50 if let Ok(v) = acc.parse::<f64>() {
51 card = card.metric(ModelMetric::new("accuracy", v));
52 }
53 }
54 if let Some(loss) = self.metadata.get("loss") {
55 if let Ok(v) = loss.parse::<f64>() {
56 card = card.metric(ModelMetric::new("loss", v).lower_is_better());
57 }
58 }
59 if let Some(f1) = self.metadata.get("f1_score") {
60 if let Ok(v) = f1.parse::<f64>() {
61 card = card.metric(ModelMetric::new("F1", v));
62 }
63 }
64
65 if let Some(task) = self.metadata.get("task") {
67 card = card.task(task);
68 }
69
70 if let Some(dataset) = self.metadata.get("dataset") {
72 card = card.dataset(dataset);
73 }
74
75 if let Some(author) = self.metadata.get("author") {
77 card = card.author(author);
78 }
79
80 for (k, v) in &self.metadata {
82 if !["accuracy", "loss", "f1_score", "task", "dataset", "author"].contains(&k.as_str())
83 {
84 card = card.metadata_entry(k, v);
85 }
86 }
87
88 card.tag("apr").tag("sovereign-ai")
89 }
90}
91
92pub trait AldDatasetExt {
94 fn to_data_card(&self, name: &str) -> DataCard;
96}
97
98impl AldDatasetExt for AldDataset {
99 fn to_data_card(&self, name: &str) -> DataCard {
100 let total_bytes: usize = self.tensors.iter().map(|t| t.data.len()).sum();
102
103 let total_elements: usize = self.tensors.iter().map(|t| t.numel()).sum();
105
106 let columns: Vec<DataColumn> = self
108 .tensors
109 .iter()
110 .map(|t| {
111 let dtype_str = match t.dtype {
112 DType::F32 => "float32",
113 DType::F64 => "float64",
114 DType::I32 => "int32",
115 DType::I64 => "int64",
116 DType::U8 => "uint8",
117 };
118 let shape_str = t
119 .shape
120 .iter()
121 .map(|d| d.to_string())
122 .collect::<Vec<_>>()
123 .join("×");
124
125 DataColumn::new(&t.name, dtype_str).description(format!("Shape: [{}]", shape_str))
126 })
127 .collect();
128
129 let mut card = DataCard::new(name)
130 .description(format!(
131 "{} elements, {} tensors, {}",
132 total_elements,
133 self.tensors.len(),
134 format_bytes(total_bytes)
135 ))
136 .source("Alimentar (.ald)")
137 .tag("ald")
138 .tag("sovereign-ai");
139
140 for col in columns {
141 card = card.column(col);
142 }
143
144 card
145 }
146}
147
148fn format_bytes(bytes: usize) -> String {
150 const KB: usize = 1024;
151 const MB: usize = KB * 1024;
152 const GB: usize = MB * 1024;
153
154 if bytes >= GB {
155 format!("{:.1} GB", bytes as f64 / GB as f64)
156 } else if bytes >= MB {
157 format!("{:.1} MB", bytes as f64 / MB as f64)
158 } else if bytes >= KB {
159 format!("{:.1} KB", bytes as f64 / KB as f64)
160 } else {
161 format!("{} B", bytes)
162 }
163}
164
165pub fn load_apr_as_card(data: &[u8]) -> Result<ModelCard, presentar_yaml::FormatError> {
171 let model = AprModel::load(data)?;
172 Ok(model.to_model_card())
173}
174
175pub fn load_ald_as_card(data: &[u8], name: &str) -> Result<DataCard, presentar_yaml::FormatError> {
181 let dataset = AldDataset::load(data)?;
182 Ok(dataset.to_data_card(name))
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use presentar_yaml::formats::{DType, ModelLayer, Tensor};
189
190 #[test]
191 fn test_apr_to_model_card() {
192 let mut model = AprModel::new("LinearRegression");
193 model.layers.push(ModelLayer {
194 layer_type: "dense".to_string(),
195 parameters: vec![Tensor::from_f32("weights", vec![10, 5], &[0.0; 50])],
196 });
197 model
198 .metadata
199 .insert("accuracy".to_string(), "0.95".to_string());
200 model
201 .metadata
202 .insert("task".to_string(), "classification".to_string());
203
204 let card = model.to_model_card();
205
206 assert_eq!(card.get_name(), "LinearRegression");
207 assert_eq!(card.get_framework(), Some("Aprender"));
208 assert_eq!(card.get_parameters(), Some(50));
209 assert!(card.get_tags().contains(&"apr".to_string()));
210 }
211
212 #[test]
213 fn test_ald_to_data_card() {
214 let mut dataset = AldDataset::new();
215 dataset.add_tensor(Tensor::from_f32("features", vec![100, 10], &[0.0; 1000]));
216 dataset.add_tensor(Tensor::from_f32("labels", vec![100], &[0.0; 100]));
217
218 let card = dataset.to_data_card("mnist_sample");
219
220 assert_eq!(card.get_name(), "mnist_sample");
221 assert_eq!(card.column_count(), 2);
222 assert!(card.get_tags().contains(&"ald".to_string()));
223 }
224
225 #[test]
226 fn test_format_bytes() {
227 assert_eq!(format_bytes(500), "500 B");
228 assert_eq!(format_bytes(1024), "1.0 KB");
229 assert_eq!(format_bytes(1536), "1.5 KB");
230 assert_eq!(format_bytes(1048576), "1.0 MB");
231 assert_eq!(format_bytes(1073741824), "1.0 GB");
232 }
233
234 #[test]
235 fn test_load_apr_roundtrip() {
236 let mut model = AprModel::new("MLP");
237 model.layers.push(ModelLayer {
238 layer_type: "dense".to_string(),
239 parameters: vec![Tensor::from_f32("w", vec![4, 4], &[1.0; 16])],
240 });
241
242 let bytes = model.save();
243 let card = load_apr_as_card(&bytes).unwrap();
244
245 assert_eq!(card.get_name(), "MLP");
246 }
247
248 #[test]
249 fn test_load_ald_roundtrip() {
250 let mut dataset = AldDataset::new();
251 dataset.add_tensor(Tensor::from_f32("data", vec![10], &[1.0; 10]));
252
253 let bytes = dataset.save();
254 let card = load_ald_as_card(&bytes, "test_data").unwrap();
255
256 assert_eq!(card.get_name(), "test_data");
257 }
258
259 #[test]
264 fn test_apr_model_with_all_metrics() {
265 let mut model = AprModel::new("Classifier");
266 model.layers.push(ModelLayer {
267 layer_type: "conv2d".to_string(),
268 parameters: vec![Tensor::from_f32(
269 "kernel",
270 vec![3, 3, 64, 128],
271 &[0.0; 73728],
272 )],
273 });
274 model
275 .metadata
276 .insert("accuracy".to_string(), "0.972".to_string());
277 model
278 .metadata
279 .insert("loss".to_string(), "0.083".to_string());
280 model
281 .metadata
282 .insert("f1_score".to_string(), "0.968".to_string());
283
284 let card = model.to_model_card();
285
286 assert_eq!(card.get_name(), "Classifier");
287 assert_eq!(card.get_parameters(), Some(73728));
288 }
289
290 #[test]
291 fn test_apr_model_with_task_and_dataset() {
292 let mut model = AprModel::new("BERT");
293 model
294 .metadata
295 .insert("task".to_string(), "text-classification".to_string());
296 model
297 .metadata
298 .insert("dataset".to_string(), "IMDB".to_string());
299 model
300 .metadata
301 .insert("author".to_string(), "research-team".to_string());
302
303 let card = model.to_model_card();
304
305 assert_eq!(card.get_task(), Some("text-classification"));
306 assert_eq!(card.get_dataset(), Some("IMDB"));
307 assert_eq!(card.get_author(), Some("research-team"));
308 }
309
310 #[test]
311 fn test_apr_model_with_custom_metadata() {
312 let mut model = AprModel::new("CustomModel");
313 model
314 .metadata
315 .insert("training_time".to_string(), "2h30m".to_string());
316 model
317 .metadata
318 .insert("epochs".to_string(), "50".to_string());
319 model
320 .metadata
321 .insert("learning_rate".to_string(), "0.001".to_string());
322
323 let card = model.to_model_card();
324
325 assert_eq!(card.get_name(), "CustomModel");
327 }
328
329 #[test]
330 fn test_apr_model_multiple_layers() {
331 let mut model = AprModel::new("DeepNet");
332 model.layers.push(ModelLayer {
333 layer_type: "dense".to_string(),
334 parameters: vec![
335 Tensor::from_f32("w1", vec![784, 256], &[0.0; 200704]),
336 Tensor::from_f32("b1", vec![256], &[0.0; 256]),
337 ],
338 });
339 model.layers.push(ModelLayer {
340 layer_type: "relu".to_string(),
341 parameters: vec![],
342 });
343 model.layers.push(ModelLayer {
344 layer_type: "dense".to_string(),
345 parameters: vec![
346 Tensor::from_f32("w2", vec![256, 10], &[0.0; 2560]),
347 Tensor::from_f32("b2", vec![10], &[0.0; 10]),
348 ],
349 });
350
351 let card = model.to_model_card();
352
353 assert_eq!(card.get_parameters(), Some(203530));
355 }
356
357 #[test]
358 fn test_apr_model_empty_layers() {
359 let model = AprModel::new("EmptyModel");
360
361 let card = model.to_model_card();
362
363 assert_eq!(card.get_name(), "EmptyModel");
364 assert_eq!(card.get_parameters(), Some(0));
365 }
366
367 #[test]
368 fn test_apr_model_invalid_metric_values() {
369 let mut model = AprModel::new("BadMetrics");
370 model
371 .metadata
372 .insert("accuracy".to_string(), "not-a-number".to_string());
373 model
374 .metadata
375 .insert("loss".to_string(), "invalid".to_string());
376
377 let card = model.to_model_card();
379 assert_eq!(card.get_name(), "BadMetrics");
380 }
381
382 #[test]
387 fn test_ald_empty_dataset() {
388 let dataset = AldDataset::new();
389 let card = dataset.to_data_card("empty");
390
391 assert_eq!(card.get_name(), "empty");
392 assert_eq!(card.column_count(), 0);
393 }
394
395 #[test]
396 fn test_ald_multiple_tensors() {
397 let mut dataset = AldDataset::new();
398 dataset.add_tensor(Tensor::from_f32("train_x", vec![1000, 784], &[0.0; 784000]));
399 dataset.add_tensor(Tensor::from_f32("train_y", vec![1000], &[0.0; 1000]));
400 dataset.add_tensor(Tensor::from_f32("test_x", vec![100, 784], &[0.0; 78400]));
401 dataset.add_tensor(Tensor::from_f32("test_y", vec![100], &[0.0; 100]));
402
403 let card = dataset.to_data_card("mnist");
404
405 assert_eq!(card.get_name(), "mnist");
406 assert_eq!(card.column_count(), 4);
407 }
408
409 #[test]
410 fn test_ald_different_dtypes() {
411 let mut dataset = AldDataset::new();
412
413 dataset.add_tensor(Tensor::from_f32("float32_tensor", vec![10], &[0.0; 10]));
415 dataset.add_tensor(Tensor::from_f32("float32_tensor2", vec![5, 2], &[0.0; 10]));
416
417 dataset.add_tensor(Tensor::new(
419 "float64_tensor",
420 DType::F64,
421 vec![10],
422 vec![0u8; 80],
423 ));
424 dataset.add_tensor(Tensor::new(
425 "int32_tensor",
426 DType::I32,
427 vec![10],
428 vec![0u8; 40],
429 ));
430 dataset.add_tensor(Tensor::new(
431 "uint8_tensor",
432 DType::U8,
433 vec![10],
434 vec![0u8; 10],
435 ));
436
437 let card = dataset.to_data_card("multi_dtype");
438
439 assert_eq!(card.column_count(), 5);
440 }
441
442 #[test]
447 fn test_format_bytes_boundaries() {
448 assert_eq!(format_bytes(0), "0 B");
450 assert_eq!(format_bytes(1023), "1023 B");
451 assert_eq!(format_bytes(1024), "1.0 KB");
452 assert_eq!(format_bytes(1024 * 1024 - 1), "1024.0 KB");
453 assert_eq!(format_bytes(1024 * 1024), "1.0 MB");
454 assert_eq!(format_bytes(1024 * 1024 * 1024 - 1), "1024.0 MB");
455 assert_eq!(format_bytes(1024 * 1024 * 1024), "1.0 GB");
456 }
457
458 #[test]
459 fn test_format_bytes_large_values() {
460 assert_eq!(format_bytes(10 * 1024 * 1024 * 1024), "10.0 GB");
462 assert_eq!(format_bytes(100 * 1024 * 1024 * 1024), "100.0 GB");
464 }
465
466 #[test]
467 fn test_format_bytes_fractional() {
468 assert_eq!(format_bytes(1536), "1.5 KB");
470 assert_eq!(format_bytes(2621440), "2.5 MB");
472 assert_eq!(format_bytes(3489660928), "3.2 GB");
474 }
475
476 #[test]
481 fn test_load_apr_invalid_data() {
482 let invalid_data = b"not a valid apr file";
483 let result = load_apr_as_card(invalid_data);
484 assert!(result.is_err());
485 }
486
487 #[test]
488 fn test_load_ald_invalid_data() {
489 let invalid_data = b"not a valid ald file";
490 let result = load_ald_as_card(invalid_data, "test");
491 assert!(result.is_err());
492 }
493
494 #[test]
495 fn test_load_apr_empty_data() {
496 let empty_data = b"";
497 let result = load_apr_as_card(empty_data);
498 assert!(result.is_err());
499 }
500
501 #[test]
502 fn test_load_ald_empty_data() {
503 let empty_data = b"";
504 let result = load_ald_as_card(empty_data, "test");
505 assert!(result.is_err());
506 }
507
508 #[test]
513 fn test_model_card_has_sovereign_ai_tag() {
514 let model = AprModel::new("SovereignModel");
515 let card = model.to_model_card();
516
517 assert!(card.get_tags().contains(&"sovereign-ai".to_string()));
518 assert!(card.get_tags().contains(&"apr".to_string()));
519 }
520
521 #[test]
522 fn test_data_card_has_sovereign_ai_tag() {
523 let dataset = AldDataset::new();
524 let card = dataset.to_data_card("SovereignData");
525
526 assert!(card.get_tags().contains(&"sovereign-ai".to_string()));
527 assert!(card.get_tags().contains(&"ald".to_string()));
528 }
529}