1db5c89d97fdd3c3580c4ba9b18eba6b5029c97d
max
  Thu Sep 17 05:09:56 2026 -0700
hgSearch: highlight MANE transcript(s) in RefSeq protein-position search results, refs #38285

#Preview2 week - bugs introduced now will need a build patch to fix
A gene-symbol + codon-range search (e.g. "BRCA1 100-200") maps to every
RefSeq isoform prediction sharing that genomic footprint, which can be
dozens of near-identical NP_ accessions with no indication of which one
is the clinically-relevant MANE transcript. Cross-reference the mane
bigGenePred track by genomic overlap + protein accession and call out
the MANE Select/Plus Clinical transcript(s) in their own section above
the rest, gated behind showManeInSearch (default off) in hg.conf.

diff --git src/hg/lib/bigBedFind.c src/hg/lib/bigBedFind.c
index 70f19589be0..3e195b0d85f 100644
--- src/hg/lib/bigBedFind.c
+++ src/hg/lib/bigBedFind.c
@@ -467,15 +467,116 @@
             indexField = "name";
         slNameFreeList(&indexFields);
         }
     if (!indexField)
         {
         bigBedFileClose(&bbi);
         continue;
         }
 
     // finally we have verified the track is searchable, add it to our list we're returning
     // careful not to mess up the old list
     slAddHead(&ret, CloneVar(tdb));
     }
 return ret;
 }
+
+struct maneLookup
+/* An open handle on the "mane" bigGenePred track, for repeated region lookups. */
+    {
+    struct bbiFile *bbi;
+    int protAccIx;
+    int maneStatIx;
+    };
+
+struct maneLookup *maneLookupOpen(char *db, struct trackDb **tdbList)
+/* Open the "mane" bigGenePred track for db, if it exists, for repeated calls to
+ * maneStatusForRegion.  Returns NULL if this assembly has no mane track (e.g. non-human,
+ * or hg19) -- callers should treat that as "can't tell", not an error.  tdbList is passed
+ * through to tdbForTrack, so pass a pointer already shared with other tdbForTrack calls in
+ * the same request to avoid a second trackDb load. */
+{
+struct trackDb *tdb = tdbForTrack(db, "mane", tdbList);
+if (tdb == NULL)
+    return NULL;
+char *fileName = trackDbSetting(tdb, "bigDataUrl");
+if (isEmpty(fileName))
+    return NULL;
+
+struct maneLookup *ml = NULL;
+struct errCatch *errCatch = errCatchNew();
+if (errCatchStart(errCatch))
+    {
+    struct bbiFile *bbi = bigBedFileOpen(hReplaceGbdb(fileName));
+    struct asObject *as = bigBedAsOrDefault(bbi);
+    AllocVar(ml);
+    ml->bbi = bbi;
+    ml->protAccIx = asColumnFindIx(as->columnList, "ncbiProtAcc");
+    ml->maneStatIx = asColumnFindIx(as->columnList, "maneStat");
+    }
+errCatchEnd(errCatch);
+if (errCatch->gotError)
+    ml = NULL;
+errCatchFree(&errCatch);
+return ml;
+}
+
+static boolean protAccMatches(char *rowProtAcc, struct slName *protAccList)
+/* Return TRUE if rowProtAcc matches one of protAccList, ignoring version (".N") suffixes. */
+{
+char *rowDot = strchr(rowProtAcc, '.');
+int rowLen = rowDot ? (rowDot - rowProtAcc) : (int)strlen(rowProtAcc);
+struct slName *pa;
+for (pa = protAccList; pa != NULL; pa = pa->next)
+    {
+    char *paDot = strchr(pa->name, '.');
+    int paLen = paDot ? (paDot - pa->name) : (int)strlen(pa->name);
+    if (rowLen > 0 && rowLen == paLen && memcmp(rowProtAcc, pa->name, rowLen) == 0)
+        return TRUE;
+    }
+return FALSE;
+}
+
+char *maneStatusForRegion(struct maneLookup *ml, char *chrom, int start, int end,
+                          struct slName *protAccList, char **retProtAcc)
+/* Look for a MANE transcript overlapping chrom:start-end whose NCBI protein accession
+ * (ignoring version suffix) matches one of protAccList.  Returns a cloned "MANE Select" /
+ * "MANE Plus Clinical" string, or NULL if ml is NULL or there is no match.  On a match,
+ * *retProtAcc is set to a cloned copy of the matching (versioned) NCBI protein accession,
+ * so callers can identify the single MANE transcript out of a group of merged accessions. */
+{
+if (ml == NULL || ml->protAccIx < 0 || ml->maneStatIx < 0)
+    return NULL;
+char *result = NULL;
+struct lm *lm = lmInit(0);
+struct errCatch *errCatch = errCatchNew();
+if (errCatchStart(errCatch))
+    {
+    struct bigBedInterval *intervalList = bigBedIntervalQuery(ml->bbi, chrom, start, end, 0, lm);
+    struct bigBedInterval *interval;
+    char startBuf[16], endBuf[16], *row[ml->bbi->fieldCount];
+    for (interval = intervalList; interval != NULL && result == NULL; interval = interval->next)
+        {
+        bigBedIntervalToRow(interval, chrom, startBuf, endBuf, row, ml->bbi->fieldCount);
+        if (protAccMatches(row[ml->protAccIx], protAccList))
+            {
+            result = cloneString(row[ml->maneStatIx]);
+            if (retProtAcc != NULL)
+                *retProtAcc = cloneString(row[ml->protAccIx]);
+            }
+        }
+    }
+errCatchEnd(errCatch);
+errCatchFree(&errCatch);
+lmCleanup(&lm);
+return result;
+}
+
+void maneLookupClose(struct maneLookup **pMl)
+/* Close a maneLookup opened by maneLookupOpen. */
+{
+if (pMl != NULL && *pMl != NULL)
+    {
+    bigBedFileClose(&(*pMl)->bbi);
+    freez(pMl);
+    }
+}