Skip to content

feat(analysis): statistical analysis pipeline (ANOVA, effect sizes) - #49

Merged
Colinho22 merged 3 commits into
mainfrom
stats-analysis-pipeline
Jun 1, 2026
Merged

feat(analysis): statistical analysis pipeline (ANOVA, effect sizes)#49
Colinho22 merged 3 commits into
mainfrom
stats-analysis-pipeline

Conversation

@Colinho22

@Colinho22 Colinho22 commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Add python -m maestro.analysis: read-only DB → descriptives, factorial ANOVA (statsmodels), Tukey HSD, Cohen's d, error taxonomy, and correctness/efficiency trade-off, written as content-named JSON + report.md to output/analysis//.

  • single_agent is the ANOVA reference (baseline); controls excluded from inferential tests but kept in descriptives as sanity anchors.
  • Factor-guard degrades gracefully: an under-leveled factor (e.g. one input tier) yields a skip-stub instead of crashing, and recomputes unchanged once the corpus grows.
  • Filenames are content-based (anova_strategy_by_tier.json), not RQ-numbered; the RQ→file mapping lives in report.md. schema_version locked at 1.0 for feat: results visualizer for the experiment #19.
  • Figures deferred to the visualizer (feat: results visualizer for the experiment #19); a documented stub marks the contract.
  • statsmodels + pandas added to deps and the provenance whitelist.

TLDR

Adds a compute-only statistical analysis pipeline (python -m maestro.analysis) that reads the experiment SQLite DB and emits JSON summaries + report.md: descriptives, factorial ANOVA (Type II), Tukey HSD post-hoc, Cohen’s d effect sizes, an error taxonomy, and correctness/efficiency trade-off summaries. Controls are excluded from inferential tests, under-leveled factors produce graceful "skipped" stubs, figure generation is deferred to the visualizer, and outputs use schema_version "1.0". Runtime deps pandas and statsmodels were added.

What's new

  • CLI entry point

    • python -m maestro.analysis validates a read-only DB, creates a timestamped run directory under output/analysis//, runs a fixed ordered set of analyses, writes content-named JSON files and a human-facing report.md (mapping RQ→file), creates figures/README.md as a visualizer contract stub, and prints the run path.
  • Statistical computations (src/maestro/analysis/statistics.py)

    • Descriptives by (strategy, model, tier) for PRIMARY_DV = entity_id_f1 and efficiency DVs (cost_usd, duration_ms, retry_count). Controls included in descriptives.
    • Type-II ANOVA helpers (strategy, by-tier, by-model) on experimental rows only; BASELINE_STRATEGY = single_agent used as reference; returns F/p/df and partial η² per term.
    • Tukey HSD post-hoc pairwise strategy comparisons (experimental rows only).
    • Pairwise Cohen’s d (pooled SD) with explicit handling for zero pooled variance (returns 0.0 when means equal, signed "inf"/"-inf" when unequal) and underpowered groups (None).
    • Error taxonomy summaries (per-strategy counts over predefined taxonomy columns), including controls (descriptive only).
    • Trade-off summaries (mean correctness vs mean cost/latency and correctness-per-USD ratio; ratio is None when cost is zero/undefined).
    • JSON-safe coercion helper mapping numpy/pandas scalars and NaN/inf to JSON-compatible primitives.
  • Robustness and contract decisions

    • Factor-guard: analyses lacking sufficient factor levels (<2) emit a skip-stub ({status: "skipped"}) rather than crashing; post-hoc similarly guarded.
    • Files are content-named (e.g., anova_strategy_by_tier.json); RQ→file mapping recorded in report.md (interpretation kept out of JSON).
    • SCHEMA_VERSION locked at "1.0" (issue #19).
    • Figure generation deferred to the visualizer; figures/README.md documents the stub/contract.
  • Exports and API

    • Re-exports analysis API in src/maestro/analysis/init.py for direct imports (describe, anova_* helpers, posthoc_strategy, effect_sizes, error_taxonomy_by_strategy, tradeoff_correctness_efficiency, etc.).
  • DB and environment

    • New read-only DB helper fetch_analysis_rows added to src/maestro/db/queries.py to produce the analysis join result set.
    • _LIB_WHITELIST extended to include statsmodels, pandas, and scipy so run_environment snapshots capture their versions.
  • Dependencies

    • pyproject.toml adds statsmodels>=0.14 and pandas>=2.2; notes scipy is expected transitively via statsmodels and its version is tracked via the environment whitelist.
  • Tests and CI-related adjustments

    • tests/analysis/test_statistics.py: end-to-end pytest coverage using an in-memory (and CLI on-disk) SQLite DB exercising load_dataframe, describe, ANOVA/post-hoc/effect-sizes, taxonomy, trade-off logic, factor-level skipping, Cohen’s d zero-variance edge cases, and strict JSON serializability. CLI smoke test validates timestamped run dir, report.md, figures/README.md, and strict JSON outputs; missing-DB CLI returns exit code 1.
    • .coderabbit.yaml added to exclude tests/ from docstring-coverage checks; commit fixes enforce deterministic lookups and strict JSON parsing in the CLI.

Notes for reviewers

  • Figure generation intentionally out of scope; visualizer will consume produced JSON artifacts.
  • SCHEMA_VERSION is stable at "1.0"—bump only for breaking output-shape changes.

@Colinho22 Colinho22 added this to the 🧪 Experimental Artifact milestone Jun 1, 2026
@Colinho22 Colinho22 self-assigned this Jun 1, 2026
@Colinho22 Colinho22 added the enhancement New feature or request label Jun 1, 2026
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Ignoring CodeRabbit configuration file changes. For security, only the configuration from the base branch is applied for open source repositories.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2fb26877-6455-43a8-ac98-9f8920307d68

📥 Commits

Reviewing files that changed from the base of the PR and between 5a16fb2 and ba24050.

📒 Files selected for processing (3)
  • .coderabbit.yaml
  • src/maestro/analysis/__main__.py
  • tests/analysis/test_statistics.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/analysis/test_statistics.py
  • src/maestro/analysis/main.py

📝 Walkthrough

Walkthrough

Adds a pandas/statsmodels-based analysis pipeline: DB join ingestion, grouped descriptives, Type‑II ANOVA with partial η², Tukey HSD and Cohen’s d effect sizes, error-taxonomy and tradeoff summaries, a CLI to run analyses and emit JSON/Markdown, environment-version tracking, package re-exports, and end-to-end tests.

Changes

Statistical Analysis Pipeline

Layer / File(s) Summary
Dependencies and database support
pyproject.toml, src/maestro/db/environment.py, src/maestro/db/queries.py, .coderabbit.yaml
Adds statsmodels>=0.14 and pandas>=2.2; records statsmodels/pandas/scipy in environment whitelist; new fetch_analysis_rows query joins run_configs/run_results/metric_results; CodeRabbit config updated.
Statistics constants and data ingestion
src/maestro/analysis/statistics.py
Adds SCHEMA_VERSION, PRIMARY_DV, EFFICIENCY_DVS, BASELINE_STRATEGY, TAXONOMY_COLUMNS; implements load_dataframe and experimental-row filtering.
Descriptive statistics and factor-level guards
src/maestro/analysis/statistics.py
describe() computes grouped mean/median/std (including controls); _factor_levels/_guard_factors provide structured skip stubs for under-leveled factors.
ANOVA implementation (Type-II, partial η²)
src/maestro/analysis/statistics.py
Core _anova fits OLS with treatment coding vs baseline, runs Type‑II tests, computes per-term F/p/df and partial η²; wrappers anova_strategy, anova_strategy_by_tier, anova_strategy_by_model.
Post-hoc and effect size analysis
src/maestro/analysis/statistics.py
posthoc_strategy runs Tukey HSD on primary DV (experimental only); effect_sizes computes pairwise Cohen’s d with _cohens_d handling small groups and zero pooled variance edge cases.
Error taxonomy and tradeoff analysis
src/maestro/analysis/statistics.py
error_taxonomy_by_strategy computes mean taxonomy counts (including controls); tradeoff_correctness_efficiency aggregates correctness/cost/latency and computes correctness_per_usd; _to_native ensures JSON-safe primitives.
Public API re-exports
src/maestro/analysis/__init__.py
Re-exports statistics functions and constants from maestro.analysis.statistics via package namespace.
CLI entry point and report orchestration
src/maestro/analysis/__main__.py
CLI parses --db/--out/--display-tz, creates timestamped run dir, runs registered analyses, writes strict JSON outputs, builds report.md with defensive ANOVA summaries, creates figures/README.md, and prints the output path.
End-to-end test suite
tests/analysis/test_statistics.py
Pytest coverage: dataframe loading, describe/ANOVA/post-hoc/effect-sizes, JSON-serializability, Cohen’s d edge-cases, CLI artifact generation and missing-DB error path.

🎯 4 (Complex) | ⏱️ ~45 minutes

"By my staff, the stats awakened:
Rows aligned, controls unshaken,
ANOVA and Tukey cast, effect sizes told,
Outputs in JSON, reports in gold,
Go forth — the run directory awaits thy hold."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title precisely captures the main changeset: adding a statistical analysis pipeline with ANOVA and effect-size functionality.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Add python -m maestro.analysis: read-only DB → descriptives, factorial
ANOVA (statsmodels), Tukey HSD, Cohen's d, error taxonomy, and
correctness/efficiency trade-off, written as content-named JSON + report.md
to output/analysis/<utc-timestamp>/.

- single_agent is the ANOVA reference (baseline); controls excluded from
  inferential tests but kept in descriptives as sanity anchors.
- Factor-guard degrades gracefully: an under-leveled factor (e.g. one
  input tier) yields a skip-stub instead of crashing, and recomputes
  unchanged once the corpus grows.
- Filenames are content-based (anova_strategy_by_tier.json), not RQ-numbered;
  the RQ→file mapping lives in report.md. schema_version locked at 1.0 for #19.
- Figures deferred to the visualizer (#19); a documented stub marks the contract.
- statsmodels + pandas added to deps and the provenance whitelist.
@Colinho22
Colinho22 force-pushed the stats-analysis-pipeline branch from 9b19d41 to 6328b90 Compare June 1, 2026 09:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
tests/analysis/test_statistics.py (1)

179-373: ⚡ Quick win

Please add one smoke test for the new CLI entry point.

This suite exercises statistics.py well, but it never calls maestro.analysis.__main__.main(). That leaves the new public contract around output filenames, report.md, figures/README.md, and exit handling untested.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/analysis/test_statistics.py` around lines 179 - 373, Add a smoke test
that invokes the new CLI entry point maestro.analysis.__main__.main() to
validate the public output contract; create a temporary directory fixture, call
main() (or run __main__.main with argv pointing at your test database and the
temp output dir), then assert that report.md and figures/README.md exist and
that main() exits/returns normally (no uncaught SystemExit with non-zero code).
Reference the entrypoint symbol maestro.analysis.__main__.main and the expected
outputs "report.md" and "figures/README.md" when locating where to insert the
test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pyproject.toml`:
- Around line 38-45: The environment recording omits SciPy from the runtime
whitelist so run_environments.lib_versions won't capture its installed version;
add "scipy" to the _LIB_WHITELIST constant used by the environment recording
code (referencing the _LIB_WHITELIST symbol in src/maestro/db/environment.py) so
scipy's version is recorded in run_environments.lib_versions, and ensure any
tests or serialization that consume _LIB_WHITELIST still pass; if your project
policy prefers explicit runtime deps, also consider adding a comment in
pyproject.toml noting SciPy is a transitive but recorded dependency.

In `@src/maestro/analysis/__main__.py`:
- Around line 127-134: The timestamped directory produced by _run_dir uses
second precision and may collide; change _run_dir to produce a unique directory
name (e.g., append microseconds or a short uuid fragment to the stamp) and
create it with mkdir(exist_ok=False) (or loop trying alternate suffixes) so
creation will fail/retry instead of silently reusing the same directory; apply
the same change to any other function that builds a stamp via
now_utc.strftime("%Y%m%dT%H%M%SZ") to make all run directories collision-proof.
- Around line 137-141: The JSON writer permits non-finite floats, risking
invalid output; enforce strict JSON in _write_json. Update the json.dumps call
in _write_json to include allow_nan=False so NaN/Infinity cause an immediate
exception, and let that error propagate (or wrap and re-raise with path
context). No changes needed in _anova; the writer should fail fast if its
payload (including fields like partial_eta_sq or sum_sq) contains non-finite
values.

In `@src/maestro/analysis/statistics.py`:
- Around line 444-448: The current branch that returns 0.0 when pooled <= 0
silently hides deterministic differences; change it to detect mean difference
and return an infinite effect size when variance is zero but means differ: if
pooled <= 0 then compute mean_diff = a.mean() - b.mean() and if mean_diff == 0
return _to_native(0.0) else return _to_native(math.copysign(float("inf"),
mean_diff)) (or equivalent float('inf') with the correct sign) so that zero
pooled variance with unequal means yields an infinite Cohen's d instead of 0.0.
- Around line 375-380: Current code reads the private tukey._results_table.data;
replace that with using the public TukeyHSDResults attributes: iterate the
upper-triangular pairs in the same order statsmodels uses and build comparisons
from tukey.groupsunique, tukey.meandiffs, tukey.confint, tukey.pvalues, and
tukey.reject (ensuring i<j ordering and mapping each index to group names), then
produce the same dict structure previously created from rows; update the logic
that builds comparisons (currently using rows/header/zip) to instead construct
dicts from these public fields so it no longer depends on _results_table.

In `@tests/analysis/test_statistics.py`:
- Around line 354-372: The test test_all_outputs_json_serializable currently
allows NaN/Inf because it calls json.dumps(payload) without rejecting non-JSON
numbers; update the assertions to call json.dumps(payload, allow_nan=False) so
any float("nan")/float("inf") in outputs from stats.describe,
stats.anova_strategy, stats.anova_strategy_by_tier,
stats.anova_strategy_by_model, stats.posthoc_strategy, stats.effect_sizes,
stats.error_taxonomy_by_strategy, and stats.tradeoff_correctness_efficiency will
fail the test. Additionally add a lightweight CLI smoke test that invokes the
module entrypoint (python -m maestro.analysis) or calls
maestro.analysis.__main__ to ensure the CLI writes an output directory
containing <out>/<timestamp>/report.md and exercises the output-directory
behavior. Ensure the new tests reference the existing helper
_conn/_populate_two_levels where appropriate and clean up any temp dirs after
running.

---

Nitpick comments:
In `@tests/analysis/test_statistics.py`:
- Around line 179-373: Add a smoke test that invokes the new CLI entry point
maestro.analysis.__main__.main() to validate the public output contract; create
a temporary directory fixture, call main() (or run __main__.main with argv
pointing at your test database and the temp output dir), then assert that
report.md and figures/README.md exist and that main() exits/returns normally (no
uncaught SystemExit with non-zero code). Reference the entrypoint symbol
maestro.analysis.__main__.main and the expected outputs "report.md" and
"figures/README.md" when locating where to insert the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1ba40910-b37b-4c11-ab93-11384aa3b99e

📥 Commits

Reviewing files that changed from the base of the PR and between 09afd2e and 9b19d41.

📒 Files selected for processing (7)
  • pyproject.toml
  • src/maestro/analysis/__init__.py
  • src/maestro/analysis/__main__.py
  • src/maestro/analysis/statistics.py
  • src/maestro/db/environment.py
  • src/maestro/db/queries.py
  • tests/analysis/test_statistics.py

Comment thread pyproject.toml
Comment thread src/maestro/analysis/__main__.py Outdated
Comment thread src/maestro/analysis/__main__.py
Comment thread src/maestro/analysis/statistics.py Outdated
Comment thread src/maestro/analysis/statistics.py Outdated
Comment thread tests/analysis/test_statistics.py Outdated
- Cohen's d: zero pooled variance with unequal means now yields a signed
  'inf'/'-inf' sentinel instead of 0.0, which silently hid maximal
  deterministic differences (e.g. control conditions). Guard uses a 1e-12
  tolerance so float-noise variance (stored 0.1 ≠ exact 0) doesn't produce
  absurd ~1e16 'finite' effect sizes.
- Tukey HSD: read public TukeyHSDResults attributes (groupsunique,
  meandiffs, confint, pvalues, reject) instead of the private
  _results_table, which can break across statsmodels versions.
- _run_dir: create with exist_ok=False and append a numeric suffix on
  collision so two runs in the same second can't silently overwrite.
- _write_json: allow_nan=False so non-finite floats fail fast instead of
  emitting non-standard NaN/Infinity tokens the visualizer (#19) rejects.
- Provenance: record scipy (transitive via statsmodels) in _LIB_WHITELIST.
- Tests: allow_nan=False in the serialization assertion; add a CLI smoke
  test asserting the output contract, a missing-DB test, and Cohen's d
  zero-variance edge cases.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/analysis/test_statistics.py`:
- Around line 240-243: The test is fragile because it uses
next(iter(out["terms"].values())) which depends on dict insertion order;
instead, locate a specific ANOVA term or assert properties across all terms in
out["terms"]. For example, pick a term by name from out["terms"] (e.g.,
out["terms"]["<expected_term_name>"]) or iterate over out["terms"].values() and
assert each term has non-None "F" and "partial_eta_sq"; update the assertions
that reference next(iter(out["terms"].values())) to use a key lookup or a loop
so the test no longer depends on dict ordering.
- Around line 477-478: The test currently calls json.loads(path.read_text())
which allows NaN/Infinity by default; change the call to force strict JSON
parsing by passing allow_nan=False to json.loads (i.e., use json.loads(...,
allow_nan=False)) so any NaN/Infinity tokens raise a ValueError and the test
fails if non-standard numbers leak into artifacts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 054b0ed1-8936-482f-9ba8-317aad1713b9

📥 Commits

Reviewing files that changed from the base of the PR and between 9b19d41 and 5a16fb2.

📒 Files selected for processing (7)
  • pyproject.toml
  • src/maestro/analysis/__init__.py
  • src/maestro/analysis/__main__.py
  • src/maestro/analysis/statistics.py
  • src/maestro/db/environment.py
  • src/maestro/db/queries.py
  • tests/analysis/test_statistics.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • pyproject.toml
  • src/maestro/db/queries.py
  • src/maestro/db/environment.py
  • src/maestro/analysis/init.py
  • src/maestro/analysis/main.py
  • src/maestro/analysis/statistics.py

Comment thread tests/analysis/test_statistics.py
Comment thread tests/analysis/test_statistics.py Outdated
- test_anova_strategy: look up the term by term_of_interest instead of
  next(iter(...)), removing a dict-ordering dependency.
- CLI smoke test: enforce strict JSON via json.loads(parse_constant=...)
  (json.loads has no allow_nan kwarg — that's dumps-only); rejects any
  NaN/Infinity token that leaks into an artifact.
- Add .coderabbit.yaml excluding tests/ from review so the docstring
  coverage metric reflects production code (src/ ~90%, above the 80%
  threshold) rather than being dragged down by undocumented test functions.
  Exclusion-only, so non-test code stays fully reviewed; check left on
  (advisory) since CI gates are ruff + pytest only.
- Document the 4 functions this PR added to analysis/__main__.py.
@Colinho22

Copy link
Copy Markdown
Owner Author

@coderabbitai review please :)
addressed your last 2 comments plus adjusted your .yaml settings to combat slipping of docstring with ba24050

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

A deep, resonant voice echoes across the repository...

"Ah, traveler Colinho22! You have heeded the counsel of old and returned with your amendments sealed in commit ba24050. The path is clear — I shall now summon the ancient Review Rite upon these lands once more. Stand ready, for the oracle speaks truth, whether welcome or not!"

I'll cast a fresh review upon your statistical analysis pipeline now. 🧙‍♂️✨

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: statistical analysis pipeline (ANOVA, effect sizes, figure export)

1 participant