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/cgilib/cartJson.c src/hg/cgilib/cartJson.c
index 43a6adc726e..0a7ca05d62c 100644
--- src/hg/cgilib/cartJson.c
+++ src/hg/cgilib/cartJson.c
@@ -1,24 +1,26 @@
 /* cartJson - parse and execute JSON commands to update cart and/or return cart data as JSON. */
 #include "common.h"
 #include "cartJson.h"
 #include "cartTrackDb.h"
 #include "cheapcgi.h"
 #include "errCatch.h"
 #include "grp.h"
 #include "hdb.h"
+#include "hgConfig.h"
 #include "hgFind.h"
+#include "bigBedFind.h"
 #include "htmlSanitize.h"
 #include "htmshell.h"
 #include "hubConnect.h"
 #include "hui.h"
 #include "jsonParse.h"
 #include "obscure.h"
 #include "regexHelper.h"
 #include "suggest.h"
 #include "trackDb.h"
 #include "trackHub.h"
 #include "web.h"
 
 char *cartJsonOptionalParam(struct hash *paramHash, char *name)
 /* Convenience function for a CartJsonHandler function: Look up name in paramHash.
  * Return the string contained in its jsonElement value, or NULL if not found. */
@@ -62,30 +64,37 @@
 	    strlen(textOut) + 1 - matches[0].rm_eo);
     if (regexMatchSubstrNoCase(textOut, "</a>", matches, ArraySize(matches)))
 	memmove(textOut+matches[0].rm_so, textOut+matches[0].rm_eo,
 		strlen(textOut) + 1 - matches[0].rm_eo);
     return textOut;
     }
 return textIn;
 }
 
 void hgPositionsJson(struct jsonWrite *jw, char *db, struct hgPositions *hgp, struct cart *cart)
 /* Write out JSON description of multiple position matches. */
 {
 struct hgPosTable *table;
 jsonWriteListStart(jw, "positionMatches");
 struct trackDb *tdbList = NULL;
+// Opened lazily, on the first RefSeq/refGene hit (most searches never need it), and
+// sharing tdbList with the tdbForTrack calls below so it costs at most one extra
+// trackDb load per request, not a second one on top of theirs.
+struct maneLookup *maneLookup = NULL;
+boolean maneLookupAttempted = FALSE;
+boolean measureTiming = cartUsualBoolean(cart, "measureTiming", FALSE);
+long maneLookupTimeMs = 0;
 for (table = hgp->tableList; table != NULL; table = table->next)
     {
     if (table->posList != NULL)
         {
         char *trackName = table->name, *tableName = table->name;
         struct trackDb *tdb = NULL;
         // these are pseudo-table names with no trackDb entry to look up. chromInfo is what
         // hgFind uses for a plain position range or a genomic HGVS match
         if (! (sameString("trackDb", tableName) || sameString("helpDocs", tableName) ||
                 sameString("publicHubs", tableName) || sameString("chromInfo", tableName)))
             {
             // a native tdbList carried over from an earlier table won't hold hub tracks,
             // drop it so tdbForTrack takes its hub lookup path
             if (isHubTrack(tableName))
                 tdbList = NULL;
@@ -126,44 +135,80 @@
             if (tdb && tdb->parent)
                 {
                 if (tdbIsSuperTrackChild(tdb))
                     jsonWriteStringf(jw, "extraSel", "%s=show&", tdb->parent->track);
                 else
                     {
                     // tdb is a subtrack of a composite or a view
                     jsonWriteStringf(jw, "extraSel", "%s_sel=1&%s_sel=1&",
                                      trackName, tdb->parent->track);
                     }
                 }
             jsonWriteString(jw, "hgFindMatches", encMatches);
             jsonWriteString(jw, "posName", htmlEncode(pos->name));
             jsonWriteString(jw, "highlight", pos->highlight);
             jsonWriteBoolean(jw, "canonical", pos->canonical);
+            // MANE annotation only applies to RefSeq transcript hits (the pseudo-HGVS
+            // gene-symbol + codon-range search returns ncbiRefSeq*/refGene matches keyed
+            // by "/"-joined NP_ protein accessions in pos->name).
+            if (cfgOptionBooleanDefault("showManeInSearch", FALSE) && pos->chrom != NULL &&
+                (startsWith("ncbiRefSeq", trackName) || sameString("refGene", trackName)))
+                {
+                if (!maneLookupAttempted)
+                    {
+                    long maneOpenT0 = clock1000();
+                    maneLookup = maneLookupOpen(db, &tdbList);
+                    maneLookupTimeMs += clock1000() - maneOpenT0;
+                    maneLookupAttempted = TRUE;
+                    }
+                if (maneLookup != NULL)
+                    {
+                    struct slName *protAccList = slNameListFromString(pos->name, '/');
+                    long maneT0 = clock1000();
+                    char *maneProtAcc = NULL;
+                    char *maneStatus = maneStatusForRegion(maneLookup, pos->chrom,
+                                                           pos->chromStart, pos->chromEnd,
+                                                           protAccList, &maneProtAcc);
+                    maneLookupTimeMs += clock1000() - maneT0;
+                    if (maneStatus != NULL)
+                        {
+                        jsonWriteString(jw, "maneStatus", maneStatus);
+                        // the single accession that is the actual MANE transcript, since
+                        // pos->name/posName above may be a "/"-joined group of transcripts
+                        // that all share this genomic footprint
+                        jsonWriteString(jw, "maneProtAcc", maneProtAcc);
+                        }
+                    slFreeList(&protAccList);
+                    }
+                }
             if (pos->description)
                 {
                 stripString(pos->description, "\n");
                 jsonWriteString(jw, "description", stripAnchor(pos->description));
                 }
             jsonWriteObjectEnd(jw); // end one match
             }
         jsonWriteListEnd(jw); // end matches
         if (table->searchTime >= 0)
             jsonWriteNumber(jw, "searchTime", table->searchTime);
         jsonWriteObjectEnd(jw); // end one table
         }
     }
     jsonWriteListEnd(jw); // end positionMatches
+if (measureTiming && maneLookupAttempted)
+    jsonWriteNumber(jw, "maneLookupTimeMs", maneLookupTimeMs);
+maneLookupClose(&maneLookup);
 }
 
 static struct hgPositions *collapseHgpList(struct hgPositions *hgpList, struct jsonWrite *jw, char *searchTerm, char *db)
 /* Combine a bunch of singlePos hgps together */
 {
 struct hgPositions *hgp = hgpList, *final = NULL;
 AllocVar(final);
 final->query = searchTerm;
 final->database = cloneString(hgp->database);
 final->tableList = hgp->tableList;
 final->posCount = hgp->posCount;
 final->singlePos = hgp->singlePos;
 final->extraCgi = cloneString(final->extraCgi);
 final->useAlias = hgp->useAlias;
 final->shortCircuited = hgp->shortCircuited;