Skip to content

fix(pkg): warning hijack + import time; recovery-test harness - #39

Merged
nceglia merged 3 commits into
refactor/pr6-9from
test/recovery-harness
Jul 30, 2026
Merged

fix(pkg): warning hijack + import time; recovery-test harness#39
nceglia merged 3 commits into
refactor/pr6-9from
test/recovery-harness

Conversation

@nceglia

@nceglia nceglia commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Stacked on #38 (base refactor/pr6-9). Three commits that were pushed after #37's merge window closed, so they are not in the integration branch — plus the new recovery-test harness.

Full suite 164 passed, 3 skipped (the slow tier).

1. import tcri was silencing the user's warnings (bbdf87f)

tcri/preprocessing/_preprocessing.py carried a blanket warnings.filterwarnings('ignore') at module scope, and that module is imported by import tcri. Verified: after importing tcri, a plain warnings.warn(...) produced nothing.

That meant the guardrails added in #37 — the K clamp, the param-store-reuse notice, the batch_size >= n_obs warning — could never reach a real user, along with any warning from numpy/pandas/scanpy or the user's own code. A library must not mutate global warning state it doesn't own.

2. import tcri took 6.65 s, 85% of it in one module (bbdf87f)

-X importtime: tcri 5991 ms, of which tcri.preprocessing 5121 ms, of which umap 2955 ms (umap → pynndescent → numba/llvmlite). The module imported umap twice and never used it — the code that needed it was deleted in PR7, and the surviving opt-in path (to_anndata(compute_umap=True)) already imports it locally.

The whole import block was pre-refactor debris: numpy ×5, pandas ×4, torch ×5, umap ×2, plus scanpy/pyro/scvi/tqdm/scipy — for a file that now holds exactly two functions using only numpy and _keys.

Measured: 6.65 s → 3.81 s (1.75×), and umap no longer enters sys.modules.

Both are guarded by regression tests. The warning test runs in a subprocess — pytest resets warning filters, so an in-process test would have masked exactly this bug.

3. Packaging cleanup (7059573)

  • gseapy — declared in pyproject.toml and requirements.txt, imported nowhere.
  • mpltern — imported at import-time, unused since PR7.
  • tcri/metrics/ — PR6 deleted its .py files but an empty dir holding a .DS_Store survived, which is why that Removal Ledger row was legitimately unticked.
  • Removal Ledger — ticked 9 rows after verifying each of the 20 symbols is genuinely gone. The ledger now has zero outstanding items.

Verified: python -m build produces sdist + wheel, twine check PASSES, and a clean-venv install imports correctly with all namespaces.

4. Statistical recovery tests (0a52544) — the missing test tier

The existing suite checks structure (contracts, identities, wiring); none of it can catch an estimator that is well-formed but numerically wrong. These check accuracy against a known truth.

tcri/datasets/simulate_tcri() implements the note's own semi-synthetic story, so I(c;φ) is available in closed form. It needs no real dataset to fit, so it is importable, seeded and fast.

Two oracles are reported, and the distinction drives every assertion:

  • true_* — the population value implied by (pi, omega): the estimand
  • empirical_* — the value implied by the realized counts: what a perfect estimator returns on this sample

They differ by sampling noise plus the plug-in estimator's upward bias, ~(C-1)(P-1)/(2N ln2) bits. Both are given under both normalizations, because tcri defaults to min while the note's benchmark used the mean denominator — comparing across that difference silently inflates the estimate.

Fast tier (10 tests, every commit): oracle bounds; omega_concentration monotonically controls the truth; fuzziness changes difficulty only — the truth is identical to 1e-12; seed determinism; tl.mutual_information == an independent oracle implementation in raw and both normalizations; the two normalizations are provably not interchangeable; bias direction; metamorphic invariance to relabeling and count replication.

Slow tier (--runslow, 3 tests, 34 s): MAE falls with N over seeds; a fitted model's MI tracks the true MI across difficulty levels; posterior HDI coverage.

Two things measured rather than assumed:

  • HDI coverage came out 8/8 for a nominal 94% interval (mean width 0.103), posterior means close to truth. With 8 replicates that is evidence of no miscalibration, not proof of 94% — establishing the rate needs ~50+ reps and belongs in benchmarks/. The bar was tightened from >=2/5 (which would pass at 40% coverage) to >=6/8.
  • Single-seed convergence is flaky: the same seed gave gap +0.003 at N=5000 but +0.012 at N=20000. Convergence is therefore asserted on a mean over seeds.

conftest gains --runslow so the per-commit suite stays fast while the accuracy tier stays runnable nightly.

Note: tcri.datasets is new public API and is not yet in _contract.pyi — it should be onboarded as part of PR 10 (public API + scverse CI) rather than freezing the surface and immediately amending it.

🤖 Generated with Claude Code

nceglia and others added 3 commits July 29, 2026 17:28
Packaging cleanup found while auditing pip-deploy readiness.

DEAD DEPENDENCIES removed (both forced an install for nothing):
  - gseapy: declared in pyproject.toml AND requirements.txt but imported nowhere
    in the package (only in pre-refactor worktree copies).
  - mpltern: imported at tcri/utils/_utils.py:11 and never used -- the ternary
    plot that needed it was deleted in PR7. `_utils` loads at `import tcri` time,
    so this was a hard import-time dependency for zero benefit; confirmed
    `mpltern` no longer enters sys.modules on import.

tcri/metrics/ removed. PR6 deleted its .py files but an empty directory holding
only a .DS_Store survived, so that Removal Ledger row was legitimately unticked.

REMOVAL LEDGER: ticked 9 rows after verifying each of the 20 symbols is genuinely
gone (grepped for a remaining `def` in tcri/). Those rows were stale bookkeeping
from PR6/PR7/PR9, not outstanding work -- except the metrics dir above. The
ledger now has zero real outstanding items.

Verified: `python -m build` produces sdist + wheel, `twine check` PASSES on both,
and the wheel's top level is exactly {tcri, dist-info} with the 7 expected
subpackages. Full suite 152 passed.

NOT included: the CI python-matrix fix (["3.10","3.11"] -> add "3.12", in both
tests.yml and release.yml). pyproject's classifiers already claim 3.12 support and
the dev venv is 3.12, so the package advertises a version CI never tests. The
push was rejected -- updating .github/workflows/ requires the GitHub `workflow`
OAuth scope, which this client does not have. Left for a maintainer push.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two release-blocking problems in tcri/preprocessing/_preprocessing.py, found
while profiling `import tcri` for pip-deploy readiness.

1. THE LIBRARY SILENCED EVERY WARNING IN THE USER'S SESSION.
   Module scope carried a blanket `warnings.filterwarnings('ignore')`, and this
   module is imported by `import tcri`. Verified: after `import tcri`, a plain
   `warnings.warn(...)` produced NOTHING. That means the guardrails added earlier
   in this branch -- the K clamp, the param-store-reuse notice, the
   batch_size >= n_obs warning -- could never reach a real user, along with any
   warning from numpy/pandas/scanpy or the user's own code. A library must not
   mutate global warning state it does not own. Removed; confirmed the K-clamp
   guardrail is now visible in a plain script.

2. `import tcri` TOOK 6.65 s, 85% OF IT IN THIS MODULE.
   -X importtime: tcri 5991 ms, of which tcri.preprocessing 5121 ms, of which
   umap 2955 ms (umap -> pynndescent -> numba/llvmlite). The module imported umap
   TWICE and never used it -- the ternary/UMAP code that needed it is gone, and
   the surviving opt-in path (to_anndata(compute_umap=True)) already imports umap
   locally.

   The whole import block was pre-refactor debris: numpy x5, pandas x4, torch x5,
   umap x2, plus scanpy/pyro/scvi/tqdm/scipy.entropy/softmax/Dirichlet/collections/
   datetime/Optional/REGISTRY_KEYS -- for a file that now contains exactly two
   functions using only numpy and _keys. Also dropped duplicate ANSI colour
   constants and unused _console imports.

   Measured: 6.65 s -> 3.81 s (1.75x), and umap no longer enters sys.modules.

Tests: two regression guards -- one asserts a warning is still VISIBLE after
`import tcri` (in a subprocess, since pytest resets filters and would mask this),
one asserts umap is not eagerly imported. Full suite 154 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the missing test tier. The existing 154 tests check STRUCTURE -- contracts,
identities, wiring -- which cannot catch an estimator that is well-formed but
numerically wrong. These check ACCURACY against a known truth.

tcri/datasets/simulate_tcri() implements the semi-synthetic generative story from
Supplementary Note 1 ("Generative Model for Semi-Synthetic Simulations"):
  pi ~ Dir, omega_c ~ Dir, z_i ~ Cat(pi), phi_i|z_i ~ Cat(omega[z_i]),
  x_i|phi_i ~ Poisson(U_i @ V) with U_i ~ Gamma(program of phi_i)
Because pi and omega are known, I(c;phi) is available in CLOSED FORM. Needs no
real dataset to fit (unlike the original sc_simulator), so it is importable,
seeded and fast.

Two oracles are reported, and the distinction drives honest assertions:
  true_*       population value implied by (pi, omega) -- the estimand
  empirical_*  value implied by the REALIZED counts -- what a perfect estimator
               returns on THIS sample
They differ by finite-sample noise plus the plug-in estimator's upward bias,
~(C-1)(P-1)/(2N ln2) bits. Both are given under BOTH normalizations, because tcri
defaults to normalize_mode="min" while the note's benchmark used the mean
denominator -- comparing across that difference silently inflates the estimate.

Fast tier (every commit, 10 tests):
  - oracle respects MI <= min(H_c, H_phi); both NMIs in [0,1]
  - omega_concentration monotonically controls the true MI (averaged over seeds)
  - fuzziness changes DIFFICULTY ONLY -- true MI identical to 1e-12
  - seed determinism; label noise lowers realized but not population MI
  - tl.mutual_information == an INDEPENDENT oracle implementation, raw and both
    normalizations (the strongest fast test: agreement is evidence, not tautology)
  - the two normalizations are provably not interchangeable
  - plug-in bias is upward at small N (documents why equality tests are invalid)
  - metamorphic: MI invariant to clone/phenotype relabeling and to uniform
    replication of counts

Slow tier (--runslow, 3 tests, 34 s total):
  - |empirical - true| falls with N, averaged over 6 seeds
  - a FITTED model's MI tracks the true MI across three difficulty levels
  - posterior HDI coverage

Measured while designing, not asserted blindly:
  - HDI coverage came out 8/8 for a nominal 94% interval (mean width 0.103), with
    posterior means close to truth (e.g. 0.2868 vs 0.2825). With 8 replicates that
    is evidence of no miscalibration, not proof of 94%; establishing the rate needs
    ~50+ reps and belongs in benchmarks/. The bar was tightened from >=2/5 (which
    would pass at 40% coverage) to >=6/8.
  - Single-seed convergence is FLAKY: the same seed gave gap +0.003 at N=5000 but
    +0.012 at N=20000. Convergence is therefore asserted on a mean over seeds.

conftest gains --runslow (canonical pytest recipe) so the per-commit suite stays
fast while the accuracy tier stays runnable nightly/pre-release.

Full suite 164 passed, 3 skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@nceglia
nceglia merged commit 419ce81 into refactor/pr6-9 Jul 30, 2026
nceglia added a commit that referenced this pull request Aug 3, 2026
fix(pkg): land #39 in main — warning hijack, import time, recovery harness
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant