The ledger_breakdown example simulates a workload — no tier runs, no meter reads — and sealed its tool touches HwShunt at the cheap tiers and ModelBased at the model tier, so the printed summary's rows claimed hardware readings and an energy model's figures for work that never ran. Every touch now comes from `simulated_touch`, graded Estimator, and the module docs say the workload is simulated. The printed floor and rows read [Estimator]. tests/ledger_breakdown_example.rs compiles the example in as a module, seals touches at three tiers through its `simulated_touch`, and checks each touch, the summary's floor, and every by-tier and by-tool row are Estimator. Co-Authored-By: Claude Opus 5.5 <[email protected]>
94 lines
4 KiB
Rust
94 lines
4 KiB
Rust
//! `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!();
|
|
}
|