tawasuyu

tawasuyu/brahman

Rust 332TOML 127JSON 17Markdown 154710 Definitions

⎇ main crates/apps/badu/src/main.rs Rust · 134 lines · 4.6 KB History Who touched what raw

feat(badu): app demo — cuaderno con grafo de enlaces y gravedad ea079a0b2 · sergio ·

1//! `badu` — demostración del cuaderno de notas.
2//!
3//! Siembra un cuaderno personal, imprime el grafo de wiki-links
4//! (forward-links, backlinks, huérfanas, enlaces colgantes) y luego la
5//! gravedad semántica: los clústeres por afinidad y los vecinos más
6//! cercanos de una nota.
7//!
8//! Los vectores semánticos van a mano —tres tópicos: cocina, jardín,
9//! oficina— para que el clustering se vea con claridad. En la app real
10//! los produce `verbo`. Corre con `cargo run -p badu`.
11
12use badu_core::{NoteId, NoteStore};
13use badu_gravity::{GravityConfig, SemanticField};
14
15/// Vector de tópico con un leve sesgo — notas del mismo tema quedan
16/// afines sin ser idénticas.
17fn topic(base: [f32; 3], nudge: f32) -> Vec<f32> {
18    vec![base[0] + nudge, base[1] + nudge * 0.3, base[2] - nudge * 0.2]
19}
20
21fn main() {
22    let cocina = [1.0, 0.0, 0.0];
23    let jardin = [0.0, 1.0, 0.0];
24    let oficina = [0.0, 0.0, 1.0];
25
26    let mut store = NoteStore::new();
27    let mut field = SemanticField::new();
28
29    // (título, cuerpo, etiquetas, vector de tópico)
30    let seed: [(&str, &str, &[&str], Vec<f32>); 7] = [
31        (
32            "Índice",
33            "mi cuaderno: [[Recetas de la abuela]], [[Jardín]] y [[Oficina]]",
34            &["meta"],
35            topic(cocina, 0.0),
36        ),
37        (
38            "Recetas de la abuela",
39            "sopa de auyama; ver también [[Lista del mercado]]",
40            &["cocina"],
41            topic(cocina, 0.05),
42        ),
43        (
44            "Lista del mercado",
45            "auyama, cilantro, pan; vuelve al [[Índice]]",
46            &["cocina"],
47            topic(cocina, 0.10),
48        ),
49        (
50            "Jardín",
51            "riego semanal; las [[Semillas de cilantro]] van en marzo",
52            &["jardín"],
53            topic(jardin, 0.05),
54        ),
55        (
56            "Semillas de cilantro",
57            "germinan en diez días",
58            &["jardín"],
59            topic(jardin, 0.10),
60        ),
61        (
62            "Oficina",
63            "[[Reunión del lunes]] y pendientes varios",
64            &["trabajo"],
65            topic(oficina, 0.05),
66        ),
67        (
68            "Diario sin enlaces",
69            "una nota suelta, no la enlaza nadie y enlaza a [[Algo Perdido]]",
70            &["personal"],
71            topic(oficina, 0.50),
72        ),
73    ];
74
75    let mut ids: Vec<(NoteId, String)> = Vec::new();
76    for (title, body, tags, vector) in seed {
77        let tags = tags.iter().map(|t| t.to_string()).collect();
78        let id = store.create(title, body, tags, 1_700_000_000);
79        field.insert(id, vector);
80        ids.push((id, title.to_string()));
81    }
82
83    let name = |id: NoteId| {
84        ids.iter()
85            .find(|(i, _)| *i == id)
86            .map(|(_, n)| n.as_str())
87            .unwrap_or("?")
88    };
89
90    println!("\n  badu · cuaderno de notas — {} notas\n", store.len());
91
92    println!("  grafo de enlaces:");
93    for note in store.iter() {
94        let fwd: Vec<&str> = store.forward_links(note.id).into_iter().map(name).collect();
95        let back: Vec<&str> = store.backlinks(note.id).into_iter().map(name).collect();
96        println!("    «{}»", note.title);
97        println!("       enlaza a  : {}", fmt_list(&fwd));
98        println!("       backlinks : {}", fmt_list(&back));
99    }
100
101    let orphans: Vec<&str> = store.orphans().iter().map(|n| n.title.as_str()).collect();
102    println!("\n  notas huérfanas (sin backlinks): {}", fmt_list(&orphans));
103    let dangling_owned = store.dangling_links();
104    let dangling: Vec<&str> = dangling_owned.iter().map(|s| s.as_str()).collect();
105    println!("  enlaces colgantes (destino inexistente): {}", fmt_list(&dangling));
106
107    println!("\n  gravedad semántica — clústeres (afinidad ≥ 0.85):");
108    for (n, cluster) in field.clusters(0.85).iter().enumerate() {
109        let titles: Vec<&str> = cluster.iter().map(|id| name(*id)).collect();
110        println!("    grupo {}: {}", n + 1, fmt_list(&titles));
111    }
112
113    let pivot = ids[1].0; // "Recetas de la abuela"
114    println!("\n  vecinos más afines a «{}»:", name(pivot));
115    for (id, score) in field.nearest(pivot, 3) {
116        println!("    {:.3}  {}", score, name(id));
117    }
118
119    let layout = field.gravity_layout(&GravityConfig::default());
120    println!("\n  layout 2D por gravedad ({} posiciones):", layout.len());
121    for p in &layout {
122        println!("    ({:7.1}, {:7.1})  {}", p.x, p.y, name(p.id));
123    }
124    println!();
125}
126
127/// Formatea una lista de nombres, o `—` si está vacía.
128fn fmt_list(items: &[&str]) -> String {
129    if items.is_empty() {
130        "—".to_string()
131    } else {
132        items.join(", ")
133    }
134}