1pub mod claims;
44pub mod context;
45pub mod model;
46pub mod report;
47pub mod runner;
48pub mod stats;
49pub mod usage;
50
51use std::collections::{BTreeMap, BTreeSet};
52use std::path::{Path, PathBuf};
53
54use serde::Serialize;
55use sha2::{Digest, Sha256};
56
57use crate::metadata::yaml;
58
59use model::*;
60use stats::{bootstrap_median, bootstrap_median_clustered, distribution, percent_text, reduction};
61
62pub const DIR: &str = ".ai/repo/benchmarks/economics";
64
65pub const RUN_SCHEMA: &str = "economics-run/v1";
67pub const CONTEXT_RUN_SCHEMA: &str = "economics-context-run/v1";
69
70#[derive(Debug, Clone)]
105pub struct Declarations {
106 pub methodology: EconomicsMethodology,
108 pub suites: BTreeMap<String, EconomicsSuite>,
110 pub tasks: BTreeMap<String, EconomicsTask>,
112}
113
114fn read_yaml<T: serde::de::DeserializeOwned>(root: &Path, path: &Path) -> Result<T, String> {
115 let shown = path
116 .strip_prefix(root)
117 .unwrap_or(path)
118 .display()
119 .to_string();
120 let text = std::fs::read_to_string(path).map_err(|e| format!("{shown}: {e}"))?;
121 yaml::parse_into(&text).map_err(|e| format!("{shown}: {e}"))
122}
123
124fn sorted_entries(dir: &Path) -> Vec<PathBuf> {
126 let mut out: Vec<Entry> = std::fs::read_dir(dir)
127 .into_iter()
128 .flatten()
129 .flatten()
130 .map(|e| Entry {
131 name: e.file_name().to_string_lossy().into_owned(),
132 path: e.path(),
133 })
134 .collect();
135 crate::order::canonical(&mut out);
136 out.into_iter().map(|e| e.path).collect()
137}
138
139struct Entry {
143 name: String,
144 path: PathBuf,
145}
146
147impl crate::order::Ordered for Entry {
148 fn order_key(&self) -> crate::order::OrderKey<'_> {
149 crate::order::OrderKey::plain(&self.name, &self.name)
150 }
151}
152
153pub fn load(root: &Path) -> Result<Option<Declarations>, Vec<String>> {
170 let dir = root.join(DIR);
171 let methodology_path = dir.join("methodology.yaml");
172 if !methodology_path.is_file() {
173 return Ok(None);
174 }
175 let mut errors = Vec::new();
176 let methodology = match read_yaml::<EconomicsMethodology>(root, &methodology_path) {
177 Ok(m) => Some(m),
178 Err(e) => {
179 errors.push(e);
180 None
181 }
182 };
183 let mut suites = BTreeMap::new();
184 for p in sorted_entries(&dir.join("suites")) {
185 if p.extension().and_then(|e| e.to_str()) != Some("yaml") {
186 continue;
187 }
188 match read_yaml::<EconomicsSuite>(root, &p) {
189 Ok(s) => {
190 suites.insert(s.id.clone(), s);
191 }
192 Err(e) => errors.push(e),
193 }
194 }
195 let mut tasks = BTreeMap::new();
196 for p in sorted_entries(&dir.join("tasks")) {
197 let file = p.join("task.yaml");
198 if !file.is_file() {
199 continue;
200 }
201 match read_yaml::<EconomicsTask>(root, &file) {
202 Ok(t) => {
203 tasks.insert(t.id.clone(), t);
204 }
205 Err(e) => errors.push(e),
206 }
207 }
208 for s in suites.values() {
209 for t in &s.tasks {
210 if !tasks.contains_key(t) {
211 errors.push(format!(
212 "suite {} names task {t}, which is not declared",
213 s.id
214 ));
215 }
216 }
217 }
218 match (methodology, errors.is_empty()) {
219 (Some(methodology), true) => Ok(Some(Declarations {
220 methodology,
221 suites,
222 tasks,
223 })),
224 _ => Err(errors),
225 }
226}
227
228pub fn inputs_digest(root: &Path, pathspecs: &[String]) -> Result<String, String> {
245 let specs: Vec<&str> = pathspecs.iter().map(String::as_str).collect();
246 let files = crate::git::ls_files_any(root, &specs).map_err(|e| e.to_string())?;
247 let mut h = Sha256::new();
248 for f in &files {
249 let bytes = std::fs::read(root.join(f)).unwrap_or_default();
250 h.update(f.as_bytes());
251 h.update([0]);
252 h.update(crate::policy::sha256_bytes_hex(&bytes).as_bytes());
253 h.update(b"\n");
254 }
255 Ok(format!("{:x}", h.finalize()))
256}
257
258pub fn revision(root: &Path) -> Result<EconomicsRevision, String> {
288 let head = crate::git::read_only(root)
289 .args(["rev-parse", "--verify", "-q", "HEAD"])
290 .output()
291 .map_err(|e| format!("git: {e}"))?;
292 if !head.status.success() {
293 return Err("git: no HEAD to record evidence against".into());
294 }
295 let status = crate::git::read_only(root)
296 .args(["status", "--porcelain", "--untracked-files=all", "--", "."])
297 .arg(format!(":(exclude){DIR}/runs"))
298 .output()
299 .map_err(|e| format!("git: {e}"))?;
300 Ok(EconomicsRevision {
301 commit: String::from_utf8_lossy(&head.stdout).trim().to_string(),
302 dirty: !String::from_utf8_lossy(&status.stdout).trim().is_empty(),
303 })
304}
305
306pub fn load_runs(root: &Path, suite: &str) -> (Vec<(String, EconomicsRun)>, Vec<String>) {
329 load_runs_from(root, suite, Source::Directory)
330}
331
332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
336enum Source {
337 Directory,
339 Tracked,
341}
342
343fn record_files(root: &Path, suite: &str, source: Source) -> Result<Vec<PathBuf>, String> {
347 let dir = root.join(DIR).join("runs").join(suite);
348 match source {
349 Source::Directory => Ok(sorted_entries(&dir)),
350 Source::Tracked => {
351 let rel = format!("{DIR}/runs/{suite}");
352 let tracked = crate::git::ls_files_any(root, &[rel.as_str()])
353 .map_err(|e| format!("{rel}: the tracked records could not be listed: {e}"))?;
354 let mut own: Vec<String> = tracked
355 .into_iter()
356 .filter(|f| Path::new(f.as_str()).parent() == Some(Path::new(&rel)))
357 .collect();
358 crate::order::canonical(&mut own);
359 Ok(own.iter().map(|f| root.join(f)).collect())
360 }
361 }
362}
363
364fn load_runs_from(
365 root: &Path,
366 suite: &str,
367 source: Source,
368) -> (Vec<(String, EconomicsRun)>, Vec<String>) {
369 let mut runs = Vec::new();
370 let mut errors = Vec::new();
371 let files = match record_files(root, suite, source) {
372 Ok(files) => files,
373 Err(e) => return (runs, vec![e]),
374 };
375 for p in files {
376 if p.extension().and_then(|e| e.to_str()) != Some("json") {
377 continue;
378 }
379 let rel = p
380 .strip_prefix(root)
381 .unwrap_or(&p)
382 .to_string_lossy()
383 .to_string();
384 match std::fs::read_to_string(&p)
385 .map_err(|e| e.to_string())
386 .and_then(|t| serde_json::from_str::<EconomicsRun>(&t).map_err(|e| e.to_string()))
387 {
388 Ok(r) if r.schema == RUN_SCHEMA => runs.push((rel, r)),
389 Ok(r) => errors.push(format!("{rel}: schema {} is not {RUN_SCHEMA}", r.schema)),
390 Err(e) => errors.push(format!("{rel}: {e}")),
391 }
392 }
393 crate::order::canonical(&mut runs);
394 let mut seen = BTreeSet::new();
395 for (rel, r) in &runs {
396 if !seen.insert(r.id.clone()) {
397 errors.push(format!("{rel}: run id {} is recorded twice", r.id));
398 }
399 }
400 (runs, errors)
401}
402
403impl crate::order::Ordered for (String, EconomicsRun) {
407 fn order_key(&self) -> crate::order::OrderKey<'_> {
408 crate::order::OrderKey::plain(&self.1.id, &self.0)
409 }
410}
411
412pub fn load_context_runs(root: &Path, suite: &str) -> (Vec<EconomicsContextRun>, Vec<String>) {
439 load_context_runs_from(root, suite, Source::Directory)
440}
441
442fn load_context_runs_from(
443 root: &Path,
444 suite: &str,
445 source: Source,
446) -> (Vec<EconomicsContextRun>, Vec<String>) {
447 let mut runs = Vec::new();
448 let mut errors = Vec::new();
449 let files = match record_files(root, suite, source) {
450 Ok(files) => files,
451 Err(e) => return (runs, vec![e]),
452 };
453 for p in files {
454 if p.extension().and_then(|e| e.to_str()) != Some("json") {
455 continue;
456 }
457 let rel = p
458 .strip_prefix(root)
459 .unwrap_or(&p)
460 .to_string_lossy()
461 .to_string();
462 match std::fs::read_to_string(&p)
463 .map_err(|e| e.to_string())
464 .and_then(|t| {
465 serde_json::from_str::<EconomicsContextRun>(&t).map_err(|e| e.to_string())
466 }) {
467 Ok(r) if r.schema == CONTEXT_RUN_SCHEMA => runs.push(r),
468 Ok(r) => errors.push(format!(
469 "{rel}: schema {} is not {CONTEXT_RUN_SCHEMA}",
470 r.schema
471 )),
472 Err(e) => errors.push(format!("{rel}: {e}")),
473 }
474 }
475 crate::order::canonical(&mut runs);
476 (runs, errors)
477}
478
479fn judge(
484 control: Option<&EconomicsRun>,
485 treatment: Option<&EconomicsRun>,
486) -> (EconomicsPairStatus, Vec<String>) {
487 let (Some(c), Some(t)) = (control, treatment) else {
488 let side = if control.is_none() {
489 "control"
490 } else {
491 "treatment"
492 };
493 return (
494 EconomicsPairStatus::Missing,
495 vec![format!("the {side} run is not recorded")],
496 );
497 };
498 let mut differ = Vec::new();
499 let mut check = |what: &str, a: &str, b: &str| {
500 if a != b {
501 differ.push(format!("{what} differs: {a} against {b}"));
502 }
503 };
504 check(
505 "suite version",
506 &c.suite_version.to_string(),
507 &t.suite_version.to_string(),
508 );
509 check("requested model", &c.model_requested, &t.model_requested);
510 check(
513 "reported models",
514 &c.models_reported.join(", "),
515 &t.models_reported.join(", "),
516 );
517 check("fixture digest", &c.fixture_digest, &t.fixture_digest);
518 check("harness version", &c.harness.version, &t.harness.version);
519 check(
520 "configuration digest",
521 &c.configuration_digest,
522 &t.configuration_digest,
523 );
524 check(
525 "repository revision",
526 &c.repository.commit,
527 &t.repository.commit,
528 );
529 check(
530 "session count",
531 &c.sessions.len().to_string(),
532 &t.sessions.len().to_string(),
533 );
534 if !differ.is_empty() {
535 return (EconomicsPairStatus::Incomparable, differ);
536 }
537 let failed = |r: &EconomicsRun| -> Vec<String> {
538 r.outcome
539 .checks
540 .iter()
541 .filter(|k| !k.passed)
542 .map(|k| format!("{} {}: {}", r.variant, k.id, k.detail))
543 .collect()
544 };
545 match (c.outcome.completed, t.outcome.completed) {
546 (false, false) => {
547 let mut r = failed(c);
548 r.extend(failed(t));
549 return (EconomicsPairStatus::BothFailed, r);
550 }
551 (false, true) => return (EconomicsPairStatus::ControlFailed, failed(c)),
552 (true, false) => return (EconomicsPairStatus::TreatmentFailed, failed(t)),
553 (true, true) => {}
554 }
555 let mut missing = Vec::new();
556 for r in [c, t] {
557 if usage::totals(r).is_none() {
558 missing.push(format!("{} reported no usage for a session", r.id));
559 }
560 }
561 if !missing.is_empty() {
562 return (EconomicsPairStatus::UsageUnavailable, missing);
563 }
564 (EconomicsPairStatus::Valid, Vec::new())
565}
566
567fn pair_status(
571 methodology: &EconomicsMethodology,
572 control: Option<&EconomicsRun>,
573 treatment: Option<&EconomicsRun>,
574) -> (EconomicsPairStatus, Vec<String>, EconomicsPairStatus) {
575 let (underlying, reasons) = judge(control, treatment);
576 if underlying == EconomicsPairStatus::Missing {
577 return (underlying, reasons, underlying);
578 }
579 let ids: Vec<&str> = [control, treatment]
580 .into_iter()
581 .flatten()
582 .map(|r| r.id.as_str())
583 .collect();
584 let mut excluded: Vec<String> = methodology
585 .outliers
586 .excluded
587 .iter()
588 .filter(|x| ids.contains(&x.run.as_str()))
589 .map(|x| format!("{} excluded: {}", x.run, x.reason))
590 .collect();
591 if excluded.is_empty() {
592 return (underlying, reasons, underlying);
593 }
594 excluded.extend(reasons);
595 (EconomicsPairStatus::Excluded, excluded, underlying)
596}
597
598fn last_session_tokens(r: &EconomicsRun) -> Option<u64> {
599 r.sessions
600 .last()
601 .and_then(usage::session_totals)
602 .map(|t| t.total)
603}
604
605struct Judged {
608 pair: EconomicsPair,
609 underlying: EconomicsPairStatus,
612 model: String,
613}
614
615pub fn pairs(
656 decl: &Declarations,
657 suite: &EconomicsSuite,
658 runs: &[EconomicsRun],
659) -> Vec<EconomicsPair> {
660 judged_pairs(decl, suite, runs)
661 .into_iter()
662 .map(|j| j.pair)
663 .collect()
664}
665
666fn judged_pairs(decl: &Declarations, suite: &EconomicsSuite, runs: &[EconomicsRun]) -> Vec<Judged> {
667 let (Some(control), Some(treatment)) = (&suite.control, &suite.treatment) else {
668 return Vec::new();
669 };
670 let current: Vec<&EconomicsRun> = runs
671 .iter()
672 .filter(|r| r.methodology == decl.methodology.version && r.suite == suite.id)
673 .collect();
674 let mut keys: BTreeSet<(String, u32)> = BTreeSet::new();
675 for t in &suite.tasks {
676 for rep in 1..=suite.repetitions.unwrap_or(1) {
677 keys.insert((t.clone(), rep));
678 }
679 }
680 for r in ¤t {
681 keys.insert((r.task.clone(), r.repetition));
682 }
683 let find = |task: &str, variant: &str, rep: u32| {
684 current
685 .iter()
686 .copied()
687 .find(|r| r.task == task && r.variant == variant && r.repetition == rep)
688 };
689 keys.into_iter()
690 .map(|(task, rep)| {
691 let c = find(&task, control, rep);
692 let t = find(&task, treatment, rep);
693 let (status, reasons, underlying) = pair_status(&decl.methodology, c, t);
694 let declared = decl.tasks.get(&task);
695 let cu = c.and_then(usage::totals);
696 let tu = t.and_then(usage::totals);
697 let computable = underlying == EconomicsPairStatus::Valid;
701 let both = |f: &dyn Fn(&EconomicsUsage) -> Option<u64>| -> Option<f64> {
702 if !computable {
703 return None;
704 }
705 reduction(f(cu.as_ref()?)?, f(tu.as_ref()?)?)
706 };
707 let multi = declared.map(|d| d.sessions.len()).unwrap_or(1) > 1;
708 let continuation = if computable && multi {
709 c.zip(t)
710 .and_then(|(c, t)| reduction(last_session_tokens(c)?, last_session_tokens(t)?))
711 } else {
712 None
713 };
714 let model = c
715 .or(t)
716 .map(|r| r.model_requested.clone())
717 .or_else(|| suite.model.clone())
718 .unwrap_or_default();
719 let pair = EconomicsPair {
720 suite: suite.id.clone(),
721 task: task.clone(),
722 category: declared
723 .map(|d| d.category.clone())
724 .unwrap_or_else(|| "undeclared".into()),
725 sessions: declared.map(|d| d.sessions.len()).unwrap_or(1),
726 repetition: rep,
727 control: c.map(|r| r.id.clone()),
728 treatment: t.map(|r| r.id.clone()),
729 status,
730 reasons,
731 token_reduction: both(&|u| Some(u.total)),
732 cost_reduction: both(&|u| u.cost_microusd),
733 tool_call_reduction: both(&|u| Some(u.tool_calls)),
734 continuation_reduction: continuation,
735 first_request_overhead: if computable {
736 let a = c
737 .and_then(|r| r.sessions.first())
738 .and_then(usage::first_request_input);
739 let b = t
740 .and_then(|r| r.sessions.first())
741 .and_then(usage::first_request_input);
742 a.zip(b).map(|(a, b)| b as i64 - a as i64)
743 } else {
744 None
745 },
746 control_usage: cu,
747 treatment_usage: tu,
748 };
749 Judged {
750 pair,
751 underlying,
752 model,
753 }
754 })
755 .collect()
756}
757
758fn freshness(
761 root: &Path,
762 decl: &Declarations,
763 suite: &EconomicsSuite,
764 recorded: &[(u32, String)],
765) -> (EconomicsFreshness, Option<String>) {
766 if recorded.is_empty() {
767 return (EconomicsFreshness::NoEvidence, None);
768 }
769 let other: Vec<u32> = recorded
770 .iter()
771 .map(|(m, _)| *m)
772 .filter(|m| *m != decl.methodology.version)
773 .collect::<BTreeSet<_>>()
774 .into_iter()
775 .collect();
776 if other.len() == recorded.len() || recorded.iter().all(|(m, _)| *m != decl.methodology.version)
777 {
778 return (
779 EconomicsFreshness::Incompatible,
780 Some(format!(
781 "recorded under methodology {:?}; the current methodology is {}",
782 other, decl.methodology.version
783 )),
784 );
785 }
786 let now = match inputs_digest(root, &suite.freshness_inputs) {
787 Ok(d) => d,
788 Err(e) => {
789 return (
790 EconomicsFreshness::Stale,
791 Some(format!("the inputs could not be read: {e}")),
792 )
793 }
794 };
795 let stale = recorded
796 .iter()
797 .filter(|(m, d)| *m == decl.methodology.version && *d != now)
798 .count();
799 if stale > 0 {
800 return (
801 EconomicsFreshness::Stale,
802 Some(format!(
803 "{stale} record(s) were measured against inputs that have changed since: {}",
804 suite.freshness_inputs.join(", ")
805 )),
806 );
807 }
808 (EconomicsFreshness::Current, None)
809}
810
811struct MetricSpec<'a> {
814 id: &'a str,
815 title: &'a str,
816 class: EconomicsClass,
817 inputs: Option<EconomicsClass>,
818 unit: &'a str,
819 formula: &'a str,
820 not: Option<&'a str>,
821}
822
823fn metric(spec: MetricSpec, suite: &str) -> EconomicsMetric {
824 EconomicsMetric {
825 id: spec.id.into(),
826 title: spec.title.into(),
827 class: spec.class,
828 inputs: spec.inputs,
829 unit: spec.unit.into(),
830 formula: spec.formula.into(),
831 not: spec.not.map(str::to_string),
832 value: None,
833 status: EconomicsMetricStatus::NotMeasured,
834 n: 0,
835 distribution: None,
836 interval: None,
837 suite: suite.into(),
838 evidence: Vec::new(),
839 warnings: Vec::new(),
840 }
841}
842
843pub const EFFECTIVE_TOKEN_REDUCTION: &str = "effective_token_reduction";
845pub const EFFECTIVE_TOKEN_REDUCTION_INCLUDING_EXCLUDED: &str =
848 "effective_token_reduction.including_excluded";
849pub const CONTEXT_REDUCTION: &str = "context_reduction_ratio";
851pub const NARROWED: &str =
853 "narrowed by the query: the publication rule is evaluated over all evidence, not over this slice";
854
855const SECONDARY: &str = "preliminary: the publication rule is evaluated for effective_token_reduction only, so this metric is a result, never a verified claim";
857
858const RUN_BASED: [&str; 3] = [
860 "completion_rate.",
861 "tokens_per_completed_task.",
862 "transcript_resume_avoided.",
863];
864
865struct Sample {
868 key: String,
869 cluster: String,
870 value: f64,
871}
872
873#[derive(Debug, Clone, Copy, PartialEq, Eq)]
875enum Resampling {
876 Tasks,
878 Independent,
880}
881
882fn cluster_of(p: &EconomicsPair) -> String {
883 format!("{}/{}", p.suite, p.task)
884}
885
886fn task_interval(
888 samples: &[Sample],
889 policy: &EconomicsStatisticsPolicy,
890) -> Option<EconomicsInterval> {
891 let mut clusters: BTreeMap<&str, Vec<f64>> = BTreeMap::new();
892 for s in samples {
893 clusters
894 .entry(s.cluster.as_str())
895 .or_default()
896 .push(s.value);
897 }
898 let clusters: Vec<Vec<f64>> = clusters.into_values().collect();
899 bootstrap_median_clustered(
900 &clusters,
901 policy.confidence_bp,
902 policy.resamples,
903 policy.seed,
904 policy.min_pairs_for_interval,
905 )
906}
907
908fn no_interval(n: usize, policy: &EconomicsStatisticsPolicy) -> &'static str {
911 if n < policy.min_pairs_for_interval.max(2) {
912 "no interval: too few pairs"
913 } else {
914 "no interval: too few tasks, and the repetitions of one task are not independent"
915 }
916}
917
918fn sampled(
922 mut m: EconomicsMetric,
923 samples: &[Sample],
924 policy: &EconomicsStatisticsPolicy,
925 resampling: Resampling,
926 verified: bool,
927) -> EconomicsMetric {
928 let v: Vec<f64> = samples.iter().map(|s| s.value).collect();
929 m.n = v.len();
930 m.evidence = samples.iter().map(|s| s.key.clone()).collect();
931 m.distribution = distribution(&v);
932 m.value = m.distribution.as_ref().map(|d| d.median);
933 m.interval = match resampling {
934 Resampling::Tasks => task_interval(samples, policy),
935 Resampling::Independent => bootstrap_median(
936 &v,
937 policy.confidence_bp,
938 policy.resamples,
939 policy.seed,
940 policy.min_pairs_for_interval,
941 ),
942 };
943 m.status = match (m.n, verified) {
944 (0, _) => EconomicsMetricStatus::NotMeasured,
945 (_, true) => EconomicsMetricStatus::Verified,
946 _ => EconomicsMetricStatus::Preliminary,
947 };
948 if m.n > 0 && m.interval.is_none() {
949 let tasks = samples
950 .iter()
951 .map(|s| s.cluster.as_str())
952 .collect::<BTreeSet<_>>()
953 .len();
954 if resampling == Resampling::Tasks && m.n >= policy.min_pairs_for_interval.max(2) {
955 m.warnings.push(format!(
956 "{} value(s) from {tasks} task(s): too few tasks for an interval, because the repetitions of one task are not independent",
957 m.n
958 ));
959 } else {
960 m.warnings.push(format!(
961 "{} value(s): too few for an interval (the methodology computes one from {})",
962 m.n, policy.min_pairs_for_interval
963 ));
964 }
965 }
966 m
967}
968
969fn secondary(mut m: EconomicsMetric) -> EconomicsMetric {
971 if m.n > 0 {
972 m.warnings.push(SECONDARY.into());
973 }
974 m
975}
976
977fn pair_key(p: &EconomicsPair) -> String {
978 format!("{}/{}/r{}", p.suite, p.task, p.repetition)
979}
980
981fn width_text(x: f64) -> String {
983 percent_text(x).trim_start_matches(['+', '-']).to_string()
984}
985
986fn bound_text(x: f64) -> String {
988 percent_text(x).trim_start_matches('+').to_string()
989}
990
991fn change_words(median: f64) -> String {
994 let shown = percent_text(median);
995 if let Some(x) = shown.strip_prefix('+') {
996 format!("reduced median total token consumption by {x}")
997 } else if let Some(x) = shown.strip_prefix('-') {
998 format!("increased median total token consumption by {x}")
999 } else {
1000 "left median total token consumption unchanged".into()
1001 }
1002}
1003
1004fn change_with_interval(median: f64, interval: Option<&EconomicsInterval>, none: &str) -> String {
1007 let words = change_words(median);
1008 let Some(i) = interval else {
1009 return format!("{words} ({none})");
1010 };
1011 let range = if percent_text(median).starts_with('-') {
1012 format!(
1013 "an increase of {} to {}",
1014 bound_text(-i.high),
1015 bound_text(-i.low)
1016 )
1017 } else {
1018 format!(
1019 "a reduction of {} to {}",
1020 bound_text(i.low),
1021 bound_text(i.high)
1022 )
1023 };
1024 format!("{words} ({} CI: {range})", stats::level_text(i.level_bp))
1025}
1026
1027struct RepetitionFloor {
1029 reps: u32,
1030 of: String,
1031}
1032
1033fn unmet(
1036 policy: &EconomicsPublicationPolicy,
1037 valid: &[&EconomicsPair],
1038 attempted: usize,
1039 floor: Option<&RepetitionFloor>,
1040 interval: Option<&EconomicsInterval>,
1041 all_current: bool,
1042 dirty: usize,
1043) -> Vec<String> {
1044 let mut out = Vec::new();
1045 if valid.len() < policy.min_valid_pairs {
1046 out.push(format!(
1047 "{} valid pair(s); the rule asks for {}",
1048 valid.len(),
1049 policy.min_valid_pairs
1050 ));
1051 }
1052 let mut per_category: BTreeMap<&str, usize> = BTreeMap::new();
1053 for p in valid {
1054 *per_category.entry(p.category.as_str()).or_insert(0) += 1;
1055 }
1056 let categories = per_category
1057 .values()
1058 .filter(|n| **n >= policy.min_pairs_per_category)
1059 .count();
1060 if categories < policy.min_categories {
1061 out.push(format!(
1062 "{categories} categor(ies) with at least {} valid pairs; the rule asks for {}",
1063 policy.min_pairs_per_category, policy.min_categories
1064 ));
1065 }
1066 match floor {
1067 None => out.push(format!(
1068 "no declared task and variant to repeat; the rule asks for {} repetition(s) of each",
1069 policy.min_repetitions
1070 )),
1071 Some(f) if f.reps < policy.min_repetitions => out.push(format!(
1072 "{} repetition(s) of {}, the least repeated; the rule asks for {} of every task and variant",
1073 f.reps, f.of, policy.min_repetitions
1074 )),
1075 Some(_) => {}
1076 }
1077 if let Some(rate) = (valid.len() * 10_000).checked_div(attempted) {
1078 if rate < policy.min_valid_pair_rate_bp as usize {
1079 out.push(format!(
1080 "{} of {attempted} attempted pairs are valid; the rule asks for {} in 10000",
1081 valid.len(),
1082 policy.min_valid_pair_rate_bp
1083 ));
1084 }
1085 }
1086 match interval {
1087 None => out.push("no interval: too few valid pairs, or too few tasks among them".into()),
1088 Some(i) if (i.high - i.low) * 10_000.0 > policy.max_interval_width_bp as f64 => {
1089 out.push(format!(
1090 "the interval is {} wide; the rule allows {} in 10000",
1091 width_text(i.high - i.low),
1092 policy.max_interval_width_bp
1093 ))
1094 }
1095 _ => {}
1096 }
1097 if policy.require_current && !all_current {
1098 out.push(
1099 "the evidence is not current: a measured mechanism changed since it was recorded"
1100 .into(),
1101 );
1102 }
1103 if dirty > 0 {
1104 out.push(format!(
1105 "{dirty} run(s) were recorded from a working tree with uncommitted changes"
1106 ));
1107 }
1108 out
1109}
1110
1111fn matches(q: &EconomicsQuery, p: &EconomicsPair, model: &str) -> bool {
1112 q.suite.as_ref().is_none_or(|s| &p.suite == s)
1113 && q.category.as_ref().is_none_or(|c| &p.category == c)
1114 && q.task.as_ref().is_none_or(|t| &p.task == t)
1115 && q.model.as_ref().is_none_or(|m| m == model)
1116}
1117
1118fn runs_of<'a>(
1120 pairs: &[&'a EconomicsPair],
1121 run_of: &BTreeMap<(&str, &str), &'a EconomicsRun>,
1122 variant: &str,
1123) -> Vec<(&'a EconomicsPair, &'a EconomicsRun)> {
1124 let mut out = Vec::new();
1125 for p in pairs {
1126 for id in [&p.control, &p.treatment].into_iter().flatten() {
1127 if let Some(r) = run_of.get(&(p.suite.as_str(), id.as_str())) {
1128 if r.variant == variant {
1129 out.push((*p, *r));
1130 }
1131 }
1132 }
1133 }
1134 out
1135}
1136
1137fn empty(statement: &str, diagnostics: Vec<String>) -> EconomicsSummary {
1138 EconomicsSummary {
1139 present: false,
1140 methodology: None,
1141 question: None,
1142 primary_metric: None,
1143 verdict: EconomicsVerdict {
1144 publishable: false,
1145 statement: statement.into(),
1146 unmet: Vec::new(),
1147 },
1148 metrics: Vec::new(),
1149 suites: Vec::new(),
1150 pairs: Vec::new(),
1151 segments: Vec::new(),
1152 context: None,
1153 history: Vec::new(),
1154 variants: Vec::new(),
1155 hypotheses: Vec::new(),
1156 publication: None,
1157 diagnostics,
1158 }
1159}
1160
1161pub const NO_CLAIM: &str = "No verified total-token-savings claim is available";
1163
1164pub fn wire<T: Serialize>(value: &T) -> String {
1177 match serde_json::to_value(value) {
1178 Ok(serde_json::Value::String(s)) => s,
1179 Ok(other) => other.to_string(),
1180 Err(e) => format!("(not serialisable: {e})"),
1181 }
1182}
1183
1184struct ContextEvidence<'a> {
1186 suite: &'a EconomicsSuite,
1187 runs: Vec<EconomicsContextRun>,
1188 freshness: EconomicsFreshness,
1189}
1190
1191pub fn summarize(root: &Path, query: &EconomicsQuery) -> EconomicsSummary {
1214 summarize_from(root, query, Source::Directory)
1215}
1216
1217pub fn summarize_tracked(root: &Path, query: &EconomicsQuery) -> EconomicsSummary {
1257 summarize_from(root, query, Source::Tracked)
1258}
1259
1260fn summarize_from(root: &Path, query: &EconomicsQuery, source: Source) -> EconomicsSummary {
1261 let decl = match load(root) {
1262 Ok(Some(d)) => d,
1263 Ok(None) => {
1264 return empty(
1265 &format!("{NO_CLAIM}: no benchmark methodology is declared in this repository."),
1266 Vec::new(),
1267 )
1268 }
1269 Err(errors) => {
1270 return empty(
1271 &format!("{NO_CLAIM}: the benchmark declarations could not be read."),
1272 errors,
1273 )
1274 }
1275 };
1276 let m = &decl.methodology;
1277 let mut diagnostics = Vec::new();
1278 let mut suite_views = Vec::new();
1279 let mut all_pairs: Vec<Judged> = Vec::new();
1280 let mut live_runs: Vec<EconomicsRun> = Vec::new();
1281 let mut context_runs: Vec<ContextEvidence> = Vec::new();
1282 let mut all_current = true;
1283 let mut floor: Option<RepetitionFloor> = None;
1284 let mut history = Vec::new();
1285
1286 for suite in decl.suites.values() {
1287 match suite.kind.as_str() {
1288 "live" => {
1289 let (runs, errors) = load_runs_from(root, &suite.id, source);
1290 diagnostics.extend(errors);
1291 let runs: Vec<EconomicsRun> = runs.into_iter().map(|(_, r)| r).collect();
1292 let recorded: Vec<(u32, String)> = runs
1293 .iter()
1294 .map(|r| (r.methodology, r.inputs_digest.clone()))
1295 .collect();
1296 let (fresh, detail) = freshness(root, &decl, suite, &recorded);
1297 if !matches!(
1298 fresh,
1299 EconomicsFreshness::Current | EconomicsFreshness::NoEvidence
1300 ) {
1301 all_current = false;
1302 }
1303 let judged = judged_pairs(&decl, suite, &runs);
1304 let mut counts = EconomicsPairCounts {
1305 declared: suite.tasks.len() * suite.repetitions.unwrap_or(1) as usize,
1306 ..Default::default()
1307 };
1308 for p in judged.iter().map(|j| &j.pair) {
1309 if p.control.is_some() || p.treatment.is_some() {
1310 counts.attempted += 1;
1311 }
1312 match p.status {
1313 EconomicsPairStatus::Valid => counts.valid += 1,
1314 EconomicsPairStatus::ControlFailed => counts.control_failed += 1,
1315 EconomicsPairStatus::TreatmentFailed => counts.treatment_failed += 1,
1316 EconomicsPairStatus::BothFailed => counts.both_failed += 1,
1317 _ if p.control.is_some() || p.treatment.is_some() => counts.other += 1,
1318 _ => {}
1319 }
1320 }
1321 for task in suite.tasks.iter().collect::<BTreeSet<_>>() {
1326 for variant in [&suite.control, &suite.treatment].into_iter().flatten() {
1327 let reps = runs
1328 .iter()
1329 .filter(|r| r.methodology == m.version && r.suite == suite.id)
1330 .filter(|r| &r.task == task && &r.variant == variant)
1331 .map(|r| r.repetition)
1332 .collect::<BTreeSet<_>>()
1333 .len() as u32;
1334 if floor.as_ref().is_none_or(|f| reps < f.reps) {
1335 floor = Some(RepetitionFloor {
1336 reps,
1337 of: format!("{task} ({variant}) in suite {}", suite.id),
1338 });
1339 }
1340 }
1341 }
1342 let mut by_rev: BTreeMap<String, Vec<f64>> = BTreeMap::new();
1344 for p in judged
1345 .iter()
1346 .map(|j| &j.pair)
1347 .filter(|p| p.status == EconomicsPairStatus::Valid)
1348 {
1349 if let Some(r) = runs.iter().find(|r| Some(&r.id) == p.control.as_ref()) {
1350 if let Some(x) = p.token_reduction {
1351 by_rev
1352 .entry(r.repository.commit.clone())
1353 .or_default()
1354 .push(x);
1355 }
1356 }
1357 }
1358 for (rev, v) in by_rev {
1359 let at = runs
1360 .iter()
1361 .filter(|r| r.repository.commit == rev)
1362 .map(|r| r.finished_at.clone())
1363 .max()
1364 .unwrap_or_default();
1365 if let Some(d) = distribution(&v) {
1366 history.push(EconomicsHistoryPoint {
1367 suite: suite.id.clone(),
1368 methodology: m.version,
1369 revision: rev,
1370 at,
1371 metric: EFFECTIVE_TOKEN_REDUCTION.into(),
1372 value: d.median,
1373 n: d.n,
1374 });
1375 }
1376 }
1377 suite_views.push(EconomicsSuiteView {
1378 id: suite.id.clone(),
1379 kind: suite.kind.clone(),
1380 version: suite.version,
1381 title: suite.title.clone(),
1382 model: suite.model.clone(),
1383 freshness: fresh,
1384 freshness_detail: detail,
1385 runs: runs.len(),
1386 pairs: counts,
1387 revisions: runs
1388 .iter()
1389 .map(|r| r.repository.commit.clone())
1390 .collect::<BTreeSet<_>>()
1391 .into_iter()
1392 .collect(),
1393 harnesses: runs
1394 .iter()
1395 .map(|r| format!("{} {}", r.harness.name, r.harness.version))
1396 .collect::<BTreeSet<_>>()
1397 .into_iter()
1398 .collect(),
1399 models_reported: runs
1400 .iter()
1401 .flat_map(|r| r.models_reported.clone())
1402 .collect::<BTreeSet<_>>()
1403 .into_iter()
1404 .collect(),
1405 });
1406 all_pairs.extend(judged);
1407 live_runs.extend(runs);
1408 }
1409 "context" => {
1410 let (runs, errors) = load_context_runs_from(root, &suite.id, source);
1411 diagnostics.extend(errors);
1412 let recorded: Vec<(u32, String)> = runs
1413 .iter()
1414 .map(|r| (r.methodology, r.inputs_digest.clone()))
1415 .collect();
1416 let latest_only: Vec<(u32, String)> =
1417 recorded.last().cloned().into_iter().collect();
1418 let (fresh, detail) = freshness(root, &decl, suite, &latest_only);
1419 for r in &runs {
1420 let v: Vec<f64> = r
1421 .seeds
1422 .iter()
1423 .filter_map(|s| reduction(s.candidate_tokens, s.selected_tokens))
1424 .collect();
1425 if let Some(d) = distribution(&v) {
1426 history.push(EconomicsHistoryPoint {
1427 suite: suite.id.clone(),
1428 methodology: r.methodology,
1429 revision: r.repository.commit.clone(),
1430 at: r.measured_at.clone(),
1431 metric: CONTEXT_REDUCTION.into(),
1432 value: d.median,
1433 n: d.n,
1434 });
1435 }
1436 }
1437 suite_views.push(EconomicsSuiteView {
1438 id: suite.id.clone(),
1439 kind: suite.kind.clone(),
1440 version: suite.version,
1441 title: suite.title.clone(),
1442 model: None,
1443 freshness: fresh,
1444 freshness_detail: detail,
1445 runs: runs.len(),
1446 pairs: EconomicsPairCounts::default(),
1447 revisions: runs
1448 .iter()
1449 .map(|r| r.repository.commit.clone())
1450 .collect::<BTreeSet<_>>()
1451 .into_iter()
1452 .collect(),
1453 harnesses: Vec::new(),
1454 models_reported: Vec::new(),
1455 });
1456 context_runs.push(ContextEvidence {
1457 suite,
1458 runs,
1459 freshness: fresh,
1460 });
1461 }
1462 other => diagnostics.push(format!("suite {}: unknown kind {other:?}", suite.id)),
1463 }
1464 }
1465
1466 let mut run_of: BTreeMap<(&str, &str), &EconomicsRun> = BTreeMap::new();
1468 for r in &live_runs {
1469 run_of.entry((r.suite.as_str(), r.id.as_str())).or_insert(r);
1470 }
1471 let token_samples = |ps: &[&EconomicsPair]| -> Vec<Sample> {
1472 ps.iter()
1473 .filter_map(|p| {
1474 p.token_reduction.map(|x| Sample {
1475 key: pair_key(p),
1476 cluster: cluster_of(p),
1477 value: x,
1478 })
1479 })
1480 .collect()
1481 };
1482
1483 let valid_all: Vec<&EconomicsPair> = all_pairs
1485 .iter()
1486 .map(|j| &j.pair)
1487 .filter(|p| p.status == EconomicsPairStatus::Valid)
1488 .collect();
1489 let attempted = all_pairs
1490 .iter()
1491 .filter(|j| j.pair.control.is_some() || j.pair.treatment.is_some())
1492 .count();
1493 let samples_all = token_samples(&valid_all);
1494 let reductions_all: Vec<f64> = samples_all.iter().map(|s| s.value).collect();
1495 let interval_all = task_interval(&samples_all, &m.statistics);
1496 let dirty = live_runs.iter().filter(|r| r.repository.dirty).count();
1497 let unmet_list = unmet(
1498 &m.publication,
1499 &valid_all,
1500 attempted,
1501 floor.as_ref(),
1502 interval_all.as_ref(),
1503 all_current,
1504 dirty,
1505 );
1506 let publishable = unmet_list.is_empty() && !valid_all.is_empty();
1507 let valid_runs: Vec<&EconomicsRun> = valid_all
1509 .iter()
1510 .flat_map(|p| {
1511 [&p.control, &p.treatment]
1512 .into_iter()
1513 .flatten()
1514 .map(move |id| (p.suite.as_str(), id.as_str()))
1515 })
1516 .filter_map(|k| run_of.get(&k).copied())
1517 .collect();
1518 let models: Vec<String> = valid_runs
1519 .iter()
1520 .flat_map(|r| r.models_reported.clone())
1521 .collect::<BTreeSet<_>>()
1522 .into_iter()
1523 .collect();
1524 let models_text = if models.is_empty() {
1525 "a model the harness did not name".to_string()
1526 } else {
1527 models.join(", ")
1528 };
1529 let revisions: BTreeSet<&str> = valid_runs
1530 .iter()
1531 .map(|r| r.repository.commit.as_str())
1532 .collect();
1533 let tasks: BTreeSet<(&str, &str)> = valid_all
1534 .iter()
1535 .map(|p| (p.suite.as_str(), p.task.as_str()))
1536 .collect();
1537 let categories: BTreeSet<&str> = valid_all.iter().map(|p| p.category.as_str()).collect();
1538 let none = no_interval(reductions_all.len(), &m.statistics);
1539 let mut statement = match (distribution(&reductions_all), publishable) {
1540 (None, _) => format!(
1541 "{NO_CLAIM}: no valid matched pair of live runs is recorded under methodology {}.",
1542 m.version
1543 ),
1544 (Some(d), true) => format!(
1545 "Across methodology {}, {} valid matched pairs across {} tasks in {} categories, at revision(s) {}, using {}, Majordomus {}, with every run passing the same acceptance tests.",
1546 m.version,
1547 d.n,
1548 tasks.len(),
1549 categories.len(),
1550 revisions.iter().map(|r| &r[..r.len().min(12)]).collect::<Vec<_>>().join(", "),
1551 models_text,
1552 change_with_interval(d.median, interval_all.as_ref(), none),
1553 ),
1554 (Some(d), false) => format!(
1555 "{NO_CLAIM}. Preliminary observation, below the publication rule: over {} valid matched pair(s) across {} task(s) in {} categor(ies), using {}, Majordomus {}.",
1556 d.n,
1557 tasks.len(),
1558 categories.len(),
1559 models_text,
1560 change_with_interval(d.median, interval_all.as_ref(), none),
1561 ),
1562 };
1563 let excluded_ids: BTreeSet<&str> = m.outliers.excluded.iter().map(|x| x.run.as_str()).collect();
1564 let excluded_runs: BTreeSet<(&str, &str)> = all_pairs
1565 .iter()
1566 .flat_map(|j| {
1567 [&j.pair.control, &j.pair.treatment]
1568 .into_iter()
1569 .flatten()
1570 .map(move |id| (j.pair.suite.as_str(), id.as_str()))
1571 })
1572 .filter(|(_, id)| excluded_ids.contains(id))
1573 .collect();
1574 if !excluded_runs.is_empty() {
1575 statement.push_str(&format!(
1576 " {} run(s) excluded by the methodology's outlier rule are left out",
1577 excluded_runs.len()
1578 ));
1579 let with_excluded: Vec<&EconomicsPair> = all_pairs
1580 .iter()
1581 .filter(|j| j.underlying == EconomicsPairStatus::Valid)
1582 .map(|j| &j.pair)
1583 .collect();
1584 let v: Vec<f64> = token_samples(&with_excluded)
1585 .into_iter()
1586 .map(|s| s.value)
1587 .collect();
1588 match distribution(&v) {
1589 Some(d) => statement.push_str(&format!(
1590 "; with them included, over {} pair(s), Majordomus {} (preliminary).",
1591 d.n,
1592 change_words(d.median)
1593 )),
1594 None => statement.push('.'),
1595 }
1596 }
1597
1598 let narrowed = query.suite.is_some()
1600 || query.category.is_some()
1601 || query.task.is_some()
1602 || query.model.is_some();
1603 let pairs_q: Vec<&Judged> = all_pairs
1604 .iter()
1605 .filter(|j| matches(query, &j.pair, &j.model))
1606 .collect();
1607 let valid: Vec<&EconomicsPair> = pairs_q
1608 .iter()
1609 .map(|j| &j.pair)
1610 .filter(|p| p.status == EconomicsPairStatus::Valid)
1611 .collect();
1612 let values = |f: &dyn Fn(&EconomicsPair) -> Option<f64>| -> Vec<Sample> {
1613 valid
1614 .iter()
1615 .filter_map(|p| {
1616 f(p).map(|x| Sample {
1617 key: pair_key(p),
1618 cluster: cluster_of(p),
1619 value: x,
1620 })
1621 })
1622 .collect()
1623 };
1624 let live_suite = decl
1625 .suites
1626 .values()
1627 .filter(|s| s.kind == "live")
1628 .map(|s| s.id.clone())
1629 .collect::<Vec<_>>()
1630 .join(",");
1631 let mut metrics = Vec::new();
1632 let mut primary = sampled(
1635 metric(
1636 MetricSpec {
1637 id: EFFECTIVE_TOKEN_REDUCTION,
1638 title: "Effective total-token reduction",
1639 class: EconomicsClass::Derived,
1640 inputs: Some(EconomicsClass::Observed),
1641 unit: "ratio",
1642 formula: "median over valid pairs of 1 - treatment_total_tokens / control_total_tokens, where total tokens are all input (uncached, cache write, cache read) and all output the provider reported for every session of the run",
1643 not: Some("not context reduction: this is everything the provider reported for the whole task, not what one mechanism selected"),
1644 },
1645 &live_suite,
1646 ),
1647 &values(&|p| p.token_reduction),
1648 &m.statistics,
1649 Resampling::Tasks,
1650 publishable && !narrowed,
1651 );
1652 if primary.n > 0 && !publishable {
1653 primary
1654 .warnings
1655 .push("preliminary: the evidence is below the publication rule".into());
1656 }
1657 if narrowed {
1658 primary.warnings.push(NARROWED.into());
1659 }
1660 if !valid.is_empty() {
1661 primary.warnings.push(
1662 "cache reads are counted in total tokens at full weight; the cost reduction weighs them by price".into(),
1663 );
1664 }
1665 metrics.push(primary);
1666 if !m.outliers.excluded.is_empty() {
1667 let put_back: Vec<&EconomicsPair> = pairs_q
1668 .iter()
1669 .filter(|j| j.underlying == EconomicsPairStatus::Valid)
1670 .map(|j| &j.pair)
1671 .collect();
1672 let excluded_pairs = put_back
1673 .iter()
1674 .filter(|p| p.status == EconomicsPairStatus::Excluded)
1675 .count();
1676 let mut including = secondary(sampled(
1677 metric(
1678 MetricSpec {
1679 id: EFFECTIVE_TOKEN_REDUCTION_INCLUDING_EXCLUDED,
1680 title: "Effective total-token reduction, excluded runs included",
1681 class: EconomicsClass::Derived,
1682 inputs: Some(EconomicsClass::Observed),
1683 unit: "ratio",
1684 formula: "the formula of effective_token_reduction, over the valid pairs and the pairs the outlier rule excluded that would otherwise be valid",
1685 not: Some("not the primary metric: the methodology excludes these runs, and this figure is reported beside the primary one so that the exclusion's effect is visible"),
1686 },
1687 &live_suite,
1688 ),
1689 &token_samples(&put_back),
1690 &m.statistics,
1691 Resampling::Tasks,
1692 false,
1693 ));
1694 including.warnings.push(format!(
1695 "includes {excluded_pairs} pair(s) the outlier rule excluded; every other metric leaves them out"
1696 ));
1697 metrics.push(including);
1698 }
1699 let mut cost = secondary(sampled(
1700 metric(
1701 MetricSpec {
1702 id: "cost_reduction",
1703 title: "Cost reduction, as the harness priced it",
1704 class: EconomicsClass::Derived,
1705 inputs: Some(EconomicsClass::Observed),
1706 unit: "ratio",
1707 formula: "median over valid pairs of 1 - treatment_cost / control_cost, where cost is the harness's own total_cost_usd at run time",
1708 not: Some("not a bill: the harness prices usage from its own table at run time, and historical runs are never re-priced"),
1709 },
1710 &live_suite,
1711 ),
1712 &values(&|p| p.cost_reduction),
1713 &m.statistics,
1714 Resampling::Tasks,
1715 false,
1716 ));
1717 let harnesses: BTreeSet<String> = valid
1719 .iter()
1720 .flat_map(|p| {
1721 [&p.control, &p.treatment]
1722 .into_iter()
1723 .flatten()
1724 .map(move |id| (p.suite.as_str(), id.as_str()))
1725 })
1726 .filter_map(|k| run_of.get(&k))
1727 .map(|r| format!("{} {}", r.harness.name, r.harness.version))
1728 .collect();
1729 if !harnesses.is_empty() {
1730 cost.warnings.push(format!(
1731 "priced by {}",
1732 harnesses.into_iter().collect::<Vec<_>>().join(", ")
1733 ));
1734 }
1735 metrics.push(cost);
1736 metrics.push(secondary(sampled(
1737 metric(
1738 MetricSpec {
1739 id: "tool_call_reduction",
1740 title: "Tool-call reduction",
1741 class: EconomicsClass::Derived,
1742 inputs: Some(EconomicsClass::Observed),
1743 unit: "ratio",
1744 formula: "median over valid pairs of 1 - treatment_tool_calls / control_tool_calls",
1745 not: Some("not a token metric"),
1746 },
1747 &live_suite,
1748 ),
1749 &values(&|p| p.tool_call_reduction),
1750 &m.statistics,
1751 Resampling::Tasks,
1752 false,
1753 )));
1754 metrics.push(secondary(sampled(
1755 metric(
1756 MetricSpec {
1757 id: "continuation_reduction",
1758 title: "Continuation-session token reduction",
1759 class: EconomicsClass::Derived,
1760 inputs: Some(EconomicsClass::Observed),
1761 unit: "ratio",
1762 formula: "median over valid multi-session pairs of 1 - the treatment's last-session total tokens / the control's",
1763 not: Some("only the session that continued the work, not the whole task"),
1764 },
1765 &live_suite,
1766 ),
1767 &values(&|p| p.continuation_reduction),
1768 &m.statistics,
1769 Resampling::Tasks,
1770 false,
1771 )));
1772 metrics.push(secondary(sampled(
1773 metric(
1774 MetricSpec {
1775 id: "first_request_overhead",
1776 title: "What the treatment adds before the model acts",
1777 class: EconomicsClass::Derived,
1778 inputs: Some(EconomicsClass::Observed),
1779 unit: "tokens",
1780 formula: "median over valid pairs of the treatment's first-request input tokens minus the control's: the instruction files and hook output a session is given before it has done anything",
1781 not: Some("not the whole overhead: what the treatment reads later because its instructions say to is in the total, not here"),
1782 },
1783 &live_suite,
1784 ),
1785 &values(&|p| p.first_request_overhead.map(|x| x as f64)),
1786 &m.statistics,
1787 Resampling::Tasks,
1788 false,
1789 )));
1790 let comparable: Vec<&EconomicsPair> = pairs_q
1793 .iter()
1794 .map(|j| &j.pair)
1795 .filter(|p| {
1796 !matches!(
1797 p.status,
1798 EconomicsPairStatus::Missing
1799 | EconomicsPairStatus::Incomparable
1800 | EconomicsPairStatus::Excluded
1801 )
1802 })
1803 .collect();
1804 for variant in &m.variants {
1805 let recorded = runs_of(&comparable, &run_of, &variant.id);
1806 let completed = recorded.iter().filter(|(_, r)| r.outcome.completed).count();
1807 let mut rate = metric(
1808 MetricSpec {
1809 id: &format!("completion_rate.{}", variant.id),
1810 title: &format!("Completion rate, {}", variant.title),
1811 class: EconomicsClass::Derived,
1812 inputs: Some(EconomicsClass::Observed),
1813 unit: "ratio",
1814 formula: "runs that passed every success gate / runs recorded, over the runs of pairs whose two sides were both recorded, are comparable and are not excluded; a failed run counts, which is the point",
1815 not: Some("not a quality score: a run passes the gates or it does not"),
1816 },
1817 &live_suite,
1818 );
1819 if !recorded.is_empty() {
1820 rate.n = recorded.len();
1821 rate.value = Some(stats::round(completed as f64 / recorded.len() as f64));
1822 rate.status = EconomicsMetricStatus::Preliminary;
1823 rate.evidence = recorded.iter().map(|(_, r)| r.id.clone()).collect();
1824 }
1825 metrics.push(secondary(rate));
1826 let in_valid = runs_of(&valid, &run_of, &variant.id);
1827 let per_task: Vec<Sample> = in_valid
1828 .iter()
1829 .filter_map(|(p, r)| {
1830 usage::totals(r).map(|u| Sample {
1831 key: r.id.clone(),
1832 cluster: cluster_of(p),
1833 value: u.total as f64,
1834 })
1835 })
1836 .collect();
1837 metrics.push(secondary(sampled(
1838 metric(
1839 MetricSpec {
1840 id: &format!("tokens_per_completed_task.{}", variant.id),
1841 title: &format!("Total tokens per completed task, {}", variant.title),
1842 class: EconomicsClass::Observed,
1843 inputs: None,
1844 unit: "tokens",
1845 formula: "median over the runs of valid pairs of the total tokens the provider reported for the run, so that both arms describe the same tasks",
1846 not: None,
1847 },
1848 &live_suite,
1849 ),
1850 &per_task,
1851 &m.statistics,
1852 Resampling::Tasks,
1853 false,
1854 )));
1855 let resume: Vec<Sample> = in_valid
1856 .iter()
1857 .filter(|(_, r)| r.sessions.len() > 1)
1858 .filter_map(|(p, r)| {
1859 let prev = r.sessions.get(r.sessions.len() - 2)?;
1860 let last_input = prev.requests.last().map(|q| {
1861 q.input_tokens + q.cache_creation_input_tokens + q.cache_read_input_tokens
1862 })?;
1863 let first = usage::first_request_input(r.sessions.last()?)?;
1864 Some(Sample {
1865 key: r.id.clone(),
1866 cluster: cluster_of(p),
1867 value: last_input as f64 - first as f64,
1868 })
1869 })
1870 .collect();
1871 let mut cf = sampled(
1872 metric(
1873 MetricSpec {
1874 id: &format!("transcript_resume_avoided.{}", variant.id),
1875 title: &format!("Context not re-sent by starting the next session fresh, {}", variant.title),
1876 class: EconomicsClass::Counterfactual,
1877 inputs: Some(EconomicsClass::Observed),
1878 unit: "tokens",
1879 formula: "over the runs of valid pairs, the previous session's last-request input minus the next session's first-request input: what resuming the previous transcript would have re-sent at the start, against what a fresh session was given",
1880 not: Some("not a saving of Majordomus: both arms start the next session fresh; this is the modelled cost of resuming a transcript instead"),
1881 },
1882 &live_suite,
1883 ),
1884 &resume,
1885 &m.statistics,
1886 Resampling::Tasks,
1887 false,
1888 );
1889 cf.warnings
1890 .push("counterfactual: modelled from observed values, never a measured saving".into());
1891 metrics.push(cf);
1892 }
1893
1894 let mut context_view = None;
1896 for evidence in &context_runs {
1897 let suite = evidence.suite;
1898 let mut ratio = metric(
1899 MetricSpec {
1900 id: CONTEXT_REDUCTION,
1901 title: "Context reduction by the context compiler",
1902 class: EconomicsClass::Derived,
1903 inputs: Some(EconomicsClass::Counted),
1904 unit: "ratio",
1905 formula: "median over seeds of 1 - selected_tokens / candidate_tokens, where candidates are the distinct files the compiler judged relevant to the work (selected, or left out only for budget) and selected are those it put in the budget, counted with the named tokenizer; files it judged irrelevant are reported, never divided by",
1906 not: Some("not total token savings: it says what the compiler selected from what it found relevant, not what a session consumed, and a session remains free to read anything"),
1907 },
1908 &suite.id,
1909 );
1910 let Some(latest) = evidence.runs.last() else {
1911 metrics.push(ratio);
1912 continue;
1913 };
1914 let compatible = latest.methodology == m.version;
1917 let mut standing: Vec<String> = Vec::new();
1918 if compatible {
1919 if evidence.freshness == EconomicsFreshness::Stale {
1920 standing.push("stale: a measured mechanism changed since this record".into());
1921 }
1922 if latest.repository.dirty {
1923 standing.push(
1924 "dirty: this record was measured from a working tree with uncommitted changes"
1925 .into(),
1926 );
1927 }
1928 }
1929 let incompatible = format!(
1930 "incompatible: the latest record was measured under methodology {}; the current methodology is {}",
1931 latest.methodology, m.version
1932 );
1933 let refused = (!latest.refused.is_empty()).then(|| {
1934 format!(
1935 "{} seed(s) refused by the compiler were not measured",
1936 latest.refused.len()
1937 )
1938 });
1939 let per_seed: Vec<Sample> = if compatible {
1940 latest
1941 .seeds
1942 .iter()
1943 .filter_map(|s| {
1944 reduction(s.candidate_tokens, s.selected_tokens).map(|r| Sample {
1945 key: s.seed.clone(),
1946 cluster: s.seed.clone(),
1947 value: r,
1948 })
1949 })
1950 .collect()
1951 } else {
1952 Vec::new()
1953 };
1954 ratio = sampled(
1955 ratio,
1956 &per_seed,
1957 &m.statistics,
1958 Resampling::Independent,
1959 false,
1960 );
1961 if ratio.n > 0 {
1962 ratio.status = EconomicsMetricStatus::Measured;
1963 ratio.warnings.push(format!(
1964 "tokens counted with {} ({}), which is not the tokenizer of every model",
1965 latest.tokenizer.encoding, latest.tokenizer.implementation
1966 ));
1967 ratio.warnings.push(
1968 "the candidates are what the compiler's own graph walk judged relevant; a worker without the compiler would not necessarily have read them, so this ratio describes the selection, not a session's saving".into(),
1969 );
1970 ratio.warnings.extend(standing.iter().cloned());
1971 } else if compatible {
1972 ratio
1973 .warnings
1974 .push("not measured: no seed of the latest record yields a ratio".into());
1975 } else {
1976 ratio.warnings.push(incompatible.clone());
1977 }
1978 ratio.warnings.extend(refused.iter().cloned());
1979 metrics.push(ratio);
1980 let sum =
1981 |f: &dyn Fn(&EconomicsContextSeed) -> u64| latest.seeds.iter().map(f).sum::<u64>();
1982 let mut excluded: BTreeMap<String, u64> = BTreeMap::new();
1983 for s in &latest.seeds {
1984 for (k, v) in &s.excluded_tokens {
1985 *excluded.entry(k.clone()).or_insert(0) += v;
1986 }
1987 }
1988 let selected = sum(&|s| s.selected_tokens);
1989 let estimated = sum(&|s| s.estimated_selected_tokens);
1990 let mut model_error = metric(
1991 MetricSpec {
1992 id: "context_cost_model_error",
1993 title: "Error of the compiler's bytes-over-four cost model",
1994 class: EconomicsClass::Derived,
1995 inputs: Some(EconomicsClass::Counted),
1996 unit: "ratio",
1997 formula: "counted selected tokens / the compiler's own estimate of them - 1, over all seeds",
1998 not: Some("not a saving: it says how far the budget's unit is from counted tokens"),
1999 },
2000 &suite.id,
2001 );
2002 if compatible && estimated > 0 {
2003 model_error.value = Some(stats::round(selected as f64 / estimated as f64 - 1.0));
2004 model_error.n = latest.seeds.len();
2005 model_error.status = EconomicsMetricStatus::Measured;
2006 model_error.evidence = latest.seeds.iter().map(|s| s.seed.clone()).collect();
2007 model_error.warnings.extend(standing.iter().cloned());
2008 } else if compatible {
2009 model_error
2010 .warnings
2011 .push("not measured: the latest record estimates no selected tokens".into());
2012 } else {
2013 model_error.warnings.push(incompatible);
2014 }
2015 model_error.warnings.extend(refused);
2016 metrics.push(model_error);
2017 context_view = Some(EconomicsContextView {
2018 revision: latest.repository.commit.clone(),
2019 measured_at: latest.measured_at.clone(),
2020 tokenizer: latest.tokenizer.clone(),
2021 seeds: latest.seeds.len(),
2022 candidate_tokens: sum(&|s| s.candidate_tokens),
2023 considered_tokens: sum(&|s| s.considered_tokens),
2024 selected_tokens: selected,
2025 estimated_selected_tokens: estimated,
2026 excluded_tokens: excluded,
2027 seeds_over_budget_counted: latest
2028 .seeds
2029 .iter()
2030 .filter(|s| s.selected_tokens > latest.budget_tokens)
2031 .count(),
2032 budget_tokens: latest.budget_tokens,
2033 });
2034 }
2035 if context_runs.is_empty() {
2036 metrics.push(metric(
2037 MetricSpec {
2038 id: CONTEXT_REDUCTION,
2039 title: "Context reduction by the context compiler",
2040 class: EconomicsClass::Derived,
2041 inputs: Some(EconomicsClass::Counted),
2042 unit: "ratio",
2043 formula: "median over seeds of 1 - selected_tokens / candidate_tokens",
2044 not: Some("not total token savings"),
2045 },
2046 "context",
2047 ));
2048 }
2049
2050 let mut segments = Vec::new();
2052 for (dimension, key) in [
2053 (
2054 "category",
2055 &(|p: &EconomicsPair| p.category.clone()) as &dyn Fn(&EconomicsPair) -> String,
2056 ),
2057 ("sessions", &|p: &EconomicsPair| {
2058 format!("{} session(s)", p.sessions)
2059 }),
2060 ("complexity", &|p: &EconomicsPair| {
2061 decl.tasks
2062 .get(&p.task)
2063 .map(|t| t.complexity.clone())
2064 .unwrap_or_default()
2065 }),
2066 ] {
2067 let mut groups: BTreeMap<String, Vec<f64>> = BTreeMap::new();
2068 for p in &valid {
2069 if let Some(x) = p.token_reduction {
2070 groups.entry(key(p)).or_default().push(x);
2071 }
2072 }
2073 for (value, v) in groups {
2074 segments.push(EconomicsSegment {
2075 dimension: dimension.into(),
2076 value,
2077 n: v.len(),
2078 token_reduction: distribution(&v),
2079 });
2080 }
2081 }
2082 crate::order::canonical(&mut history);
2083
2084 EconomicsSummary {
2085 present: true,
2086 methodology: Some(m.version),
2087 question: Some(m.question.clone()),
2088 primary_metric: Some(m.primary_metric.clone()),
2089 verdict: EconomicsVerdict {
2090 publishable,
2091 statement,
2092 unmet: unmet_list,
2093 },
2094 metrics,
2095 suites: suite_views,
2096 pairs: pairs_q.iter().map(|j| j.pair.clone()).collect(),
2097 segments,
2098 context: context_view,
2099 history,
2100 variants: m.variants.clone(),
2101 hypotheses: m.hypotheses.clone(),
2102 publication: Some(m.publication.clone()),
2103 diagnostics,
2104 }
2105}
2106
2107pub fn explain(root: &Path, metric: &str) -> Result<EconomicsExplanation, String> {
2155 let summary = summarize(root, &EconomicsQuery::default());
2156 if !summary.present {
2157 return Ok(EconomicsExplanation {
2158 present: false,
2159 statement: Some(summary.verdict.statement),
2160 metric: None,
2161 class_meaning: String::new(),
2162 methodology: None,
2163 suites: Vec::new(),
2164 pairs: Vec::new(),
2165 excluded: Vec::new(),
2166 variants: Vec::new(),
2167 reproduce: Vec::new(),
2168 });
2169 }
2170 let Some(found) = summary.metrics.iter().find(|x| x.id == metric).cloned() else {
2171 let known: Vec<&str> = summary.metrics.iter().map(|x| x.id.as_str()).collect();
2172 return Err(format!(
2173 "no metric {metric:?}; the metrics are: {}",
2174 known.join(", ")
2175 ));
2176 };
2177 let decl = load(root).ok().flatten();
2178 let class_meaning = decl
2179 .as_ref()
2180 .and_then(|d| d.methodology.classes.iter().find(|c| c.id == found.class))
2181 .map(|c| c.meaning.clone())
2182 .unwrap_or_default();
2183 let evidence: BTreeSet<&str> = found.evidence.iter().map(String::as_str).collect();
2184 let of_suite = |s: &str| found.suite.split(',').any(|x| x == s);
2185 let pairs: Vec<EconomicsPair> = if found.class == EconomicsClass::Derived
2186 && found.inputs == Some(EconomicsClass::Counted)
2187 {
2188 Vec::new()
2189 } else if RUN_BASED.iter().any(|p| found.id.starts_with(p)) {
2190 summary
2191 .pairs
2192 .iter()
2193 .filter(|p| of_suite(&p.suite))
2194 .filter(|p| {
2195 [&p.control, &p.treatment]
2196 .into_iter()
2197 .flatten()
2198 .any(|id| evidence.contains(id.as_str()))
2199 })
2200 .cloned()
2201 .collect()
2202 } else {
2203 summary
2204 .pairs
2205 .iter()
2206 .filter(|p| {
2207 evidence.contains(pair_key(p).as_str())
2208 || (p.status != EconomicsPairStatus::Valid && of_suite(&p.suite))
2209 })
2210 .cloned()
2211 .collect()
2212 };
2213 let suites: Vec<EconomicsSuiteView> = summary
2214 .suites
2215 .iter()
2216 .filter(|s| of_suite(&s.id))
2217 .cloned()
2218 .collect();
2219 let reproduce = suites
2220 .iter()
2221 .map(|s| match s.kind.as_str() {
2222 "live" => format!("majordomus economics run --suite {} # live: needs a provider credential and spends usage", s.id),
2223 _ => format!("majordomus economics measure --suite {} # deterministic: no model is called", s.id),
2224 })
2225 .chain(std::iter::once(format!("majordomus economics explain {metric}")))
2226 .collect();
2227 Ok(EconomicsExplanation {
2228 present: true,
2229 statement: None,
2230 class_meaning,
2231 methodology: summary.methodology,
2232 suites,
2233 pairs,
2234 excluded: decl
2235 .map(|d| d.methodology.outliers.excluded)
2236 .unwrap_or_default(),
2237 variants: summary.variants.clone(),
2238 reproduce,
2239 metric: Some(found),
2240 })
2241}
2242
2243pub fn runs(root: &Path, q: &EconomicsRunsQuery) -> EconomicsRunList {
2281 let decl = match load(root) {
2282 Ok(Some(d)) => d,
2283 Ok(None) => {
2284 return EconomicsRunList {
2285 count: 0,
2286 runs: Vec::new(),
2287 diagnostics: Vec::new(),
2288 }
2289 }
2290 Err(e) => {
2291 return EconomicsRunList {
2292 count: 0,
2293 runs: Vec::new(),
2294 diagnostics: e,
2295 }
2296 }
2297 };
2298 let mut out = Vec::new();
2299 let mut diagnostics = Vec::new();
2300 for suite in decl.suites.values().filter(|s| s.kind == "live") {
2301 if q.suite.as_ref().is_some_and(|x| x != &suite.id) {
2302 continue;
2303 }
2304 let (runs, errors) = load_runs(root, &suite.id);
2305 diagnostics.extend(errors);
2306 for (path, r) in runs {
2307 if q.task.as_ref().is_some_and(|t| t != &r.task)
2308 || q.variant.as_ref().is_some_and(|v| v != &r.variant)
2309 {
2310 continue;
2311 }
2312 out.push(EconomicsRunView {
2313 usage: usage::totals(&r),
2314 id: r.id,
2315 suite: r.suite,
2316 task: r.task,
2317 variant: r.variant,
2318 repetition: r.repetition,
2319 model: r.model_requested,
2320 revision: r.repository.commit,
2321 completed: r.outcome.completed,
2322 checks: r.outcome.checks,
2323 sessions: r.sessions.len(),
2324 path,
2325 });
2326 }
2327 }
2328 crate::order::canonical(&mut out);
2329 EconomicsRunList {
2330 count: out.len(),
2331 runs: out,
2332 diagnostics,
2333 }
2334}