Skip to content

Human 2.0.1#1061

Open
edkerk wants to merge 45 commits into
mainfrom
develop
Open

Human 2.0.1#1061
edkerk wants to merge 45 commits into
mainfrom
develop

Conversation

@edkerk

@edkerk edkerk commented Jul 15, 2026

Copy link
Copy Markdown
Member

Main improvements in this PR:

Short summary:

This release contains three sets of changes:

Longer details

I hereby confirm that I have:

  • Any removed reactions and metabolites have been moved to the corresponding deprecated identifier lists in data/deprecatedIdentifiers/.
  • This PR has develop as target branch, and will be resolved with a squash-merge.
  • This PR has main as target branch, and will be resolved with a merge commit.

JHL-452b and others added 30 commits March 20, 2026 23:15
…nd Beta oxidation (#929)

* Fix: GPR curation for Linoleate_metabolism and Fatty_acid_elongation_and_Beta_oxidation

* chore: add macaw test result

* Fix: remove MAM01622x

* chore: add macaw test result

* chore: add gene essentiality test result

* chore: add macaw test result

* chore: add macaw test result

* chore: add gene essentiality test result

* chore: tsv style

* fix: remove ENSG00000160870 from MAR0245x

---------

Co-authored-by: JHL-452b <JHL-452b@users.noreply.github.com>
Co-authored-by: Eduard Kerkhoven <eduardk@chalmers.se>
Co-authored-by: edkerk <edkerk@users.noreply.github.com>
* feat: clearer instructions for location of test results

* chore: add macaw test result

---------

Co-authored-by: mihai-sysbio <mihai-sysbio@users.noreply.github.com>
Co-authored-by: Eduard Kerkhoven <eduardk@chalmers.se>
* chore: bump memote-docker

* chore: add macaw test result

* chore: add macaw test result

---------

Co-authored-by: mihai-sysbio <mihai-sysbio@users.noreply.github.com>
Co-authored-by: Eduard Kerkhoven <eduardk@chalmers.se>
Co-authored-by: edkerk <edkerk@users.noreply.github.com>
…mide metabolism (#934)

* Fix: GPR curation for Acyl-CoA hydrolysis and Nicotinate and nicotinamide metabolism

* chore: add macaw test result

* chore: add macaw test result

* chore: add macaw test result

* chore: add gene essentiality test result

* fix: curation the GPR of MAR03009

* chore: tsv style

* chore: add macaw test result

---------

Co-authored-by: JHL-452b <JHL-452b@users.noreply.github.com>
Co-authored-by: Eduard Kerkhoven <eduardk@chalmers.se>
Co-authored-by: edkerk <edkerk@users.noreply.github.com>
* fix: remove peroxisomal genes from mitochondrial acyl-CoA dehydrogenase reactions

* chore: add macaw test result

* fix: adds ACAD11 to GPRs of MAR03107 and MAR03111 to ensure it is still associated with reactions

* chore: add gene essentiality test result

* fix: removed ACADM from GPRs of MAR03107 and MAR03111

* fix: undo addition of ACAD8 to GPRs of MAR03212 and MAR03163

* fix: add ACAD8 to GPRs of MAR03752 and MAR03784 to ensure it is still associated with at least one reaction

* fix: used wrong Ensembl ID for ACAD8 in GPRs of MAR03275 and MAR03284 in previous commit

* chore: tsv style

* chore: add macaw test result

---------

Co-authored-by: Devlin-Moyer <Devlin-Moyer@users.noreply.github.com>
Co-authored-by: Eduard Kerkhoven <eduardk@chalmers.se>
Co-authored-by: edkerk <edkerk@users.noreply.github.com>
* fix: remove SLC15A19 from GPR of MAR05047

* fix: removed MAR04933 for being less accurate duplicate of MAR05992

* fix: removed MAR06332-MAR06343 for misrepresenting transport of (d)NTPs into mitochondria

* fix: removed MAR07804-MAR07854 for misrepresenting transport of (d)NTPs into mitochondria

* chore: add macaw test result

* chore: tsv style

* chore: add macaw test result

---------

Co-authored-by: Devlin-Moyer <Devlin-Moyer@users.noreply.github.com>
Co-authored-by: Eduard Kerkhoven <eduardk@chalmers.se>
Co-authored-by: edkerk <edkerk@users.noreply.github.com>
* fix: GPRs and references for MAR01801 and MAR01808

* fix: replace ubiquinol with NADPH and update GPR accordingly for MAR03047

* fix: replace ubiquinol with NADH and existing GPR with Complex I subunits in MAR06500

* feat: added MAR20189 to represet reduction of alpha-tocopheryl quinone by Complex III

* feat: added MAR20190 to represet reduction of alpha-tocopheryl quinone with NADH in cytosol

* chore: add macaw test result

* fix: removed cytochrome b5 reductases from GPRs and PMID:16569397 from references of MAR01808 and MAR20190 and removed inaccurate names for MAR01801 and MAR01808

* chore: add gene essentiality test result

* chore: tsv style

* chore: add macaw test result

* chore: add macaw test result

---------

Co-authored-by: Devlin-Moyer <Devlin-Moyer@users.noreply.github.com>
Co-authored-by: Eduard Kerkhoven <eduardk@chalmers.se>
Co-authored-by: edkerk <edkerk@users.noreply.github.com>
* Fix: Revise GPR of MAR06980

* chore: add macaw test result

---------

Co-authored-by: JHL-452b <JHL-452b@users.noreply.github.com>
Co-authored-by: Eduard Kerkhoven <eduardk@chalmers.se>
* Feat: add MAR20189

* chore: add macaw test result

---------

Co-authored-by: JHL-452b <JHL-452b@users.noreply.github.com>
Co-authored-by: Eduard Kerkhoven <eduardk@chalmers.se>
* docs: update citation for Human2 in README.md

* chore: add macaw test result

---------

Co-authored-by: mihai-sysbio <mihai-sysbio@users.noreply.github.com>
* refactor: macaw message template

* chore: add macaw test result

* fix: revert to hard-coded url

* chore: update Actions dependencies for all workflows (#1026)

* chore: update Actions dependencies for all workflows

* chore: add macaw test result

---------

Co-authored-by: mihai-sysbio <23480589+mihai-sysbio@users.noreply.github.com>

* refactor: build action URL from context variables instead of hard-coding repo path

* chore: add macaw test result

---------

Co-authored-by: mihai-sysbio <mihai-sysbio@users.noreply.github.com>
Co-authored-by: Eduard Kerkhoven <eduardk@chalmers.se>
Co-authored-by: mihai-sysbio <23480589+mihai-sysbio@users.noreply.github.com>
Co-authored-by: edkerk <7326655+edkerk@users.noreply.github.com>
* fix: correct invalid species cross-references (#1023)

Move mis-filed identifiers to their correct annotation field so no
cross-reference is lost, and assign the correct KEGG identifiers.org
namespace on export.

metabolites.tsv:
- move 10 KEGG glycan IDs (G-numbers) from the ChEBI field to the KEGG
  field (each metabolite already has its KEGG compound ID)
- move CE2416, an EHMN ID, from the KEGG field to the EHMN field
- set the water[i] metabolite (MAM02040i) MetaNetX ID to MNXM2, matching
  the other eight compartments
- clear the placeholder MetaNetX ID "BIOMASS" on the biomass metabolites
  (MAM03970, MAM03971), which have no MetaNetX ID

reactions.tsv:
- move HMR_3422, an HMR2 ID, from the MetaNetX field to the HMR2 field of
  MAR05383 (MAR03422 no longer exists and is listed among its retired IDs)

annotateGEM.m:
- assign kegg.drug to KEGG drug IDs (Dxxxxx) and kegg.glycan to KEGG
  glycan IDs (Gxxxxx) instead of kegg.compound when building metabolite
  MIRIAMs

Exported model files (.xml, .xlsx, ...) are regenerated from these
sources at release.

* chore: add macaw test result
…1020) (#1031)

From Human-GEM v2.0.0 the reactions.tsv is written without quoted fields.
importTsvFile interprets a file with no quotes as all-text, so
rxns_tsv.spontaneous is returned as a cell array of char rather than
numeric, and "spont == 1" then errors with "Operator '==' is not
supported for operands of type cell". In v1.x reactions.tsv was quoted,
so the same column was read as numeric.

Coerce the spontaneous column to numeric when it is returned as text, so
the function works regardless of whether the tsv is quoted.
…1003) (#1032)

MAR20070 had its subsystem set to "Pentose Phosphate Pathway" (title
case) when it was added in #551, while the other 25 reactions of that
pathway use "Pentose phosphate pathway". This made the pentose phosphate
pathway appear as two subsystems, one containing only MAR20070. Correct
MAR20070 to "Pentose phosphate pathway" so the two merge into one.
… compound (#766) (#1034)

MAM01585 is cis-vaccenic acid ((11Z)-octadecenoic acid), but three of its
cross-references pointed to the trans compound, vaccenic acid ((11E)):
- metKEGGID: C08367 (vaccenic acid) -> C21944 (cis-vaccenic acid)
- metMetaNetXID: MNXM92713 (trans-vaccenic acid) -> MNXM1372019 (cis)
- metHMDBID: HMDB0003231 (Vaccenic acid) -> HMDB0240219 (cis-Vaccenic acid)

The ChEBI (CHEBI:50464), PubChem (5282761) and LipidMaps (LMFA01030076)
references were already correct. Metabolic Atlas renders the structure
from a SMILES mapped via these cross-references, so the trans MetaNetX ID
is why the trans structure was displayed for cis-vaccenic acid (#766).
Applied to all four compartments (c, e, l, r).
The metabolite IDs and formulas requested in #711 are already consistent
on develop; this completes the annotation consistency.

- KEGG IDs were misfiled in the BiGG column: move C00061 (FMN,
  MAM01828r/i) and C01847 (FMNH2, MAM20019c/i/r) from metBiGGID to
  metKEGGID, and set metBiGGID to fmn / fmnh2 (matching FAD/FADH2).
- Harmonise the FMN[r]/[i] external cross-references to the generic forms
  already used by FMN[c]/[x]/[e] and the analogous FAD: ChEBI CHEBI:17621
  (was the charge-mismatched FMN(3-) CHEBI:58210), MetaNetX MNXM119 (was
  the non-canonical MNXM1105928), plus the missing HMDB0001520 and
  PubChem 643976. All five FMN compartments now share identical chemical
  cross-references.

Rationale: no protonation-state convention is documented, but the model's
de-facto convention is generic/canonical cross-references (MetaNetX uses
canonical IDs for 5454 metabolites vs 43 specific; FAD uses neutral
CHEBI:16238). The charged FMN ChEBI (58210) is the -3 trianion and does
not match the model's -2 FMN.
* feat: add mass and charge balance report (#704)

Add code/test/balanceTest.py, which uses cobrapy's check_mass_balance() to
report reactions whose elemental (mass) or charge sums do not balance,
excluding boundary and biomass reactions. The unbalanced reactions are
written to data/testResults/balance_results.csv, run as a step in the
existing MACAW workflow so the results are auto-committed and a pull
request that introduces a new imbalance is visible in the diff.

This is a report, not a hard gate: the model currently has about 220
charge-unbalanced non-boundary reactions (and some mass-unbalanced ones),
so failing the build on any imbalance would need those resolved first.
Documented in data/testResults/README.md.

* chore: add macaw test result

* Rename macaw workflow to QC and report macaw + balance in one comment

The workflow already runs both the MACAW tests and the mass/charge balance
report, so rename it to cover both:

- macaw-tests.yml -> qc-tests.yml (job and display name 'Run QC tests').
- macaw_summary.md -> qc_summary.md, now a combined summary with a MACAW section
  and a mass/charge balance section.
- commentMacaw.md -> commentQC.md; the PR comment shows both summaries, passed
  through as a multiline Markdown output so the sections render.
- Capture the balance report's stdout (it was previously discarded) and add
  [skip ci] to the results commit.

The detailed per-test outputs stay in macaw_results.csv and balance_results.csv.

* Trigger QC workflow on the renamed test setup
… (#1045)

Adds a CITATION.cff so GitHub's "Cite this repository" feature works, as
requested in #303. The preferred citation points to the published Human2
article (Luo et al., Proc. Natl. Acad. Sci. 123, e2516511123, 2026;
doi:10.1073/pnas.2516511123). The README citation on develop already
references this article.
* fix: update ferrochelatase (FECH) EC number to 4.98.1.1 (#366)

EC 4.99.1.1 is a transferred entry pointing to EC 4.98.1.1
(protoporphyrin ferrochelatase), following the IUBMB creation of
sub-class 4.98 for enzymes forming carbon-metal bonds. Update MAR01044
to the current EC 4.98.1.1. The old code is a deleted entry, so it is
replaced rather than kept, to avoid a dead lookup for EC-based tools.

* chore: add QC test results [skip ci]
…e types) (#1049)

* feat: add contributors on develop via a comment-triggered workflow (#414)

The all-contributors GitHub App can only open pull requests against the
default branch (main); see all-contributors/app#243. Additions on main
are overwritten when main is rebuilt from develop at release, so credited
contributors were lost.

Replace the App with a self-hosted workflow that reacts to the same
"@all-contributors please add @user for <types>" comment, runs the
all-contributors CLI, and opens the pull request against develop instead.
Update CONTRIBUTING.md to describe the new flow.

* chore: add QC test results [skip ci]

* docs: add crediting-contributors guide, PR reminder, drop Gitter (#414)

Add a "Crediting contributors" section to CONTRIBUTING.md that explains the
comment-triggered workflow, and a maintainer reminder in the pull request
template so new contributors get credited after merge.

Remove the retired Gitter references from the README badge, CONTRIBUTING,
and .standard-GEM.md.

* docs: list the most common contribution types in CONTRIBUTING

* docs: refresh contributor list and consolidate to six badge types

- Add 12 contributors active since Nov 2023 and restore Jiahao Luo
  (previously README-only) to .all-contributorsrc; credit recent work by
  edkerk, Devlin-Moyer and johan-gson.
- Consolidate the 16 contribution types in use to six (bug, research,
  code, review, doc, ideas), combining overlapping credits.
- Show 10 contributors per row at a smaller image size (80 -> 64 px); the
  regenerated grid also repairs 105 corrupted emoji and drops a duplicate
  Christoff1993 cell.
- Document the six types in CONTRIBUTING.md.

* docs: update contributor credits
* feat: add names to 3397 reactions (#181)

Fill in descriptive names for reactions that had none, a long-standing
request in #181. Names come from three sources:

- Transport reactions (2457): generated from the transported metabolite
  and its compartments, e.g. "transport of GSH (cytosol to mitochondria)".
- Single-gene reactions (934): named from the gene's enzyme name plus the
  primary substrate, e.g. "cytochrome P450 (ethanol)".
- KEGG reaction names (6): remaining reactions whose KEGG entry has a
  systematic name.

Only reactions that had no name were touched and the change is confined to
the name field, so model structure and balance are unchanged. This leaves
2263 reactions still unnamed (mostly metabolic reactions without a KEGG
name or with several genes), for a later pass.

* feat: name a further 1710 reactions via EC and gene consensus (#181)

Extend the reaction naming to reactions with an EC code or several genes:

- EC-based (1526): the ExPASy/IUBMB enzyme name for the reaction's EC,
  plus the primary substrate, e.g. "carboxylesterase (substrate)".
  Transferred EC numbers are resolved to the current entry first.
- Gene consensus (184): where a reaction's genes share one enzyme name,
  unanimously or by clear majority, that name plus the substrate, e.g.
  "sulfotransferase (PAPS)".

Reaction name coverage is now 96% (12324 of 12877). The change is confined
to the name field. The ~553 still unnamed have no EC, no gene agreement and
no usable KEGG name.

* chore: add QC test results [skip ci]

* feat: name the remaining 553 reactions from reaction chemistry (#181)

Name the reactions that have no EC, no gene agreement and no KEGG name by
reconstructing the transformation from the reaction stoichiometry, in the
style "<substrate> <transformation>":

- cofactor signature (262): e.g. "fructose phosphorylation" (ATP -> ADP),
  "propane-1,2-diol oxidation" (NAD+), "retinoate activation" (ATP ->
  AMP + PPi + CoA), "5-hydroxyisourate decarboxylation" (CO2 released),
  "3,3-diiodo-L-thyronine sulfation" (PAPS).
- pool reactions (39): "<pool> formation".
- remaining conversions (252): "<substrate> to <product> conversion".

Reaction name coverage is now 100% (12877 of 12877). Names are derived
from the reaction's own chemistry rather than an external database, so a
few edge cases are approximate. The change is confined to the name field.

* style: clean up reaction name characters and shorten long names (#181)

Follow-up cleanup of the reaction names:

- Normalise problematic characters: collapse double/triple hyphens from
  IUBMB-style names (e.g. "glutamic--pyruvic" -> "glutamic-pyruvic",
  "NADPH---hemoprotein" -> "NADPH-hemoprotein"), remove the stray space
  left by ExPASy line-wrapping ("4-beta-N- acetyl..." -> "4-beta-N-acetyl..."
  while keeping genuine elisions like "formate- and CO-forming"), replace
  semicolons with commas, collapse double spaces, and trim trailing
  punctuation and leading/trailing whitespace.
- Shorten 117 over-long names (>100 characters) by dropping the appended
  substrate where the enzyme name alone is descriptive, e.g.
  "mannosyl-oligosaccharide 1,2-alpha-mannosidase (long glycan)" ->
  "mannosyl-oligosaccharide 1,2-alpha-mannosidase".

Change is confined to the name field. The names that are still long are
driven by long metabolite names (transport of large N-glycans, conversions
of complex lipids) or are pre-existing systematic KEGG names, and would
need metabolite-name abbreviation to shorten further.

* style: abbreviate long glycan names with their KEGG glycan id (#181)

Where a reaction name is long because of a long glycan metabolite name and
that metabolite has a KEGG glycan identifier (Gxxxxx), use the identifier
as the abbreviation, e.g. "transport of fucacgalfucgalacglcgalgluside
heparan sulfate (cytosol to Golgi apparatus)" -> "transport of G00043
(cytosol to Golgi apparatus)".

Only whole metabolite names are replaced (boundary-checked), so a glycan
name that is a substring of a larger metabolite is not touched. Most of the
longest glycan names (e.g. the N-glycan transport reactions) have no KEGG
glycan id in the model and are unchanged; adding those identifiers is a
metabolite-annotation task.

* style: shorten the longest reaction names (#181)

Aggressively shorten reaction names over ~80 characters so they roughly
convey the reaction rather than fully specifying it: names represent
meaning, they do not need to match a systematic string. 730 names
shortened; the longest drops from 324 to 90 characters (median 45).

For each long name a short one is generated in priority order:
- gene or EC enzyme name (plus the primary substrate when short), so the
  324-character "dolichyl ... glucosyltransferase" systematic names become
  "ALG6/ALG8 alpha-1,3-glucosyltransferase", and cytochrome P450 systematic
  names become "cytochrome P450 (substrate)";
- transport reactions become "transport of <metabolite> (<c1> to <c2>)"
  and exchanges "Exchange of <metabolite>", taking the primary metabolite
  name and dropping synonyms and lipid-detail parentheticals;
- otherwise a chemistry-derived name ("<substrate> <transformation>" or
  "<substrate> to <product> conversion").

Long metabolite names are abbreviated to their KEGG glycan id where present,
otherwise truncated. Change is confined to the name field.
* feat: fill in missing EC numbers for 236 reactions (#416)

Curate EC numbers for metabolic reactions that have a GPR but no EC,
keeping to the "fill only the empty fields" approach agreed in the issue.

EC numbers were assigned from three sources, preferring reaction-level
evidence and requiring gene corroboration:
- KEGG reaction -> EC (via rxnKEGGID), which ties the EC to the reaction
  chemistry;
- gene -> EC from UniProt and KEGG (via the geneUniProtID / geneEntrezID
  already in genes.tsv), used when the gene set gives one unambiguous EC;
- MetaNetX reaction EC, used only to disambiguate multifunctional genes.

Reactions were left untouched when gene- and reaction-level evidence
disagreed, or when a multifunctional gene could not be disambiguated, so
no low-confidence EC is introduced. Transport and exchange reactions were
excluded, as transporters are classified by TC number rather than EC.

All 236 assignments are complete four-part EC numbers, none overwrite an
existing value, and a biochemical spot-check confirmed they match the
reaction chemistry. EC codes are annotation only, so model structure and
balance are unchanged. This leaves 531 metabolic GPR reactions without an
EC (multifunctional or without database evidence), better suited to a
dedicated GECKO getECfromDatabases run or manual curation.

* fix: correct obsolete and erroneous EC numbers (#366)

Systematic sweep of the existing EC numbers against the IUBMB/ExPASy
ENZYME database (enzyme.dat), addressing the general checking question
raised in #366.

- 510 reactions updated where an EC had been transferred to a new number,
  e.g. the ABC-transporter ATPases 3.6.3.44 -> 7.6.2.2 and 3.6.3.1 ->
  7.6.2.1 (moved into the translocase class EC 7), and many cytochrome
  P450 monooxygenases 1.14.13.x -> 1.14.14.x. Transfer chains were followed
  to the current entry; where one old EC was split into several, the
  successor was chosen from KEGG reaction and gene evidence, or all closely
  related successors were kept.
- Corrected erroneous EC strings: 1.4.11.1 -> 1.14.11.1 (typo, gamma-
  butyrobetaine dioxygenase), 2.1.3.85 -> 2.3.1.85 (typo, fatty-acid
  synthase, matching six sibling reactions), removed the spurious 5.2.2.2
  from phosphoglucomutase, and updated the deleted 3.1.2.15 -> 3.4.19.12
  (ubiquitinyl hydrolase).

Nine reactions with ambiguous deleted or divergent-split ECs are left
unchanged for manual review. MAR01044 (FECH, 4.99.1.1 -> 4.98.1.1) is
handled separately in #1048. EC codes are annotation only, so model
structure and balance are unchanged.

* fix: resolve the 9 flagged EC numbers (#366)

Resolve the nine reactions left for manual review in the obsolete-EC
sweep, by a per-reaction decision against the reaction chemistry, genes,
and ExPASy.

Reassigned:
- MAR06916 -> 7.1.2.2 (proton-translocating ATP synthase; replaces a
  grab-bag of generic ATPase codes).
- MAR07145 -> 4.1.99.22 and 4.6.1.17 (the reaction spans both MOCS1
  domains, GTP to cyclic pyranopterin monophosphate).
- MAR09473, MAR09482 -> 3.4.17.1 and 3.4.17.15 (carboxypeptidase A, from
  the CPA1/CPA2/CPA3 gene set).
- MAR09491 -> 2.3.2.23, 2.3.2.27 and 6.2.1.45 (the lumped ubiquitin
  E1/E2/E3 cascade).

Removed the obsolete EC where no valid current entry fits the reaction and
there is no gene to support one:
- MAR04372 (3.1.3.13 deleted; acylphosphatase 3.6.1.7 does not act on
  bisphosphoglycerate).
- MAR08442 (1.2.7.2 deleted, a bacterial ferredoxin enzyme).
- MAR04767 (2.4.1.95 deleted; the UDP-glucuronosyltransferase 2.4.1.17
  uses a different donor).
- MAR03911 (1.8.1.3 deleted, hypotaurine dehydrogenase not substantiated).

EC codes are annotation only, so model structure and balance are unchanged.

* fix: fill missing EC numbers by gene-consensus propagation (#416)

Assign EC numbers to 94 metabolic reactions that had a GPR but no EC.
For each, propagate the EC shared by its genes across the model's
already-annotated reactions: a full EC where the genes agree exactly, a
sub-subclass wildcard (a.b.c.-) where they agree only to that level.
Transport/exchange reactions and SLC/ABC transporter genes are excluded
as evidence.

Every assignment was cross-checked against the gene's UniProt EC. The 94
applied here (35 full, 59 wildcard) are UniProt-confirmed; 13 candidates
that UniProt contradicts or cannot support were left out.

* fix: fill more EC numbers from UniProt gene annotations (#416)

Add EC numbers to 32 further metabolic reactions that had a GPR but no
EC, taking the code directly from the UniProt annotation of the reaction's
genes: a full EC where the genes agree, a sub-subclass wildcard otherwise.
Transport reactions and SLC/ABC genes are excluded; two reactions with a
non-catalytic or spurious GPR were left out.
* Add model QC checks workflow (completeness, annotation, growth, GPR/bounds, MEMOTE)

Adds a "Model QC checks" GitHub Actions workflow with two jobs:

- checks (every PR, fast): metabolite formula/charge completeness, reaction
  bound and GPR sanity, a growth sanity gate, and annotation cross-reference
  validation across namespaces. Posts a compact status table as a PR comment
  with the change versus the target branch and an icon per check.
- memote (separate job): runs MEMOTE in-process and builds the snapshot report
  to obtain the total score (memote run alone does not compute one). A fast core
  subset (skipping the three FVA/loopless tests) runs on every PR; the full suite
  runs only on PRs to main. It updates the same PR comment when it finishes.

Detailed per-finding output is written to diff-friendly CSVs in data/testResults;
the compact comment is for a quick visual check.

* chore: add QC test results [skip ci]

* chore: add model QC results [skip ci]

* Combine MACAW, balance and gene-essentiality into the QC comment

Adds buildReport.py, which builds one consolidated model-quality comment from
the committed result files: a compact Model QC status table, the MACAW and
mass/charge balance summary, and the Hart 2015 gene-essentiality metrics. Any
result set not yet committed for the pull request shows as pending.

The Model QC and QC-tests workflows both build and post this comment to a single
shared identifier (GITHUB_COMMENT_QC), each rebuilding it from the committed
files so the last to finish shows the complete picture. Replaces the separate
buildQcComment.py / commentModelQC.md / commentQC.md.

* Skip the heavy consistency and matrix MEMOTE tests in the core subset

The core subset still ran the stoichiometric-consistency / energy-cycle MILPs and
the per-metabolite open-bound checks (~32 min) and the O(n^3) matrix rank / null-
space tests, so it exceeded the per-PR budget. Skip those twelve tests in the
subset; the full suite (PRs to main) still runs them.

* Update MEMOTE subset description to the broader skip list

* Use the latest GitHub Actions versions in the Model QC workflow

checkout v4->v7, setup-python v5->v6, upload-artifact v4->v7,
git-auto-commit-action v5->v7.
* chore: add metabolite-name vs ChEBI-name diff (#1037)

Regenerated against the current model/metabolites.tsv and the ChEBI flat
files, comparing each metabolite name to its ChEBI id's preferred name and
all synonyms. Categorized (close-typo / moderate / far-check-id / pool /
unresolved-chebi-id / stylistic) to guide correction batches.

* fix: correct 6 garbled metabolite names against ChEBI (#1037)

Fix clear typos in metabolite names and the reaction names that embed them:
betaine_aldehyde, Docosanedioicacid, W-Hydroxydecanoicacid,
W-Hydroxydocosanoicacid, Phenylacetylglycine_phacgly, Sebacicacid.
Names only; model structure and balance unchanged. The Title-Case (Recon3D)
name cluster is left for a separate follow-up.

* chore: add QC test results [skip ci]
* fix: normalise Title-Case acyl-CoA metabolite names (#1037)

Lowercase the Recon3D Title-Case acyl-CoA metabolite names and change
'Coenzyme A' to the model's '-CoA' convention (e.g. 'Trans,Cis-Dodeca-2,5-
Dienoyl Coenzyme A' -> 'trans,cis-dodeca-2,5-dienoyl-CoA'), preserving
stereo descriptors. Reaction names embedding these strings are updated in
sync. Names only; structure and balance unchanged.

* fix: normalise Title-Case acid/complex metabolite names (#1037)

Lowercase Recon3D Title-Case acid and fibre-bile-acid-complex metabolite
names (e.g. 'Lysophosphatidic Acid' -> 'lysophosphatidic acid'); embedding
reaction names updated in sync. Names only.

* fix: normalise Title-Case acyl-[ACP] metabolite names (#1037)

Lowercase the Title-Case acyl-carrier-protein names and use the model's
'[ACP]' convention (e.g. 'Cis-Tetradec-7-Enoyl-[Acyl-Carrier Protein]' ->
'cis-tetradec-7-enoyl-[ACP]'); reaction names synced. Names only.

* fix: normalise Title-Case 'Coenzyme A' reaction names (#1037)

Lowercase the 138 Recon3D Title-Case reaction names that use 'Coenzyme A'
as a generic term and switch to '-CoA' (e.g. 'Stearoyl Coenzyme A
Desaturase' -> 'stearoyl-CoA desaturase'), preserving shorthand like
N-C18:0CoA. Reaction names only.

* chore: add QC test results [skip ci]

* fix: replace semicolon name-lists on bile-acid metabolites (#229, #1037)

Ten bile-acid metabolites carried a semicolon-separated list of names as
their name (from the Recon3D/HMDB import). Replace each with a single clean
lowercase name, e.g. '12-Dehydrocholic acid; 12-Oxodeoxycholic acid; ...' ->
'12-dehydrocholic acid'. Fix a typo (MAM03206 'Chenodeoxychollyc' ->
'chenodeoxycholic') and a wrong first name (MAM03704 is the trihydroxy
compound isocholic acid, not the dihydroxy isochenodeoxycholic acid).
Metabolite names only; no structural changes.

* chore: add QC test results [skip ci]
* chore: prune the .deprecated archive for the Human2 release

Remove the archived Human1-era scripts, data and model files from
.deprecated/ (they remain in the git history and in the last Human1
release, v1.19.0) and leave a README pointing there.

* chore: remove completed one-off curation scripts and stale dumps

Remove the Human1-era one-off curation scripts (code/modelCuration/*,
code/curateReactionNames.py) with their input data, and the static
external dumps (SwissProt/Cell Atlas/DeepLoc2 compartments, Rhea
associations, metabolite SMILES/InChI) that had gone stale. The compartment
data they produced is already in genes.tsv; the dumps are better queried
fresh. All remain in the git history and in v1.19.0. READMEs updated.

* refactor: remove unused code utilities and group animal-GEM functions

Human2 code/ cleanup, no model changes. Continues the chore/human2-cleanup
branch.

Remove functions with no remaining callers or superseded by RAVEN:
- root: removeReactionsFull, standardizeMetFormulas, addMetCompsField
- misc: countEmpty, flattenCell, getNonEmptyList, nestCell, prettyJson,
  reformatElements
- qc: overlapRxnDetection, identifyMassVariableRxns, detectDuplicateRxns
- GPRs: addComplexesToGeneRules, updateGrRules, fetch_ensembl_gene_annotations.py

Merge getCompNetwork into getSubNetworksInComp as a local function (it was
the only caller).

Group the animal-GEM derivation and its helpers under code/animalGEM/:
updateAnimalGEM, updateAnimalAnnotations, getModelFromOrthology,
gapfill4EssentialTasks, cleanGrRules, translateGrRules, replaceGrRules,
extractAllianceGenomeOrthologs, countFrequency.

* refactor: use RAVEN closeModel instead of local addBoundaryMets

addBoundaryMets and RAVEN's closeModel both add boundary metabolites for
metabolic task checking. Verified functionally equivalent on Human-GEM:
identical essential (57/57) and verification (21/21) checkTasks outcomes.

Migrate all callers to closeModel and remove addBoundaryMets:
- test/testMetabolicTasks, animalGEM/gapfill4EssentialTasks (calls)
- test/estimateEssentialGenes (call)
- tINIT/getINITModel2, test/evalGeneEssentialityPred (doc/error text)

* refactor: group addMetabolicNetwork with the animalGEM subfolder

Its only caller is updateAnimalGEM (now under animalGEM/), which returns its
modelChanges log as speciesSpecNetwork. Keep the encapsulation and move it
alongside the other animal-GEM functions rather than inlining it.

* chore: add QC test results [skip ci]

* fix: remove duplicate empty name field from MAR01801

MAR01801 had two name fields: the descriptive name and a leftover empty
`name: ""`. This made cobra.io.load_yaml_model raise a duplicate-key
AssertionError, which was failing qcModelChecks.py (checks) and
memoteTest.py (yaml-validation) on develop, independent of the cleanup.

Remove the empty duplicate, keeping "thioredoxin-disulfide reductase
(NADPH) (ubiquinone)" (EC 1.8.1.9). Both scripts pass locally after the fix.

* chore: add MEMOTE result [skip ci]

* chore: add QC test results [skip ci]
edkerk added 7 commits July 12, 2026 23:43
* feat: add duplicate-key QC gate to qcModelChecks

A repeated key inside a metabolite/reaction/gene !!omap block (e.g. two name
fields on one reaction, as happened with MAR01801) is tolerated by RAVEN's
YAML reader/writer but makes cobra.io.load_yaml_model raise a bare
AssertionError with no location, failing the QC and MEMOTE jobs opaquely.

Add check_no_duplicate_keys, a PyYAML event scan that runs before the cobra
load and fails the build with a clear message naming the entry, key and line
numbers. Covers both duplicate fields and duplicate stoichiometry
metabolites. Install pyyaml in the model-qc checks job.

* chore: add QC test results [skip ci]

* feat: report MACAW/balance as a status table, fix the QC README list and MEMOTE detail (#1056)

- buildReport.py: render the MACAW and mass/charge balance findings (dead-end,
  duplicate, and mass- and charge-imbalanced reaction counts) as a status table
  with the value, the change versus the target branch and an icon, matching the
  Model QC checks table, instead of embedding the prose summary. A result set not
  committed yet shows an hourglass.
- Fetch macaw_results.csv and balance_results.csv from the target branch in both
  QC workflows so the new table can show a delta.
- README test-results list: give each result set its own line, updated by its own
  workflow (model QC checks, MEMOTE, MACAW and mass/charge balance, gene
  essentiality). The template carried two "(QC)" lines that the qc-tests sed both
  rewrote, and had no MEMOTE or MACAW entry; the Model QC checks and memote jobs
  now each update their own line.
- memoteSnapshot.py: add a per-test detailed-score table grouped by section, in
  addition to the section summary.

* chore: add MEMOTE result [skip ci]

* refactor: consolidate model QC checks, detail files and the PR comment (#1056)

The adversarial review surfaced two gaps: the checks that block a merge
(duplicate/empty/inconsistent reactions, growth) left their findings only in
the workflow log, and the pull-request comment covered only the soft report
checks, so it could read all-green while a required check was red. This
reworks the pipeline so every failing check has a committed detail file and
the comment shows a clear problem-or-not verdict with a delta vs the target
branch.

- Consolidate the structural checks into code/test/qcModelChecks.py: gates
  (duplicate !!omap keys, reactions with no metabolites, model vs
  annotation-table consistency incl. deprecated ids and the spontaneous
  column, growth) and reports (metabolite completeness, reaction sanity,
  exact-duplicate reactions, unused entities), each writing a detailed CSV.
  On a growth failure it writes the blocking biomass precursors.
- buildReport.py: a one-line verdict (merge blocked / regressions / running /
  clean), a Build gates table, a Model QC reports table and the MACAW/balance
  table, each value linked to its CSV with a delta and an icon. Each result
  set is stamped with the head commit (qc_checks/memote/macaw.sha) so a set
  that has not re-run for the current commit shows as pending, not stale.
- The Model QC checks job commits the detail and posts the comment even when
  a gate fails, then fails the build, so the failure is visible in both.
- Remove the now-duplicated memoteTest.py and sanityCheck.py; trim
  yaml-validation to the YAML lint. Guard the MACAW step so a crash no longer
  skips the balance report and comment; balanceTest.py records reactions it
  cannot check instead of silently dropping them.

Branch protection should require the "Model QC checks / checks" job (which now
owns the structural gates) in place of "yaml-validation".

* chore: add model QC results [skip ci]

* chore: add MEMOTE result [skip ci]

* chore: add QC test results [skip ci]

* feat: exclude pseudo-metabolites from the formula-completeness report

The 7 formula-less metabolites (MAM10001-3 generic class sinks; MAM10012-15
*_pool_biomass) intrinsically have no molecular formula, so flagging them as
"missing formula" is noise. Exclude them via an explicit documented set -
not the MAM10xxx id range, which also holds real metabolites (bile acids,
CoAs). Missing-formula count 7 -> 0.

* chore: add model QC results [skip ci]

* chore: add MEMOTE result [skip ci]

* chore: add QC test results [skip ci]

* fix: correct QC comment links, icon semantics, and staleness

- Link finding counts to their CSV with an absolute repo blob URL on the PR
  branch, so the links resolve to the file instead of a PR-relative path.
- Icon rule: red cross when a count rose vs the target branch (a regression
  this PR introduced), warning sign when a count is non-zero but did not rise
  (a pre-existing, non-blocking finding), check mark when zero. Growth stays
  pass/fail; the MEMOTE score warns only when it drops.
- A result set is treated as current only when its commit stamp matches the
  PR head; an absent or older stamp now shows the rows as running (hourglass)
  instead of a previous run's numbers.
- Empty reactions and model/annotation inconsistencies are non-blocking
  reports (a rising count still flags as a regression); only duplicate keys
  (model unloadable) and no growth block the merge.

* chore: add model QC results [skip ci]

* chore: add MEMOTE result [skip ci]

* chore: add QC test results [skip ci]
…ces (#1057)

* fix: correct malformed and cross-compartment metabolite cross-references

annotationTest flagged 59 malformed and 59 cross-compartment-inconsistent
metabolite cross-references. This resolves them by chemistry, not deletion.

- Remove 59 invalid metLipidMapsID="PROTEIN" values from 22 protein
  metabolites (albumin, apoA1, apoB100, fibrinogen, LPL, STAR, ...); PROTEIN
  is not a valid identifier in any tracked namespace and is not a misplaced
  id of another type.
- Reconcile cross-references across compartments for 35 metabolites by
  matching each candidate id's own formula and charge to the model
  metabolite (ChEBI/KEGG/MetaNetX/PubChem lookups): correct misassignments
  (pantetheine carrying palmitoyl-CoA's C00154; beta-D-glucose carrying
  Amylose's MNXM105; mead acid's non-existent CHEBI:1306412), adopt the
  charge-correct form (NADH CHEBI:57945, NADP+ CHEBI:58349, acetate
  CHEBI:30089, PPi CHEBI:33019, ...), and follow deprecated MetaNetX ids to
  current primaries.

Malformed 59 -> 0. Inconsistent 59 -> 13. The 13 remaining (9 metabolites)
have compartment instances with different formulas/charges, or their only
cross-refs point to a different isomer/compound; these need individual
curation and are left flagged.

* fix: correct anion formulas on neutral-charge nitro-aromatics

MAM01019 (4-nitrocatechol) and MAM02754 (4-nitrophenol/PNP) each had two
compartment instances (i, r) carrying the deprotonated anion formula
(C6H4NO4 / C6H4NO3) while their charge is 0, contradicting both the charge
and the other compartments. Correct the formula to the neutral form
(C6H5NO4 / C6H5NO3) and set the ChEBI/MetaNetX cross-references to the
matching neutral-form current ids.

Mass/charge balance unchanged (277 unbalanced reactions, no change).
Inconsistent cross-references 59 -> 9.

* chore: add model QC results [skip ci]

* chore: add QC test results [skip ci]
…ion (#1059)

* ci: combine QC workflows, drop stamp files, update comment action

Combine the model-qc and qc-tests workflows into a single Model QC workflow with
one job that runs every check and edits one pull-request comment as results come
in: it posts a 'running' comment immediately, fills in the fast checks, then fills
in the MEMOTE score. Results are committed once, at the end, and only if they
changed, so a change that does not affect the model adds no commit.

Remove the qc_*.sha stamp files: freshness is now passed to buildReport as the
RUNNING_GROUPS environment variable instead of being tracked by committing a stamp
per commit (which forced a commit on every push even with no result change).

Replace the deprecated Node 16 NejcZdovc/comment-pr action with a reusable
github-script (Node 20) composite action that upserts the comment by a hidden
marker; do the same for the gene-essentiality comment.

* ci: set BASE_DIR in a step (runner context is unavailable in job env)

* chore: update model QC results [skip ci]
…ross-references, pH-7.3 structures) (#1058)

* chore: annotation overhaul plan and structure/formula/charge audit

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).

* feat: store SMILES/InChI in metabolites.tsv with a consistency QC test

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.

* fix: regenerate protonation-mismatched structures to the pH-7.3 microspecies

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.

* feat: structure-based cross-reference verifier (MetaNetX hub)

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.

* fix: update 2792 deprecated MetaNetX metabolite IDs to current

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.

* feat: add 7673 structure-verified metabolite cross-references

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.

* feat: add ModelSEED cross-references (metSeedID column) (#968)

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.

* chore: tooling to classify model formula/charge errors (deferred)

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.

* fix: correct 12 invalid metabolite charges, improving balance 277->271

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.

* fix: update 1217 deprecated MetaNetX reaction IDs to current (#967)

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.

* feat: add structure-verified reaction cross-references (#967)

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.

* fix: correct 61 metabolite cross-references to the right compound

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.

* fix: harmonize cross-references across compartments, drop malformed MetaNetX 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.

* chore: reconcile with #1057 after rebasing on 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.

* refactor: replace one-off overhaul scripts with a reusable annotation 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.

* chore: update model QC results [skip ci]
Port the MATLAB/RAVEN CI test workflows to Python (raven-toolbox): YAML conversion,
metabolic-task checks, gene essentiality (ftINIT, matching the DevelopWBM pipeline),
and model QC. Gene-essentiality results reproduce the MATLAB DevelopWBM reference.
* ci: overhaul model-QC comment and result-file handling

- Show "Model file and metabolic tasks" rows as running until the checks
  phase completes, instead of displaying the previous run's committed values.
- Add QC check: flag reactions/metabolites removed since the base branch that
  were not moved to the deprecated identifier lists (qc_deprecation_completeness.csv).
- Merge the structural-checks and model-QC-reports tables into one; link every
  check name to its explanation in the testResults README.
- Reorganise data/testResults/README.md: per-file provenance, per-test
  explanations (anchors matching the comment), and a file index.
- Combine the one-line result files (round-trip, YAML lint, metabolic tasks,
  growth) into a single qc_status.tsv via a qcStatus.py upsert helper.
- Update the PR comment only after results are committed, in two phases (fast
  checks, then MEMOTE), so shown numbers and CSV links are always on the branch.
- Store MEMOTE core-subset and full-suite scores in separate sections of
  memote_score.md so a routine run never overwrites a full-suite score; each
  section is compared only against the same section on the base branch.
- Bump actions/github-script to v9 (Node 24) to clear the Node 20 deprecation.
- Fix gene-essentiality README PR-number stamping (guarded on a never-true condition).

* chore: update model QC results (fast checks) [skip ci]

* chore: update model QC results [skip ci]

* ci(memote): enrich the model with table cross-references before MEMOTE

The committed YAML model carries only ids and names, so MEMOTE scored every
annotation section 0% even though the cross-references exist in the annotation
tables. Add annotateModel.py, which attaches the database identifiers from
metabolites.tsv / reactions.tsv / genes.tsv to an in-memory model, and call it in
memoteSnapshot.py before writing the temporary SBML.

- Maps only registry (identifiers.org) namespaces MEMOTE can validate; legacy-only
  columns (EHMN, HepatoNET1, Recon3D, HMR2, Ratcon) are skipped.
- Normalises values to each namespace: Rhea loses its "RHEA:" prefix, KEGG
  metabolite ids split into compound/glycan/drug by prefix, genes get ensembl (from
  the id) plus uniprot and ncbigene.
- The enriched model exists only in memory for the temporary SBML; nothing extra is
  committed.

* ci(memote): use canonical annotateGEM (with extended SBO) to enrich for MEMOTE

Bring the canonical annotation helper code/annotateGEM.py (a port of annotateGEM.m)
and its release caller code/io/increaseHumanGEMVersion.py into the repo, and use
annotate_gem from memoteSnapshot.py instead of an ad-hoc helper. This attaches the
TSV cross-references and SBO terms to the in-memory model before the temporary SBML
that MEMOTE reads (nothing extra is committed).

Extend annotateGEM's SBO assignment to everything MEMOTE checks:
- metabolites get SBO:0000247 (simple chemical) and genes SBO:0000243 (gene);
- boundary reactions split into exchange / demand / sink (SBO:0000627 / 0000628 /
  0000632) via cobra's own classification, which MEMOTE also uses, so each reaction
  carries the term its check expects. Falls back to exchange-for-all if cobra
  cannot classify.

Remove the interim code/test/annotateModel.py in favour of annotateGEM.

* ci: collapse model-QC back to a single results commit

The MEMOTE fast subset finishes quickly, so the interim fast-checks commit and
comment update are unnecessary. Run every check, commit once at the end, and post
the comment from the committed files - keeping the invariant that the comment never
shows numbers or CSV links that are not yet on the branch.

* chore: update model QC results (fast checks) [skip ci]

* refactor: use raven-toolbox for SBO terms and model I/O

annotateGEM now delegates metabolite and reaction SBO assignment to the canonical
raven_toolbox.annotation.add_sbo_terms (passing Human-GEM's biomass reaction name)
instead of a hand-rolled version; it keeps the Human-GEM-specific TSV cross-reference
merge, and still sets the gene SBO term (SBO:0000243) that add_sbo_terms does not
cover.

memoteSnapshot loads the model with raven_toolbox.io.read_yaml_model, like the other
RAVEN-based tests.

increaseHumanGEMVersion writes its exports with raven_toolbox.io.export_for_git (yml/
mat plain, xml/xlsx/txt annotated) instead of calling cobra's writers and hand-rolled
txt / dependencies writers directly.

Validated on the full model: SBO terms assigned to all metabolites/reactions/genes
(biomass MAR13082 -> SBO:0000629), cross-references merged without clobbering existing
ones, and export_for_git writes the annotated SBML.

* chore: update model QC results [skip ci]

* fix: keep the Human-GEM.mat variable named humanGEM

export_for_git writes the .mat with cobra's default variable name (the model id,
HumanGEM). Write the plain YAML/MATLAB exports explicitly instead - YAML via
raven-toolbox, MATLAB via cobra with varname=humanGEM - and keep export_for_git for
the annotated xml/xlsx/txt exports.

* refactor: pin the .mat variable via export_for_git's varname

Revert the increaseHumanGEMVersion workaround (explicit write_yaml_model +
save_matlab_model) now that raven-toolbox's export_for_git takes a varname argument.
The plain yml/mat export is a single export_for_git call again, with
varname='humanGEM' pinning the MATLAB struct name.
main carried every release commit back to v1.1 that develop had never seen,
leaving the merge base at a10375c and forcing each release merge to
re-reconcile the same divergence.

This merge is ancestry only: the resulting tree is identical to develop. All
nine conflicts resolve in develop's favour because develop is newer in every
case (2.0.1 curation, the Python/RAVEN migration, the published PNAS citation,
the 43-contributor list on the six-badge scheme from PR #1049). No deprecated
identifiers or contributors are lost; develop is a superset of both lists.

main's release artifacts (Human-GEM.xml/.mat/.xlsx/.txt, dependencies.txt,
version.txt) are not brought over: increaseHumanGEMVersion generates them on
main at release time and develop does not track them.
…o-develop

chore: merge main into develop to restore a shared ancestor
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

Model quality report

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

Each check name links to its explanation in the testResults README.

Model checks

Duplicate keys (model unloadable) and no growth block the merge; every other row is a non-blocking report.

Check Result Δ vs main
Duplicate !!omap keys 0 new
Growth (biomass producible) 125 new
Reactions with no metabolites 0 new
Model / annotation-table inconsistencies 0 new
Removed reactions or metabolites not deprecated 0 new
Metabolites missing formula 0 new
Metabolites missing charge 0 new
Reaction bound / GPR issues 0 new
Exact-duplicate reaction groups 0 new
Unused metabolites 0 new
Unused genes 0 new
Malformed cross-references 0 new
Cross-refs inconsistent across compartments 3 new ⚠️

MACAW and mass/charge balance

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

Model file and metabolic tasks

Check Result
YAML round-trip (cobrapy) pass
YAML round-trip (RAVEN) pass
YAML lint pass
Essential metabolic tasks 57 passed
Verification metabolic tasks 21 passed

MEMOTE

Total score: 63.2% (core subset)  

Section Score Δ vs base
consistency 42.4%
annotation_met 73.0%
annotation_rxn 72.7%
annotation_gene 46.7%
annotation_sbo 81.7%
Per-test scores
Section Test Score
Consistency Stoichiometric Consistency 100.0%
Consistency Mass Balance 0.8%
Consistency Charge Balance 2.1%
Consistency Metabolite Connectivity 0.0%
Consistency Unbounded Flux In Default Medium 100.0%
Annotation - Metabolites Presence of Metabolite Annotation 0.0%
Annotation - Metabolites Metabolite Annotations Per Database 62.3%
Annotation - Metabolites Metabolite Annotation Conformity Per Database 45.8%
Annotation - Metabolites Uniform Metabolite Identifier Namespace 0.0%
Annotation - Reactions Presence of Reaction Annotation 0.0%
Annotation - Reactions Reaction Annotations Per Database 75.9%
Annotation - Reactions Reaction Annotation Conformity Per Database 33.3%
Annotation - Reactions Uniform Reaction Identifier Namespace 0.0%
Annotation - Genes Presence of Gene Annotation 0.0%
Annotation - Genes Gene Annotations Per Database 80.0%
Annotation - Genes Gene Annotation Conformity Per Database 80.0%
Annotation - SBO Terms Metabolite General SBO Presence 0.0%
Annotation - SBO Terms Metabolite SBO:0000247 Presence 0.1%
Annotation - SBO Terms Reaction General SBO Presence 0.0%
Annotation - SBO Terms Metabolic Reaction SBO:0000176 Presence 0.0%
Annotation - SBO Terms Transport Reaction SBO:0000185 Presence 0.7%
Annotation - SBO Terms Exchange Reaction SBO:0000627 Presence 0.0%
Annotation - SBO Terms Demand Reaction SBO:0000628 Presence 100.0%
Annotation - SBO Terms Sink Reactions SBO:0000632 Presence 100.0%
Annotation - SBO Terms Gene General SBO Presence 0.0%
Annotation - SBO Terms Gene SBO:0000243 Presence 0.0%
Annotation - SBO Terms Biomass Reactions SBO:0000629 Presence 0.0%

Full suite: 64.2%   · from the last /run memote.

The score above is the fast core subset. Comment /run memote to run the full suite on this pull request; the score updates here when it finishes.

Gene essentiality (Hart 2015)

Not run automatically (it takes hours). Comment /run gene-essentiality to run it on this pull request; the result posts as its own comment.

❌ = 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

@edkerk
edkerk requested a review from JHL-452b July 16, 2026 20:45
Two problems surfaced on the release pull request (#1061), whose head is develop:

The results push was rejected (GH006, protected branch update failed). Every check
had passed; only the commit failed. actions/checkout persists the credentials the
push later uses, so the token belongs there: use CI_PUSH_TOKEN when it is set and
fall back to GITHUB_TOKEN, which keeps every ordinary topic-branch head working and
leaves forks unaffected.

That failed push then skipped everything after it, because steps default to an
implicit success(). On #1061 the MEMOTE artifact and the build-gate verdict were
both skipped, so a plumbing failure hid which gate had actually run. The same shape
would silently discard a multi-hour gene-essentiality or full-MEMOTE run, since
their publish steps also sit after the commit.

Guard the publish and verdict steps with !cancelled() instead: they now run whether
or not the push succeeded, but still not on cancellation.
edkerk and others added 6 commits July 16, 2026 21:17
ci: let results reach a protected branch, and never lose a long run
…rashes

A dispatched full-MEMOTE run (29537354961) reported success in 65 seconds having
produced nothing. It died 1.4s in:

    from raven_toolbox.annotation import add_sbo_terms
    ModuleNotFoundError: No module named 'raven_toolbox'

memoteSnapshot began importing annotateGEM, and so raven_toolbox, in #1062.
model-qc.yml installs raven-toolbox in a step of its own before MEMOTE, but
memote-full.yml installed only cobra, memote and gurobipy, so the script and one of
its two callers went out of sync. Install from requirements.txt there too.

The run looked green because `timeout ... || echo "::warning::MEMOTE did not finish
within the time limit"` swallows every non-zero exit, so an instant crash was
reported as a slow run, and with continue-on-error and nothing to commit the job
passed. Branch on the exit code instead: 124 stays a tolerated timeout warning,
anything else is an error.

In memote-full the score is the entire deliverable, so a crash now fails the run
(continue-on-error dropped; the publish steps are !cancelled(), so results are still
salvaged). In model-qc MEMOTE is one report among many and must not block the other
results being committed, so it keeps continue-on-error but now annotates honestly.
…pendency

fix: install raven-toolbox for the full MEMOTE run, and stop hiding crashes
@github-actions

Copy link
Copy Markdown

Gene essentiality was run on this pull request via GH Actions. Summary:

Gene essentiality vs Hart 2015 fitness genes

cellLine TP TN FP FN accuracy sensitivity specificity F1 MCC
DLD1 125 2172 124 224 0.8684 0.3582 0.946 0.4181 0.3525
GBM 111 2145 138 251 0.8529 0.3066 0.9396 0.3633 0.2897
HCT116 141 2189 130 246 0.861 0.3643 0.9439 0.4286 0.3595
HELA 114 2233 164 197 0.8667 0.3666 0.9316 0.3871 0.3132
RPE1 86 2183 162 214 0.8578 0.2867 0.9309 0.3139 0.2367
all 59 2355 159 69 0.9137 0.4609 0.9368 0.341 0.3103

The full per-gene essentiality matrix is committed to data/testResults/gene-essential.csv.

Note: in the case of multiple test runs, this post is edited.

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.

6 participants