feat: add ΔCharge (pH 7.4 → 6.0) metric + R9 instability-floor info banner - #5
Merged
Merged
Conversation
Captures pH-switching capacity (FcRn recycling, endosomal release). Negative values mean the molecule gains positive charge on acidification — the productive direction for histidine-driven pH switching. Software: charge_shift / fv_charge_shift in properties.py; chargeShift_peptide / chargeShift_<chain>_CDR3 / chargeShift_Fv in pipeline.py TSV emission. VH/VL chain-level ΔCharge omitted per spec — framework histidines dilute the binding-relevant signal. Workflow: pl7.app/chargeShift PColumn at each scope. Domain carries pl7.app/pH/from and pl7.app/pH/to so additional pH pairs land later without breaking v1 column identity. Default-visible alongside the static charge column at each scope; isScore omitted (interpretive, not a Lead Selection ranking criterion). Description per spec R6b lifted to a CHARGE_SHIFT_DESC constant. Tests: TestChargeShift / TestFvChargeShift behavioral tests in test_properties.py (identity, dose response, NA, anti-symmetry, Cys policy, additivity). M3 validation in test_m3_validation.py — 7 CDR-H3 sequences vs textbook HH; 2 paired Fv chains for the additive identity. CID quantization prefix expanded to chargeShift_*. Corpus manifest expectations extended for all peptide / antibody / TCR cases; TCR's expected_columns_absent extended with chargeShift_Fv.
- R9 — peptide mode emits an info banner when any peptide falls below the Instability Index length floor (INSTABILITY_MIN_LENGTH). Added hasPeptideBelowInstabilityFloor to run_peptide stats; banner via messages.peptidesShortInstability(). Mirrors the per-row gate inside instability_index so the banner gates on the same condition that produces NA cells. - ΔCharge tooltip — tightened from spec R6b verbatim text (~110 words → ~70). Substance preserved: pH endpoints, sign convention, His dominance, magnitude → switching-capacity rule, scope exclusion. Lead-with-the-rule, parallel small/large structure, dropped filler. - Test polish — ids= on TestFvChargeShift parametrize so test output shows [natural]/[his_rich] instead of full VH-VL string concat.
There was a problem hiding this comment.
Code Review
This pull request introduces the ΔCharge metric (pl7.app/chargeShift), which measures the net charge change between pH 7.4 and 6.0 across peptide, CDR3, and Fv scopes to capture pH-switching capacity. It also implements a mechanism to flag and notify users when peptides are too short for the Instability Index calculation. Feedback identifies a logic error where empty sequences incorrectly trigger the instability warning banner and suggests an optimization for the charge_shift function to avoid redundant sequence processing and object instantiation.
…e_shift shares IP instance PR #5 review feedback. pipeline.py — `has_below_floor` previously fired on empty cells and crashed on Polars nulls (effective_length called .upper() on None). Filter `if s` to skip falsy values and `0 < effective_length(s)` to skip all-non-standard sequences that clean to empty. Banner now fires only on real peptides shorter than INSTABILITY_MIN_LENGTH, matching the banner text. properties.py — charge_shift now prepares the sequence once and shares a single IsoelectricPoint instance across the two pH points. Removes redundant cleanup pass and BioPython instantiation. Same pattern is already used in isoelectric_point's bisection loop. TestChargeShift.test_matches_two_point_subtraction guarantees the result still equals charge(ph_from) − charge(ph_to). Tests — reworked test_instability_floor_flag_tracks_na_rows parametrize cases. Added empty-and-null-skip and all-nonstandard-skip to lock in regression coverage for both bug paths.
PaulNewling
marked this pull request as ready for review
May 5, 2026 17:23
Builds one ProteinAnalysis and one IsoelectricPoint per (sequence, pka_set, include_cys) instead of one per property. Pipeline reuses the full-chain context for the Fv pass so VH/VL IsoelectricPoint(IPC2_PROTEIN, include_cys=False) is shared between charge_at_pH(7.0) and the pI bisection. DataFrames build columnarly (dict-of-lists) rather than list-of-dicts. Output is byte-identical to pre-refactor on the corpus tests (verified by sha256 across peptide / antibody / tcr modes for properties.tsv, aa_fraction.tsv, and stats.json). ~1.7x faster on per-property micro-bench; end-to-end ~40k peptides/s and ~10k antibody-clones/s with full-chain + Fv. Adds chain-mode byte-stability tests (two-run + row-permutation invariance) for the antibody path that the existing peptide tests don't cover.
Both changes ship together in this branch — keep one changeset for the combined release notes and to consolidate the version bump.
Signed-off-by: Paul Newling <paulnewling@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ΔCharge metric (pH 7.4 → 6.0)
New
pl7.app/chargeShiftPColumn at three scopes — peptide, CDR3 (per chain), and Fv. Captures pH-switching capacity (FcRn recycling, endosomal release): negative values mean the molecule gains positive charge on acidification, the productive direction for histidine-driven switching. Histidine dominates the metric (~−0.46 per His; pKa ~6.0 sits in the window).Domain carries the pH endpoints (
pl7.app/pH/from: "7.4",pl7.app/pH/to: "6.0") so additional pH pairs (tumor microenvironment ~6.5, lysosomal ~5.0) can land later without breaking the v1 column identity. Default-visible alongside the static charge column at each scope; not markedisScore(interpretive metric, not a Lead Selection ranking criterion). VH/VL chain-level ΔCharge is out of scope per spec — framework histidines dilute the binding-relevant signal.Test coverage
test_properties.py::TestChargeShift— 6 behavior-focused tests: identity vs two-point subtraction (5 sequences), no-titrator-in-window magnitude bound, His dose-response, NA propagation, anti-symmetry of pH endpoints, Cys-include vs exclude.test_properties.py::TestFvChargeShift— additivity across paired chains, NA propagation.test_m3_validation.py::TestChargeShiftManualHH— 7 CDR-H3 sequences vs an independent pure-Python HH reference (catches BioPython formula drift).test_m3_validation.py::TestFvChargeShiftAdditiveIdentity— 2 paired chains, additivity + manual two-point cross-check.software/tests/data/corpus/manifest.json— chargeShift expectations on every existing peptide / antibody / TCR fixture; TCR'sexpected_columns_absentextended withchargeShift_Fv.200 python tests pass; 99% src/ coverage.
R9 instability-floor info banner
Peptide mode emits a block-level info message whenever any peptide falls below the Instability Index length floor. Mirrors the per-row gate inside
instability_indexso the banner gates on the same condition that produces NA cells. Newmessages.peptidesShortInstability()helper; newhasPeptideBelowInstabilityFloorflag inrun_peptidestats; new banner branch inprocess.tpl.tengo.Tooltip polish
ΔCharge description tightened from spec R6b verbatim text (~110 words → ~70). Lead-with-the-rule, parallel small/large structure, dropped filler. Substance unchanged.