a05e08a84139434ed78426823738269d12cc323e
chmalee
  Tue Jul 27 17:13:48 2021 -0700
When an hgvs search results in multiple transcripts at that map to the same location, only give the user the list of unique positions, or just go straight to the position if there is only one unique position, refs #15554

diff --git src/hg/lib/hgFind.c src/hg/lib/hgFind.c
index c2b7e24..d0a06bf 100644
--- src/hg/lib/hgFind.c
+++ src/hg/lib/hgFind.c
@@ -2716,106 +2716,145 @@
 	hfs = hfsFind(longList, search);
     if (hfs != NULL)
 	foundIt = hgFindUsingSpec(cart, db, hfs, term, limitResults, hgp, FALSE, 0,0, FALSE);
     else
 	warn("Unrecognized singleSearch=%s in URL", search);
     }
 if (foundIt)
     {
     fixSinglePos(hgp);
     if (cart != NULL)
         cartSetString(cart, "hgFind.matches", hgp->tableList->posList->browserName);
     }
 return foundIt;
 }
 
+// a little data structure for combining multiple transcripts that resolve
+// to the same hgvs change. This struct can be used to fill out a struct hgPos
+struct hgvsHelper
+    {
+    struct hgvsHelper *next;
+    char *chrom; // chromosome name of position
+    int chromStart; // start of position
+    int chromEnd; // end of position
+    struct slName *validTranscripts; // valid transcripts/protein accessions for this position
+    char *label; // corresponding hgvs term
+    char *table; // type of match, LRG, NCBI, etc
+    };
+
 static boolean matchesHgvs(struct cart *cart, char *db, char *term, struct hgPositions *hgp)
 /* Return TRUE if the search term looks like a variant encoded using the HGVS nomenclature
  * See http://varnomen.hgvs.org/
  * If search term is a pseudo hgvs term like GeneName AminoAcidPosition (RUNX2 Arg155) and
  * matches more than one transcript, fill out the hgp with the potential matches so the user
  * can choose where to go, otherwise return a singlePos */
 {
 boolean foundIt = FALSE;
 struct hgvsVariant *hgvsList = hgvsParseTerm(term);
 if (hgvsList == NULL)
     hgvsList = hgvsParsePseudoHgvs(db, term);
 if (hgvsList)
     {
     struct hgvsVariant *hgvs = NULL;
     int hgvsListLen = slCount(hgvs);
     struct hgPosTable *table;
     AllocVar(table);
     table->description = "HGVS";
+    int padding = 5;
     struct dyString *dyWarn = dyStringNew(0);
+    struct hgvsHelper *helper = NULL;
+    struct hash *uniqHgvsPos = hashNew(0);
+    struct dyString *chromPosIndex = dyStringNew(0);
     for (hgvs = hgvsList; hgvs != NULL; hgvs = hgvs->next)
         {
         dyStringClear(dyWarn);
+        dyStringClear(chromPosIndex);
         char *pslTable = NULL;
         struct bed *mapping = hgvsValidateAndMap(hgvs, db, term, dyWarn, &pslTable);
         if (dyStringLen(dyWarn) > 0)
             {
             if (hgvsListLen == 1)
                 {
                 warn("%s", dyStringContents(dyWarn));
                 }
             else
                 {
                 continue;
                 }
             }
         if (mapping)
             {
-            int padding = 5;
             char *trackTable;
             if (isEmpty(pslTable))
                 trackTable = "chromInfo";
             else if (startsWith("lrg", pslTable))
                 trackTable = "lrgTranscriptAli";
             else if (startsWith("wgEncodeGencode", pslTable))
                 trackTable = pslTable;
             else if (startsWith("ncbiRefSeqPsl", pslTable))
                 {
                 if (startsWith("NM_", hgvs->seqAcc) || startsWith("NR_", hgvs->seqAcc) ||
                     startsWith("NP_", hgvs->seqAcc) || startsWith("YP_", hgvs->seqAcc))
                     trackTable = "ncbiRefSeqCurated";
                 else if (startsWith("XM_", hgvs->seqAcc) || startsWith("XR_", hgvs->seqAcc) ||
                          startsWith("XP_", hgvs->seqAcc))
                     trackTable = "ncbiRefSeqPredicted";
                 else
                     trackTable = "ncbiRefSeq";
                 }
             else
                 trackTable = "refGene";
+            dyStringPrintf(chromPosIndex, "%s%s%d%d", trackTable, mapping->chrom,
+                    mapping->chromStart-padding, mapping->chromStart+padding);
+            if ((helper = hashFindVal(uniqHgvsPos, chromPosIndex->string)) != NULL)
+                {
+                slNameAddHead(&helper->validTranscripts, hgvs->seqAcc);
+                }
+            else
+                {
+                AllocVar(helper);
+                helper->chrom = mapping->chrom;
+                helper->chromStart = mapping->chromStart;
+                helper->chromEnd = mapping->chromEnd;
+                helper->validTranscripts = slNameNew(hgvs->seqAcc);
+                helper->label = cloneString(term);
+                helper->table = trackTable;
+                hashAdd(uniqHgvsPos, chromPosIndex->string, helper);
+                }
+            }
+        }
+    struct hashEl *hel, *helList= hashElListHash(uniqHgvsPos);
+    for (hel = helList; hel != NULL; hel = hel->next)
+        {
         if (hgp->tableList == NULL)
             hgp->tableList = table;
-            table->name = trackTable;
+        helper = (struct hgvsHelper *)hel->val;
+        table->name = helper->table;
         struct hgPos *pos;
         AllocVar(pos);
-            pos->chrom = mapping->chrom;
-            pos->chromStart = mapping->chromStart-padding;
-            pos->chromEnd = mapping->chromEnd+padding;
-            pos->name = cloneString(hgvs->seqAcc);
-            pos->description = cloneString(term);
+        pos->chrom = helper->chrom;
+        pos->chromStart = helper->chromStart - padding;
+        pos->chromEnd = helper->chromEnd + padding;
+        pos->name = slNameListToString(helper->validTranscripts, '/');
+        pos->description = cloneString(helper->label);
         pos->browserName = "";
         slAddHead(&table->posList, pos);
         // highlight the mapped bases to distinguish from padding
-            hgp->tableList->posList->highlight = addHighlight(db, mapping->chrom,
-                                                    mapping->chromStart, mapping->chromEnd);
+        hgp->tableList->posList->highlight = addHighlight(db, helper->chrom,
+                                                helper->chromStart, helper->chromEnd);
         foundIt = TRUE;
         }
-        }
     dyStringFree(&dyWarn);
     }
 return foundIt;
 }
 
 struct hgPositions *hgPositionsFind(char *db, char *term, char *extraCgi,
 	char *hgAppNameIn, struct cart *cart, boolean multiTerm)
 /* Return container of tracks and positions (if any) that match term. */
 {
 struct hgPositions *hgp = NULL, *hgpItem = NULL;
 regmatch_t substrs[4];
 boolean canonicalSpec = FALSE;
 boolean gbrowserSpec = FALSE;
 boolean lengthSpec = FALSE;
 boolean singleBaseSpec = FALSE;