diff --git a/tests/test_norm.py b/tests/test_norm.py index bb83fb0..11ae4ed 100644 --- a/tests/test_norm.py +++ b/tests/test_norm.py @@ -86,3 +86,33 @@ def test_normalize_and_remove_extra_edges(self): with open(self.chebi_graph_edges, "r") as infile: afterlen = len(infile.readlines()) self.assertLess(afterlen, beforelen, "Edges not removed?") + + +class TestObohandle(TestCase): + """Tests for `obo_handle`'s prefix-casing behaviour.""" + + def test_uppercase_prefix_unchanged(self): + """Pure-uppercase OBO prefixes survive obo_handle as-is. + + HP, MONDO, UBERON, CL, etc. were already uppercase, so the previous + `.upper()` was a no-op for them. Removing that call must not regress + the no-op. + """ + from universalizer.norm import obo_handle + self.assertEqual(obo_handle("OBO:HP_0000118"), "HP:0000118") + self.assertEqual(obo_handle("OBO:MONDO_0000001"), "MONDO:0000001") + self.assertEqual(obo_handle("OBO:UBERON_0000061"), "UBERON:0000061") + self.assertEqual(obo_handle("OBO:CL_0000000"), "CL:0000000") + + def test_mixed_case_prefix_preserved(self): + """Mixed-case OBO prefixes (FBbt, WBbt) keep their canonical case. + + Regression test for the bug where `obo_handle` `.upper()`-ed the + prefix, turning `OBO:FBbt_00000001` into `FBBT:00000001` instead of + the canonical `FBbt:00000001`. This silently dropped every + Drosophila and C. elegans gene-expression edge in downstream + consumers (e.g. monarch-kg's alliance_gene_to_expression). + """ + from universalizer.norm import obo_handle + self.assertEqual(obo_handle("OBO:FBbt_00000001"), "FBbt:00000001") + self.assertEqual(obo_handle("OBO:WBbt_0000001"), "WBbt:0000001") diff --git a/universalizer/norm.py b/universalizer/norm.py index 574ca3f..02ca6af 100644 --- a/universalizer/norm.py +++ b/universalizer/norm.py @@ -502,10 +502,15 @@ def obo_handle(old_id: str) -> str: # Replace the first underscore with colon new_id = new_id.replace("_", ":", 1) - # Capitalize the prefix, but not the rest - # of the ID - split_id = new_id.split(":", 1) - new_id = f"{split_id[0].upper()}:{split_id[1]}" + # Preserve the canonical case of the prefix as it appears in the + # source OBO PURL — `FBbt`, `WBbt` etc. stay mixed-case; pure + # uppercase prefixes (HP, MONDO, UBERON, CL, ...) are unchanged. + # Previously this `.upper()`-ed the whole prefix, which broke any + # mixed-case OBO namespace by emitting e.g. `FBBT:` instead of the + # canonical `FBbt:`. That caused every Drosophila and C. elegans + # gene-expression edge in monarch-kg to dangle (~503k edges per + # release on `alliance_gene_to_expression_edges`), because Alliance + # was looking up nodes by the canonical mixed-case CURIE. else: new_id = old_id