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)]
186#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
187mod tests {
188 use super::*;
189 use presentar_yaml::formats::{DType, ModelLayer, Tensor};
190
191 #[test]
192 fn test_apr_to_model_card() {
193 let mut model = AprModel::new("LinearRegression");
194 model.layers.push(ModelLayer {
195 layer_type: "dense".to_string(),
196 parameters: vec![Tensor::from_f32("weights", vec![10, 5], &[0.0; 50])],
197 });
198 model
199 .metadata
200 .insert("accuracy".to_string(), "0.95".to_string());
201 model
202 .metadata
203 .insert("task".to_string(), "classification".to_string());
204
205 let card = model.to_model_card();
206
207 assert_eq!(card.get_name(), "LinearRegression");
208 assert_eq!(card.get_framework(), Some("Aprender"));
209 assert_eq!(card.get_parameters(), Some(50));
210 assert!(card.get_tags().contains(&"apr".to_string()));
211 }
212
213 #[test]
214 fn test_ald_to_data_card() {
215 let mut dataset = AldDataset::new();
216 dataset.add_tensor(Tensor::from_f32("features", vec![100, 10], &[0.0; 1000]));
217 dataset.add_tensor(Tensor::from_f32("labels", vec![100], &[0.0; 100]));
218
219 let card = dataset.to_data_card("mnist_sample");
220
221 assert_eq!(card.get_name(), "mnist_sample");
222 assert_eq!(card.column_count(), 2);
223 assert!(card.get_tags().contains(&"ald".to_string()));
224 }
225
226 #[test]
227 fn test_format_bytes() {
228 assert_eq!(format_bytes(500), "500 B");
229 assert_eq!(format_bytes(1024), "1.0 KB");
230 assert_eq!(format_bytes(1536), "1.5 KB");
231 assert_eq!(format_bytes(1_048_576), "1.0 MB");
232 assert_eq!(format_bytes(1_073_741_824), "1.0 GB");
233 }
234
235 #[test]
236 fn test_load_apr_roundtrip() {
237 let mut model = AprModel::new("MLP");
238 model.layers.push(ModelLayer {
239 layer_type: "dense".to_string(),
240 parameters: vec![Tensor::from_f32("w", vec![4, 4], &[1.0; 16])],
241 });
242
243 let bytes = model.save();
244 let card = load_apr_as_card(&bytes).unwrap();
245
246 assert_eq!(card.get_name(), "MLP");
247 }
248
249 #[test]
250 fn test_load_ald_roundtrip() {
251 let mut dataset = AldDataset::new();
252 dataset.add_tensor(Tensor::from_f32("data", vec![10], &[1.0; 10]));
253
254 let bytes = dataset.save();
255 let card = load_ald_as_card(&bytes, "test_data").unwrap();
256
257 assert_eq!(card.get_name(), "test_data");
258 }
259
260 #[test]
265 fn test_apr_model_with_all_metrics() {
266 let mut model = AprModel::new("Classifier");
267 let kernel_data = vec![0.0_f32; 73_728];
268 model.layers.push(ModelLayer {
269 layer_type: "conv2d".to_string(),
270 parameters: vec![Tensor::from_f32(
271 "kernel",
272 vec![3, 3, 64, 128],
273 &kernel_data,
274 )],
275 });
276 model
277 .metadata
278 .insert("accuracy".to_string(), "0.972".to_string());
279 model
280 .metadata
281 .insert("loss".to_string(), "0.083".to_string());
282 model
283 .metadata
284 .insert("f1_score".to_string(), "0.968".to_string());
285
286 let card = model.to_model_card();
287
288 assert_eq!(card.get_name(), "Classifier");
289 assert_eq!(card.get_parameters(), Some(73728));
290 }
291
292 #[test]
293 fn test_apr_model_with_task_and_dataset() {
294 let mut model = AprModel::new("BERT");
295 model
296 .metadata
297 .insert("task".to_string(), "text-classification".to_string());
298 model
299 .metadata
300 .insert("dataset".to_string(), "IMDB".to_string());
301 model
302 .metadata
303 .insert("author".to_string(), "research-team".to_string());
304
305 let card = model.to_model_card();
306
307 assert_eq!(card.get_task(), Some("text-classification"));
308 assert_eq!(card.get_dataset(), Some("IMDB"));
309 assert_eq!(card.get_author(), Some("research-team"));
310 }
311
312 #[test]
313 fn test_apr_model_with_custom_metadata() {
314 let mut model = AprModel::new("CustomModel");
315 model
316 .metadata
317 .insert("training_time".to_string(), "2h30m".to_string());
318 model
319 .metadata
320 .insert("epochs".to_string(), "50".to_string());
321 model
322 .metadata
323 .insert("learning_rate".to_string(), "0.001".to_string());
324
325 let card = model.to_model_card();
326
327 assert_eq!(card.get_name(), "CustomModel");
329 }
330
331 #[test]
332 fn test_apr_model_multiple_layers() {
333 let mut model = AprModel::new("DeepNet");
334 let w1_data = vec![0.0_f32; 200_704];
335 model.layers.push(ModelLayer {
336 layer_type: "dense".to_string(),
337 parameters: vec![
338 Tensor::from_f32("w1", vec![784, 256], &w1_data),
339 Tensor::from_f32("b1", vec![256], &[0.0; 256]),
340 ],
341 });
342 model.layers.push(ModelLayer {
343 layer_type: "relu".to_string(),
344 parameters: vec![],
345 });
346 model.layers.push(ModelLayer {
347 layer_type: "dense".to_string(),
348 parameters: vec![
349 Tensor::from_f32("w2", vec![256, 10], &[0.0; 2560]),
350 Tensor::from_f32("b2", vec![10], &[0.0; 10]),
351 ],
352 });
353
354 let card = model.to_model_card();
355
356 assert_eq!(card.get_parameters(), Some(203_530));
358 }
359
360 #[test]
361 fn test_apr_model_empty_layers() {
362 let model = AprModel::new("EmptyModel");
363
364 let card = model.to_model_card();
365
366 assert_eq!(card.get_name(), "EmptyModel");
367 assert_eq!(card.get_parameters(), Some(0));
368 }
369
370 #[test]
371 fn test_apr_model_invalid_metric_values() {
372 let mut model = AprModel::new("BadMetrics");
373 model
374 .metadata
375 .insert("accuracy".to_string(), "not-a-number".to_string());
376 model
377 .metadata
378 .insert("loss".to_string(), "invalid".to_string());
379
380 let card = model.to_model_card();
382 assert_eq!(card.get_name(), "BadMetrics");
383 }
384
385 #[test]
390 fn test_ald_empty_dataset() {
391 let dataset = AldDataset::new();
392 let card = dataset.to_data_card("empty");
393
394 assert_eq!(card.get_name(), "empty");
395 assert_eq!(card.column_count(), 0);
396 }
397
398 #[test]
399 fn test_ald_multiple_tensors() {
400 let mut dataset = AldDataset::new();
401 let train_x = vec![0.0_f32; 784_000];
402 let test_x = vec![0.0_f32; 78_400];
403 dataset.add_tensor(Tensor::from_f32("train_x", vec![1000, 784], &train_x));
404 dataset.add_tensor(Tensor::from_f32("train_y", vec![1000], &[0.0; 1000]));
405 dataset.add_tensor(Tensor::from_f32("test_x", vec![100, 784], &test_x));
406 dataset.add_tensor(Tensor::from_f32("test_y", vec![100], &[0.0; 100]));
407
408 let card = dataset.to_data_card("mnist");
409
410 assert_eq!(card.get_name(), "mnist");
411 assert_eq!(card.column_count(), 4);
412 }
413
414 #[test]
415 fn test_ald_different_dtypes() {
416 let mut dataset = AldDataset::new();
417
418 dataset.add_tensor(Tensor::from_f32("float32_tensor", vec![10], &[0.0; 10]));
420 dataset.add_tensor(Tensor::from_f32("float32_tensor2", vec![5, 2], &[0.0; 10]));
421
422 dataset.add_tensor(Tensor::new(
424 "float64_tensor",
425 DType::F64,
426 vec![10],
427 vec![0u8; 80],
428 ));
429 dataset.add_tensor(Tensor::new(
430 "int32_tensor",
431 DType::I32,
432 vec![10],
433 vec![0u8; 40],
434 ));
435 dataset.add_tensor(Tensor::new(
436 "uint8_tensor",
437 DType::U8,
438 vec![10],
439 vec![0u8; 10],
440 ));
441
442 let card = dataset.to_data_card("multi_dtype");
443
444 assert_eq!(card.column_count(), 5);
445 }
446
447 #[test]
452 fn test_format_bytes_boundaries() {
453 assert_eq!(format_bytes(0), "0 B");
455 assert_eq!(format_bytes(1023), "1023 B");
456 assert_eq!(format_bytes(1024), "1.0 KB");
457 assert_eq!(format_bytes(1024 * 1024 - 1), "1024.0 KB");
458 assert_eq!(format_bytes(1024 * 1024), "1.0 MB");
459 assert_eq!(format_bytes(1024 * 1024 * 1024 - 1), "1024.0 MB");
460 assert_eq!(format_bytes(1024 * 1024 * 1024), "1.0 GB");
461 }
462
463 #[test]
464 fn test_format_bytes_large_values() {
465 assert_eq!(format_bytes(10 * 1024 * 1024 * 1024), "10.0 GB");
467 assert_eq!(format_bytes(100 * 1024 * 1024 * 1024), "100.0 GB");
469 }
470
471 #[test]
472 fn test_format_bytes_fractional() {
473 assert_eq!(format_bytes(1536), "1.5 KB");
475 assert_eq!(format_bytes(2_621_440), "2.5 MB");
477 assert_eq!(format_bytes(3_489_660_928), "3.2 GB");
479 }
480
481 #[test]
486 fn test_load_apr_invalid_data() {
487 let invalid_data = b"not a valid apr file";
488 let result = load_apr_as_card(invalid_data);
489 assert!(result.is_err());
490 }
491
492 #[test]
493 fn test_load_ald_invalid_data() {
494 let invalid_data = b"not a valid ald file";
495 let result = load_ald_as_card(invalid_data, "test");
496 assert!(result.is_err());
497 }
498
499 #[test]
500 fn test_load_apr_empty_data() {
501 let empty_data = b"";
502 let result = load_apr_as_card(empty_data);
503 assert!(result.is_err());
504 }
505
506 #[test]
507 fn test_load_ald_empty_data() {
508 let empty_data = b"";
509 let result = load_ald_as_card(empty_data, "test");
510 assert!(result.is_err());
511 }
512
513 #[test]
518 fn test_model_card_has_sovereign_ai_tag() {
519 let model = AprModel::new("SovereignModel");
520 let card = model.to_model_card();
521
522 assert!(card.get_tags().contains(&"sovereign-ai".to_string()));
523 assert!(card.get_tags().contains(&"apr".to_string()));
524 }
525
526 #[test]
527 fn test_data_card_has_sovereign_ai_tag() {
528 let dataset = AldDataset::new();
529 let card = dataset.to_data_card("SovereignData");
530
531 assert!(card.get_tags().contains(&"sovereign-ai".to_string()));
532 assert!(card.get_tags().contains(&"ald".to_string()));
533 }
534}