//! `ledger_breakdown` — *breakdown at the top.* //! //! Drives a few cascade walks with intent-tagged receipts, appends //! them to an in-memory `jouleclaw-ledger`, and prints the rolled-up //! `CostSummary`. The same payload the `jclaw ledger summary` //! subcommand and the (feature-gated) MCP `get_cost_summary` tool //! render against. //! //! Run with: //! cargo run -p jouleclaw-stack --example ledger_breakdown //! //! The workload is simulated: no tier runs and no meter reads a figure, so //! every tool touch is graded `Estimator` ([`simulated_touch`]), and the //! summary's provenance floor says so. A deployment's receipts carry the //! grade of whatever metered each touch. use jouleclaw_energy::Provenance; use jouleclaw_intent::{HeuristicClassifier, IntentClassifier}; use jouleclaw_ledger::{InMemoryLedger, Ledger, LedgerStore, Window}; use jouleclaw_prov::{input_hash, CascadeTier, ReceiptBuilder, ToolTouch}; fn main() { let classifier = HeuristicClassifier::new(); let mut ledger = InMemoryLedger::new(); // Simulate a small workload: 5 comprehension queries, 1 generation, // 1 modification — the rough shape of Priscilla's 116-session split. let queries: &[(&str, CascadeTier, u64, &str)] = &[ ("explain the cascade", CascadeTier::L0Cache, 5, "cache.lookup"), ("what does jouleclaw-prov do?", CascadeTier::L1Lawful, 12, "iac:lawful.lookup"), ("how does the verifier work?", CascadeTier::L1Lawful, 18, "iac:lawful.lookup"), ("trace the auth flow", CascadeTier::L2Embed, 200, "iac:rag.retrieve"), ("git blame this function", CascadeTier::L1Lawful, 10, "iac:lawful.blame"), ("write a hello-world fn", CascadeTier::L3Model, 1_200_000, "model:claude-haiku"), ("rename foo to bar", CascadeTier::L3Model, 900_000, "model:claude-haiku"), ]; for (q, tier, joules_uj, tool_id) in queries { let intent = classifier.classify(q); let model_id = matches!(tier, CascadeTier::L3Model) .then(|| "anthropic/claude-haiku-4-5".to_string()); let mut b = ReceiptBuilder::new() .input_hash(input_hash(q)) .tier(*tier) .account_tool(simulated_touch(tool_id, *joules_uj)) .intent(intent); if let Some(m) = model_id { b = b.model_id(m); } ledger.append(b.seal().expect("seal")).expect("append"); } let summary = Ledger::new(&ledger).summary(&Window::any()); println!("cost summary ({} receipts):", summary.receipt_count); println!(" total joules {:.6} J", summary.total_joules()); println!(" provenance floor {:?}", summary.provenance_floor); if let (Some(c), Some(g)) = (summary.comprehension_share(), summary.generation_share()) { println!( " comprehension / gen {:.1}% / {:.1}% (Priscilla baseline: 67% / 2%)", c * 100.0, g * 100.0 ); } println!(); print_rows("by tier", &summary.by_tier); print_rows("by tool", &summary.by_tool); print_rows("by model", &summary.by_model); print_rows("by intent", &summary.by_intent); } /// A simulated tool touch: its figure is chosen, not read, so it is graded /// `Estimator` at every tier. Graded `HwShunt` (as the cheap tiers were) it /// would claim a hardware reading, and `ModelBased` (as the model tier was) an /// energy model's figure, of work that never ran. pub fn simulated_touch(tool_id: &str, joules_uj: u64) -> ToolTouch { ToolTouch { tool_id: tool_id.to_string(), joules_uj, energy_provenance: Provenance::Estimator } } fn print_rows(label: &str, rows: &[jouleclaw_ledger::CostRow]) { if rows.is_empty() { return; } println!("{label}:"); for r in rows { let key = if r.key.is_empty() { "(none)" } else { &r.key }; println!( " {key:<32} {count:>3} {joules:>12.6} J [{prov:?}]", count = r.receipt_count, joules = r.joules(), prov = r.provenance_floor, ); } println!(); }