Summary
GET /api/ontology/ribbon/ returns 500 for subset=goslim_agr (the
default Alliance / MOD gene-ribbon use). The failure is deterministic — it does
not depend on the subject — so the ribbon widget is effectively down for every
caller.
This is triggered by a GOlr ontology-data change, not by an API version. It
reproduces on both the current v0.3.x production build and v0.4.0,
which share the identical GOlr-based subset code — so rolling the API back does
not mitigate it. (The separate v0.4.0 GO-CAM index-file 500s were a
different fault, already fixed by #158/#161 — please don't conflate the two.)
Index-backed endpoints (/api/models/go, /api/gp/{id}/models, slimmer,
ontology/term, subgraph, bioentity) are all healthy; this is isolated to
the ribbon / subset path.
Live reproduction (for others to test)
1. Public API endpoint — currently 500ing on production:
https://api.geneontology.org/api/ontology/ribbon/?subset=goslim_agr&subject=MGI:98214&exclude_IBA=false&exclude_PB=false&cross_aspect=false
curl -sS -o /dev/null -w "HTTP %{http_code}\n" \
"https://api.geneontology.org/api/ontology/ribbon/?subset=goslim_agr&subject=MGI:98214&exclude_IBA=false&exclude_PB=false&cross_aspect=false"
# -> HTTP 500 {"message":"An unexpected error occurred. Please try again later."}
2. Durable root-cause check at the GOlr layer (version-independent — this is
the underlying data condition, and will keep reproducing even if the API is
redeployed/rolled back):
curl -sG 'https://golr.geneontology.org/solr/select' \
--data-urlencode 'q=annotation_class_label:"molecular_function"' \
--data-urlencode 'fq=document_category:"ontology_class"' \
--data-urlencode 'rows=0' --data-urlencode 'wt=json'
# -> "numFound":0 (biological_process and cellular_component each return 1)
Root cause
Subset resolution is served by GOlr in
app/utils/ontology_utils.py::get_ontology_subsets_by_id (present since the
Blazegraph-removal work; in v0.3.9, v0.3.11, and v0.4.0 alike). It:
- Queries GOlr for the slim terms and groups them by the
source field, keying
each category by the aspect string — molecular_function,
biological_process, cellular_component (ontology_utils.py:102).
- Runs a second GOlr query and back-fills each category's root
annotation_class only when an ontology doc has
annotation_class_label == <the source string> (ontology_utils.py:111-118).
That self-join now breaks for molecular_function, because the MF root term
GO:0003674 has been relabelled in the current GOlr index:
# GOlr ontology_class doc for GO:0003674
annotation_class: GO:0003674
annotation_class_label: "gene product or complex activity" # <- no longer "molecular_function"
source: molecular_function
# numFound for annotation_class_label:<aspect> (document_category:ontology_class)
molecular_function -> 0 <-- no match, annotation_class never set
biological_process -> 1 (GO:0008150)
cellular_component -> 1 (GO:0005575)
So tr["molecular_function"] is produced without an annotation_class key,
then consumed unguarded:
ontology_utils.py:132 — in the goslim_agr reorder: if category["annotation_class"] == cat: → KeyError
app/routers/ribbon.py:117 — category["id"] = category["annotation_class"] (same bare index on the non-reordered path; line 114 in v0.3.9)
→ unhandled KeyError → 500. Same data-drift class as #159 (GOlr
annotation_class / annotation_class_label drift).
Suggested fix
The aspect→root mapping is known unambiguously and shouldn't depend on a label
round-trip:
- Resolve each category's root
annotation_class from the aspect / GO ID
rather than by matching annotation_class_label. ribbon.py already defines
aspect_map = {"P": "GO:0008150", "F": "GO:0003674", "C": "GO:0005575"}; the
same source→GO-ID mapping can populate annotation_class directly in
get_ontology_subsets_by_id.
- Defensively guard the unguarded reads (
ontology_utils.py:132,
ribbon.py:114-118) — .get(...) / skip incomplete categories — so a single
ontology label change can never 500 the entire widget again.
- Add a ribbon regression test covering
subset=goslim_agr asserting all three
aspects resolve.
Separately: if the GO:0003674 relabel to "gene product or complex activity"
in the GOlr index was unintended, fixing the ontology data is the other lever
— but the code should be robust to it regardless.
Related
— Filed by Claude Code agent on behalf of @kltm.
Summary
GET /api/ontology/ribbon/returns 500 forsubset=goslim_agr(thedefault Alliance / MOD gene-ribbon use). The failure is deterministic — it does
not depend on the
subject— so the ribbon widget is effectively down for everycaller.
This is triggered by a GOlr ontology-data change, not by an API version. It
reproduces on both the current
v0.3.xproduction build andv0.4.0,which share the identical GOlr-based subset code — so rolling the API back does
not mitigate it. (The separate
v0.4.0GO-CAM index-file 500s were adifferent fault, already fixed by #158/#161 — please don't conflate the two.)
Index-backed endpoints (
/api/models/go,/api/gp/{id}/models,slimmer,ontology/term,subgraph,bioentity) are all healthy; this is isolated tothe ribbon / subset path.
Live reproduction (for others to test)
1. Public API endpoint — currently 500ing on production:
2. Durable root-cause check at the GOlr layer (version-independent — this is
the underlying data condition, and will keep reproducing even if the API is
redeployed/rolled back):
Root cause
Subset resolution is served by GOlr in
app/utils/ontology_utils.py::get_ontology_subsets_by_id(present since theBlazegraph-removal work; in
v0.3.9,v0.3.11, andv0.4.0alike). It:sourcefield, keyingeach category by the aspect string —
molecular_function,biological_process,cellular_component(ontology_utils.py:102).annotation_classonly when an ontology doc hasannotation_class_label == <the source string>(ontology_utils.py:111-118).That self-join now breaks for molecular_function, because the MF root term
GO:0003674has been relabelled in the current GOlr index:So
tr["molecular_function"]is produced without anannotation_classkey,then consumed unguarded:
ontology_utils.py:132— in thegoslim_agrreorder:if category["annotation_class"] == cat:→KeyErrorapp/routers/ribbon.py:117—category["id"] = category["annotation_class"](same bare index on the non-reordered path; line 114 inv0.3.9)→ unhandled
KeyError→ 500. Same data-drift class as #159 (GOlrannotation_class/annotation_class_labeldrift).Suggested fix
The aspect→root mapping is known unambiguously and shouldn't depend on a label
round-trip:
annotation_classfrom the aspect / GO IDrather than by matching
annotation_class_label.ribbon.pyalready definesaspect_map = {"P": "GO:0008150", "F": "GO:0003674", "C": "GO:0005575"}; thesame source→GO-ID mapping can populate
annotation_classdirectly inget_ontology_subsets_by_id.ontology_utils.py:132,ribbon.py:114-118) —.get(...)/ skip incomplete categories — so a singleontology label change can never 500 the entire widget again.
subset=goslim_agrasserting all threeaspects resolve.
Separately: if the
GO:0003674relabel to "gene product or complex activity"in the GOlr index was unintended, fixing the ontology data is the other lever
— but the code should be robust to it regardless.
Related
annotation_classdrift (same root-cause class)v0.4.0GO-CAM index issue (not this)— Filed by Claude Code agent on behalf of @kltm.