From 2295ef3571f64dec95e3fb3e42036b35d2efa43d Mon Sep 17 00:00:00 2001 From: Rached Rayeh Date: Wed, 11 Mar 2026 11:41:12 +0100 Subject: [PATCH] feat(cli): add aggregate output format options for docs parity The README/CONTRIBUTING examples used `obliteratus aggregate --format ...` but the CLI only accepted `--dir`. This adds `--format {summary,latex}`, `--metric`, and `--min-runs` to the aggregate command, reuses community LaTeX table generation, and adds CLI parsing tests to align behavior with documented usage. --- obliteratus/cli.py | 36 +++++++++++++++++++++++++++++++++++- tests/test_cli.py | 23 +++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/obliteratus/cli.py b/obliteratus/cli.py index c0a874d..5c43d97 100644 --- a/obliteratus/cli.py +++ b/obliteratus/cli.py @@ -316,6 +316,24 @@ def main(argv: list[str] | None = None): "--dir", type=str, default="community_results", help="Directory containing contribution JSON files", ) + aggregate_parser.add_argument( + "--format", + choices=["summary", "latex"], + default="summary", + help="Output format (default: summary)", + ) + aggregate_parser.add_argument( + "--metric", + type=str, + default="refusal_rate", + help="Metric to display in LaTeX output (default: refusal_rate)", + ) + aggregate_parser.add_argument( + "--min-runs", + type=int, + default=1, + help="Minimum runs per (model, method) to include (default: 1)", + ) # --- tourney --- tourney_parser = subparsers.add_parser( @@ -747,7 +765,7 @@ def _cmd_report(args): def _cmd_aggregate(args): - from obliteratus.community import aggregate_results, load_contributions + from obliteratus.community import aggregate_results, generate_latex_table, load_contributions contrib_dir = args.dir records = load_contributions(contrib_dir) @@ -756,6 +774,22 @@ def _cmd_aggregate(args): return aggregated = aggregate_results(records) + min_runs = max(args.min_runs, 1) + if min_runs > 1: + for model in list(aggregated.keys()): + for method in list(aggregated[model].keys()): + if aggregated[model][method]["n_runs"] < min_runs: + del aggregated[model][method] + if not aggregated[model]: + del aggregated[model] + + if not aggregated: + console.print("[yellow]No results meet the minimum run threshold.[/yellow]") + return + + if args.format == "latex": + console.print(generate_latex_table(aggregated, metric=args.metric)) + return from rich.table import Table diff --git a/tests/test_cli.py b/tests/test_cli.py index 98ed2ab..43fbeed 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -101,6 +101,29 @@ class TestCLIDispatch: ) assert "no contributions found" in printed_text.lower() or mock_console.print.called + def test_aggregate_accepts_format_metric_min_runs(self): + """aggregate accepts --format, --metric and --min-runs flags.""" + with patch("obliteratus.cli._cmd_aggregate") as mock_cmd: + main([ + "aggregate", + "--format", "latex", + "--metric", "refusal_rate", + "--min-runs", "3", + ]) + mock_cmd.assert_called_once() + args_passed = mock_cmd.call_args[0][0] + assert args_passed.format == "latex" + assert args_passed.metric == "refusal_rate" + assert args_passed.min_runs == 3 + + def test_aggregate_rejects_invalid_format(self): + """aggregate rejects unknown --format choices.""" + stderr_text = _capture_exit( + ["aggregate", "--format", "invalid"], + expect_code=2, + ) + assert "invalid choice" in stderr_text.lower() + # 7. --help flag prints help def test_help_flag(self): """Calling main(['--help']) should print help and exit 0."""