Skip to main content

lean_ctx/core/knowledge_router/
receipt.rs

1use super::{context_bundle::ContextBundle, planner::ContextCandidate};
2use chrono::Utc;
3use std::collections::BTreeSet;
4#[derive(Debug, Clone, Default, PartialEq, Eq)]
5pub struct KnowledgeReceipt {
6    pub receipt_id: String,
7    pub task_id: String,
8    pub bundle_id: String,
9    pub budget_tokens: u64,
10    pub materialized_tokens: u64,
11    pub candidates_considered: u32,
12    pub candidates_selected: u32,
13    pub sources_used: Vec<String>,
14    pub strategy: String,
15    pub timestamp: String,
16}
17pub fn create_receipt(
18    task_id: &str,
19    bundle: &ContextBundle,
20    all_candidates: &[ContextCandidate],
21    budget: u64,
22) -> KnowledgeReceipt {
23    let selected = bundle.candidates.iter().collect::<BTreeSet<_>>();
24    let sources_used = all_candidates
25        .iter()
26        .filter(|candidate| selected.contains(&candidate.candidate_id))
27        .map(|candidate| candidate.source_id.clone())
28        .collect::<BTreeSet<_>>()
29        .into_iter()
30        .collect();
31    KnowledgeReceipt {
32        receipt_id: format!("receipt-{task_id}-{}", bundle.candidates.len()),
33        task_id: task_id.into(),
34        bundle_id: bundle.bundle_id.clone(),
35        budget_tokens: budget,
36        materialized_tokens: bundle.total_tokens.min(budget),
37        candidates_considered: all_candidates.len() as u32,
38        candidates_selected: bundle.candidates.len() as u32,
39        sources_used,
40        strategy: format!("{:?}", bundle.strategy).to_lowercase(),
41        timestamp: Utc::now().to_rfc3339(),
42    }
43}
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::core::knowledge_router::{
48        context_bundle::{BundleStrategy, create_bundle},
49        planner::ContextCandidate,
50    };
51    fn candidate(id: &str, source: &str) -> ContextCandidate {
52        ContextCandidate {
53            candidate_id: id.into(),
54            source_id: source.into(),
55            kind: "file".into(),
56            relevance_milli: 900,
57            estimated_tokens: 100,
58            reference: None,
59        }
60    }
61    #[test]
62    fn receipt_accounts_for_selected_candidates() {
63        let all = vec![candidate("a", "local_files"), candidate("b", "jira")];
64        let bundle = create_bundle("task", &all[..1], BundleStrategy::Enriched);
65        let receipt = create_receipt("task", &bundle, &all, 200);
66        assert_eq!(receipt.candidates_selected, 1);
67        assert_eq!(receipt.materialized_tokens, 100);
68    }
69    #[test]
70    fn receipt_deduplicates_sources() {
71        let all = vec![candidate("a", "jira"), candidate("b", "jira")];
72        let receipt = create_receipt(
73            "task",
74            &create_bundle("task", &all, BundleStrategy::Enriched),
75            &all,
76            200,
77        );
78        assert_eq!(receipt.sources_used, ["jira"]);
79    }
80}