diff --git a/docs/communities/AMD_Acidophile_Heterotroph_Network.html b/docs/communities/AMD_Acidophile_Heterotroph_Network.html
index bd18ff1c..dd12db44 100644
--- a/docs/communities/AMD_Acidophile_Heterotroph_Network.html
+++ b/docs/communities/AMD_Acidophile_Heterotroph_Network.html
@@ -841,8 +841,8 @@
Organic Carbon Scavenging and Remineralization
nitrogen compound metabolic process
- (GO:0008152)
+ (GO:0071941)
diff --git a/docs/communities/Anammox_Bioreactor_DNRA_Destabilization_Community.html b/docs/communities/Anammox_Bioreactor_DNRA_Destabilization_Community.html
index 4e4c5059..e921e3fa 100644
--- a/docs/communities/Anammox_Bioreactor_DNRA_Destabilization_Community.html
+++ b/docs/communities/Anammox_Bioreactor_DNRA_Destabilization_Community.html
@@ -604,8 +604,8 @@ DNRA Competes with Anammox for Nitrogen Removal
nitrogen compound metabolic process
- (GO:0008152)
+ (GO:0071941)
diff --git a/docs/communities/Coastal_Forested_Wetland_Seawater_Ion_Microcosm_Community.html b/docs/communities/Coastal_Forested_Wetland_Seawater_Ion_Microcosm_Community.html
index 9d53dc19..e798cac7 100644
--- a/docs/communities/Coastal_Forested_Wetland_Seawater_Ion_Microcosm_Community.html
+++ b/docs/communities/Coastal_Forested_Wetland_Seawater_Ion_Microcosm_Community.html
@@ -667,8 +667,8 @@ Non-Sulfate Seawater Ions Drive Community And Emission Responses
organic substance catabolic process
- (GO:0008152)
+ (GO:0009056)
@@ -813,8 +813,8 @@ Artificial Seawater Alters Carbon Nitrogen And Phosphorus Gene Response
organic substance catabolic process
- (GO:0008152)
+ (GO:0009056)
diff --git a/docs/communities/Model_Lignocellulose_Formaldehyde_Crossfeeding_Community.html b/docs/communities/Model_Lignocellulose_Formaldehyde_Crossfeeding_Community.html
index dc2cbbf5..d18f2356 100644
--- a/docs/communities/Model_Lignocellulose_Formaldehyde_Crossfeeding_Community.html
+++ b/docs/communities/Model_Lignocellulose_Formaldehyde_Crossfeeding_Community.html
@@ -751,17 +751,6 @@ Methylotrophic Formaldehyde Mitigation
- Biological Processes:
-
-
@@ -1307,10 +1296,6 @@ Evidence
var processes = [];
-
- processes.push("metabolic process");
-
-
var evidenceCount = 1;
nodes.push({
diff --git a/docs/communities/ORNL_PMI_Populus_PD10_SynCom.html b/docs/communities/ORNL_PMI_Populus_PD10_SynCom.html
index 5f8a6732..961471fc 100644
--- a/docs/communities/ORNL_PMI_Populus_PD10_SynCom.html
+++ b/docs/communities/ORNL_PMI_Populus_PD10_SynCom.html
@@ -918,12 +918,6 @@ Medium-Dependent Cross-Feeding
class="term-link" target="_blank" rel="noopener noreferrer">GO:0044419)
-
- metabolic process
- (GO:0008152)
-
-
@@ -1028,12 +1022,6 @@ Metabolite Exchange with Pantoea
class="term-link" target="_blank" rel="noopener noreferrer">GO:0044419)
-
- metabolic process
- (GO:0008152)
-
-
@@ -1086,12 +1074,6 @@ Cross-Feeding with Rhizobium
class="term-link" target="_blank" rel="noopener noreferrer">GO:0044419)
-
- metabolic process
- (GO:0008152)
-
-
@@ -1144,12 +1126,6 @@ Cross-Feeding with Bacillus
class="term-link" target="_blank" rel="noopener noreferrer">GO:0044419)
-
- metabolic process
- (GO:0008152)
-
-
@@ -1202,12 +1178,6 @@ Cross-Feeding with Caulobacter
class="term-link" target="_blank" rel="noopener noreferrer">GO:0044419)
-
- metabolic process
- (GO:0008152)
-
-
@@ -1260,12 +1230,6 @@ Cross-Feeding with Duganella
class="term-link" target="_blank" rel="noopener noreferrer">GO:0044419)
-
- metabolic process
- (GO:0008152)
-
-
@@ -1318,12 +1282,6 @@ Cross-Feeding with Streptomyces
class="term-link" target="_blank" rel="noopener noreferrer">GO:0044419)
-
- metabolic process
- (GO:0008152)
-
-
@@ -1376,12 +1334,6 @@ Cross-Feeding with Paraburkholderia
class="term-link" target="_blank" rel="noopener noreferrer">GO:0044419)
-
- metabolic process
- (GO:0008152)
-
-
@@ -1780,8 +1732,6 @@ Evidence
processes.push("interspecies interaction between organisms");
- processes.push("metabolic process");
-
var evidenceCount = 1;
@@ -1858,8 +1808,6 @@ Evidence
processes.push("interspecies interaction between organisms");
- processes.push("metabolic process");
-
var evidenceCount = 1;
@@ -1898,8 +1846,6 @@ Evidence
processes.push("interspecies interaction between organisms");
- processes.push("metabolic process");
-
var evidenceCount = 1;
@@ -1938,8 +1884,6 @@ Evidence
processes.push("interspecies interaction between organisms");
- processes.push("metabolic process");
-
var evidenceCount = 1;
@@ -1978,8 +1922,6 @@ Evidence
processes.push("interspecies interaction between organisms");
- processes.push("metabolic process");
-
var evidenceCount = 1;
@@ -2018,8 +1960,6 @@ Evidence
processes.push("interspecies interaction between organisms");
- processes.push("metabolic process");
-
var evidenceCount = 1;
@@ -2058,8 +1998,6 @@ Evidence
processes.push("interspecies interaction between organisms");
- processes.push("metabolic process");
-
var evidenceCount = 1;
@@ -2098,8 +2036,6 @@ Evidence
processes.push("interspecies interaction between organisms");
- processes.push("metabolic process");
-
var evidenceCount = 1;
diff --git a/docs/communities/Prairie_Pothole_Wetland_Sulfur_Carbon_Virus_Community.html b/docs/communities/Prairie_Pothole_Wetland_Sulfur_Carbon_Virus_Community.html
index 1aa1c2d3..8d8541f4 100644
--- a/docs/communities/Prairie_Pothole_Wetland_Sulfur_Carbon_Virus_Community.html
+++ b/docs/communities/Prairie_Pothole_Wetland_Sulfur_Carbon_Virus_Community.html
@@ -730,8 +730,8 @@ Diverse Sulfate-Reduction Guild
organic substance catabolic process
- (GO:0008152)
+ (GO:0009056)
diff --git a/docs/communities/Stordalen_Mire_Methylotrophic_Methanogenesis_Community.html b/docs/communities/Stordalen_Mire_Methylotrophic_Methanogenesis_Community.html
index f18d9ce0..9f05682d 100644
--- a/docs/communities/Stordalen_Mire_Methylotrophic_Methanogenesis_Community.html
+++ b/docs/communities/Stordalen_Mire_Methylotrophic_Methanogenesis_Community.html
@@ -766,8 +766,8 @@ Bacterial Anaerobic Methylotrophy
organic substance catabolic process
- (GO:0008152)
+ (GO:0009056)
diff --git a/docs/communities/Wetland_Oxygen_Sulfate_GHG_Microcosm_Community.html b/docs/communities/Wetland_Oxygen_Sulfate_GHG_Microcosm_Community.html
index 07788109..2bb3c606 100644
--- a/docs/communities/Wetland_Oxygen_Sulfate_GHG_Microcosm_Community.html
+++ b/docs/communities/Wetland_Oxygen_Sulfate_GHG_Microcosm_Community.html
@@ -853,8 +853,8 @@ Lactate-Sulfate Carbon And Sulfur Subnetwork
organic substance catabolic process
- (GO:0008152)
+ (GO:0009056)
diff --git a/kb/communities/AMD_Acidophile_Heterotroph_Network.yaml b/kb/communities/AMD_Acidophile_Heterotroph_Network.yaml
index 86bd0430..a1f5f07c 100644
--- a/kb/communities/AMD_Acidophile_Heterotroph_Network.yaml
+++ b/kb/communities/AMD_Acidophile_Heterotroph_Network.yaml
@@ -344,8 +344,8 @@ ecological_interactions:
label: aerobic respiration
- preferred_term: nitrogen compound metabolic process
term:
- id: GO:0008152
- label: metabolic process
+ id: GO:0071941
+ label: nitrogen cycle metabolic process
evidence:
- reference: doi:10.3389/fmicb.2015.00475
supports: SUPPORT
diff --git a/kb/communities/Anammox_Bioreactor_DNRA_Destabilization_Community.yaml b/kb/communities/Anammox_Bioreactor_DNRA_Destabilization_Community.yaml
index 1382e38b..3fa5e46d 100644
--- a/kb/communities/Anammox_Bioreactor_DNRA_Destabilization_Community.yaml
+++ b/kb/communities/Anammox_Bioreactor_DNRA_Destabilization_Community.yaml
@@ -164,8 +164,8 @@ ecological_interactions:
biological_processes:
- preferred_term: nitrogen compound metabolic process
term:
- id: GO:0008152
- label: metabolic process
+ id: GO:0071941
+ label: nitrogen cycle metabolic process
evidence:
- reference: PMID:31980038
supports: SUPPORT
diff --git a/kb/communities/Coastal_Forested_Wetland_Seawater_Ion_Microcosm_Community.yaml b/kb/communities/Coastal_Forested_Wetland_Seawater_Ion_Microcosm_Community.yaml
index 07c19b8a..62505ac2 100644
--- a/kb/communities/Coastal_Forested_Wetland_Seawater_Ion_Microcosm_Community.yaml
+++ b/kb/communities/Coastal_Forested_Wetland_Seawater_Ion_Microcosm_Community.yaml
@@ -129,8 +129,8 @@ ecological_interactions:
label: methanogenesis
- preferred_term: organic substance catabolic process
term:
- id: GO:0008152
- label: metabolic process
+ id: GO:0009056
+ label: catabolic process
evidence:
- reference: PMID:38628812
supports: SUPPORT
@@ -198,8 +198,8 @@ ecological_interactions:
biological_processes:
- preferred_term: organic substance catabolic process
term:
- id: GO:0008152
- label: metabolic process
+ id: GO:0009056
+ label: catabolic process
evidence:
- reference: PMID:38628812
supports: SUPPORT
diff --git a/kb/communities/Model_Lignocellulose_Formaldehyde_Crossfeeding_Community.yaml b/kb/communities/Model_Lignocellulose_Formaldehyde_Crossfeeding_Community.yaml
index ae57eb40..d2606535 100644
--- a/kb/communities/Model_Lignocellulose_Formaldehyde_Crossfeeding_Community.yaml
+++ b/kb/communities/Model_Lignocellulose_Formaldehyde_Crossfeeding_Community.yaml
@@ -236,10 +236,6 @@ ecological_interactions:
id: CHEBI:16842
label: formaldehyde
biological_processes:
- - preferred_term: metabolic process
- term:
- id: GO:0008152
- label: metabolic process
evidence:
- reference: doi:10.3390/microorganisms9020321
supports: SUPPORT
diff --git a/kb/communities/ORNL_PMI_Populus_PD10_SynCom.yaml b/kb/communities/ORNL_PMI_Populus_PD10_SynCom.yaml
index ee26916c..f4f0a3e0 100644
--- a/kb/communities/ORNL_PMI_Populus_PD10_SynCom.yaml
+++ b/kb/communities/ORNL_PMI_Populus_PD10_SynCom.yaml
@@ -327,10 +327,6 @@ ecological_interactions:
term:
id: GO:0044419
label: biological process involved in interspecies interaction between organisms
- - preferred_term: metabolic process
- term:
- id: GO:0008152
- label: metabolic process
evidence:
- reference: PMID:33995895
supports: SUPPORT
@@ -381,10 +377,6 @@ ecological_interactions:
term:
id: GO:0044419
label: biological process involved in interspecies interaction between organisms
- - preferred_term: metabolic process
- term:
- id: GO:0008152
- label: metabolic process
evidence:
- reference: PMID:33995895
supports: SUPPORT
@@ -410,10 +402,6 @@ ecological_interactions:
term:
id: GO:0044419
label: biological process involved in interspecies interaction between organisms
- - preferred_term: metabolic process
- term:
- id: GO:0008152
- label: metabolic process
evidence:
- reference: PMID:33995895
supports: SUPPORT
@@ -439,10 +427,6 @@ ecological_interactions:
term:
id: GO:0044419
label: biological process involved in interspecies interaction between organisms
- - preferred_term: metabolic process
- term:
- id: GO:0008152
- label: metabolic process
evidence:
- reference: PMID:33995895
supports: SUPPORT
@@ -468,10 +452,6 @@ ecological_interactions:
term:
id: GO:0044419
label: biological process involved in interspecies interaction between organisms
- - preferred_term: metabolic process
- term:
- id: GO:0008152
- label: metabolic process
evidence:
- reference: PMID:33995895
supports: SUPPORT
@@ -497,10 +477,6 @@ ecological_interactions:
term:
id: GO:0044419
label: biological process involved in interspecies interaction between organisms
- - preferred_term: metabolic process
- term:
- id: GO:0008152
- label: metabolic process
evidence:
- reference: PMID:33995895
supports: SUPPORT
@@ -526,10 +502,6 @@ ecological_interactions:
term:
id: GO:0044419
label: biological process involved in interspecies interaction between organisms
- - preferred_term: metabolic process
- term:
- id: GO:0008152
- label: metabolic process
evidence:
- reference: PMID:33995895
supports: SUPPORT
@@ -555,10 +527,6 @@ ecological_interactions:
term:
id: GO:0044419
label: biological process involved in interspecies interaction between organisms
- - preferred_term: metabolic process
- term:
- id: GO:0008152
- label: metabolic process
evidence:
- reference: PMID:33995895
supports: SUPPORT
diff --git a/kb/communities/Prairie_Pothole_Wetland_Sulfur_Carbon_Virus_Community.yaml b/kb/communities/Prairie_Pothole_Wetland_Sulfur_Carbon_Virus_Community.yaml
index 27f6c697..d912c7a9 100644
--- a/kb/communities/Prairie_Pothole_Wetland_Sulfur_Carbon_Virus_Community.yaml
+++ b/kb/communities/Prairie_Pothole_Wetland_Sulfur_Carbon_Virus_Community.yaml
@@ -165,8 +165,8 @@ ecological_interactions:
label: dissimilatory sulfate reduction
- preferred_term: organic substance catabolic process
term:
- id: GO:0008152
- label: metabolic process
+ id: GO:0009056
+ label: catabolic process
evidence:
- reference: PMID:30086797
supports: SUPPORT
diff --git a/kb/communities/Stordalen_Mire_Methylotrophic_Methanogenesis_Community.yaml b/kb/communities/Stordalen_Mire_Methylotrophic_Methanogenesis_Community.yaml
index 277d5b72..35bc7029 100644
--- a/kb/communities/Stordalen_Mire_Methylotrophic_Methanogenesis_Community.yaml
+++ b/kb/communities/Stordalen_Mire_Methylotrophic_Methanogenesis_Community.yaml
@@ -189,8 +189,8 @@ ecological_interactions:
biological_processes:
- preferred_term: organic substance catabolic process
term:
- id: GO:0008152
- label: metabolic process
+ id: GO:0009056
+ label: catabolic process
evidence:
- reference: PMID:38063415
supports: SUPPORT
diff --git a/kb/communities/Wetland_Oxygen_Sulfate_GHG_Microcosm_Community.yaml b/kb/communities/Wetland_Oxygen_Sulfate_GHG_Microcosm_Community.yaml
index 67237861..ca01e829 100644
--- a/kb/communities/Wetland_Oxygen_Sulfate_GHG_Microcosm_Community.yaml
+++ b/kb/communities/Wetland_Oxygen_Sulfate_GHG_Microcosm_Community.yaml
@@ -233,8 +233,8 @@ ecological_interactions:
label: dissimilatory sulfate reduction
- preferred_term: organic substance catabolic process
term:
- id: GO:0008152
- label: metabolic process
+ id: GO:0009056
+ label: catabolic process
evidence:
- reference: PMID:38961111
supports: SUPPORT
diff --git a/tests/test_no_vacuous_go_annotations.py b/tests/test_no_vacuous_go_annotations.py
new file mode 100644
index 00000000..938572eb
--- /dev/null
+++ b/tests/test_no_vacuous_go_annotations.py
@@ -0,0 +1,158 @@
+"""Some GO terms are true of every record here, so they annotate nothing (#182).
+
+The #180 id↔label cleanup remapped obsolete GO ids to their nearest *valid*
+term. For a handful that meant climbing to a near-root process, and the result
+was sixteen annotations reading
+
+ - preferred_term: metabolic process
+ term:
+ id: GO:0008152
+ label: metabolic process
+
+on records in a knowledge base about **microbial communities**, every one of
+which does metabolism. Nine were exactly that and were dropped. Seven carried a
+real concept in `preferred_term` that had been flattened onto the generic
+parent, and were re-grounded rather than deleted:
+
+ organic substance catabolic process -> GO:0009056 catabolic process
+ nitrogen compound metabolic process -> GO:0071941 nitrogen cycle metabolic process
+
+Both targets are current; the precise terms a curator would want
+(`GO:0006807`, `GO:1901575`) are **obsolete in GO**, which is why they were
+flattened in the first place. `GO:0071941` was already used twice elsewhere in
+the KB, so this follows an existing convention rather than inventing one.
+
+`term` is `required: true` on `BiologicalProcessDescriptor`, so "drop the
+annotation" necessarily means dropping the whole descriptor — there is no way to
+keep an ungrounded `preferred_term`. That is why upgrading beats deleting
+wherever a current term exists.
+"""
+
+from __future__ import annotations
+
+import pathlib
+
+import pytest
+import yaml
+
+REPO = pathlib.Path(__file__).parent.parent
+COMMUNITIES = REPO / "kb/communities"
+
+# GO terms so close to the root of the biological-process branch that asserting
+# them of a microbial community conveys nothing. Each needs a reason, so that
+# adding one is a decision rather than a reflex.
+_VACUOUS = {
+ "GO:0008152": "'metabolic process' — true of every organism in the KB",
+ "GO:0008150": "'biological_process' — the branch root",
+}
+
+
+def _walk(node, filename):
+ """Descriptors under `node`. Module-level so it does not close over the loop
+ variable — a nested closure here reads `path` from the enclosing scope at
+ *call* time, which for a generator is after the loop has moved on (ruff
+ B023). Harmless in this shape, wrong the moment the generator is not drained
+ immediately."""
+ if isinstance(node, dict):
+ term = node.get("term")
+ if node.get("preferred_term") and isinstance(term, dict) and term.get("id"):
+ yield filename, node["preferred_term"], term["id"], term.get("label")
+ for value in node.values():
+ yield from _walk(value, filename)
+ elif isinstance(node, list):
+ for value in node:
+ yield from _walk(value, filename)
+
+
+def _descriptors(corpus: pathlib.Path | None = None):
+ """Every (file, preferred_term, id, label) biological-process descriptor."""
+ for path in sorted((corpus or COMMUNITIES).glob("*.yaml")):
+ document = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
+ yield from _walk(document, path.name)
+
+
+def test_no_record_is_annotated_with_a_vacuous_process():
+ """The gate."""
+ offenders = [
+ f"{name}: {preferred!r} -> {identifier} ({_VACUOUS[identifier]})"
+ for name, preferred, identifier, _label in _descriptors()
+ if identifier in _VACUOUS
+ ]
+ assert offenders == [], (
+ "these annotations are true of every community in the KB and so "
+ "distinguish nothing (#182). Ground the concept in `preferred_term` to "
+ "a specific current GO term, or drop the descriptor:\n"
+ + "\n".join(f" {line}" for line in offenders)
+ )
+
+
+def test_the_upgraded_terms_are_present_and_correctly_labelled():
+ """The seven that were re-grounded rather than deleted.
+
+ Asserts the label travelled with the id: a remap that changed one and not
+ the other is the exact defect #180 existed to clear, and re-introducing it
+ here would be ironic.
+ """
+ by_id = {}
+ for _name, _preferred, identifier, label in _descriptors():
+ by_id.setdefault(identifier, set()).add(label)
+
+ assert by_id.get("GO:0009056") == {
+ "catabolic process"
+ }, f"GO:0009056 carries unexpected labels: {by_id.get('GO:0009056')}"
+ assert by_id.get("GO:0071941") == {
+ "nitrogen cycle metabolic process"
+ }, f"GO:0071941 carries unexpected labels: {by_id.get('GO:0071941')}"
+
+
+@pytest.mark.parametrize("identifier", sorted(_VACUOUS))
+def test_every_blocked_term_has_a_reason(identifier):
+ """A blocklist without reasons becomes a place things get added silently."""
+ assert _VACUOUS[identifier].strip()
+
+
+def test_the_walk_reaches_the_corpus():
+ """An empty walk passes the gate as surely as a clean corpus does."""
+ seen = list(_descriptors())
+ assert len(seen) > 500, f"only {len(seen)} descriptors walked; the walk is broken"
+
+
+def test_the_gate_can_fire(tmp_path):
+ """Mutation check, driving the real walk over a record built to offend."""
+ corpus = tmp_path / "communities"
+ corpus.mkdir()
+ (corpus / "r.yaml").write_text(
+ "id: CommunityMech:000999\n"
+ "ecological_interactions:\n"
+ "- biological_processes:\n"
+ " - preferred_term: metabolic process\n"
+ " term:\n"
+ " id: GO:0008152\n"
+ " label: metabolic process\n",
+ encoding="utf-8",
+ )
+ offenders = [d for d in _descriptors(corpus) if d[2] in _VACUOUS]
+ assert offenders, "the gate found nothing in a record built to contain the defect"
+ assert offenders[0][1] == "metabolic process"
+
+
+def test_a_specific_process_is_not_flagged(tmp_path):
+ """Guard against the gate being over-broad.
+
+ A correctly grounded specific term also has `preferred_term == label`; that
+ is what good curation looks like, not a defect. The gate must key on the
+ identifier, never on the two strings matching.
+ """
+ corpus = tmp_path / "communities"
+ corpus.mkdir()
+ (corpus / "r.yaml").write_text(
+ "id: CommunityMech:000998\n"
+ "ecological_interactions:\n"
+ "- biological_processes:\n"
+ " - preferred_term: nitrogen cycle metabolic process\n"
+ " term:\n"
+ " id: GO:0071941\n"
+ " label: nitrogen cycle metabolic process\n",
+ encoding="utf-8",
+ )
+ assert [d for d in _descriptors(corpus) if d[2] in _VACUOUS] == []