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, "", 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;