lean_ctx/core/knowledge_router/
receipt.rs1use 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}