6b0035d19769346baffe193ef9419c269d46f8d8
max
  Wed Sep 9 06:09:41 2026 -0700
hprc2annot: pcLAI column 10 is the ancestry centroid, not a segment coordinate

Reading the pcLAI authors' own format description
(github.com/AI-sandbox/hprc-pclai, README "Output format (BED)") while building
the same annotation as a native hg38 track showed that column 10 of the source
BED had been described wrongly here. It is not the PCA coordinate of a longer
ancestry segment the window belongs to; the authors call it the centroid, the
discretized pcLAI ancestry of the window written as the PCA centroid of its
ancestry cluster. That is why it only ever takes four values -- four clusters,
not four long shared segments. The old reading also implied a segmentation step
the method does not have: pcLAI predicts one coordinate per window, and the
blocks visible in the display are runs of windows with similar predictions.

Field renamed pcaSegment -> centroid in pclai.as with the description corrected,
and the mouseOver, the detailsScript exportFields and the description page
follow. The README also settles that windows are a fixed 1000 SNPs rather than a
fixed number of bases, and that thickStart is specified to equal chromStart, so
the occasional thickStart == chromStart-1 the converter works around is a bug in
their files rather than something we misread.

A field name and its description live inside each bigBed, so editing pclai.as
does nothing to a built collection. hprc2annotRewriteAs.sh re-emits a built
bigBed with the current .as -- no re-download, no column change, item count
checked across the round trip, and safe to re-run, unlike hprc2annotFixBed.sh.
All 460 pclai.bb were rewritten with it. Worth knowing: those files had been
built from an older pclai.as than the tree and nothing had noticed, so this is
the tool to run after any .as description edit.

genark: the "...Url" inside a detailsScript value must not be rebased the way
bigDataUrl is. hgc resolves a relative detailsScript Url against the track's own
bigDataUrl when it builds the details page, and bigDataUrl has already been
rebased, so the prefix landed twice: the pcLAI scatterplot had been asking for
contrib/hprc2annot/contrib/hprc2annot/pclaiRefPanel.json and quietly getting
nothing on every GenArk hub. In this layout the panel file is symlinked beside
the .bb, so relative-to-the-.bb is the bare file name; rebaseBeside() does that
and is idempotent, so addContrib can be re-run.

refs #35415

diff --git src/hg/makeDb/scripts/hprc2annot/hprc2annotRewriteAs.sh src/hg/makeDb/scripts/hprc2annot/hprc2annotRewriteAs.sh
new file mode 100755
index 00000000000..d8708b72745
--- /dev/null
+++ src/hg/makeDb/scripts/hprc2annot/hprc2annotRewriteAs.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+# Rewrite an already-built bigBed in place with the current .as, without
+# re-downloading anything. The column layout is not touched: this is only for
+# picking up edits to field names and field descriptions in the .as, which are
+# stored inside the bigBed and so cannot be changed by editing the .as alone.
+#
+# Unlike hprc2annotFixBed.sh this is safe to run repeatedly -- it re-emits the
+# same columns -- but it will refuse a file whose column count does not match the
+# .as, which is the signal that the layout, not just the labels, has changed and
+# that a real rebuild (hprc2annotBuild.sh) is what is needed.
+#
+# Usage: hprc2annotRewriteAs.sh TRACK BBFILE
+set -u -o pipefail
+track=$1; bb=$2
+SCR=$HOME/kent/src/hg/makeDb/scripts/hprc2annot
+
+case "$track" in
+  pclai)   as=$SCR/pclai.as;   type=bed9+3; want=12 ;;
+  segdups) as=$SCR/segdups.as; type=bed9+6; want=15 ;;
+  *) echo "UNKNOWN_TRACK $track" >&2; exit 2 ;;
+esac
+[ -s "$as" ] || { echo "NO_AS $as" >&2; exit 2; }
+
+tmp=$(mktemp -d "${TMPDIR:-/data/tmp}/rewriteAs.XXXXXX"); trap 'rm -rf "$tmp"' EXIT
+
+# chrom.sizes straight from the bigBed header (PanSN names, no assembly lookup)
+bigBedInfo -chroms "$bb" | awk 'NF==3 && $2~/^[0-9]+$/ && $3~/^[0-9]+$/{print $1"\t"$3}' > "$tmp/sizes"
+[ -s "$tmp/sizes" ] || { echo "NO_SIZES $bb" >&2; exit 3; }
+
+bigBedToBed "$bb" "$tmp/in.bed" 2>/dev/null
+inCount=$(wc -l < "$tmp/in.bed")
+fields=$(awk -F'\t' 'NR==1{print NF; exit}' "$tmp/in.bed")
+[ "$fields" = "$want" ] || { echo "WRONG_LAYOUT $track $bb has $fields fields, .as wants $want" >&2; exit 4; }
+
+# bigBedToBed already emits in chrom order, but sort anyway so the input is
+# unambiguously valid for bedToBigBed.
+LC_COLLATE=C sort -k1,1 -k2,2n "$tmp/in.bed" > "$tmp/out.bed"
+bedToBigBed -type=$type -tab -as="$as" "$tmp/out.bed" "$tmp/sizes" "$tmp/new.bb" 2>"$tmp/err" \
+  || { echo "BB_FAIL $track $bb" >&2; cat "$tmp/err" >&2; exit 6; }
+
+outCount=$(bigBedInfo "$tmp/new.bb" | awk '/^itemCount:/{gsub(/,/,"",$2); print $2}')
+[ "$inCount" = "$outCount" ] || { echo "COUNT_MISMATCH $bb $inCount -> $outCount" >&2; exit 7; }
+
+mv "$tmp/new.bb" "$bb"
+echo "OK $track $bb $outCount"