use crate::session::SessionState;
use m1nd_core::error::M1ndResult;
#[derive(Clone, Debug)]
pub struct ActivateConfig {
pub top_k: usize,
pub dimensions: Vec<String>,
pub xlr: bool,
pub include_ghost_edges: bool,
pub include_structural_holes: bool,
}
impl Default for ActivateConfig {
fn default() -> Self {
Self {
top_k: 8, dimensions: vec![
"structural".into(),
"semantic".into(),
"temporal".into(),
"causal".into(),
],
xlr: true,
include_ghost_edges: true,
include_structural_holes: true,
}
}
}
#[derive(Clone, Debug)]
pub enum ImpactDirection {
Forward,
Reverse,
Both,
}
#[derive(Clone, Debug)]
pub struct ActivatedNode {
pub node_id: String,
pub label: String,
pub node_type: String,
pub activation: f32,
pub pagerank: f32,
pub source_path: Option<String>,
pub line_start: Option<u32>,
pub line_end: Option<u32>,
}
#[derive(Clone, Debug)]
pub struct ActivateResult {
pub nodes: Vec<ActivatedNode>,
pub ghost_edges: Vec<GhostEdge>,
pub structural_holes: Vec<StructuralHole>,
pub elapsed_ms: f64,
}
#[derive(Clone, Debug)]
pub struct GhostEdge {
pub source: String,
pub target: String,
pub strength: f32,
}
#[derive(Clone, Debug)]
pub struct StructuralHole {
pub node_id: String,
pub label: String,
pub node_type: String,
pub reason: String,
}
#[derive(Clone, Debug)]
pub struct ImpactResult {
pub blast_radius: Vec<ImpactEntry>,
pub total_energy: f32,
}
#[derive(Clone, Debug)]
pub struct ImpactEntry {
pub node_id: String,
pub label: String,
pub signal_strength: f32,
pub hop_distance: u8,
}
#[derive(Clone, Debug)]
pub struct MissingResult {
pub holes: Vec<StructuralHole>,
}
#[derive(Clone, Debug)]
pub struct WhyResult {
pub paths: Vec<WhyPath>,
}
#[derive(Clone, Debug)]
pub struct WhyPath {
pub nodes: Vec<String>,
pub relations: Vec<String>,
pub cumulative_strength: f32,
}
#[derive(Clone, Debug)]
pub struct ResonateResult {
pub harmonics: Vec<HarmonicEntry>,
}
#[derive(Clone, Debug)]
pub struct HarmonicEntry {
pub node_id: String,
pub label: String,
pub amplitude: f32,
}
pub fn activate_readonly(
state: &SessionState,
query: &str,
config: ActivateConfig,
) -> M1ndResult<ActivateResult> {
todo!("activate_readonly: extract from tools::handle_activate")
}
pub fn impact_readonly(
state: &SessionState,
node: &str,
direction: ImpactDirection,
) -> M1ndResult<ImpactResult> {
todo!("impact_readonly: extract from tools::handle_impact")
}
pub fn missing_readonly(state: &SessionState, query: &str) -> M1ndResult<MissingResult> {
todo!("missing_readonly: extract from tools::handle_missing")
}
pub fn why_readonly(state: &SessionState, from: &str, to: &str) -> M1ndResult<WhyResult> {
todo!("why_readonly: extract from tools::handle_why")
}
pub fn resonate_readonly(state: &SessionState, query: &str) -> M1ndResult<ResonateResult> {
todo!("resonate_readonly: extract from tools::handle_resonate")
}
pub struct SynthesisBudget {
pub max_calls: u32,
pub calls_used: u32,
pub timeout_ms: f64,
pub start_time: std::time::Instant,
}
impl SynthesisBudget {
pub fn new() -> Self {
Self {
max_calls: 8,
calls_used: 0,
timeout_ms: 500.0,
start_time: std::time::Instant::now(),
}
}
pub fn can_call(&self) -> bool {
self.calls_used < self.max_calls && self.elapsed_ms() < self.timeout_ms
}
pub fn record_call(&mut self) {
self.calls_used += 1;
}
pub fn elapsed_ms(&self) -> f64 {
self.start_time.elapsed().as_secs_f64() * 1000.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn budget_starts_with_capacity() {
let budget = SynthesisBudget::new();
assert!(budget.can_call());
assert_eq!(budget.calls_used, 0);
assert_eq!(budget.max_calls, 8);
}
#[test]
fn budget_exhausts() {
let mut budget = SynthesisBudget::new();
for _ in 0..8 {
assert!(budget.can_call());
budget.record_call();
}
assert!(!budget.can_call());
}
#[test]
fn activate_config_defaults() {
let config = ActivateConfig::default();
assert_eq!(config.top_k, 8); assert_eq!(config.dimensions.len(), 4);
}
}