Skip to main content

oxibrain_cli/cmd/
why.rs

1use oxibrain::Brain;
2use oxibrain::BrainConfig;
3use oxibrain_core::{DropReason, Query, QueryMode};
4use std::path::Path;
5
6/// `oxibrain why --dropped "<query>"` — print what `rank` discarded for a
7/// query, from the conservation guarantee (DESIGN §11.8). Empty output
8/// means nothing was dropped.
9pub async fn run_dropped(
10    dir: &Path,
11    query_text: &str,
12    space: &str,
13    min_confidence: f32,
14) -> anyhow::Result<()> {
15    let brain = Brain::open(BrainConfig::at(dir)).await?;
16    let space_id = brain.ensure_space(space).await?;
17    let q = Query {
18        text: query_text.to_string(),
19        mode: QueryMode::Hybrid,
20        space: space_id,
21        as_of: None,
22        limit: 20,
23        min_confidence,
24    };
25    let result = brain.query(q).await?;
26    if result.dropped.is_empty() {
27        println!(
28            "no candidates were dropped (limit {})",
29            result.total_candidates
30        );
31        return Ok(());
32    }
33    println!(
34        "dropped {} of {} candidates:",
35        result.dropped.len(),
36        result.total_candidates
37    );
38    for d in &result.dropped {
39        let reason = match &d.reason {
40            DropReason::BelowConfidenceFloor { actual, floor } => {
41                format!("below confidence floor: {actual:.2} < {floor:.2}")
42            }
43            DropReason::OutsideValidWindow { valid_at } => {
44                format!("outside valid window at {valid_at:?}")
45            }
46            DropReason::BeforeKnownAt {
47                known_at,
48                recorded_at,
49            } => {
50                format!("recorded {recorded_at:?} after known_at {known_at:?}")
51            }
52            DropReason::TrustExcluded { tier } => format!("trust tier excluded: {tier:?}"),
53            DropReason::PredicateDenied { predicate } => {
54                format!("predicate denied: {predicate}")
55            }
56            DropReason::EntityTypeMismatch { expected } => {
57                format!("entity type mismatch, expected {expected:?}")
58            }
59            DropReason::TruncatedByBudget { position } => {
60                format!("truncated by budget (position {position})")
61            }
62        };
63        println!("  {} — {reason}", d.target.rrf_key());
64    }
65    Ok(())
66}
67
68pub async fn run(dir: &Path, statement_id: &str, space: &str) -> anyhow::Result<()> {
69    let brain = Brain::open(BrainConfig::at(dir)).await?;
70    let space_id = brain.ensure_space(space).await?;
71    let block = brain.why(&space_id, statement_id).await?;
72    println!("statement: {}", block.statement.id);
73    println!("  subject: {}", block.statement.subject);
74    println!("  predicate: {}", block.statement.predicate);
75    println!("  object: {:?}", block.statement.object);
76    println!("status: {}", block.status);
77    println!(
78        "confidence: raw={:.3} support={} contradiction={}",
79        block.confidence_breakdown.raw_confidence,
80        block.confidence_breakdown.support_count,
81        block.confidence_breakdown.contradiction_count
82    );
83    println!("assertions: {}", block.assertions.len());
84    for a in &block.assertions {
85        println!(
86            "  [{}] episode={} extractor={:?} conf={:.3} recorded={}",
87            a.polarity, a.episode_id, a.extractor, a.confidence, a.recorded_at
88        );
89    }
90    Ok(())
91}