fix: replicate VEP merge_features HGNC_ID propagation in cache builder#162
Conversation
The Parquet cache builder now propagates gene_hgnc_id across transcripts sharing the same gene_symbol, replicating VEP's merge_features() behaviour. This closes 82 HGNC_ID gaps across 7 gene symbols genome-wide. Resolves biodatageeks/datafusion-bio-functions#105 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@claude pls review |
|
Claude finished @mwiewior's task in 1m 39s —— View job Code Review
|
Picks up the HGNC_ID propagation fix from biodatageeks/datafusion-bio-formats#162. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…results FIRST_VALUE(gene_hgnc_id) IGNORE NULLS without ORDER BY is non-deterministic — DataFusion may pick a NULL row as "first" in the undefined partition order, causing the propagation to silently fail. Adding ORDER BY gene_hgnc_id NULLS LAST ensures the non-null value is always found first. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…106) * fix: fjall compaction, Drop deadlock, and pre-commit hook toolchain 1. load_sift_cache: add major_compact() after persist — without this, every SIFT lookup probes hundreds of L0 SSTs (~10% slower) 2. CacheLoader (loader.rs): add mem::forget after persist + compact to avoid fjall Drop deadlock on large datasets (bounded flume channel fills up when background compaction is still running) 3. cache_builder.rs: apply mem::forget fix for both build_variation_fjall_from_parquet() and build_sift_fjall() 4. pre-commit: switch from doublify/pre-commit-rust v1.0 (2019) to local hooks using the project's own cargo toolchain — the old hooks used a rustfmt that didn't support edition 2024 let-chains Also adds compact_fjall utility for re-compacting existing uncompacted stores without re-ingesting data. Closes #86 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: bump datafusion-bio-format-ensembl-cache to b82396d Picks up the HGNC_ID propagation fix from biodatageeks/datafusion-bio-formats#162. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add mem::forget to load_context_kv to prevent potential Drop deadlock Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * VEP cache fix * fix: prevent extra fjall keyspaces that cause ~10% annotation perf degradation load_context_kv was writing translations/exons keyspaces directly into variation.fjall, adding unused keyspaces that increased fjall block cache pressure during point lookups. Profiling showed variation_lookup time growing progressively across chromosomes (+41s over a full WGS run). Changes: - load_context_kv: write to separate context.fjall instead of variation.fjall - profile_annotation: auto-detect variation.fjall and enable use_fjall=true - strip_fjall: new diagnostic tool to check for extra keyspaces - kv_store: add tests ensuring create/open produce only meta+data keyspaces - sift_store: add tests ensuring create/ingest produce only sift keyspace Expected keyspace layout after rebuild: - variation.fjall: default + meta + data (3 dirs) - translation_sift.fjall: default + sift (2 dirs) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review — keyspace_exists guard, strip_fjall sift fix, cfg(not(test)) guard 1. compact_fjall: add keyspace_exists() check before db.keyspace() to prevent creating extra keyspaces when compacting (db.keyspace() with default options creates the keyspace if missing) 2. strip_fjall: remove "meta" from expected keyspaces for translation_sift.fjall — SiftKvStore only creates "sift", no "meta" 3. cache_builder: wrap mem::forget in #[cfg(not(test))] consistent with loader.rs, so tests properly release fjall lock files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove translations/exons from compact_fjall known keyspaces These keyspaces should never exist in production fjall stores. Keeping them in the list would compact keyspaces that shouldn't be there. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: compact_fjall auto-detects store type to avoid cross-contamination variation.fjall should only compact meta+data; translation_sift.fjall should only compact sift. The previous flat list would attempt to open all keyspace names on any store, risking creation of foreign keyspaces. Now auto-detects from path: translation_sift → sift only, else → meta+data. Explicit --keyspace arg still honored for manual use. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace mem::forget with drop() to enable fjall GC of dead SSTs mem::forget skipped fjall's Drop handler which runs GC/maintenance to delete old pre-compaction SST files. This left ~220GB of dead files on disk after building variation.fjall (331GB instead of 110GB). After major_compact() + persist(), there is no pending work for the worker thread, so Drop completes without the deadlock from issue #86. Changed in: cache_builder.rs, loader.rs, compact_fjall.rs, load_sift_cache.rs, load_context_kv.rs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add HGNC_ID propagation to cache_builder transcript query (#105) cache_builder's build_query() for transcripts used SELECT * which didn't propagate gene_hgnc_id from sibling transcripts sharing the same gene_symbol. This caused 1,730 HGNC_ID mismatches on chr9 (FGF7P3 — 19/46 transcripts missing HGNC:26671). Port the same fix from datafusion-bio-formats PR #162: replace SELECT * with an explicit column list using COALESCE + FIRST_VALUE IGNORE NULLS OVER (PARTITION BY gene_symbol) for gene_hgnc_id. TODO: consolidate build_dedup_query() into bio-formats as a shared library function to avoid duplicate query logic across repos. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * bio-format update * fix: restrict HGNC propagation to HGNC-source transcripts only (#104) VEP only emits HGNC_ID for transcripts with gene_symbol_source='HGNC'. The cache-level propagation was filling HGNC_ID for EntrezGene/RFAM transcripts too, causing 39 false-positive mismatches on chr9. Add gene_symbol_source = 'HGNC' guard to the COALESCE expression. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: revert gene_symbol_source guard from cache-level HGNC propagation VEP's merge_features() propagates gene_hgnc_id purely by gene_symbol, regardless of gene_symbol_source. The gene_symbol_source='HGNC' guard was wrong at cache build time — FGF7P3 has 45/46 transcripts with source=EntrezGene that still need HGNC_ID in the cache. The source-based filtering is a runtime concern (hgnc_backfill flag, #104) that controls whether HGNC_ID appears in annotation output, not whether it's stored in the cache. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Summary
merge_features()HGNC_ID propagation in the Parquet cache builder (storable_to_parquet)COALESCE + FIRST_VALUE IGNORE NULLS OVER (PARTITION BY gene_symbol)window function in the transcript dedup queryResolves biodatageeks/datafusion-bio-functions#105
Changes
storable_to_parquet.rs: Modifiedbuild_dedup_query()to build an explicit column list for transcripts, replacinggene_hgnc_idwith the propagation expressionhgnc_propagation_tests.rs: 6 new unit tests using synthetic MemTable data covering: basic propagation, no-overwrite, NULL symbol, all-NULL HGNC, multiple symbols, mixed scenarioreal_vep_cache_tests.rs: 1 new integration test against real VEP 115 chr22 fixture verifying zero remaining gaps after propagationPerformance
The window function adds one hash partition over
gene_symbol(~20K distinct values). On ~3M transcript rows this is negligible (+1-2s) compared to the existing ROW_NUMBER window + ORDER BY + Parquet write.Test plan
hgnc_propagation_fills_all_siblings) passes on chr22 fixturecargo clippycleancargo fmt --checkclean🤖 Generated with Claude Code