# 📦 WP-128 — `refine(tests): regroup flat-naming test functions into classes`

> Engineering log · [Phase 11 — Rolling, toward v0.5.0](https://borda.github.io/lucid-YOLO/engineering/phase-11-rolling/)
>
> 🔗 Depends on [WP-117](https://borda.github.io/lucid-YOLO/work_packages/wp-117/) · Needed by none
>
> ⬅️ [WP-117](https://borda.github.io/lucid-YOLO/work_packages/wp-117/) · [Roadmap](https://borda.github.io/lucid-YOLO/ROADMAP/) · [WP-129](https://borda.github.io/lucid-YOLO/work_packages/wp-129/) ➡️


## WP-128 — a shared prefix is not a shared caller

<a id="wp-128"></a>

[WP-117](https://borda.github.io/lucid-YOLO/work_packages/wp-117/)'s own count -- "8 groups across 6 files" -- was a one-off human read, not a reproducible measurement, and the row it left behind said so: a first-shared-word `ast` scan over `test_` names returns roughly 90 candidate groups, because this suite's house style already writes full descriptive sentences (`test_boundary_sits_at_midpoint_of_overlap_band`) that share a topic noun with unrelated tests -- indistinguishable from genuinely flat, mechanical enumeration (`test_<subject>_<case>`) by prefix-matching alone.

The discriminator that breaks the tie: a candidate prefix confirms only if it is a contiguous token subsequence -- prefix, suffix, or infix -- of some `ast.Call` function-name target actually present in the same file (`Attribute.attr` or `Name.id`, underscore-split, leading-underscore tokens dropped). A house-style sentence sharing a topic noun calls nothing that looks like it and never confirms; `test_check_data_a/b/c` calling `check_data(...)` does. Confirmed groups need 3+ members to enforce, matching the size floor `tests/eval/test_tile_merge.py`'s own precedent already implied by example. Verified both directions against real cases in this tree: correctly includes `check_data`, `plan_archives`, `verify_split`, `configure_optimizers`, `warmup_decay`, `on_after_batch_transfer`, `detections_to_predictions`, and `rotated_topk` (confirmed as an infix of `o2o_rotated_topk`); correctly excludes `deployed_view`-style near-misses and the `a_named_worker_count_*` / `an_auto_chosen_*` house-style family [WP-117](https://borda.github.io/lucid-YOLO/work_packages/wp-117/)'s row already flagged as the hard case.

New `scripts/lint/audit_flat_test_groups.py` owns this rather than extending `audit_test_doctests.py` (129) -- that script's own docstring disclaims scanning for anything but missing doctest Examples, and stapling an unrelated structural check onto it would have made neither job legible from the file alone. Same shape as every other `scripts/lint/` checker: `main(argv) -> int` CLI, a `flat-test-group-audit` local hook gated on `files: ^(tests/.*/test_[^/]+\.py|scripts/_tests/test_[^/]+\.py)$` (pure repo content, not installed-environment state, so no `always_run`), and `scripts/_tests/test_audit_flat_test_groups.py` as the functional core -- synthetic `tmp_path` fixtures per confirmed/rejected case, plus the two "live tree is currently clean" sanity tests the hook itself depends on staying true.

Nine files regrouped under `class Test<Subject>:` once the discriminator was run against the whole `tests/` and `scripts/_tests/` tree rather than continuing [WP-117](https://borda.github.io/lucid-YOLO/work_packages/wp-117/)'s by-eye survey: `test_verify.py`, `test_download.py`, `test_coco_eval.py`, `test_schedule.py`, `test_dota_parse.py`, `test_coco.py`, `test_obb_head.py`, `test_tiling.py`, `test_proto.py`, `test_module.py`, and `scripts/_tests/test_audit_docs_present.py` (all 12 of its check-function pairs, for whole-file consistency, though the discriminator's crude grouping key only flagged 6) and `test_audit_docs_site.py`. Regrouping moves node ids, not test bodies -- Python resolves a module-level helper name at call time, not at class-body-definition time, so relocating a scattered function into a contiguous class needed no change to whatever module-level fixtures it called. Every citation of a moved node id in `docs/ROADMAP.md` (rows 044, 051, 110) and `docs/ASSUMPTIONS.md` (A45) was checked against a grep for the old id and updated; row 044's citation had already drifted stale before this WP touched the file (`test_oracle` moved under a pre-existing `TestOracleRoundTrip` by earlier, unrelated work) and was corrected in the same pass since the row was already open.

