Skip to content

Fix 6 correctness bugs found in code review - #247

Merged
iskandr merged 1 commit into
masterfrom
review-fixes-correctness
Jul 10, 2026
Merged

Fix 6 correctness bugs found in code review#247
iskandr merged 1 commit into
masterfrom
review-fixes-correctness

Conversation

@iskandr

@iskandr iskandr commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

First of two PRs from a full-package code review (4 parallel reviewers + verification). This one is the behavioral bug fixes; a follow-up handles the wrapper-consolidation refactor + LLMism/docstring cleanup.

Each fix has a regression test; A2 and A4 were also verified end-to-end.

# Bug Fix
A1 PRIME/MixMHC2pred predict_dataframe overrides drop n_flanks/c_flanksTypeError, though predict() accepts them. 100% redundant with the base. Delete both overrides (inherit BasePredictor.predict_dataframe); drop the now-unused COLUMNS import.
A2 New-model-only predictors (bigmhc, calis, deeptap, eramer, netchop, pepsickle) crash with AttributeError on the legacy --mhc-predictor X --sequence CLI (they have predict() but no predict_peptides). The help text even shows pepsickle. Clear error pointing to predict-table.
A3 predict-table stability column is always all-NaN — the token mapped to value, but NetMHCstabpan puts half-life in score (parse_netmhcstabpan sets no ic50). Map stabilityscore.
A4 Generic score/rank tokens silently return the wrong kind on multi-kind predictors: mhcflurry:col:score gave presentation, not affinity (last-wins with kind=None). Raise an "ambiguous field" error directing to a kind-specific token.
A5 NetCleave* exported + in __all__ but not in the CLI registry → unusable via predict-table (inconsistent with netchop/pepsickle). Register netcleave / netcleave-i / netcleave-ii.
A6 nettcr._suppress_native_stderr leaks an fd if os.open(devnull) fails (acquired before try). Acquire inside try, close-if-not-None in finally.

Verified end-to-end: mhcflurry:col:score now raises Ambiguous 'score' field: predictor emits multiple kinds (pMHC_affinity, pMHC_presentation)…; --mhc-predictor calis --sequence … now raises Calis does not support this command … Use \mhctools predict-table` instead.` Full public test subset: 492 passed.

Version 3.27.0 → 3.28.0.

https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG

A1. prime.py / mixmhc2pred.py: delete the redundant predict_dataframe
    overrides. They duplicated BasePredictor.predict_dataframe but with a
    narrower signature that dropped n_flanks/c_flanks, so
    predict_dataframe(peps, n_flanks=[...]) raised TypeError even though both
    predict() methods accept flanks. Inherit the base method instead (and drop
    the now-unused COLUMNS import).

A2. cli/script.py: give a clear error when a new-model-only predictor (bigmhc,
    calis, deeptap, eramer, netchop, pepsickle — predict() but no
    predict_peptides) is used on the legacy prediction CLI, pointing to the
    predict-table subcommand, instead of failing later with AttributeError.

A3. annotate.py: the predict-table `stability` token mapped to `value`, but
    NetMHCstabpan reports half-life in `score` (parse_netmhcstabpan sets no
    ic50), so the column was always all-NaN. Map it to `score`.

A4. annotate.py: a kind-agnostic token (score/percentile_rank/rank) matched
    every kind, so on a multi-kind predictor (e.g. MHCflurry, which emits
    affinity + presentation for the same peptide+allele) the (peptide, allele)
    key was silently overwritten last-one-wins — `mhcflurry:col:score` returned
    presentation, not affinity. Now raise an "ambiguous field" error directing
    the caller to a kind-specific token.

A5. cli/args.py: register the NetCleave family (netcleave / netcleave-i /
    netcleave-ii). They were exported and in __all__ but absent from the
    registry, so unusable via predict-table (inconsistent with netchop/pepsickle).

A6. nettcr.py: make _suppress_native_stderr leak-safe — acquire both fds inside
    the try and close-if-not-None in finally, so a failure at os.open (fd
    exhaustion, exactly when it matters) can't leak the already-dup'd fd 2.

Tests: regression tests for each (flank signature inherited; legacy-CLI guard;
stability reads score; ambiguous multi-kind token raises while kind-specific
works; NetCleave registered). Verified A2/A4 end-to-end against calis/mhcflurry.

Version 3.27.0 -> 3.28.0.

Claude-Session: https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG
@iskandr
iskandr merged commit 3ca940f into master Jul 10, 2026
5 checks passed
@iskandr
iskandr deleted the review-fixes-correctness branch July 10, 2026 13:28
iskandr added a commit that referenced this pull request Jul 10, 2026
… cleanup (#248)

Follow-up to the code-review correctness PR (#247). Structural + cosmetic:
net -129 lines in the wrapper files with no behavior change (except the BigMHC
fixes below).

Consolidation (new mhctools/wrapper_base.py):
- NewModelPredictorMixin holds the members that were copy-pasted across the
  standalone wrappers: predict_dataframe, supported_kinds, __repr__, and
  peptide normalization (single-string + strip/upper).
- AlleleFreePredictor adds the allele-independent kind_support shape.
- Calis / DeepTAP / ERAMER now subclass AlleleFreePredictor; BigMHC uses
  NewModelPredictorMixin. Each keeps only its constructor, _default_pred_kind,
  tool-specific validation (bounds/messages differ), and scoring.

BigMHC consistency fixes (were flagged in review):
- Build predictions from the *normalized* peptide, not the raw input, so
  "siinfekl " is no longer scored as SIINFEKL but returned verbatim.
- Rename _pred_kind -> _default_pred_kind to match every other wrapper (generic
  consumers can now call it uniformly).

Minor code:
- eramer: early-return on empty input (was loading the whole PWM first); drop
  the unreachable score-is-None branch; delete the dead _predictor_name.
- calis/deeptap: delete the dead _predictor_name.
- pred: add reduce_op(kind, field) and use it in PeptideResult.best_by and
  AnnotationSpec.direction_op (the max/min mapping was written twice).
- mixmhc2pred: resolve the %Rank_/Score_ column positions once, not per row.

Docs / LLMism cleanup:
- Trim the copy-pasted "Note on interpretation" caveats to one sentence each
  (calis/prime/eramer/deeptap); drop PRIME's benchmark editorializing and the
  marketing/hedging phrasing; remove **bold** from reST docstrings.
- Fix stale docstrings: calis position_weights ("leading slice"), eramer's
  class docstring (add $ERAMER_PWM to the resolution order).
- Trim the bloated best_direction docstring, filler class docstrings, an
  over-defensive annotate comment, and the verbose nettcr suppression docstring.

Tests: new tests/test_wrapper_base.py covers the shared base and asserts the
wrappers inherit it (and BigMHC standardizes on _default_pred_kind). Full public
subset: 496 passed.

Version 3.28.0 -> 3.29.0.

Claude-Session: https://claude.ai/code/session_01LZahFhBSCiehXTESCYQ7wG
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