**Epic A — Batch v3** · `aggregate.py` + `qc.py` + `outputs.py`. Plan §4.5. **Acceptance** - [ ] `combine_summaries(cells, journal) -> pl.DataFrame` — one tidy long-format frame (`cell`, `group`, `sub_group`), replacing the wide/multiindex join machinery. - [ ] `qc.check(cells, journal) -> pl.DataFrame` — the `_check_cell_*` family as one function returning a tidy pass/fail frame. - [ ] `outputs.write_csv/write_excel/write_parquet` — pure writers taking an explicit path from `BatchPaths`; never mkdir implicitly. **Blocks Epic B** (`collect/summary.py` builds on `combine_summaries`). Part of #696.
Epic A — Batch v3 ·
aggregate.py+qc.py+outputs.py. Plan §4.5.Acceptance
combine_summaries(cells, journal) -> pl.DataFrame— one tidy long-format frame (cell,group,sub_group), replacing the wide/multiindex join machinery.qc.check(cells, journal) -> pl.DataFrame— the_check_cell_*family as one function returning a tidy pass/fail frame.outputs.write_csv/write_excel/write_parquet— pure writers taking an explicit path fromBatchPaths; never mkdir implicitly.Blocks Epic B (
collect/summary.pybuilds oncombine_summaries). Part of #696.