af613a331e6839c6513c3e366abcb67af0fe8386
max
  Wed Sep 9 06:47:14 2026 -0700
UniProt otto: get the monthly update running again and make a stalled run visible

The monthly UniProt job had produced nothing since January 2025. The tracks
served release 2024_06 while the download sitting on disk was at 2026_02, on
every assembly the job builds.

Cause: uniprotToTab appended a personal conda site-packages directory to
sys.path, and doUpdate.sh sourced a virtualenv, both built for python 3.6. A
venv's python is only a symlink to the system one, so when hgwdev moved to
python 3.9 the compiled lxml in there stopped loading and every run died at the
parse step. Removed both. The system python3 has lxml from python3-lxml and the
two are upgraded together, so there is nothing left here to go stale. Verified
by parsing real 2026_02 records under python 3.9 with lxml 5.4.

Why nobody noticed for nineteen months:

- doUpdate.sh read $? after an intervening echo, so it captured the echo's exit
code and mailed "Big Uniprot update OK" every month while the job was dying.
It now reads the real exit code, says FAILED, prints the tail of the log and
exits nonzero. A month with no new UniProt release stays silent, which is the
normal otto behaviour, so silence again means "nothing to do".
- The logs were overwritten on every run, so a failure left no trace on disk.
doUpdate.sh now appends one line per run to runLog.txt, which is never
truncated, and keeps a failing log as lastFail.log.
- version.txt in each bigBed directory was rewritten on every run even when the
release string was identical. That is the file the trackDb dataVersion setting
shows, and its date is what people check to decide whether a pipeline is still
alive, so a stalled track could look freshly updated. It is now written only
when the release actually changes.

Also, so this cannot come back:

- doUniprot checks that uniprotToTab can start before the download, instead of
finding out 35 minutes later.
- pylint on hgwdev is itself pinned to pythons that no longer exist, so
"make install" aborted on its first line and could not be used. Replaced with
a syntax check that needs nothing but python3; pylint stays best-effort.
- uniprotToTab, pslProtCnv, trackDb.template.txt and README.txt ran from
/hive/data/outside/otto/uniprot without being in the makefile's copy list.
The tree copy of uniprotToTab was still python 2 from 2021. All are now
listed and in sync, and "make diff" reports drift.
- Brought the two live-only fixes into the tree: mkdir -p in makeUniProtPsl.sh
and the pslMap -inType/-mapType flags.

refs #38300

diff --git src/hg/utils/otto/uniprot/makeUniProtPsl.sh src/hg/utils/otto/uniprot/makeUniProtPsl.sh
index c2166844c38..61090c7db74 100755
--- src/hg/utils/otto/uniprot/makeUniProtPsl.sh
+++ src/hg/utils/otto/uniprot/makeUniProtPsl.sh
@@ -94,32 +94,32 @@
 #rm -rf $WORKDIR
 
 mkdir -p $WORKDIR
 
 cp ${UNIPROTFAGZ} $WORKDIR/uniProt.fa
 cp $TRANSCRIPTFA $WORKDIR/transcripts.fa
 cp $TRANSCRIPTPSL $WORKDIR/transcripts.psl
 if [ "$PAIRNAME" != "" ] ; then
    cp $PAIRNAME $WORKDIR/upToTrans.pairs
 fi
 
 # setup blast uniprot -> transcript cDNAs
 if [ -f $WORKDIR/bestAln.psl ] ; then
         echo WARNING: re-using existing protein-transcript alignments to save time! see $WORKDIR/bestAln.psl
 else
-        mkdir $WORKDIR/queries 
-        mkdir $WORKDIR/aligns 
+        mkdir -p $WORKDIR/queries
+        mkdir -p $WORKDIR/aligns
         faSplit about $WORKDIR/uniProt.fa 2500 $WORKDIR/queries/
         ${BLASTDIR}/formatdb -i $WORKDIR/transcripts.fa -p F
 
         # create joblist and run
         set +x # quiet for now
         >$WORKDIR/jobList
         for i in $WORKDIR/queries/*.fa; do
                 echo "mapUniprot_doBlast transcripts.fa queries/`basename $i` {check out exists aligns/`basename $i .fa`.psl}" >> $WORKDIR/jobList
         done; 
         set -x
         cp mapUniprot_doBlast $WORKDIR/
         ssh $CLUSTER "cd `pwd`/$WORKDIR && para make jobList"
         echo Concatenating and filtering protein/transcript alignments
         # sort and pick the best alignments for each protein
         find $WORKDIR/aligns -name '*.psl' | xargs cat | pslReps -noIntrons -nohead -nearTop=0.01 -minAli=$MINALI stdin stdout /dev/null > $WORKDIR/bestAln.psl
@@ -131,22 +131,24 @@
     # - pass through all PSLs for queries that do not appear in our tables (=all Trembl and a few swissprot sequences missed by our tables)
     # - only compare the part before the dot
     # - use pslSwap as a workaround, as pslSelect doesn't have -tDelim yet and the query IDs don't have dots in them anyways
     #   too lazy to patch pslSelect now
     cat $WORKDIR/upToTrans.pairs | gawk '{OFS="\t"; print $2, $1; }' > $WORKDIR/transToUp.pairs
     pslSwap $WORKDIR/bestAln.psl stdout | pslSelect -qPass -qDelim=. -qtPairs=$WORKDIR/transToUp.pairs stdin stdout | pslSwap stdin $WORKDIR/uniProtVsTranscripts.psl
     # when we have no known gene table, blat the proteins directly and
     # take the top 1% alignments with 93% query coverage. Hopefully that gives similar results...
     # inspired by kent/src/hg/makeDb/doc/ucscGenes/hg19.ucscGenes13.csh
     # as requested by Alejo: lower to 93%, which are the pslReps defaults
 else
     cp $WORKDIR/bestAln.psl $WORKDIR/uniProtVsTranscripts.psl
 fi
 
 # now combine the two alignments with pslMap
-pslMap $WORKDIR/uniProtVsTranscripts.psl $WORKDIR/transcripts.psl $WORKDIR/uniProtVsGenome.psl -mapInfo=$WORKDIR/mapInfo.tab
+# the query is protein and the target is nucleotide, so pslMap has to be told the types,
+# otherwise it guesses and the block sizes come out in the wrong units
+pslMap $WORKDIR/uniProtVsTranscripts.psl $WORKDIR/transcripts.psl $WORKDIR/uniProtVsGenome.psl -mapInfo=$WORKDIR/mapInfo.tab -inType=prot_na -mapType=na_na
 # 2016: lowering to 95% identity due to hg38 alt loci sucking up our main (and more important) alignments from the
 # 2021: using MINALI is more consistent
 # normal chromosomes
 sort -k10,10 $WORKDIR/uniProtVsGenome.psl | pslCDnaFilter stdin -globalNearBest=$MINALI  -bestOverlap -filterWeirdOverlapped stdout | sort | uniq > $OUTFNAME
 cp $WORKDIR/mapInfo.tab ${OUTFNAME}.mapInfo
 #rm -rf $WORKDIR