Skip to content

Modification-pool detection for specs/039 (pools.csv, default-inert) - #100

Merged
adamjohnwright merged 7 commits into
mainfrom
feat/pools
Sep 28, 2026
Merged

adamjohnwright merged 7 commits into
mainfrom
feat/pools

Conversation

@adamjohnwright

Copy link
Copy Markdown
Contributor

Detects each protein's modification pool once, at generation time, for deltasignal specs/039 (DS_CYCLE_MODE=balance). DeltaSignal ignores the three new files by default. The rule was measured and not adopted (deltasignal specs/039 has the numbers), so this PR adds data, not behaviour.

Output: every pathway gets pools.csv, pool_transitions.csv and pool_carriers.csv, written even when empty.

Detection (per reference protein R, at uuid level):

  • R-steps: reactions with exactly one R-containing input and one R-containing output, stoichiometry 1.
  • Candidate pools: strongly connected sets of R-forms with 2 or more modification signatures. A signature is the residues on R's leaves plus the small molecules bound to R's leaf.
  • States: forms carrying only the core proteins (amendment 3). The other forms are intermediates, i.e. enzyme complexes.
  • Paths: state → intermediates → state, at most 6 steps. Each step lists its uuid copies of one curated reaction.
  • Base state: residues, then donor, then components; ties are counted.
  • Carriers: the enzyme's free form and the release steps that produce it. A node any pool manages is never a carrier.
  • Shared nodes are dropped from both pools and counted. The log also counts autocatalysis.

Catalog (build 20260928-0115_bdaed8e_pools039d): 40 pools in 18 pathways, 7 of them multi-step. The networks themselves are unchanged (same node and edge counts).

Tests: tests/test_pools.py has 15 tests. Full suite: 1086 passed; the one failure is the MHC ratio test, which already fails on main and is unrelated. ruff is clean.

Review: four adversarial end-to-end reviews before any arm, each against the real bundle. Every defect they found is fixed.

Detection was implemented mostly by a Fable 5.1 subagent, against the pre-registered contract in deltasignal specs/039.

🤖 Generated with Claude Code

adamjohnwright and others added 7 commits September 27, 2026 22:56
…sion pools found at NODE level (the loop A->F->B->R->A must exist, so uuid-separated copies are not re-merged); base = resting form (the modified side is the product of the donor-consuming direction: ATP/GTP/SAM/acetyl-CoA/NAD+/Ub; else more components; else more modified residues). Additive files; network unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- get_interconversion_pairs: each reaction's only non-small-molecule input is
  the source form and only output the other form (ubiquitin allowed as a
  co-substrate), and the two forms share a reference entity. Excludes enzyme
  binding cycles (E + S -> E:S), release, degradation and two-form reactions,
  which were 78 of the 102 shipped pools.
- find_pools: a reaction node converting two form pairs is dropped with its
  loops (its node would be written by two fluxes); counted in stats.
- orientation: residues first (as pre-registered), then donor, then components
  (specs/039 amendment 1); undecided pools counted as ties.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nt 2)

A consumer weighs an intrinsic (uncatalysed) step beside a catalysed one for
the same pair; the equal split made SOS1 KO read RAS:GTP 0.53 (Fable review).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t 2)

Detection now follows one reference entity R through every R-step (a reaction
whose exactly one input and exactly one output contain R, both at
stoichiometry 1, and differ) instead of one-reaction A <-> B pairs, so a cycle
curated as bind / modify / release (RAS:GTP + GAP -> RAS:GTP:GAP -> RAS:GDP +
GAP; S -> S:E -> S*:E -> S* -> S*:P -> S:P -> S) is one pool.

Neo4j: get_reaction_participants (inputs, outputs, catalysts per reaction),
get_form_profiles (proteins, modification signature per R = residues on R's
leaves + small molecules of the complex R's leaf sits in through sets, slots,
components, residues; cached across pathways), get_set_descendants. r_steps
computes the steps and whether each is enzyme-driven (a catalyst or joining
input containing a protein and not R; a catalyst that is a form of R is
self-catalysis, non-enzyme). get_interconversion_pairs and
get_form_modification_counts are replaced.

Node level (find_pools): the R-graph is built from reaction nodes whose
R-containing input and output nodes map to the step's forms; per R, a
strongly connected set with >= 2 forms and >= 2 signatures is a pool (one
signature is a carrier loop); states are the fewest-slot forms per signature,
the rest intermediates; transitions are simple paths state -> intermediates*
-> another state of <= 6 steps, longer dropped and counted; base state by
residues, donor, components (ties counted); pools identical for co-travelling
proteins are one pool, a node claimed by two different pools is removed from
both and everything recomputed; carriers are the enzyme free-form nodes with
the release reaction nodes that produce them. A pool with more than 20,000
uuid paths (the copies per step multiply through a shared intermediate) is
dropped and counted.

Files: pools.csv (pool_id,node_uuid,stable_id,role,is_base),
pool_transitions.csv (pool_id,path_id,step,source_uuid,target_uuid,
reaction_uuid,reaction_stid,enzyme_driven — the step's flag; a path is
enzyme-driven if any step is), pool_carriers.csv (pool_id,carrier_uuid,
release_reaction_uuid); all written even when empty. The log reports pools,
states, intermediates, paths, multi-step pools, dropped long paths, shared
nodes, ties, carriers, carrier loops, merged proteins and autocatalysis by
source form / product form / other form.

tests/test_pools.py: 12 tests (RAS shape with GEF, self-catalysed hydrolysis
and GAP bind-release; six-form ring; an enzyme's own loop is not a pool;
shared-node drop; co-travelling merge; path cap; orientation and ties; set
member mapping; stoichiometry).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ontract)

A path is now its NODE sequence plus the reaction stId at each step. The
reaction nodes of that stId between the step's two nodes (variant and set
copies) are the step's parallel copies, one row each in
pool_transitions.csv: rows sharing (pool_id, path_id, step) have the same
source_uuid, target_uuid, reaction_stid and enzyme_driven and differ in
reaction_uuid. A consumer reads a step as the mean of its copies and a path
as the product of its steps, which equals the sum over the uuid paths the
previous commit enumerated (RAF: 48 bind copies x 48 release copies were
2,304 two-step paths; now one path of 96 rows). Different reactions between
the same two nodes stay different paths (GEF exchange beside intrinsic
exchange), because each is weighed on its own as enzyme-driven or not.
POOL_PATH_BUDGET is kept as a guard on the path count; nothing hits it now
(before: HRR and EGFR pool1 did). The log adds the step-copy row count.

Interpretations of the pre-registered rule, accepted by the deltasignal side
and recorded here:
- "a catalyst or joining input containing a protein other than R" is read as
  containing a protein and NOT containing R, so the set p21 RAS:GTP catalysing
  intrinsic hydrolysis is self-catalysis (non-enzyme) for each RAS member;
- "the complex whose direct children include R's leaf" looks through set
  membership (R97 puts RAS in a set inside p21 RAS:GTP); GAPs in
  RAS:GTP:GAP get an empty signature, as the derivation checked;
- enzyme_driven is written per step; a path is enzyme-driven if any step is;
- co-travelling reference entities with identical pools (paralogs of one
  set) are one pool, counted as merged; only DIFFERENT pools sharing a node
  trigger the shared-node drop;
- states and intermediates on no kept path are omitted and counted; a
  candidate whose kept states have fewer than two signatures is dropped;
- a variant node's proteins are its parent's non-set proteins plus its
  chosen members'; its signature is the parent's;
- the base rule's residues are summed over all leaves (amendment 1 as
  written), not R's leaves only.

tests/test_pools.py: 13 tests; the new one asserts that two reaction nodes of
one stId collapse into one path with one row per copy while an intrinsic
exchange beside the GEF stays a separate path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…g paths counted whole

Pre-arm review of specs/039 amendment 2 found two generator defects.

1. States from annotation-dependent signatures. Activated FGFR4 <->
   FGFR4:PLCG1 <-> FGFR4:p-4Y-PLCG1 (R-HSA-5654743) was a three-state pool of
   FGFR4, and EGF:p-EGFR:p-ERBB2 / :PLCG1 / p-ERBB2 heterodimers:PTK6
   (R-HSA-1227986) three states of ERBB2, because a bound partner's
   phosphorylation is a signature change for the partner and the receptor's
   forms differed only by what was bound. Rule (amendment 3): the CORE of a
   strongly connected set is the proteins every form of it carries; a form is
   eligible to be a state only if its proteins are exactly the core (small
   molecules allowed); states are per signature among those; the set is a pool
   only if its core-only forms carry >= 2 signatures; every other form is an
   intermediate. An enzyme's own loop has one core-only form, so it is not a
   pool, as before. On the pools039b build: 5654743 dropped; 1227986 now has
   two states (p-6Y-EGFR:p-6Y,Y1112-ERBB2 base, p-7Y,Y1112-ERBB2) with the
   PLCG1 and PTK6 complexes as intermediates; the other 39 pools unchanged.

2. A carrier that is a node of another pool (R-HSA-74752: SHC1's carrier
   Insulin:p-6Y-IR is the receptor pool's intermediate) is no longer a
   carrier: nodes any pool manages (states, intermediates, step reaction
   nodes) are excluded from every carrier list and counted
   (carrier_conflicts; 1 on the build).

3. long_paths_dropped counts dropped paths (state -> ... -> state over 6
   steps), not truncated prefixes.

tests/test_pools.py: 15 tests. The shared-node fixture is now two genuine
pools (X and Y both phosphorylated inside X:Y); new tests cover the FGFR4
shape (not a pool) against the receptor-phosphorylated shape (a pool), the
carrier conflict, and whole-path counting.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…reused loop variables)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@adamjohnwright
adamjohnwright merged commit 14c4a57 into main Sep 28, 2026
4 checks passed
@adamjohnwright
adamjohnwright deleted the feat/pools branch September 28, 2026 11:44
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