Skip to main content

batuta/hf/
tree.rs

1//! HuggingFace Ecosystem Tree Visualization
2//!
3//! Provides hierarchical views of:
4//! - HuggingFace ecosystem components
5//! - PAIML-HuggingFace integration mapping
6
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10// ============================================================================
11// HF-TREE-001: Core Types
12// ============================================================================
13
14/// HuggingFace ecosystem category
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct HfCategory {
17    pub name: String,
18    pub components: Vec<HfComponent>,
19}
20
21impl HfCategory {
22    pub fn new(name: impl Into<String>) -> Self {
23        Self { name: name.into(), components: Vec::new() }
24    }
25
26    #[allow(clippy::should_implement_trait)]
27    pub fn add(mut self, component: HfComponent) -> Self {
28        self.components.push(component);
29        self
30    }
31}
32
33/// A component in the HuggingFace ecosystem
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct HfComponent {
36    pub name: String,
37    pub description: String,
38    pub url: Option<String>,
39}
40
41impl HfComponent {
42    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
43        Self { name: name.into(), description: description.into(), url: None }
44    }
45
46    pub fn with_url(mut self, url: impl Into<String>) -> Self {
47        self.url = Some(url.into());
48        self
49    }
50}
51
52/// The complete HuggingFace ecosystem tree
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct HfTree {
55    pub name: String,
56    pub categories: Vec<HfCategory>,
57}
58
59impl HfTree {
60    pub fn new(name: impl Into<String>) -> Self {
61        Self { name: name.into(), categories: Vec::new() }
62    }
63
64    pub fn add_category(mut self, category: HfCategory) -> Self {
65        self.categories.push(category);
66        self
67    }
68
69    pub fn total_components(&self) -> usize {
70        self.categories.iter().map(|c| c.components.len()).sum()
71    }
72}
73
74// ============================================================================
75// HF-TREE-002: Integration Types
76// ============================================================================
77
78/// Integration type between PAIML and HuggingFace
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80pub enum IntegrationType {
81    /// PAIML component is compatible with HF format/API
82    Compatible,
83    /// PAIML provides a native Rust alternative
84    Alternative,
85    /// PAIML orchestrates/wraps HF functionality
86    Orchestrates,
87    /// Uses HF library directly
88    Uses,
89    /// Not interoperable
90    Incompatible,
91}
92
93impl fmt::Display for IntegrationType {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match self {
96            Self::Compatible => write!(f, "✓ COMPATIBLE"),
97            Self::Alternative => write!(f, "⚡ ALTERNATIVE"),
98            Self::Orchestrates => write!(f, "🔄 ORCHESTRATES"),
99            Self::Uses => write!(f, "📦 USES"),
100            Self::Incompatible => write!(f, "✗ INCOMPATIBLE"),
101        }
102    }
103}
104
105/// A mapping between PAIML and HuggingFace components
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct IntegrationMapping {
108    pub paiml_component: String,
109    pub hf_equivalent: String,
110    pub integration_type: IntegrationType,
111    pub notes: Option<String>,
112}
113
114impl IntegrationMapping {
115    pub fn new(
116        paiml: impl Into<String>,
117        hf: impl Into<String>,
118        integration: IntegrationType,
119    ) -> Self {
120        Self {
121            paiml_component: paiml.into(),
122            hf_equivalent: hf.into(),
123            integration_type: integration,
124            notes: None,
125        }
126    }
127
128    pub fn with_notes(mut self, notes: impl Into<String>) -> Self {
129        self.notes = Some(notes.into());
130        self
131    }
132}
133
134/// Integration category for grouping mappings
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct IntegrationCategory {
137    pub name: String,
138    pub mappings: Vec<IntegrationMapping>,
139}
140
141impl IntegrationCategory {
142    pub fn new(name: impl Into<String>) -> Self {
143        Self { name: name.into(), mappings: Vec::new() }
144    }
145
146    #[allow(clippy::should_implement_trait)]
147    pub fn add(mut self, mapping: IntegrationMapping) -> Self {
148        self.mappings.push(mapping);
149        self
150    }
151}
152
153/// The complete PAIML-HuggingFace integration tree
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct IntegrationTree {
156    pub categories: Vec<IntegrationCategory>,
157}
158
159impl IntegrationTree {
160    pub fn new() -> Self {
161        Self { categories: Vec::new() }
162    }
163
164    pub fn add_category(mut self, category: IntegrationCategory) -> Self {
165        self.categories.push(category);
166        self
167    }
168
169    pub fn total_mappings(&self) -> usize {
170        self.categories.iter().map(|c| c.mappings.len()).sum()
171    }
172
173    pub fn compatible_count(&self) -> usize {
174        self.categories
175            .iter()
176            .flat_map(|c| &c.mappings)
177            .filter(|m| m.integration_type == IntegrationType::Compatible)
178            .count()
179    }
180
181    pub fn alternative_count(&self) -> usize {
182        self.categories
183            .iter()
184            .flat_map(|c| &c.mappings)
185            .filter(|m| m.integration_type == IntegrationType::Alternative)
186            .count()
187    }
188}
189
190impl Default for IntegrationTree {
191    fn default() -> Self {
192        Self::new()
193    }
194}
195
196// ============================================================================
197// HF-TREE-003: Formatters
198// ============================================================================
199
200/// Format HF tree as ASCII
201pub fn format_hf_tree_ascii(tree: &HfTree) -> String {
202    let mut output = format!("{} ({} categories)\n", tree.name, tree.categories.len());
203
204    for (cat_idx, category) in tree.categories.iter().enumerate() {
205        let is_last_cat = cat_idx == tree.categories.len() - 1;
206        let cat_prefix = if is_last_cat { "└── " } else { "├── " };
207        output.push_str(&format!("{}{}\n", cat_prefix, category.name));
208
209        for (comp_idx, comp) in category.components.iter().enumerate() {
210            let is_last_comp = comp_idx == category.components.len() - 1;
211            let comp_prefix = if is_last_cat { "    " } else { "│   " };
212            let comp_branch = if is_last_comp { "└── " } else { "├── " };
213            output.push_str(&format!(
214                "{}{}{:<20} ({})\n",
215                comp_prefix, comp_branch, comp.name, comp.description
216            ));
217        }
218    }
219
220    output
221}
222
223/// Format integration tree as ASCII table
224pub fn format_integration_tree_ascii(tree: &IntegrationTree) -> String {
225    let mut output = String::from("PAIML ↔ HuggingFace Integration Map\n");
226    output.push_str(&"═".repeat(65));
227    output.push('\n');
228    output.push('\n');
229
230    output.push_str(&format!("┌{:─<20}┬{:─<20}┬{:─<22}┐\n", "", "", ""));
231    output.push_str(&format!(
232        "│ {:^18} │ {:^18} │ {:^20} │\n",
233        "PAIML Component", "HF Equivalent", "Integration Type"
234    ));
235    output.push_str(&format!("├{:─<20}┼{:─<20}┼{:─<22}┤\n", "", "", ""));
236
237    for category in &tree.categories {
238        output.push_str(&format!(
239            "│ {:^18} │ {:^18} │ {:^20} │\n",
240            category.name.to_uppercase(),
241            "",
242            ""
243        ));
244        output.push_str(&format!("├{:─<20}┼{:─<20}┼{:─<22}┤\n", "", "", ""));
245
246        for mapping in &category.mappings {
247            output.push_str(&format!(
248                "│ {:<18} │ {:<18} │ {:<20} │\n",
249                mapping.paiml_component,
250                mapping.hf_equivalent,
251                format!("{}", mapping.integration_type)
252            ));
253        }
254
255        output.push_str(&format!("├{:─<20}┼{:─<20}┼{:─<22}┤\n", "", "", ""));
256    }
257
258    output.push_str(&format!("└{:─<20}┴{:─<20}┴{:─<22}┘\n", "", "", ""));
259
260    output.push_str("\nLegend:\n");
261    output.push_str("  ✓ COMPATIBLE  - Interoperates with HF format/API\n");
262    output.push_str("  ⚡ ALTERNATIVE - PAIML native replacement (pure Rust)\n");
263    output.push_str("  🔄 ORCHESTRATES - PAIML wraps/orchestrates HF\n");
264    output.push_str("  📦 USES        - PAIML uses HF library directly\n");
265
266    output.push_str(&format!(
267        "\nSummary: {} compatible, {} alternatives, {} total mappings\n",
268        tree.compatible_count(),
269        tree.alternative_count(),
270        tree.total_mappings()
271    ));
272
273    output
274}
275
276/// Format HF tree as JSON
277pub fn format_hf_tree_json(tree: &HfTree) -> Result<String, serde_json::Error> {
278    serde_json::to_string_pretty(tree)
279}
280
281/// Format integration tree as JSON
282pub fn format_integration_tree_json(tree: &IntegrationTree) -> Result<String, serde_json::Error> {
283    serde_json::to_string_pretty(tree)
284}
285
286// ============================================================================
287// HF-TREE-004: Builders
288// ============================================================================
289
290/// Build the HuggingFace ecosystem tree
291pub fn build_hf_tree() -> HfTree {
292    HfTree::new("HuggingFace Ecosystem")
293        .add_category(
294            HfCategory::new("hub")
295                .add(HfComponent::new("models", "700K+ models"))
296                .add(HfComponent::new("datasets", "100K+ datasets"))
297                .add(HfComponent::new("spaces", "300K+ spaces"))
298                .add(HfComponent::new("papers", "Research papers")),
299        )
300        .add_category(
301            HfCategory::new("libraries")
302                .add(HfComponent::new("transformers", "Model architectures"))
303                .add(HfComponent::new("diffusers", "Diffusion models"))
304                .add(HfComponent::new("accelerate", "Distributed training"))
305                .add(HfComponent::new("peft", "Parameter-efficient fine-tuning"))
306                .add(HfComponent::new("trl", "Reinforcement learning"))
307                .add(HfComponent::new("optimum", "Hardware optimization"))
308                .add(HfComponent::new("datasets", "Dataset loading"))
309                .add(HfComponent::new("tokenizers", "Fast tokenization"))
310                .add(HfComponent::new("safetensors", "Safe serialization"))
311                .add(HfComponent::new("huggingface_hub", "Hub API client")),
312        )
313        .add_category(
314            HfCategory::new("inference")
315                .add(HfComponent::new("inference-api", "Serverless inference"))
316                .add(HfComponent::new("inference-endpoints", "Dedicated endpoints"))
317                .add(HfComponent::new("text-generation-inference", "TGI server")),
318        )
319        .add_category(
320            HfCategory::new("training")
321                .add(HfComponent::new("autotrain", "AutoML training"))
322                .add(HfComponent::new("trainer", "Training loops"))
323                .add(HfComponent::new("evaluate", "Metrics & evaluation")),
324        )
325        .add_category(
326            HfCategory::new("formats")
327                .add(HfComponent::new("safetensors", "Safe tensor format"))
328                .add(HfComponent::new("gguf", "Quantized format"))
329                .add(HfComponent::new("onnx", "Cross-platform"))
330                .add(HfComponent::new("pytorch", "Native PyTorch")),
331        )
332        .add_category(
333            HfCategory::new("tasks")
334                .add(HfComponent::new("text-generation", "LLM generation"))
335                .add(HfComponent::new("text-classification", "Classification"))
336                .add(HfComponent::new("question-answering", "QA"))
337                .add(HfComponent::new("translation", "Translation"))
338                .add(HfComponent::new("summarization", "Summarization"))
339                .add(HfComponent::new("image-classification", "Vision"))
340                .add(HfComponent::new("text-to-image", "Diffusion"))
341                .add(HfComponent::new("speech-recognition", "ASR")),
342        )
343}
344
345/// Build the PAIML-HuggingFace integration tree
346pub fn build_integration_tree() -> IntegrationTree {
347    IntegrationTree::new()
348        .add_category(
349            IntegrationCategory::new("formats")
350                .add(
351                    IntegrationMapping::new(".apr", "pickle/.joblib", IntegrationType::Alternative)
352                        .with_notes("Safe sklearn model format"),
353                )
354                .add(
355                    IntegrationMapping::new(".apr", "safetensors", IntegrationType::Alternative)
356                        .with_notes("Safe tensor serialization"),
357                )
358                .add(
359                    IntegrationMapping::new(".apr", "gguf", IntegrationType::Alternative)
360                        .with_notes("Quantized model format"),
361                )
362                .add(
363                    IntegrationMapping::new("realizar/gguf", "gguf", IntegrationType::Compatible)
364                        .with_notes("Import GGUF models"),
365                )
366                .add(
367                    IntegrationMapping::new(
368                        "realizar/safetensors",
369                        "safetensors",
370                        IntegrationType::Compatible,
371                    )
372                    .with_notes("Import SafeTensors"),
373                ),
374        )
375        .add_category(
376            IntegrationCategory::new("hub_access")
377                .add(
378                    IntegrationMapping::new(
379                        "aprender/hf_hub",
380                        "huggingface_hub",
381                        IntegrationType::Uses,
382                    )
383                    .with_notes("hf-hub crate"),
384                )
385                .add(
386                    IntegrationMapping::new(
387                        "batuta/hf",
388                        "huggingface_hub",
389                        IntegrationType::Orchestrates,
390                    )
391                    .with_notes("CLI orchestration"),
392                ),
393        )
394        .add_category(
395            IntegrationCategory::new("registry")
396                .add(
397                    IntegrationMapping::new(
398                        "pacha",
399                        "HF Hub registry",
400                        IntegrationType::Alternative,
401                    )
402                    .with_notes("Model/data/recipe registry"),
403                )
404                .add(
405                    IntegrationMapping::new("pacha", "MLflow/W&B", IntegrationType::Alternative)
406                        .with_notes("Full lineage tracking"),
407                ),
408        )
409        .add_category(
410            IntegrationCategory::new("inference")
411                .add(
412                    IntegrationMapping::new(
413                        "realizar",
414                        "transformers",
415                        IntegrationType::Alternative,
416                    )
417                    .with_notes("Pure Rust LLM inference"),
418                )
419                .add(
420                    IntegrationMapping::new("realizar", "TGI", IntegrationType::Alternative)
421                        .with_notes("Native server"),
422                )
423                .add(
424                    IntegrationMapping::new(
425                        "realizar/moe",
426                        "optimum",
427                        IntegrationType::Alternative,
428                    )
429                    .with_notes("MoE backend selection"),
430                ),
431        )
432        .add_category(
433            IntegrationCategory::new("classical_ml")
434                .add(
435                    IntegrationMapping::new("aprender", "sklearn", IntegrationType::Alternative)
436                        .with_notes("Pure Rust ML algorithms"),
437                )
438                .add(
439                    IntegrationMapping::new(
440                        "aprender",
441                        "xgboost/lightgbm",
442                        IntegrationType::Alternative,
443                    )
444                    .with_notes("Gradient boosting"),
445                ),
446        )
447        .add_category(
448            IntegrationCategory::new("deep_learning")
449                .add(
450                    IntegrationMapping::new(
451                        "entrenar",
452                        "PyTorch training",
453                        IntegrationType::Alternative,
454                    )
455                    .with_notes("DL training loops"),
456                )
457                .add(
458                    IntegrationMapping::new("alimentar", "datasets", IntegrationType::Alternative)
459                        .with_notes("Data loading/streaming"),
460                ),
461        )
462        .add_category(
463            IntegrationCategory::new("data_formats")
464                .add(
465                    IntegrationMapping::new(".ald", "parquet/arrow", IntegrationType::Alternative)
466                        .with_notes("Secure dataset format"),
467                )
468                .add(
469                    IntegrationMapping::new(".ald", "json/csv", IntegrationType::Alternative)
470                        .with_notes("+encryption/signing/licensing"),
471                ),
472        )
473        .add_category(
474            IntegrationCategory::new("compute")
475                .add(
476                    IntegrationMapping::new(
477                        "trueno",
478                        "NumPy/PyTorch tensors",
479                        IntegrationType::Alternative,
480                    )
481                    .with_notes("SIMD tensor ops"),
482                )
483                .add(
484                    IntegrationMapping::new("repartir", "accelerate", IntegrationType::Alternative)
485                        .with_notes("Distributed compute"),
486                ),
487        )
488        .add_category(
489            IntegrationCategory::new("tokenization")
490                .add(
491                    IntegrationMapping::new(
492                        "realizar/tokenizer",
493                        "tokenizers",
494                        IntegrationType::Compatible,
495                    )
496                    .with_notes("Load HF tokenizers"),
497                )
498                .add(
499                    IntegrationMapping::new(
500                        "trueno-rag",
501                        "tokenizers",
502                        IntegrationType::Compatible,
503                    )
504                    .with_notes("RAG tokenization"),
505                ),
506        )
507        .add_category(
508            IntegrationCategory::new("apps")
509                .add(
510                    IntegrationMapping::new("presentar", "gradio", IntegrationType::Alternative)
511                        .with_notes("Rust app framework"),
512                )
513                .add(
514                    IntegrationMapping::new(
515                        "trueno-viz",
516                        "visualization",
517                        IntegrationType::Alternative,
518                    )
519                    .with_notes("GPU rendering"),
520                ),
521        )
522        .add_category(
523            IntegrationCategory::new("quality").add(
524                IntegrationMapping::new("certeza", "evaluate", IntegrationType::Alternative)
525                    .with_notes("Rust metrics"),
526            ),
527        )
528        .add_category(
529            IntegrationCategory::new("mcp_tooling")
530                .add(
531                    IntegrationMapping::new(
532                        "pforge",
533                        "LangChain Tools",
534                        IntegrationType::Alternative,
535                    )
536                    .with_notes("Declarative MCP servers"),
537                )
538                .add(
539                    IntegrationMapping::new(
540                        "pmat",
541                        "code analysis tools",
542                        IntegrationType::Alternative,
543                    )
544                    .with_notes("AI context generation"),
545                )
546                .add(
547                    IntegrationMapping::new("pmcp", "mcp-sdk", IntegrationType::Alternative)
548                        .with_notes("Rust MCP runtime"),
549                ),
550        )
551}
552
553// ============================================================================
554// Tests
555// ============================================================================
556
557#[cfg(test)]
558#[allow(non_snake_case)]
559mod tests {
560    use super::*;
561
562    // ========================================================================
563    // HF-TREE-001: HfComponent Tests
564    // ========================================================================
565
566    #[test]
567    fn test_HF_TREE_001_hf_component_new() {
568        let comp = HfComponent::new("transformers", "Model architectures");
569        assert_eq!(comp.name, "transformers");
570        assert_eq!(comp.description, "Model architectures");
571        assert!(comp.url.is_none());
572    }
573
574    #[test]
575    fn test_HF_TREE_001_hf_component_with_url() {
576        let comp = HfComponent::new("transformers", "Models")
577            .with_url("https://huggingface.co/docs/transformers");
578        assert!(comp.url.is_some());
579    }
580
581    #[test]
582    fn test_HF_TREE_001_hf_category_new() {
583        let cat = HfCategory::new("libraries");
584        assert_eq!(cat.name, "libraries");
585        assert!(cat.components.is_empty());
586    }
587
588    #[test]
589    fn test_HF_TREE_001_hf_category_add() {
590        let cat = HfCategory::new("libraries").add(HfComponent::new("transformers", "Models"));
591        assert_eq!(cat.components.len(), 1);
592    }
593
594    // ========================================================================
595    // HF-TREE-002: HfTree Tests
596    // ========================================================================
597
598    #[test]
599    fn test_HF_TREE_002_hf_tree_new() {
600        let tree = HfTree::new("Test");
601        assert_eq!(tree.name, "Test");
602        assert!(tree.categories.is_empty());
603    }
604
605    #[test]
606    fn test_HF_TREE_002_hf_tree_add_category() {
607        let tree = HfTree::new("Test").add_category(HfCategory::new("hub"));
608        assert_eq!(tree.categories.len(), 1);
609    }
610
611    #[test]
612    fn test_HF_TREE_002_hf_tree_total_components() {
613        let tree = HfTree::new("Test").add_category(
614            HfCategory::new("hub")
615                .add(HfComponent::new("models", "Models"))
616                .add(HfComponent::new("datasets", "Datasets")),
617        );
618        assert_eq!(tree.total_components(), 2);
619    }
620
621    // ========================================================================
622    // HF-TREE-003: IntegrationType Tests
623    // ========================================================================
624
625    #[test]
626    fn test_HF_TREE_003_integration_type_display() {
627        assert_eq!(format!("{}", IntegrationType::Compatible), "✓ COMPATIBLE");
628        assert_eq!(format!("{}", IntegrationType::Alternative), "⚡ ALTERNATIVE");
629        assert_eq!(format!("{}", IntegrationType::Orchestrates), "🔄 ORCHESTRATES");
630        assert_eq!(format!("{}", IntegrationType::Uses), "📦 USES");
631    }
632
633    #[test]
634    fn test_HF_TREE_003_integration_mapping_new() {
635        let mapping =
636            IntegrationMapping::new("realizar", "transformers", IntegrationType::Alternative);
637        assert_eq!(mapping.paiml_component, "realizar");
638        assert_eq!(mapping.hf_equivalent, "transformers");
639    }
640
641    #[test]
642    fn test_HF_TREE_003_integration_mapping_with_notes() {
643        let mapping =
644            IntegrationMapping::new("a", "b", IntegrationType::Compatible).with_notes("Test notes");
645        assert_eq!(mapping.notes, Some("Test notes".to_string()));
646    }
647
648    // ========================================================================
649    // HF-TREE-004: IntegrationTree Tests
650    // ========================================================================
651
652    #[test]
653    fn test_HF_TREE_004_integration_tree_new() {
654        let tree = IntegrationTree::new();
655        assert!(tree.categories.is_empty());
656    }
657
658    #[test]
659    fn test_HF_TREE_004_integration_tree_counts() {
660        let tree = IntegrationTree::new().add_category(
661            IntegrationCategory::new("test")
662                .add(IntegrationMapping::new("a", "b", IntegrationType::Compatible))
663                .add(IntegrationMapping::new("c", "d", IntegrationType::Alternative)),
664        );
665        assert_eq!(tree.total_mappings(), 2);
666        assert_eq!(tree.compatible_count(), 1);
667        assert_eq!(tree.alternative_count(), 1);
668    }
669
670    // ========================================================================
671    // HF-TREE-005: Formatter Tests
672    // ========================================================================
673
674    #[test]
675    fn test_HF_TREE_005_format_hf_tree_ascii() {
676        let tree = HfTree::new("Test")
677            .add_category(HfCategory::new("hub").add(HfComponent::new("models", "700K+ models")));
678        let output = format_hf_tree_ascii(&tree);
679        assert!(output.contains("Test"));
680        assert!(output.contains("hub"));
681        assert!(output.contains("models"));
682    }
683
684    #[test]
685    fn test_HF_TREE_005_format_integration_tree_ascii() {
686        let tree = IntegrationTree::new().add_category(
687            IntegrationCategory::new("formats").add(IntegrationMapping::new(
688                "a",
689                "b",
690                IntegrationType::Compatible,
691            )),
692        );
693        let output = format_integration_tree_ascii(&tree);
694        assert!(output.contains("PAIML"));
695        assert!(output.contains("HuggingFace"));
696        assert!(output.contains("Legend"));
697    }
698
699    #[test]
700    fn test_HF_TREE_005_format_hf_tree_json() {
701        let tree = HfTree::new("Test");
702        let json = format_hf_tree_json(&tree).expect("unexpected failure");
703        assert!(json.contains("\"name\": \"Test\""));
704    }
705
706    #[test]
707    fn test_HF_TREE_005_format_integration_tree_json() {
708        let tree = IntegrationTree::new();
709        let json = format_integration_tree_json(&tree).expect("unexpected failure");
710        assert!(json.contains("categories"));
711    }
712
713    // ========================================================================
714    // HF-TREE-006: Builder Tests
715    // ========================================================================
716
717    #[test]
718    fn test_HF_TREE_006_build_hf_tree() {
719        let tree = build_hf_tree();
720        assert_eq!(tree.name, "HuggingFace Ecosystem");
721        assert!(!tree.categories.is_empty());
722        assert!(tree.total_components() > 20);
723    }
724
725    #[test]
726    fn test_HF_TREE_006_build_hf_tree_has_hub() {
727        let tree = build_hf_tree();
728        let hub = tree.categories.iter().find(|c| c.name == "hub");
729        assert!(hub.is_some());
730        assert!(hub.expect("unexpected failure").components.iter().any(|c| c.name == "models"));
731    }
732
733    #[test]
734    fn test_HF_TREE_006_build_hf_tree_has_libraries() {
735        let tree = build_hf_tree();
736        let libs = tree.categories.iter().find(|c| c.name == "libraries");
737        assert!(libs.is_some());
738        assert!(libs
739            .expect("unexpected failure")
740            .components
741            .iter()
742            .any(|c| c.name == "transformers"));
743    }
744
745    #[test]
746    fn test_HF_TREE_006_build_integration_tree() {
747        let tree = build_integration_tree();
748        assert!(!tree.categories.is_empty());
749        assert!(tree.total_mappings() > 10);
750    }
751
752    #[test]
753    fn test_HF_TREE_006_build_integration_tree_has_formats() {
754        let tree = build_integration_tree();
755        let formats = tree.categories.iter().find(|c| c.name == "formats");
756        assert!(formats.is_some());
757    }
758
759    #[test]
760    fn test_HF_TREE_006_build_integration_tree_has_compatible() {
761        let tree = build_integration_tree();
762        assert!(tree.compatible_count() > 0);
763    }
764
765    #[test]
766    fn test_HF_TREE_006_build_integration_tree_has_alternatives() {
767        let tree = build_integration_tree();
768        assert!(tree.alternative_count() > 0);
769    }
770
771    // ========================================================================
772    // HF-TREE-007: Integration Tests
773    // ========================================================================
774
775    #[test]
776    fn test_HF_TREE_007_full_hf_tree_output() {
777        let tree = build_hf_tree();
778        let output = format_hf_tree_ascii(&tree);
779        assert!(output.contains("HuggingFace Ecosystem"));
780        assert!(output.contains("hub"));
781        assert!(output.contains("libraries"));
782        assert!(output.contains("transformers"));
783    }
784
785    #[test]
786    fn test_HF_TREE_007_full_integration_tree_output() {
787        let tree = build_integration_tree();
788        let output = format_integration_tree_ascii(&tree);
789        assert!(output.contains("PAIML"));
790        assert!(output.contains("realizar"));
791        assert!(output.contains("COMPATIBLE"));
792        assert!(output.contains("ALTERNATIVE"));
793    }
794
795    #[test]
796    fn test_HF_TREE_007_json_roundtrip() {
797        let tree = build_hf_tree();
798        let json = format_hf_tree_json(&tree).expect("unexpected failure");
799        let parsed: HfTree = serde_json::from_str(&json).expect("json deserialize failed");
800        assert_eq!(parsed.name, tree.name);
801    }
802}