3632060685c8b5ca508635ff45b0c27bf0df0dc1
lrnassar
  Fri Aug 21 10:32:48 2026 -0700
Harden Cardiomyopathy VCEP scripts against silent data-source failures. refs #38139

- cmpVCEPProvisionalClass: a SpliceAI read failure (wrong/renamed bigBed path) now
stops the script with a clear error instead of returning an empty dict, which had
silently fired BP7 for every synonymous variant with the mouseover showing "no
record" as if it were a measurement. Also stop suppressing bigBedToBed's stderr.
- cmpVCEPWalsh2019: compare the ClinVar transcript by accession without the version
so a ClinVar version bump does not silently drop every matched row for a gene.

diff --git src/hg/makeDb/scripts/cardiomyopathyVCEP/cmpVCEPProvisionalClass.py src/hg/makeDb/scripts/cardiomyopathyVCEP/cmpVCEPProvisionalClass.py
index d1449537f69..f3f7219875e 100644
--- src/hg/makeDb/scripts/cardiomyopathyVCEP/cmpVCEPProvisionalClass.py
+++ src/hg/makeDb/scripts/cardiomyopathyVCEP/cmpVCEPProvisionalClass.py
@@ -286,49 +286,56 @@
             m = PROT_MISSENSE_RE.search(h)
             if m and m.group(3) in AA3TO1:
                 codon = int(m.group(2))
                 alt_aa1 = AA3TO1[m.group(3)]
                 break
         if codon is not None and alt_aa1 is not None:
             ref.append({'gkey': gkey, 'gene': gene, 'codon': codon, 'alt_aa1': alt_aa1})
     print(f'  EvRepo P/LP missense reference: {len(ref)} entries', file=sys.stderr)
     return ref
 
 
 def batch_spliceai(regions):
     """Per gene region: (chrom, pos1, ref, alt) -> max SpliceAI delta (bed9+4: AIscore=col9, name='ref>alt')."""
     sa = {}
     for chrom, start, end in regions:
+        # A read failure here (e.g. a wrong/renamed SpliceAI path) must stop the script, not be
+        # skipped: an empty result would give every synonymous variant sa_score 0.0, firing BP7 for
+        # all of them with the mouseover showing "no record" as if it were a measurement. Let
+        # bigBedToBed's own error reach stderr (no DEVNULL) so the real cause is visible.
         try:
             out = subprocess.check_output(['bigBedToBed', f'-chrom={chrom}', f'-start={start}',
-                                           f'-end={end}', SPLICEAI_BB, 'stdout'],
-                                          text=True, stderr=subprocess.DEVNULL)
-        except subprocess.CalledProcessError:
-            continue
+                                           f'-end={end}', SPLICEAI_BB, 'stdout'], text=True)
+        except (subprocess.CalledProcessError, OSError) as e:
+            sys.exit(f'ERROR: bigBedToBed failed on {SPLICEAI_BB} for {chrom}:{start}-{end} ({e}); '
+                     f'cannot compute BP7. Fix the SpliceAI path, or pass --no-spliceai to skip it.')
         for line in out.splitlines():
             f = line.split('\t')
             if len(f) < 10 or '>' not in f[3]:
                 continue
             ref, alt = f[3].split('>', 1)
             pos1 = int(f[2])   # chromEnd == 1-based SNV pos
             try:
                 score = float(f[9])
             except ValueError:
                 continue
             k = (chrom, pos1, ref, alt)
             if score > sa.get(k, -1):
                 sa[k] = score
+    if not sa:
+        sys.exit(f'ERROR: no SpliceAI records read from {SPLICEAI_BB}; refusing to emit BP7 for every '
+                 f'synonymous variant. Check the file and path, or pass --no-spliceai to skip it.')
     print(f'  SpliceAI entries: {len(sa)}', file=sys.stderr)
     return sa
 
 
 TRUNCATING_SO = {'stop_gained', 'frameshift_variant'}
 
 
 def variant_kind(so):
     """Most-relevant consequence label for display."""
     for k in ('stop_gained', 'frameshift_variant', 'stop_lost', 'splice_acceptor_variant',
               'splice_donor_variant', 'missense_variant', 'inframe_deletion', 'inframe_insertion',
               'initiator_codon_variant', 'splice_region_variant', 'synonymous_variant',
               'intron_variant', '5_prime_UTR_variant', '3_prime_UTR_variant'):
         if k in so:
             return k