Design note: linking metabolite evidence to genes
Status: implemented (MVP). Decisions in section 7 were confirmed on 2026-10-16.
1. Problem
The multi_omics_convergence tier (src/prioritize/rank.py, EvidenceIndex.supporting_layers / other_layers) links molecular layers only by an identical feature_id_standardized. RNA-seq, methylation and proteomics evidence all standardize to an NCBI GeneID, so they can meet. Metabolite evidence (src/harmonize/metabolomics.py) standardizes to the putative metabolite name (lactate, succinate, …), so it never shares an ID with gene-level evidence. As a result, metabolomics can never take part in convergence, whatever the data show.
Metabolites are not gene products, so a shared ID would be the wrong fix. The honest link is biochemical: this metabolite is a substrate or product of a reaction that this gene’s product catalyses. That is a weaker claim than “the same gene was measured twice”. It must be labelled weaker, scored as weaker, and explained on the card.
2. The link chain
metabolite name (as reported, preserved)
└─ curated name → compound (KEGG C-number, ChEBI ID) [curation step]
└─ compound → reaction (KEGG REACTION, substrate/product side)
└─ reaction → KO (KEGG Orthology)
└─ KO → crg gene (KEGG GENES, organism code `crg`)
└─ crg gene = NCBI GeneID → AREE crosswalk / orthogroup
Facts checked on 2026-10-16 against the KEGG REST API:
crg(T03920, Magallana gigas) has 26,085 proteins, and 10,575 of them have a KO.link/ko/crgreturns 10,878 gene→KO pairs.crggene IDs are NCBI GeneIDs. 10,874 of the 10,878 are current GeneIDs in AREE’s RS_2024_06 crosswalk (data/reference/crosswalk/mgigas_gene_id_crosswalk.tsv). None are retired, and 4 are unknown. The builder still routes every ID through the retired-ID sidecar and records the 4 that don’t resolve. It does not drop them silently.- Currency metabolites fan out enormously. For example, ATP (C00002) appears in 810 KEGG reactions. Without a hub filter, ATP alone would “link” most of the metabolic genome to any ATP change.
3. Proposed files (data/mappings/)
| File | Contents |
|---|---|
metabolite_compound_map.tsv |
Curated metabolite_name (normalized, lowercase), synonyms, kegg_compound, chebi_id, match_basis (exact_name / synonym / curator_judgement), curator, date, note. Hand-curated and small. It holds only the names that appear in registered studies. |
currency_metabolites.yaml |
The explicit list of compounds that are never used as a link (ATP, ADP, AMP, NAD⁺/NADH, NADP⁺/NADPH, H₂O, H⁺, CO₂, O₂, Pi, PPi, CoA, NH₃), with the reason. These metabolites still appear as evidence. They just don’t link. |
metabolite_gene_links_mgigas.tsv |
Built locally, not committed (decision A), one row per (compound, reaction, KO, gene): kegg_compound, kegg_reaction, equation_side (left/right), ko, ncbi_gene_id, link_type. The per-compound gene count and link_confidence are derived when the table is loaded, so they cannot drift from the rows. |
metabolite_gene_links_mgigas.provenance.json |
Same shape as the crosswalk provenance. It records the KEGG release (info/kegg), the access date, every REST URL fetched with its sha256, the builder version, the sha256 of the output, coverage counts, unresolved GeneIDs, and limitations. |
metabolite_gene_links_demo.tsv (+ provenance) |
Simulated, against the fabricated demo LOC IDs. It obeys the same rule as the demo crosswalk: it is never merged with the real table. |
A builder, src/mappings/build_metabolite_links.py, fetches only the compounds listed in metabolite_compound_map.tsv. It makes one link/reaction/<cpd> call per compound, then the reaction→KO and reaction-equation lookups, plus a single link/ko/crg call. It never mirrors KEGG. genome_assemblies.yaml gains a metabolite_gene_links: key next to identifier_crosswalk:, so a real study resolves links through its assembly and a simulated one through the demo table, exactly as identifiers do today.
No change to the evidence schema. Metabolite records keep feature_id_original (the source feature ID) and feature_id_standardized (the name). The link is resolved at ranking time from the table, so the link table can be rebuilt without re-harmonizing any study.
4. Mapping confidence
The existing mapping_confidence vocabulary is reused for link_confidence. A metabolite→gene link is never exact or one_to_one_ortholog:
| Situation | link_confidence |
|---|---|
| Compound reaches exactly one crg gene, through a KO transferred from KEGG’s ortholog assignment | inferred |
| Compound reaches 2 or more crg genes (the usual case: paralogs, or several reactions) | one_to_many_ortholog |
Name→compound match was curator_judgement, not an exact name or synonym |
downgraded to inferred at most, and flagged |
Compound reaches more than MAX_GENES_PER_COMPOUND genes (proposed: 25) |
link not credited. Recorded as hub_compound |
Compound in currency_metabolites.yaml |
link not credited |
Only metabolites at MSI annotation level 1–2 have a standardized name today (level 3–4 → unresolved), so only those can link. That gate stays as it is.
link_type is enzyme or transporter. Transporter links were added on 2026-10-17; see section 9.
5. Ranking changes (src/prioritize/rank.py)
A second, separately named kind of convergence. It never merges into the first.
EvidenceIndexloads the partition’s link table and builds both directions: gene → linked metabolite names, and metabolite name → linked GeneIDs.- New per-candidate fields:
linked_layers: layers with a significant record (adj. p ≤ 0.05) under the same phenotype and partition for a linked feature, excluding layers already insupporting_layers.n_linked_layers,linked_features(for example,lactate→LOC… via R00703/K00016),link_confidences,link_table_sha256.convergence_basis:shared_identifier,metabolite_gene_link, ornone.
- New tier
metabolite_linked_convergence. It is ordered aftermulti_omics_convergenceand beforeemerging. The gate is the candidate’s own layer is significant AND at least one linked layer is significant. High-priority and shared-identity gates are evaluated first, so a link never promotes a candidate past a stronger tier and never demotes one. - Score: linked layers do not enter
n_supporting_layersorassay_diversity_score. The transparent score stays a function of shared-identity evidence only. The tier and the card carry the link. (See decision B for the alternative.) - Direction across the link is reported but not required to agree. That is the same policy as shared-identity convergence. A product rising while its enzyme falls has several valid explanations.
6. Evidence card
There is a new subsection under “Multi-omics context”: “Metabolite–gene links (biochemical, weaker than shared gene identity)”. It contains:
- A plain statement: “Linked through KEGG reaction annotation, not measured. The enzyme assignment is inferred by KEGG orthology. The link means the gene’s product is annotated to catalyse a reaction involving this metabolite. It does not mean the gene regulates or controls the metabolite’s level.”
- A table with study, layer, linked feature, compound, reaction, KO, side,
link_confidence, n genes for the compound, direction, adj. p, same phenotype, significant, and counts. - The source line: link table file, KEGG release, access date, sha256.
- Excluded links listed with their reason (
currency_metabolite,hub_compound), so the filter is visible rather than silent. - A stressor caveat when the linked records come from a different stressor (for example, a hypoxia metabolite and a heat transcript under
growth_under_stress).
7. Decisions (confirmed 2026-10-16)
A. Source and licensing: builder only, no committed KEGG table. KEGG REST is provided for academic use, and KEGG does not clearly grant redistribution of derived tables. AREE therefore commits the builder (aree build-metabolite-links), the curated compound map, the currency list and the provenance schema (schemas/metabolite_gene_links_provenance.schema.json). It does not commit the KEGG-derived link table. data/mappings/metabolite_gene_links_mgigas.* is git-ignored. When it has not been built, real-data candidates carry link_table_status = not_built and no linked convergence: the absence is reported, never silent. CI uses only the simulated demo table. An open-licence route (Rhea + ChEBI + an eggNOG-mapper run on the RS_2024_06 proteome) remains future work, because it would allow the table to be committed.
B. Scoring: tier and card only. Linked layers never enter n_supporting_layers or assay_diversity_score.
C. A fourth tier, metabolite_linked_convergence, after multi_omics_convergence and before emerging.
D. Demo: two fabricated demo genes were added to the demo crosswalk and to GIGAS_HEAT01 chronic_heat_vs_control:
ldh(LOC105342001): significant, linked to lactate through R00703/K00016.sdha(LOC105342002): not significant, linked to succinate through R02164/K00234.glut1(LOC105342003), added 2026-10-17: significant, linked to glucose by a transporter link (GLUT1, K07299,ko_definition, with its real ko02000 family path). Demo glucose in GIGAS_GROW06 was moved from adj. p = 0.05 (exactly on the threshold) to 0.02 (p 0.021 → 0.006) on 2026-10-17, so the example does not hinge on<=versus<. Its direction (down) is opposite toglut1(up), which the card shows and the gate does not require to agree.
Both are simulated. The KEGG reaction and KO IDs in the demo table are real KEGG identifiers, but the gene IDs they point to are fabricated.
E. Cap: MAX_GENES_PER_COMPOUND = 25 (src/mappings/metabolite_links.py).
Confidence rule as implemented
A metabolite→gene link is never exact or one_to_one_ortholog. A compound that reaches more than one crg gene is one_to_many_ortholog. A compound that reaches exactly one gene is inferred, because the enzyme assignment is still KEGG orthology transfer, not oyster biochemistry. A name→compound match by curator_judgement makes the link inferred, which is the weaker rank in mapping_confidence.yaml. The same applies when one reported name maps to more than one compound. For example, lactate maps to both L-lactate (C00186) and D-lactate (C00256), because LC-MS does not resolve stereoisomers, and molluscs carry both L- and D-lactate dehydrogenases.
KEGG writes every reaction equation with <=>. The table therefore records the compound’s equation_side (left/right), not substrate or product: the equation does not encode the physiological direction.
Pumps and transporters have no KEGG REACTION, so they get no enzyme link. For example, SERCA (K05853) has none. Transporters are linked separately (section 9). ATP-driven pumps such as SERCA still get no link: what they move is an ion, and ATP is a currency metabolite.
First local build (2026-10-16, not committed)
aree build-metabolite-links against KEGG REST produced 191 links for 6 of the 7 queried compounds:
- ATP was skipped as a currency compound.
- Pyruvate (47 genes) exceeds the cap and is a hub, so it is not credited.
- 10,875 of 10,879 KEGG
crggenes with a KO resolved to current RS_2024_06 GeneIDs. The 4 unresolved IDs are rRNA/tRNA pseudo-IDs (s-rRNA-1,tRNA-Arg, …), and they are listed in the provenance. - One biologically meaningful result: no
crggene carries the L-lactate dehydrogenase KO (K00016), so (S)-lactate has no link, andlactatelinks only through D-lactate (C00256, 2 genes). This is consistent with bivalve anaerobic metabolism relying on D-LDH and opine dehydrogenases. It is also why reportedlactateis mapped to both stereoisomers rather than assumed to be L. The simulated demo, by contrast, links lactate through K00016 to a fabricated gene. That is a demo device, not oyster biology.
8. Assumptions
- KEGG’s crg KO assignments are adequate as inferred enzyme annotation for C. gigas. They come from orthology, not oyster biochemistry.
- Reaction participation (enzymes) and curated transport (transporters) are the only link types. Pathway co-membership is deliberately not a link, because it would connect almost everything.
- The real table is built for RS_2024_06 only. Other assemblies resolve through NCBI GeneIDs, as identifiers already do.
- The only real metabolomics study (DUPERRET2025_POMS_STARVE) failed QC (sr320/AREE#70). Real-data convergence is therefore not expected yet, and tests use the demo.
9. Transporter links (added 2026-10-17)
A transporter changes a metabolite’s level in a tissue without making or consuming it, so transporters are a second kind of biochemical link:
metabolite name → KEGG compound → transporter KO → crg gene
KEGG BRITE ko02000 has no substrates. The Transporters hierarchy classifies about 1,070 KOs into families (SLC families, the ABC types, the MFS families with TC numbers), but it records no compound IDs. The substrate appears only in free-text family and KO names. The KO → compound step is therefore AREE curation, kept in data/mappings/transporter_substrates.tsv (43 rows on 2026-10-17). Each row gives the KO, the compound, the human ortholog it rests on, a note, and its substrate_basis:
substrate_basis |
Meaning | Example |
|---|---|---|
ko_definition |
The KEGG KO definition itself names the compound | SLC2A1/GLUT1, “facilitated glucose transporter” → D-glucose |
substrate_class |
The definition names only a class, and the compound is a curator’s call from the characterized human members | SLC16A1/3/7/8 (MCT1–4), “monocarboxylic acid transporters” → L-/D-lactate, pyruvate |
Curation is deliberately narrow. Within SLC16, only MCT1–4 (the characterized lactate/pyruvate carriers) are listed. MCT8/10 (thyroid hormone, aromatic amino acids) and the orphan members are not. For alanine, only system A (SNAT1/2/4), ASCT1/2 and PAT are listed; the broad L-type exchangers are not. A KO whose KEGG definition names a substrate the human protein mainly does not carry is kept, with the caveat in its note (for example, GLUT9, which is chiefly a urate transporter).
How the builder uses ko02000. aree build-metabolite-links fetches get/br:ko02000 and does two things with it:
- It verifies every curated KO is a transporter. A KO absent from ko02000 is rejected and listed under
transporter_substrates.rejected_not_in_ko02000in the provenance. - It records the KO’s family path as
brite_familyon each row, for exampleSolute carrier family (SLC) > SLC16: Monocarboxylate transporter.
Curated KOs with no crg gene are listed under kos_without_crg_genes.
Confidence and ranking.
- A transporter link is always
inferred. Its substrate is curated, and the oyster ortholog’s substrate specificity has not been measured. - It is credited for
metabolite_linked_convergenceexactly as an enzyme link is, and adds nothing to the score. - The hub cap (25 genes) applies per link type, so adding transporter links never removes the credit of a compound’s enzyme links, or the reverse.
- Cards show each link’s type, basis and BRITE family, plus a paragraph explaining that transporter links rest on curation.
First local build (2026-10-17, not committed). The build produced 249 links: 191 enzyme and 58 transporter.
- All 33 curated KOs present in
crgwere found in ko02000. None were rejected. - 22 curated KOs have no
crggene (for example, BGT1/SLC6A12, so betaine has no transporter link). - Transporter genes per compound: glucose 16, succinate 11, L-lactate 9, D-lactate 9, pyruvate 9, alanine 4.
- Pyruvate stays a hub for enzyme links only (47 genes), so its 9 transporter links are credited.
- The result that matters most for lactate: (S)-lactate, which has no enzyme link in
crg, now links through MCT/SMCT transporters.
Demo. The simulated glut1 gene (section 7, decision D) gives the demo one transporter case: glucose ↔︎ glut1 → metabolite_linked_convergence.
Not covered. TCDB substrate annotations; ABC transporters (broad and poorly defined substrates); and ion channels and pumps.