sara-tasks 2.7.6

Sara — folder-aware task manager
use anyhow::Result;
use rusqlite::Connection;
use serde_json::json;
use std::collections::HashSet;

use crate::commands::recall::types::{Hit, Related};
use crate::commands::shared::{item_snippet, memory_handle};
use crate::infrastructure::db;

pub(in crate::commands::recall) const ASSOCIATIVE_CAP: usize = 5;

pub(in crate::commands::recall) const AUTO_SPREAD_HIT_FLOOR: usize = 3;

pub(in crate::commands::recall) fn should_auto_spread(hits: &[Hit]) -> bool {
    let memory_hits = hits.iter().filter(|h| h.item_uuid.is_some()).count();
    (1..AUTO_SPREAD_HIT_FLOOR).contains(&memory_hits)
}

pub(in crate::commands::recall) fn spreading_related(
    conn: &Connection,
    hits: &[Hit],
) -> Result<Vec<Related>> {
    let seeds: Vec<uuid::Uuid> = hits.iter().filter_map(|h| h.item_uuid).collect();
    if seeds.is_empty() {
        return Ok(vec![]);
    }
    let graph = crate::infrastructure::memory::graph::MemoryGraph::build(conn)?;
    if graph.is_empty() {
        return Ok(vec![]);
    }
    let seed_set: HashSet<uuid::Uuid> = seeds.iter().copied().collect();
    let mut out = vec![];
    for act in graph.spread_activation_explained(&seeds, 2, 0.6, 1e-6) {
        if seed_set.contains(&act.uuid) {
            continue;
        }
        if let Ok(item) = db::get_item_by_uuid(conn, &act.uuid.to_string()) {
            out.push(Related {
                item,
                activation: act.activation,
                path: act.path,
            });
        }
        if out.len() >= ASSOCIATIVE_CAP {
            break;
        }
    }
    let max = out.iter().map(|r| r.activation).fold(0.0_f64, f64::max);
    if max > 0.0 {
        for r in &mut out {
            r.activation /= max;
        }
    }
    Ok(out)
}

pub(in crate::commands::recall) fn associative_guide(
    conn: &Connection,
    related: &[Related],
) -> Vec<serde_json::Value> {
    related
        .iter()
        .map(|r| {
            let _ = db::record_memory_surfaced(conn, &r.item.uuid);
            json!({
                "label": memory_handle(&r.item),
                "preview": item_snippet(&r.item, 160),
                "activation": r.activation,
                "strength": db::item_strength(conn, &r.item),
                "via": r.path,
            })
        })
        .collect()
}