Skip to content

Commit b67b692

Browse files
authored
fix(proteins): match O/P/Q UniProt accessions in looksLikeAccession (#104)
The leading character class /^[A-NR-Z0-9].../ excluded O, P and Q — the letters that begin most Swiss-Prot accessions (P04637, P38398, O43426, Q9Y6K9, and the docstring's own P00520). looksLikeAccession is the fast-path guard in the AlphaFold and SIFTS connectors: when it returns false the direct exact-accession lookup is skipped, so the most common human proteins silently fell through to the slower name-search path. Widen the first character to [A-Z0-9] and add coverage for O/P/Q prefixes, long TrEMBL accessions, and rejection of free-text names.
1 parent ca259fd commit b67b692

2 files changed

Lines changed: 50 additions & 2 deletions

File tree

backend/cli/src/science/connectors/proteins/util.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,14 @@ export function asArray<T = unknown>(value: unknown): T[] {
3030
return Array.isArray(value) ? (value as T[]) : []
3131
}
3232

33-
/** True when a string looks like a UniProt accession (e.g. P00520, A0A0B5AC95). */
33+
/** True when a string looks like a UniProt accession (e.g. P00520, A0A0B5AC95).
34+
* The leading character must accept O, P and Q: Swiss-Prot accessions
35+
* overwhelmingly begin with them (P04637, P38398, O43426, Q9Y6K9, and this
36+
* file's own P00520 example). An earlier `[A-NR-Z0-9]` class silently excluded
37+
* O/P/Q, so the AlphaFold and SIFTS connectors skipped their fast-path exact-ac
38+
* lookup for the most common human proteins. */
3439
export function looksLikeAccession(value: string): boolean {
35-
return /^[A-NR-Z0-9][A-Z0-9]{5,9}$/i.test(value.trim())
40+
return /^[A-Z0-9][A-Z0-9]{5,9}$/i.test(value.trim())
3641
}
3742

3843
interface UniProtLite {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { describe, expect, test } from "bun:test"
2+
import { clampLimit, firstString, looksLikeAccession } from "../../src/science/connectors/proteins/util"
3+
4+
describe("looksLikeAccession", () => {
5+
test("accepts Swiss-Prot accessions that begin with O, P or Q", () => {
6+
// The O/P/Q prefixes cover most human proteins; the old [A-NR-Z0-9] class
7+
// excluded them, breaking the AlphaFold/SIFTS exact-ac fast path.
8+
for (const ac of ["P04637", "P38398", "O43426", "Q9Y6K9", "P00520"]) {
9+
expect(looksLikeAccession(ac)).toBe(true)
10+
}
11+
})
12+
13+
test("accepts long (10-char) TrEMBL accessions", () => {
14+
expect(looksLikeAccession("A0A0B5AC95")).toBe(true)
15+
})
16+
17+
test("trims surrounding whitespace before matching", () => {
18+
expect(looksLikeAccession(" P04637 ")).toBe(true)
19+
})
20+
21+
test("rejects free-text names and out-of-range lengths", () => {
22+
expect(looksLikeAccession("p53")).toBe(false)
23+
expect(looksLikeAccession("tumor protein")).toBe(false)
24+
expect(looksLikeAccession("A")).toBe(false)
25+
expect(looksLikeAccession("A0A0B5AC95X1")).toBe(false)
26+
})
27+
})
28+
29+
describe("clampLimit", () => {
30+
test("clamps into [1, max] and defaults when unset", () => {
31+
expect(clampLimit(undefined, 5, 25)).toBe(5)
32+
expect(clampLimit(0, 5, 25)).toBe(1)
33+
expect(clampLimit(999, 5, 25)).toBe(25)
34+
expect(clampLimit(3.9, 5, 25)).toBe(3)
35+
})
36+
})
37+
38+
describe("firstString", () => {
39+
test("returns the first non-empty string", () => {
40+
expect(firstString(undefined, "", " ", "hit", "next")).toBe("hit")
41+
expect(firstString(1, null, {})).toBeUndefined()
42+
})
43+
})

0 commit comments

Comments
 (0)