Skip to content

feat: structure-verified annotation overhaul (metabolite + reaction cross-references, pH-7.3 structures)#1058

Merged
edkerk merged 16 commits into
developfrom
annotation-overhaul
Jul 14, 2026
Merged

feat: structure-verified annotation overhaul (metabolite + reaction cross-references, pH-7.3 structures)#1058
edkerk merged 16 commits into
developfrom
annotation-overhaul

Conversation

@edkerk

@edkerk edkerk commented Jul 13, 2026

Copy link
Copy Markdown
Member

Summary

A structure-first overhaul of metabolite and reaction annotations, consolidating #964, #967 and #968. Every identifier is verified against chemical structure via MetaNetX (MNXref 4.5): metabolites matched on a protonation-invariant InChIKey, reactions on a participant-structure signature.

Metabolites: SMILES/InChI moved into metabolites.tsv (with annotateGEM wiring and a CI consistency test); 1,025 structures regenerated to the pH-7.3 microspecies; 2,792 deprecated MetaNetX ids updated; 7,673 verified cross-references added; new metSeedID column (3,429 ModelSEED, #968); 61 wrong-compound corrections; 12 invalid charges fixed (balance 277→271).

Reactions (#967): 1,217 deprecated MetaNetX ids updated; 3,652 verified cross-references added (BiGG, MetaNetX, Rhea, KEGG).

Coverage BiGG ChEBI HMDB MetaNetX ModelSEED Rhea (rxn)
before → after 63→82% 43→59% 26→49% 75→89% 0→40% 6→12%

Deferred (documented, not here): broader charge/formula rebalancing (coupled to reaction proton accounting), stereochemistry-only and unresolvable cross-ref corrections, and the #626/#618 duplicate merges. Reproducible tooling is under code/annotation/; the plan is in data/annotation/OVERHAUL_PLAN.md.

@edkerk
edkerk force-pushed the annotation-overhaul branch from 0308e0d to 48b0e84 Compare July 13, 2026 21:11
edkerk added 14 commits July 14, 2026 07:28
Adds the overhaul plan (issues #964/#967/#968/#626/#618) and a first
report-only audit (code/annotation/identity.py) classifying every metabolite
by how its stored SMILES relates to the curated formula/charge:
41% ok, 33% wrong protonation state, 16% no structure, 9% generic R-group,
99 with the wrong molecule stored (real formula errors).
Structures now live with the other metabolite annotations:
- add metSmiles and metInChI columns to model/metabolites.tsv (populated from
  the #728 structure set)
- annotateGEM.m adds them to the model (model.metSmiles, and model.inchis via a
  remap of the metInChI column) so the release exports on main carry structure
  combined with the annotations
- formula and charge stay in the YAML

Add a CI QC test (code/test/structureConsistencyTest.py) that checks each
metabolite's YAML formula/charge against its tsv SMILES/InChI with RDKit and
writes qc_structure_consistency.csv; wired into qc-tests.yml and the report.
Baseline: 2882 metabolites inconsistent (2783 wrong protonation state, 99 wrong
molecule stored), to be resolved in the structure-curation phase.
…species

Enumerate protonation states of each mismatched structure (dimorphite-dl) and
keep the one whose element formula and net charge exactly match the curated
model formula/charge, preferring acid-oxygen deprotonation over alkoxides. Of
1190 distinct structures: 1025 resolved and applied (2467 metabolite rows),
48 ambiguous and 117 unresolved left for review (the unresolved are model-side
charge errors: a neutral charge on an anion formula, i.e. half-integer DBE).

Structure/formula/charge inconsistencies drop 2882 -> 415. identity.py now reads
structures from metabolites.tsv (the source of truth); the large derived audit
tables are gitignored.
crossref_verify.py computes each metabolite's InChIKey from its metSmiles and
matches it to MetaNetX chem_prop on the pragmatic key (InChIKey without the final
protonation char, so charged model species match neutral database entries). From
the matched MNXM it reads structure-verified identifiers for every namespace
(KEGG/ChEBI/BiGG/HMDB/LipidMaps/ModelSEED) via chem_xref and classifies each
existing cross-reference confirmed / wrong / unverified, and reports metabolites
that could gain a verified new id. Existing MetaNetX ids are checked by their own
structure so deprecated-but-valid ids are not falsely flagged.
95% of the model's MetaNetX IDs that fail structure verification are simply
deprecated (absent from the current MNXref 4.5 chem_prop). For each, replace the
old MNXM with the current one whose structure matches the metabolite (preferring
the MNXM whose full InChIKey equals the model's). Structure-based, so it does not
repeat the #964 error of blanking still-valid IDs. apply_crossref.py generates
the change from the MetaNetX hub.
Fill empty cross-reference cells with the best structure-verified identifier
(one per namespace, chosen by consensus across matched MNXM then format
preference), from the MetaNetX hub matched on the pragmatic InChIKey. Coverage:
HMDB +2036, BiGG +1652, ChEBI +1431, LipidMaps +934, KEGG +418, MetaNetX +1189.
Add-only: no existing value is overwritten. Unlike #964 these are full
skeleton+stereo InChIKey matches, not skeleton-only.
Add a metSeedID column to metabolites.tsv with 3429 structure-verified ModelSEED
compound IDs (seed.compound) matched via the MetaNetX hub, and normalize all rows
to a uniform width (two rows had ragged trailing fields). annotateGEM maps
metSeedID to the seed.compound namespace so it exports as a MIRIAM annotation and
model field. Grows ModelSEED coverage from structure, as one of the checked
databases rather than a bulk score-boosting import.
charge_fix.py identifies metabolites whose curated formula/charge is a physically
impossible radical (odd electron count) and, using the physiological protonation
state, classifies each as a charge error (formula right), a formula error (charge
right), or both. apply_charge.py can apply them. 70 such cases exist, but applying
them shifts mass/charge balance (they expose reactions that were only balanced by
the invalid data, and some target intentional radical species), so the correction
is coupled to reaction re-balancing and is deferred to the balancing phase rather
than this annotation-only release.
Apply the unambiguous charge corrections: metabolites whose formula is valid but
whose charge makes it a physically impossible radical (a carboxylate carried at
charge 0, e.g. 15(S)-HEPE C20H29O3 0->-1). Intentional radical species (octanoate
radical, semiquinones, monodehydroascorbate) are excluded from the closed-shell
test. Balance improves by 6 reactions (7 fixed, 1 exposed). The formula-side fixes
and the broader charge rebalancing (enforcing the pH-7.3 microspecies convention
model-wide) remain, as does the reaction proton accounting they cascade into.
reaction_verify.py reduces each reaction to a structural signature (participant
InChIKeys with coefficients, protons/water removed, direction-invariant) and
matches it to MetaNetX reac_prop; reac_xref then gives KEGG/BiGG/Rhea/MetaNetX
reaction ids. As with metabolites, most rxnMetaNetXID that fail verification are
deprecated MNXR; replace each with the current structure-matched one.
Fill empty reaction cross-reference cells with the best structure-verified id
(one per namespace) from the MetaNetX reaction hub: BiGG +1344, MetaNetX +1288,
Rhea +849 (master id of the quartet), KEGG +171. Add-only; full structural
signature matching, so far more and more reliable than the skeleton-based
estimates in #967.
Replace cross-reference IDs whose own structure (verified via the MetaNetX hub)
resolves to a DIFFERENT skeleton than the metabolite, i.e. the id points to the
wrong compound (ChEBI 26, LipidMaps 12, BiGG 11, HMDB 7, KEGG 5). Only skeleton-
level mismatches are corrected; stereochemistry-only differences (114) and ids
MetaNetX cannot resolve (265) are left untouched, since the model's own structure
often has less-defined stereo and overwriting would degrade the annotation.
…etaNetX ids

Cross-references were applied per compartment, so compartments whose stored
structures differ slightly received different ids (flagged by annotationTest).
Collapse each metabolite's cross-references to one value per column: metMetaNetXID
(rewritten wholesale by the drift update) is unified to the consensus; the other
columns are only fixed where this PR introduced the inconsistency, leaving
develop's pre-existing ones untouched. Also drop metMetaNetXID values that are not
of the form MNXM<digits> (water was assigned the named id WATER). No new malformed
or inconsistent cross-references remain vs develop.
The rebase (-X theirs) let this branch's structure-verified data win conflicts,
which reverted the cross-reference reconciliation and two formula fixes merged in
#1057. Restore #1057's values only where this branch had not changed them (a
three-way merge against develop's parent), keeping the structure-verified values
elsewhere, then re-harmonize across compartments. No new malformed or inconsistent
cross-references vs develop.
@edkerk
edkerk force-pushed the annotation-overhaul branch from c3d2190 to 998e872 Compare July 14, 2026 05:32
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

Model quality report

⚠️ 6 pre-existing finding(s), no regressions vs develop. Non-blocking.

Structural checks

Duplicate keys (model unloadable) and no growth block the merge; the other rows are non-blocking.

Check Result Δ vs develop
Duplicate !!omap keys 0 0
Reactions with no metabolites 0 0
Model / annotation-table inconsistencies 0 0
Growth (biomass producible) 125 0

Model QC reports

Check Result Δ vs develop
Metabolites missing formula 0 0
Metabolites missing charge 0 0
Reaction bound / GPR issues 0 0
Exact-duplicate reaction groups 0 0
Unused metabolites 0 0
Unused genes 0 0
Malformed cross-references 0 0
Cross-refs inconsistent across compartments 3 -6 ⚠️
MEMOTE score (%) 20.2 0

MACAW and mass/charge balance

Check Result Δ vs develop
Reactions flagged by MACAW dead-end test 2510 0 ⚠️
Reactions flagged as MACAW duplicates 377 0 ⚠️
Mass-imbalanced reactions 87 0 ⚠️
Charge-imbalanced reactions 234 -6 ⚠️
Structure vs formula/charge inconsistencies 397 new ⚠️

Gene essentiality (Hart 2015)

cellLine TP TN FP FN accuracy sensitivity specificity F1 MCC
DLD1 38 2158 60 280 0.8659 0.1195 0.9729 0.1827 0.1588
GBM 34 2137 64 300 0.8564 0.1018 0.9709 0.1574 0.1276
HCT116 47 2181 55 308 0.8599 0.1324 0.9754 0.2057 0.1906
HELA 32 2241 70 250 0.8766 0.1135 0.9697 0.1667 0.1332
RPE1 15 2179 83 258 0.8655 0.05495 0.9633 0.08086 0.02935
all 7 2379 95 112 0.9202 0.05882 0.9616 0.06335 0.02199

❌ = a count rose vs the target branch (regression) · ⚠️ = a pre-existing non-zero finding (non-blocking) · ⏳ = still running. Counts link to the CSV listing the exact entries.

Full workflow run · this comment is edited as results come in

@SysBioChalmers SysBioChalmers deleted a comment from github-actions Bot Jul 14, 2026
… verifier

The step-specific scripts used to run the overhaul (identity, protonate, charge_fix,
the apply_* and *_verify scripts, harmonize_xref) are removed. In their place,
code/annotation/verifyAnnotations.py checks metabolite and reaction cross-references
against chemical structure via the MetaNetX hub and classifies each as confirmed /
wrong / missing / drift. It runs on the whole model or on just the entities a pull
request changed (--mets / --rxns), so new annotation mistakes - e.g. from a newly
added reaction - are caught; --fix applies the safe corrections (add missing, update
deprecated MetaNetX ids) and leaves genuinely wrong ids for review. MetaNetX tables
are downloaded once to a cache. structureConsistencyTest.py is now self-contained.
@edkerk
edkerk force-pushed the annotation-overhaul branch from 13fb21a to 6bcdcac Compare July 14, 2026 06:03
@edkerk

edkerk commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

No structural model changes

@edkerk
edkerk merged commit 43308d7 into develop Jul 14, 2026
@edkerk
edkerk deleted the annotation-overhaul branch July 14, 2026 07:03
@edkerk edkerk mentioned this pull request Jul 15, 2026
3 tasks
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