allelix 2.1.0__tar.gz → 2.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. {allelix-2.1.0 → allelix-2.1.2}/PKG-INFO +125 -28
  2. {allelix-2.1.0 → allelix-2.1.2}/README.md +122 -27
  3. {allelix-2.1.0 → allelix-2.1.2}/allelix/annotators/__init__.py +10 -12
  4. {allelix-2.1.0 → allelix-2.1.2}/allelix/annotators/base.py +0 -29
  5. {allelix-2.1.0 → allelix-2.1.2}/allelix/annotators/cadd.py +9 -7
  6. {allelix-2.1.0 → allelix-2.1.2}/allelix/annotators/clinvar.py +3 -2
  7. {allelix-2.1.0 → allelix-2.1.2}/allelix/cli/_helpers.py +7 -4
  8. {allelix-2.1.0 → allelix-2.1.2}/allelix/cli/utility.py +6 -36
  9. {allelix-2.1.0 → allelix-2.1.2}/allelix/config.py +3 -1
  10. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/cpic_loader.py +2 -2
  11. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/gwas_loader.py +2 -2
  12. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/snpedia_parser.py +6 -4
  13. {allelix-2.1.0 → allelix-2.1.2}/allelix/exporters/plink.py +50 -1
  14. {allelix-2.1.0 → allelix-2.1.2}/allelix/models.py +36 -1
  15. {allelix-2.1.0 → allelix-2.1.2}/allelix/reports/_pipeline.py +10 -5
  16. {allelix-2.1.0 → allelix-2.1.2}/allelix/reports/diff.py +4 -2
  17. {allelix-2.1.0 → allelix-2.1.2}/allelix/reports/json_report.py +3 -5
  18. {allelix-2.1.0 → allelix-2.1.2}/allelix.egg-info/PKG-INFO +125 -28
  19. {allelix-2.1.0 → allelix-2.1.2}/allelix.egg-info/requires.txt +2 -0
  20. {allelix-2.1.0 → allelix-2.1.2}/pyproject.toml +37 -2
  21. {allelix-2.1.0 → allelix-2.1.2}/tests/test_models.py +46 -0
  22. {allelix-2.1.0 → allelix-2.1.2}/LICENSE +0 -0
  23. {allelix-2.1.0 → allelix-2.1.2}/allelix/__init__.py +0 -0
  24. {allelix-2.1.0 → allelix-2.1.2}/allelix/annotators/alphamissense.py +0 -0
  25. {allelix-2.1.0 → allelix-2.1.2}/allelix/annotators/gnomad.py +0 -0
  26. {allelix-2.1.0 → allelix-2.1.2}/allelix/annotators/gwas.py +0 -0
  27. {allelix-2.1.0 → allelix-2.1.2}/allelix/annotators/pharmgkb.py +0 -0
  28. {allelix-2.1.0 → allelix-2.1.2}/allelix/annotators/snpedia.py +0 -0
  29. {allelix-2.1.0 → allelix-2.1.2}/allelix/cli/__init__.py +0 -0
  30. {allelix-2.1.0 → allelix-2.1.2}/allelix/cli/_options.py +0 -0
  31. {allelix-2.1.0 → allelix-2.1.2}/allelix/cli/analyze.py +0 -0
  32. {allelix-2.1.0 → allelix-2.1.2}/allelix/cli/config.py +0 -0
  33. {allelix-2.1.0 → allelix-2.1.2}/allelix/cli/db.py +0 -0
  34. {allelix-2.1.0 → allelix-2.1.2}/allelix/cli/focused.py +0 -0
  35. {allelix-2.1.0 → allelix-2.1.2}/allelix/compare.py +0 -0
  36. {allelix-2.1.0 → allelix-2.1.2}/allelix/data/__init__.py +0 -0
  37. {allelix-2.1.0 → allelix-2.1.2}/allelix/data/clinvar_clnsig_snapshot.yaml +0 -0
  38. {allelix-2.1.0 → allelix-2.1.2}/allelix/data/high_value_snps.yaml +0 -0
  39. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/__init__.py +0 -0
  40. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/_versions.py +0 -0
  41. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/alphamissense_loader.py +0 -0
  42. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/cadd_loader.py +0 -0
  43. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/gnomad_loader.py +0 -0
  44. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/loader_utils.py +0 -0
  45. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/manager.py +0 -0
  46. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/pharmgkb_loader.py +0 -0
  47. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/schema.py +0 -0
  48. {allelix-2.1.0 → allelix-2.1.2}/allelix/databases/snpedia_loader.py +0 -0
  49. {allelix-2.1.0 → allelix-2.1.2}/allelix/exporters/__init__.py +0 -0
  50. {allelix-2.1.0 → allelix-2.1.2}/allelix/parsers/__init__.py +0 -0
  51. {allelix-2.1.0 → allelix-2.1.2}/allelix/parsers/_helpers.py +0 -0
  52. {allelix-2.1.0 → allelix-2.1.2}/allelix/parsers/ancestrydna.py +0 -0
  53. {allelix-2.1.0 → allelix-2.1.2}/allelix/parsers/base.py +0 -0
  54. {allelix-2.1.0 → allelix-2.1.2}/allelix/parsers/ftdna.py +0 -0
  55. {allelix-2.1.0 → allelix-2.1.2}/allelix/parsers/ftdna_illumina.py +0 -0
  56. {allelix-2.1.0 → allelix-2.1.2}/allelix/parsers/livingdna.py +0 -0
  57. {allelix-2.1.0 → allelix-2.1.2}/allelix/parsers/myhappygenes.py +0 -0
  58. {allelix-2.1.0 → allelix-2.1.2}/allelix/parsers/myheritage.py +0 -0
  59. {allelix-2.1.0 → allelix-2.1.2}/allelix/parsers/twentythreeandme.py +0 -0
  60. {allelix-2.1.0 → allelix-2.1.2}/allelix/parsers/vcf.py +0 -0
  61. {allelix-2.1.0 → allelix-2.1.2}/allelix/py.typed +0 -0
  62. {allelix-2.1.0 → allelix-2.1.2}/allelix/reports/__init__.py +0 -0
  63. {allelix-2.1.0 → allelix-2.1.2}/allelix/reports/high_value.py +0 -0
  64. {allelix-2.1.0 → allelix-2.1.2}/allelix/reports/html.py +0 -0
  65. {allelix-2.1.0 → allelix-2.1.2}/allelix/reports/methylation.py +0 -0
  66. {allelix-2.1.0 → allelix-2.1.2}/allelix/reports/terminal.py +0 -0
  67. {allelix-2.1.0 → allelix-2.1.2}/allelix/utils/__init__.py +0 -0
  68. {allelix-2.1.0 → allelix-2.1.2}/allelix/utils/allele.py +0 -0
  69. {allelix-2.1.0 → allelix-2.1.2}/allelix/utils/build_detect.py +0 -0
  70. {allelix-2.1.0 → allelix-2.1.2}/allelix.egg-info/SOURCES.txt +0 -0
  71. {allelix-2.1.0 → allelix-2.1.2}/allelix.egg-info/dependency_links.txt +0 -0
  72. {allelix-2.1.0 → allelix-2.1.2}/allelix.egg-info/entry_points.txt +0 -0
  73. {allelix-2.1.0 → allelix-2.1.2}/allelix.egg-info/top_level.txt +0 -0
  74. {allelix-2.1.0 → allelix-2.1.2}/setup.cfg +0 -0
  75. {allelix-2.1.0 → allelix-2.1.2}/tests/test_cli.py +0 -0
  76. {allelix-2.1.0 → allelix-2.1.2}/tests/test_cli_helpers.py +0 -0
  77. {allelix-2.1.0 → allelix-2.1.2}/tests/test_compare.py +0 -0
  78. {allelix-2.1.0 → allelix-2.1.2}/tests/test_config.py +0 -0
  79. {allelix-2.1.0 → allelix-2.1.2}/tests/test_end_to_end.py +0 -0
  80. {allelix-2.1.0 → allelix-2.1.2}/tests/test_mock_data_invariants.py +0 -0
  81. {allelix-2.1.0 → allelix-2.1.2}/tests/test_registry.py +0 -0
  82. {allelix-2.1.0 → allelix-2.1.2}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: allelix
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: Open-source genotype analysis toolkit. Format-agnostic ingestion, database-agnostic annotation, offline-first.
5
5
  Author: Allelix
6
6
  Maintainer-email: dial481 <dial481@users.noreply.github.com>
@@ -29,37 +29,42 @@ Requires-Dist: pysam>=0.22; extra == "cadd"
29
29
  Provides-Extra: vcf-index
30
30
  Requires-Dist: pysam>=0.22; extra == "vcf-index"
31
31
  Provides-Extra: dev
32
+ Requires-Dist: mypy>=1.11; extra == "dev"
32
33
  Requires-Dist: pre-commit>=3.7; extra == "dev"
33
34
  Requires-Dist: pytest>=8.0; extra == "dev"
34
35
  Requires-Dist: pytest-cov<8,>=7; extra == "dev"
35
36
  Requires-Dist: ruff>=0.6; extra == "dev"
37
+ Requires-Dist: types-PyYAML>=6.0; extra == "dev"
36
38
  Dynamic: license-file
37
39
 
40
+ <div align="center">
41
+
42
+ <img src="https://allelix.io/apple-touch-icon.png" width="180" alt="Allelix" />
43
+
44
+ **Open-source genotype analysis. Format-agnostic. Offline-first.**
45
+
46
+ [Website](https://allelix.io) · [Quickstart](#quickstart) · [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md)
47
+
48
+ [![python](https://img.shields.io/pypi/pyversions/allelix.svg)](https://www.python.org/downloads/)
49
+ [![pypi](https://img.shields.io/pypi/v/allelix.svg)](https://pypi.org/project/allelix/)
50
+ [![license](https://img.shields.io/pypi/l/allelix.svg)](LICENSE)
51
+ [![CI](https://github.com/allelix/allelix/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/allelix/allelix/actions/workflows/ci.yml)
52
+ [![privacy: local-first](https://img.shields.io/badge/privacy-local--first-success)](#privacy)
53
+
54
+ </div>
55
+
38
56
  # Allelix
39
57
 
40
- Open-source command-line toolkit for analyzing raw genotype files from consumer DNA testing services. Format-agnostic ingestion, database-agnostic annotation, offline-first.
41
-
42
- > **Status:** Production — eight parser formats (including VCF + gVCF),
43
- > four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three
44
- > enrichment sources (gnomAD population frequencies + AlphaMissense
45
- > pathogenicity + CADD deleteriousness), licensable-source gating for
46
- > commercial users, dual-build ClinVar caches (GRCh37 + GRCh38),
47
- > HTML/JSON/terminal reports, methylation + pharmacogenomics focused
48
- > commands, report diffing, persistent config with commercial-mode
49
- > safety switch. Build auto-detection from position data (ADR-0021).
50
- > No regex on prose anywhere in production. **Latest: v2.0.0** — VCF +
51
- > gVCF parser with multi-sample handling, batched annotation pipeline
52
- > for WGS scale, FTDNA Illumina raw parser, R-4 ClinVar CLNSIG drift CI
53
- > test, CLI package restructure.
54
- > Release notes:
55
- > [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/main/CHANGELOG.md).
58
+ > **Status:** Production. Eight parser formats (including VCF + gVCF), four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three enrichment sources (gnomAD + AlphaMissense + CADD), licensable-source gating for commercial users, dual-build ClinVar caches (GRCh37 + GRCh38), HTML/JSON/terminal reports, methylation + pharmacogenomics focused commands, report diffing, persistent config with commercial-mode safety switch. Build auto-detection from position data (ADR-0021). No regex on prose anywhere in production. The [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) tracks every release.
56
59
 
57
60
  ## Quickstart
58
61
 
62
+ **Requires Python 3.11 or newer.** On Python 3.10 or older, `pip install allelix` succeeds but the first command fails with `ImportError: cannot import name 'UTC' from 'datetime'` — that's the symptom; the fix is to install on 3.11+. Check with `python --version`.
63
+
59
64
  ```bash
60
65
  pip install allelix
61
66
 
62
- # Download reference databases (~15GB with all sources).
67
+ # Download reference databases (~16 GB default install; ~22 GB if you opt into CADD).
63
68
  # Use --no-gnomad / --no-alphamissense to skip the large ones.
64
69
  # CADD is opt-in: allelix db update --cadd
65
70
  allelix db update
@@ -77,7 +82,7 @@ allelix analyze trio.vcf.gz --sample HG002 --output report.html
77
82
  allelix analyze your_genotype_file.txt --filter-file my_panel.txt --output report.html
78
83
  ```
79
84
 
80
- Requires Python 3.11+. See [Development](#development) for source installs and running tests.
85
+ See [Development](#development) for source installs and running tests, [Managing your data](#managing-your-data) for cache locations and cleanup, and [Troubleshooting](#troubleshooting) for common failure states.
81
86
 
82
87
  ## Supported Formats
83
88
 
@@ -94,16 +99,17 @@ Requires Python 3.11+. See [Development](#development) for source installs and r
94
99
 
95
100
  Adding a new format means adding one file to `allelix/parsers/` and registering an instance in the `PARSERS` list in `allelix/parsers/__init__.py`.
96
101
 
97
- ### v2.1+ roadmap
102
+ ### Roadmap
98
103
 
99
- | Feature | Notes |
104
+ | Release | Theme |
100
105
  |---|---|
101
- | Per-source scoring | Magnitude breakdown by database. Users see which source drove the composite score. |
102
- | Annotator-level strand awareness (R-1) | Strand-flip matching wired into every annotator's carrier check. Basic `compare` strand support shipped in v1.1; full annotator integration deferred from v2.0.0. |
103
- | Good / Bad / Neutral repute | Per-annotation repute field. Reframes the report from "here's what's wrong" to "here's your full picture." Requires Annotation model change + renderer updates. |
104
- | PLINK import | Read .bed/.bim/.fam as an input format (complement to the v1.7.0 export). |
105
- | PharmCAT integration | Wrap CPIC's PharmCAT as an optional external engine for star-allele / diplotype calling. Requires VCF input (shipped in v2.0.0). |
106
- | Genome Watchtower | Real-time variant monitoring via database delta feeds. Privacy-preserving: server publishes universal feed, matching happens locally against your deviation set. Replaces full re-analysis with millisecond set intersection. |
106
+ | **v2.2** | Annotation model expansion (cross-source conflict surfacing, risk-allele display, structured MTAG flag), custom-panel UX improvements, methylation panel retune + citations, ClinVar source switch (VCF → `variant_summary.txt`), CLI cleanups (`db clean` / `db path`). |
107
+ | **v2.3** | Per-source magnitude decomposition, Good / Bad / Neutral annotation repute, JSON v3 (AI-legible output contract), plausibility flagging via zygosity × gnomAD MAF, ClinVar review-status weighting, functional-medicine +/− notation. |
108
+ | **v2.4** | ACMG/AMP automated classification engine, variant consequence annotation (VEP-equivalent). |
109
+ | **v2.5+** | CNVs and repeat-expansion support, dbSNP resolution for rsID-less VCFs, gnomAD genomes (non-coding population frequencies), supplemental genotype file merging. |
110
+ | **v3.0** | Ancestry estimation (PCA + 1KG/HGDP reference panels), Polygenic Risk Score (PRS) integration. |
111
+
112
+ Issues and milestones are tracked at [github.com/allelix/allelix/issues](https://github.com/allelix/allelix/issues). Annotator-level strand-aware carrier matching (R-1) shipped in v2.1.0's ADR-0035 cluster.
107
113
 
108
114
  ## Supported Databases
109
115
 
@@ -116,7 +122,20 @@ Adding a new format means adding one file to `allelix/parsers/` and registering
116
122
  | GWAS Catalog | ✓ | Public domain (EBI/NHGRI). Trait–SNP associations with p-values and effect sizes. Carrier rule (ADR-0007) requires the user to carry the risk allele. P-value magnitude scoring (ADR-0024) maps continuous p-values to the 0–10 scale; unknown-risk-allele entries fire on rsID match alone but are capped at 3.0. |
117
123
  | gnomAD | ✓ | ODbL v1.0. **Enrichment annotator** — adds population allele frequency context to existing annotations. Shows how common each variant is in the general population (~16M exome variants from 730K individuals). A pathogenic variant that 35% of people carry reads very differently from one seen in 0.001%. Pre-built cache downloaded via `db update` (~6GB on disk). Use `--no-gnomad` to skip. |
118
124
  | AlphaMissense | ✓ | CC BY 4.0. **Enrichment annotator** — adds DeepMind's protein-structure-based pathogenicity predictions to existing annotations. Scores 71M missense variants on a 0–1 scale: <0.34 = likely benign, >0.564 = likely pathogenic. Complements ClinVar's expert classifications with computational predictions — especially valuable for variants ClinVar hasn't reviewed yet. Pre-built cache downloaded via `db update` (~8GB on disk). Use `--no-alphamissense` to skip. |
119
- | CADD | ✓ | LicenseRef-CADD (non-commercial). **Enrichment annotator** — adds PHRED-scaled deleteriousness scores from CADD v1.7. Ranks how deleterious any single-nucleotide variant is using 100+ annotation tracks (coding, non-coding, regulatory). PHRED 10 = top 10% most deleterious, 20 = top 1%, 30 = top 0.1%. **Opt-in** — disabled by default (`sources.cadd = false`). Enable via `allelix db update --cadd` or `allelix config set sources.cadd true`. Use `--no-cadd` to skip enrichment for a single run. Pre-built cache (~5 GB on disk, ~120M variant keys). Full mode available via pysam for GRCh38 data (`options.cadd_full = true`). Cache mode covers the large majority of variants present in gnomAD, AlphaMissense, and ClinVar — nearly every position allelix can annotate from its other databases. For genotyping chip data (23andMe, AncestryDNA, MyHappyGenes, etc.), cache and full mode produce effectively identical results because chip probes overwhelmingly target known, cataloged variants. Full mode adds coverage for novel or private variants that appear only in whole-genome or whole-exome sequencing data and are not in any pre-computed database. If your input is a genotyping chip file, cache mode is all you need. |
125
+ | CADD | ✓ | LicenseRef-CADD (non-commercial). **Enrichment annotator** — adds PHRED-scaled deleteriousness scores from CADD v1.7. **Opt-in**, disabled by default; see [CADD modes](#cadd-modes) below for details. |
126
+
127
+ ### CADD modes
128
+
129
+ CADD ranks how deleterious any single-nucleotide variant is using 100+ annotation tracks (coding, non-coding, regulatory). PHRED 10 = top 10% most deleterious, 20 = top 1%, 30 = top 0.1%.
130
+
131
+ **Opt-in**: enable via `allelix db update --cadd` or `allelix config set sources.cadd true`. Use `--no-cadd` to skip enrichment for a single run.
132
+
133
+ Two modes:
134
+
135
+ - **Cache mode** (default): pre-built ~5.8 GB SQLite cache (~120M variant keys). Covers nearly every position allelix can annotate from its other databases (gnomAD, AlphaMissense, ClinVar). For genotyping chip data (23andMe, AncestryDNA, MyHappyGenes, etc.), this mode is functionally complete — chip probes overwhelmingly target known, cataloged variants.
136
+ - **Full mode** (`options.cadd_full = true`): tabix queries against the complete CADD v1.7 file via pysam (GRCh38 only). Adds coverage for novel or private variants that appear only in WGS/WES data and are not in any pre-computed database. Requires `pip install allelix[cadd]` for the pysam dependency.
137
+
138
+ If your input is a genotyping chip file, cache mode is all you need.
120
139
 
121
140
  ### Build coverage asymmetry (GRCh37 vs GRCh38)
122
141
 
@@ -214,6 +233,48 @@ gnomAD and AlphaMissense are the largest but add the most interpretive context.
214
233
 
215
234
  To skip either during download: `allelix db update --no-gnomad --no-alphamissense`. To disable permanently: `allelix config set sources.gnomad false`.
216
235
 
236
+ ### Managing your data
237
+
238
+ Allelix stores reference database caches at `~/.local/share/allelix/` (Linux/macOS) or the XDG-equivalent on other platforms. Default install (no CADD) is ~16 GB on disk; with the CADD opt-in it's ~22 GB.
239
+
240
+ ```
241
+ ~/.local/share/allelix/
242
+ ├── alphamissense.sqlite ~7.8 GB
243
+ ├── gnomad.sqlite ~6.1 GB
244
+ ├── cadd.sqlite ~5.8 GB (only if you opted into --cadd)
245
+ ├── clinvar.GRCh37.sqlite ~470 MB
246
+ ├── clinvar.GRCh38.sqlite ~480 MB
247
+ ├── gwas_catalog_associations.tsv ~700 MB
248
+ ├── snpedia.sqlite ~390 MB
249
+ ├── gwas.sqlite ~190 MB
250
+ └── pharmgkb.sqlite ~14 MB
251
+ ```
252
+
253
+ **The caches are disposable and re-downloadable.** Everything here was fetched by `allelix db update` and can be re-fetched at any time. There's no per-user state in the cache — your genotype files never enter this directory. Safe to delete to reclaim space (or to force a full refresh if you suspect a cache went stale):
254
+
255
+ ```bash
256
+ # Reclaim all ~16-22 GB
257
+ rm -rf ~/.local/share/allelix/
258
+
259
+ # Reclaim everything except CADD (avoids re-downloading the slowest one)
260
+ find ~/.local/share/allelix/ -mindepth 1 ! -name 'cadd*.sqlite*' -delete
261
+
262
+ # Re-populate from scratch next time you analyze a file
263
+ allelix db update
264
+ ```
265
+
266
+ **The one thing worth backing up: `config.toml`.** It lives separately (XDG config dir, typically `~/.config/allelix/config.toml`) and stores your license assertions (commercial-mode toggle, CADD opt-in confirmation) and per-source enable/disable settings. It's the only non-reproducible state in the install. The caches will rebuild on the next `db update`; `config.toml` won't.
267
+
268
+ To fully uninstall allelix and reclaim everything:
269
+
270
+ ```bash
271
+ pip uninstall allelix
272
+ rm -rf ~/.local/share/allelix/ # reference database caches
273
+ rm -rf ~/.config/allelix/ # license + source-toggle config
274
+ ```
275
+
276
+ A `allelix db clean` subcommand to automate the cache-clear step (with confirmation prompt and size report) is planned for v2.2 alongside `allelix db path` (prints the resolved data directory for scripting / backup integration).
277
+
217
278
  ## Data Sources & Licensing
218
279
 
219
280
  Allelix source code is licensed under the **GNU Affero General Public License v3.0 or later** (AGPL-3.0-or-later). Allelix ships with **zero third-party data**. All reference databases are downloaded by the user at runtime via `allelix db update`. Each database retains its original license on the user's machine:
@@ -256,6 +317,42 @@ Of the 104,806 genotype pages in the archive:
256
317
 
257
318
  None of these are scraping errors. They are editorial inconsistencies on the source wiki. The annotator handles all of them correctly: incomplete entries are skipped, variant title formats are matched, and no false annotations are produced.
258
319
 
320
+ ## Troubleshooting
321
+
322
+ ### `ImportError: cannot import name 'UTC' from 'datetime'`
323
+
324
+ You're on Python 3.10 or older. Allelix requires 3.11+. `python --version` to check; upgrade and reinstall.
325
+
326
+ ### `allelix db update` failed partway through
327
+
328
+ Run it again. The update is idempotent — completed downloads are skipped (signal-matched against the upstream freshness probe), so re-running picks up where it left off. If a specific source keeps failing, you can isolate it: `allelix db update --no-gnomad` (or `--no-alphamissense` etc.) to skip the failing source for now, then retry that source standalone later.
329
+
330
+ If the failure is a network timeout on the same source repeatedly, the issue is likely on the source's end (HuggingFace / NCBI / EBI / etc. transient outage). Wait an hour and retry.
331
+
332
+ ### `db status` shows an annotator as "not ready" but the cache files exist on disk
333
+
334
+ Almost always a schema or interpreter version bump in a recent allelix release that invalidates the pre-bump cache. The fix is `allelix db update` — it'll re-ingest only the affected annotators (everything else signal-skips). See the relevant `CHANGELOG.md` entry for "Cache invalidation" notes per release.
335
+
336
+ ### Build mismatch warning when analyzing a known-good file
337
+
338
+ Either: (a) the file's header claims one build and the position data is actually a different build (real-world MHG / Tempus exports do this — header says 37, positions are 38; ADR-0021 documents the auto-detection), or (b) the file is a GRCh36-era export (pre-2012), which allelix detects accurately but doesn't ship a ClinVar cache for (see [`docs/grch36-liftover.md`](https://github.com/allelix/allelix/blob/main/docs/grch36-liftover.md) for the external-liftover path).
339
+
340
+ If you trust the file's header more than allelix's detection, force the build: `--build grch37` or `--build grch38`. The build banner in the analyze output will reflect what was actually used.
341
+
342
+ ### Is my database stale? How fresh is the data?
343
+
344
+ `allelix db status` shows the ingestion date of each cache. The freshness model is signal-driven: `allelix db update` queries an upstream freshness signal (md5 / etag) per source and re-ingests only when the signal changed. There's no time-based staleness — a 6-month-old cache against an unchanged source IS current.
345
+
346
+ If you want to force a refresh: `allelix db update --force` re-downloads every cache regardless of freshness signal.
347
+
348
+ ### Output reports contain real genome annotations
349
+
350
+ Anything `allelix analyze` writes to disk contains real annotations of your genome. Treat the output files (HTML, JSON, terminal redirects) as sensitive — they're as private as the input genotype file. Allelix does not transmit them; the lifecycle is local.
351
+
352
+ ### Where does allelix store its data?
353
+
354
+ See [Managing your data](#managing-your-data) above for cache locations, the disposability rule, what to back up, and the uninstall procedure.
355
+
259
356
  ## Architecture & Design Decisions
260
357
 
261
358
  The "why" behind major design choices lives in [`docs/adr/`](https://github.com/allelix/allelix/blob/main/docs/adr/README.md) as Architecture Decision Records. Read these before proposing changes that touch the parser/annotator interfaces, the regulatory posture, or the data-handling model.
@@ -1,28 +1,31 @@
1
+ <div align="center">
2
+
3
+ <img src="https://allelix.io/apple-touch-icon.png" width="180" alt="Allelix" />
4
+
5
+ **Open-source genotype analysis. Format-agnostic. Offline-first.**
6
+
7
+ [Website](https://allelix.io) · [Quickstart](#quickstart) · [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md)
8
+
9
+ [![python](https://img.shields.io/pypi/pyversions/allelix.svg)](https://www.python.org/downloads/)
10
+ [![pypi](https://img.shields.io/pypi/v/allelix.svg)](https://pypi.org/project/allelix/)
11
+ [![license](https://img.shields.io/pypi/l/allelix.svg)](LICENSE)
12
+ [![CI](https://github.com/allelix/allelix/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/allelix/allelix/actions/workflows/ci.yml)
13
+ [![privacy: local-first](https://img.shields.io/badge/privacy-local--first-success)](#privacy)
14
+
15
+ </div>
16
+
1
17
  # Allelix
2
18
 
3
- Open-source command-line toolkit for analyzing raw genotype files from consumer DNA testing services. Format-agnostic ingestion, database-agnostic annotation, offline-first.
4
-
5
- > **Status:** Production — eight parser formats (including VCF + gVCF),
6
- > four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three
7
- > enrichment sources (gnomAD population frequencies + AlphaMissense
8
- > pathogenicity + CADD deleteriousness), licensable-source gating for
9
- > commercial users, dual-build ClinVar caches (GRCh37 + GRCh38),
10
- > HTML/JSON/terminal reports, methylation + pharmacogenomics focused
11
- > commands, report diffing, persistent config with commercial-mode
12
- > safety switch. Build auto-detection from position data (ADR-0021).
13
- > No regex on prose anywhere in production. **Latest: v2.0.0** — VCF +
14
- > gVCF parser with multi-sample handling, batched annotation pipeline
15
- > for WGS scale, FTDNA Illumina raw parser, R-4 ClinVar CLNSIG drift CI
16
- > test, CLI package restructure.
17
- > Release notes:
18
- > [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/main/CHANGELOG.md).
19
+ > **Status:** Production. Eight parser formats (including VCF + gVCF), four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three enrichment sources (gnomAD + AlphaMissense + CADD), licensable-source gating for commercial users, dual-build ClinVar caches (GRCh37 + GRCh38), HTML/JSON/terminal reports, methylation + pharmacogenomics focused commands, report diffing, persistent config with commercial-mode safety switch. Build auto-detection from position data (ADR-0021). No regex on prose anywhere in production. The [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) tracks every release.
19
20
 
20
21
  ## Quickstart
21
22
 
23
+ **Requires Python 3.11 or newer.** On Python 3.10 or older, `pip install allelix` succeeds but the first command fails with `ImportError: cannot import name 'UTC' from 'datetime'` — that's the symptom; the fix is to install on 3.11+. Check with `python --version`.
24
+
22
25
  ```bash
23
26
  pip install allelix
24
27
 
25
- # Download reference databases (~15GB with all sources).
28
+ # Download reference databases (~16 GB default install; ~22 GB if you opt into CADD).
26
29
  # Use --no-gnomad / --no-alphamissense to skip the large ones.
27
30
  # CADD is opt-in: allelix db update --cadd
28
31
  allelix db update
@@ -40,7 +43,7 @@ allelix analyze trio.vcf.gz --sample HG002 --output report.html
40
43
  allelix analyze your_genotype_file.txt --filter-file my_panel.txt --output report.html
41
44
  ```
42
45
 
43
- Requires Python 3.11+. See [Development](#development) for source installs and running tests.
46
+ See [Development](#development) for source installs and running tests, [Managing your data](#managing-your-data) for cache locations and cleanup, and [Troubleshooting](#troubleshooting) for common failure states.
44
47
 
45
48
  ## Supported Formats
46
49
 
@@ -57,16 +60,17 @@ Requires Python 3.11+. See [Development](#development) for source installs and r
57
60
 
58
61
  Adding a new format means adding one file to `allelix/parsers/` and registering an instance in the `PARSERS` list in `allelix/parsers/__init__.py`.
59
62
 
60
- ### v2.1+ roadmap
63
+ ### Roadmap
61
64
 
62
- | Feature | Notes |
65
+ | Release | Theme |
63
66
  |---|---|
64
- | Per-source scoring | Magnitude breakdown by database. Users see which source drove the composite score. |
65
- | Annotator-level strand awareness (R-1) | Strand-flip matching wired into every annotator's carrier check. Basic `compare` strand support shipped in v1.1; full annotator integration deferred from v2.0.0. |
66
- | Good / Bad / Neutral repute | Per-annotation repute field. Reframes the report from "here's what's wrong" to "here's your full picture." Requires Annotation model change + renderer updates. |
67
- | PLINK import | Read .bed/.bim/.fam as an input format (complement to the v1.7.0 export). |
68
- | PharmCAT integration | Wrap CPIC's PharmCAT as an optional external engine for star-allele / diplotype calling. Requires VCF input (shipped in v2.0.0). |
69
- | Genome Watchtower | Real-time variant monitoring via database delta feeds. Privacy-preserving: server publishes universal feed, matching happens locally against your deviation set. Replaces full re-analysis with millisecond set intersection. |
67
+ | **v2.2** | Annotation model expansion (cross-source conflict surfacing, risk-allele display, structured MTAG flag), custom-panel UX improvements, methylation panel retune + citations, ClinVar source switch (VCF → `variant_summary.txt`), CLI cleanups (`db clean` / `db path`). |
68
+ | **v2.3** | Per-source magnitude decomposition, Good / Bad / Neutral annotation repute, JSON v3 (AI-legible output contract), plausibility flagging via zygosity × gnomAD MAF, ClinVar review-status weighting, functional-medicine +/− notation. |
69
+ | **v2.4** | ACMG/AMP automated classification engine, variant consequence annotation (VEP-equivalent). |
70
+ | **v2.5+** | CNVs and repeat-expansion support, dbSNP resolution for rsID-less VCFs, gnomAD genomes (non-coding population frequencies), supplemental genotype file merging. |
71
+ | **v3.0** | Ancestry estimation (PCA + 1KG/HGDP reference panels), Polygenic Risk Score (PRS) integration. |
72
+
73
+ Issues and milestones are tracked at [github.com/allelix/allelix/issues](https://github.com/allelix/allelix/issues). Annotator-level strand-aware carrier matching (R-1) shipped in v2.1.0's ADR-0035 cluster.
70
74
 
71
75
  ## Supported Databases
72
76
 
@@ -79,7 +83,20 @@ Adding a new format means adding one file to `allelix/parsers/` and registering
79
83
  | GWAS Catalog | ✓ | Public domain (EBI/NHGRI). Trait–SNP associations with p-values and effect sizes. Carrier rule (ADR-0007) requires the user to carry the risk allele. P-value magnitude scoring (ADR-0024) maps continuous p-values to the 0–10 scale; unknown-risk-allele entries fire on rsID match alone but are capped at 3.0. |
80
84
  | gnomAD | ✓ | ODbL v1.0. **Enrichment annotator** — adds population allele frequency context to existing annotations. Shows how common each variant is in the general population (~16M exome variants from 730K individuals). A pathogenic variant that 35% of people carry reads very differently from one seen in 0.001%. Pre-built cache downloaded via `db update` (~6GB on disk). Use `--no-gnomad` to skip. |
81
85
  | AlphaMissense | ✓ | CC BY 4.0. **Enrichment annotator** — adds DeepMind's protein-structure-based pathogenicity predictions to existing annotations. Scores 71M missense variants on a 0–1 scale: <0.34 = likely benign, >0.564 = likely pathogenic. Complements ClinVar's expert classifications with computational predictions — especially valuable for variants ClinVar hasn't reviewed yet. Pre-built cache downloaded via `db update` (~8GB on disk). Use `--no-alphamissense` to skip. |
82
- | CADD | ✓ | LicenseRef-CADD (non-commercial). **Enrichment annotator** — adds PHRED-scaled deleteriousness scores from CADD v1.7. Ranks how deleterious any single-nucleotide variant is using 100+ annotation tracks (coding, non-coding, regulatory). PHRED 10 = top 10% most deleterious, 20 = top 1%, 30 = top 0.1%. **Opt-in** — disabled by default (`sources.cadd = false`). Enable via `allelix db update --cadd` or `allelix config set sources.cadd true`. Use `--no-cadd` to skip enrichment for a single run. Pre-built cache (~5 GB on disk, ~120M variant keys). Full mode available via pysam for GRCh38 data (`options.cadd_full = true`). Cache mode covers the large majority of variants present in gnomAD, AlphaMissense, and ClinVar — nearly every position allelix can annotate from its other databases. For genotyping chip data (23andMe, AncestryDNA, MyHappyGenes, etc.), cache and full mode produce effectively identical results because chip probes overwhelmingly target known, cataloged variants. Full mode adds coverage for novel or private variants that appear only in whole-genome or whole-exome sequencing data and are not in any pre-computed database. If your input is a genotyping chip file, cache mode is all you need. |
86
+ | CADD | ✓ | LicenseRef-CADD (non-commercial). **Enrichment annotator** — adds PHRED-scaled deleteriousness scores from CADD v1.7. **Opt-in**, disabled by default; see [CADD modes](#cadd-modes) below for details. |
87
+
88
+ ### CADD modes
89
+
90
+ CADD ranks how deleterious any single-nucleotide variant is using 100+ annotation tracks (coding, non-coding, regulatory). PHRED 10 = top 10% most deleterious, 20 = top 1%, 30 = top 0.1%.
91
+
92
+ **Opt-in**: enable via `allelix db update --cadd` or `allelix config set sources.cadd true`. Use `--no-cadd` to skip enrichment for a single run.
93
+
94
+ Two modes:
95
+
96
+ - **Cache mode** (default): pre-built ~5.8 GB SQLite cache (~120M variant keys). Covers nearly every position allelix can annotate from its other databases (gnomAD, AlphaMissense, ClinVar). For genotyping chip data (23andMe, AncestryDNA, MyHappyGenes, etc.), this mode is functionally complete — chip probes overwhelmingly target known, cataloged variants.
97
+ - **Full mode** (`options.cadd_full = true`): tabix queries against the complete CADD v1.7 file via pysam (GRCh38 only). Adds coverage for novel or private variants that appear only in WGS/WES data and are not in any pre-computed database. Requires `pip install allelix[cadd]` for the pysam dependency.
98
+
99
+ If your input is a genotyping chip file, cache mode is all you need.
83
100
 
84
101
  ### Build coverage asymmetry (GRCh37 vs GRCh38)
85
102
 
@@ -177,6 +194,48 @@ gnomAD and AlphaMissense are the largest but add the most interpretive context.
177
194
 
178
195
  To skip either during download: `allelix db update --no-gnomad --no-alphamissense`. To disable permanently: `allelix config set sources.gnomad false`.
179
196
 
197
+ ### Managing your data
198
+
199
+ Allelix stores reference database caches at `~/.local/share/allelix/` (Linux/macOS) or the XDG-equivalent on other platforms. Default install (no CADD) is ~16 GB on disk; with the CADD opt-in it's ~22 GB.
200
+
201
+ ```
202
+ ~/.local/share/allelix/
203
+ ├── alphamissense.sqlite ~7.8 GB
204
+ ├── gnomad.sqlite ~6.1 GB
205
+ ├── cadd.sqlite ~5.8 GB (only if you opted into --cadd)
206
+ ├── clinvar.GRCh37.sqlite ~470 MB
207
+ ├── clinvar.GRCh38.sqlite ~480 MB
208
+ ├── gwas_catalog_associations.tsv ~700 MB
209
+ ├── snpedia.sqlite ~390 MB
210
+ ├── gwas.sqlite ~190 MB
211
+ └── pharmgkb.sqlite ~14 MB
212
+ ```
213
+
214
+ **The caches are disposable and re-downloadable.** Everything here was fetched by `allelix db update` and can be re-fetched at any time. There's no per-user state in the cache — your genotype files never enter this directory. Safe to delete to reclaim space (or to force a full refresh if you suspect a cache went stale):
215
+
216
+ ```bash
217
+ # Reclaim all ~16-22 GB
218
+ rm -rf ~/.local/share/allelix/
219
+
220
+ # Reclaim everything except CADD (avoids re-downloading the slowest one)
221
+ find ~/.local/share/allelix/ -mindepth 1 ! -name 'cadd*.sqlite*' -delete
222
+
223
+ # Re-populate from scratch next time you analyze a file
224
+ allelix db update
225
+ ```
226
+
227
+ **The one thing worth backing up: `config.toml`.** It lives separately (XDG config dir, typically `~/.config/allelix/config.toml`) and stores your license assertions (commercial-mode toggle, CADD opt-in confirmation) and per-source enable/disable settings. It's the only non-reproducible state in the install. The caches will rebuild on the next `db update`; `config.toml` won't.
228
+
229
+ To fully uninstall allelix and reclaim everything:
230
+
231
+ ```bash
232
+ pip uninstall allelix
233
+ rm -rf ~/.local/share/allelix/ # reference database caches
234
+ rm -rf ~/.config/allelix/ # license + source-toggle config
235
+ ```
236
+
237
+ A `allelix db clean` subcommand to automate the cache-clear step (with confirmation prompt and size report) is planned for v2.2 alongside `allelix db path` (prints the resolved data directory for scripting / backup integration).
238
+
180
239
  ## Data Sources & Licensing
181
240
 
182
241
  Allelix source code is licensed under the **GNU Affero General Public License v3.0 or later** (AGPL-3.0-or-later). Allelix ships with **zero third-party data**. All reference databases are downloaded by the user at runtime via `allelix db update`. Each database retains its original license on the user's machine:
@@ -219,6 +278,42 @@ Of the 104,806 genotype pages in the archive:
219
278
 
220
279
  None of these are scraping errors. They are editorial inconsistencies on the source wiki. The annotator handles all of them correctly: incomplete entries are skipped, variant title formats are matched, and no false annotations are produced.
221
280
 
281
+ ## Troubleshooting
282
+
283
+ ### `ImportError: cannot import name 'UTC' from 'datetime'`
284
+
285
+ You're on Python 3.10 or older. Allelix requires 3.11+. `python --version` to check; upgrade and reinstall.
286
+
287
+ ### `allelix db update` failed partway through
288
+
289
+ Run it again. The update is idempotent — completed downloads are skipped (signal-matched against the upstream freshness probe), so re-running picks up where it left off. If a specific source keeps failing, you can isolate it: `allelix db update --no-gnomad` (or `--no-alphamissense` etc.) to skip the failing source for now, then retry that source standalone later.
290
+
291
+ If the failure is a network timeout on the same source repeatedly, the issue is likely on the source's end (HuggingFace / NCBI / EBI / etc. transient outage). Wait an hour and retry.
292
+
293
+ ### `db status` shows an annotator as "not ready" but the cache files exist on disk
294
+
295
+ Almost always a schema or interpreter version bump in a recent allelix release that invalidates the pre-bump cache. The fix is `allelix db update` — it'll re-ingest only the affected annotators (everything else signal-skips). See the relevant `CHANGELOG.md` entry for "Cache invalidation" notes per release.
296
+
297
+ ### Build mismatch warning when analyzing a known-good file
298
+
299
+ Either: (a) the file's header claims one build and the position data is actually a different build (real-world MHG / Tempus exports do this — header says 37, positions are 38; ADR-0021 documents the auto-detection), or (b) the file is a GRCh36-era export (pre-2012), which allelix detects accurately but doesn't ship a ClinVar cache for (see [`docs/grch36-liftover.md`](https://github.com/allelix/allelix/blob/main/docs/grch36-liftover.md) for the external-liftover path).
300
+
301
+ If you trust the file's header more than allelix's detection, force the build: `--build grch37` or `--build grch38`. The build banner in the analyze output will reflect what was actually used.
302
+
303
+ ### Is my database stale? How fresh is the data?
304
+
305
+ `allelix db status` shows the ingestion date of each cache. The freshness model is signal-driven: `allelix db update` queries an upstream freshness signal (md5 / etag) per source and re-ingests only when the signal changed. There's no time-based staleness — a 6-month-old cache against an unchanged source IS current.
306
+
307
+ If you want to force a refresh: `allelix db update --force` re-downloads every cache regardless of freshness signal.
308
+
309
+ ### Output reports contain real genome annotations
310
+
311
+ Anything `allelix analyze` writes to disk contains real annotations of your genome. Treat the output files (HTML, JSON, terminal redirects) as sensitive — they're as private as the input genotype file. Allelix does not transmit them; the lifecycle is local.
312
+
313
+ ### Where does allelix store its data?
314
+
315
+ See [Managing your data](#managing-your-data) above for cache locations, the disposability rule, what to back up, and the uninstall procedure.
316
+
222
317
  ## Architecture & Design Decisions
223
318
 
224
319
  The "why" behind major design choices lives in [`docs/adr/`](https://github.com/allelix/allelix/blob/main/docs/adr/README.md) as Architecture Decision Records. Read these before proposing changes that touch the parser/annotator interfaces, the regulatory posture, or the data-handling model.
@@ -57,18 +57,16 @@ def get_annotators(
57
57
  return [clinvar, pharmgkb, gwas, snpedia, gnomad, alphamissense, cadd]
58
58
 
59
59
 
60
- _ANNOTATOR_CLASSES: dict[str, type[Annotator]] = {
61
- cls.name: cls
62
- for cls in [
63
- ClinVarAnnotator,
64
- PharmGKBAnnotator,
65
- GWASCatalogAnnotator,
66
- SNPediaAnnotator,
67
- GnomadAnnotator,
68
- AlphaMissenseAnnotator,
69
- CaddAnnotator,
70
- ]
71
- }
60
+ _ANNOTATOR_CLASS_LIST: list[type[Annotator]] = [
61
+ ClinVarAnnotator,
62
+ PharmGKBAnnotator,
63
+ GWASCatalogAnnotator,
64
+ SNPediaAnnotator,
65
+ GnomadAnnotator,
66
+ AlphaMissenseAnnotator,
67
+ CaddAnnotator,
68
+ ]
69
+ _ANNOTATOR_CLASSES: dict[str, type[Annotator]] = {cls.name: cls for cls in _ANNOTATOR_CLASS_LIST}
72
70
 
73
71
 
74
72
  def get_annotator_class(name: str) -> type[Annotator] | None:
@@ -4,7 +4,6 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- import contextlib
8
7
  from abc import ABC, abstractmethod
9
8
  from dataclasses import dataclass
10
9
  from enum import Enum, auto
@@ -141,34 +140,6 @@ class Annotator(ABC):
141
140
  """Bind the annotator to a data directory (created elsewhere)."""
142
141
  self.data_dir = data_dir
143
142
 
144
- def __del__(self) -> None:
145
- """Safety-net resource release on GC. Deliberately retained.
146
-
147
- GH #36 (audit second pass) flagged ``__del__`` as a Python
148
- antipattern — GC timing is nondeterministic and raised exceptions
149
- are silently swallowed. The correct usage pattern is the
150
- ``__enter__`` / ``__exit__`` context manager pair below, wired
151
- through ``contextlib.ExitStack`` in ``reports/_pipeline.py``.
152
-
153
- However: removing ``__del__`` exposes residual SQLite connection
154
- leaks in code paths that construct an annotator outside a
155
- context manager. ``ResourceWarning`` is elevated to error by
156
- ``pytest`` config, so leaks fail the suite as
157
- ``PytestUnraisableExceptionWarning`` — caught in the v2.0.2
158
- ship gate when ``__del__`` was first removed. Until every call
159
- site is verified to use ``with`` / ``ExitStack`` / explicit
160
- ``close()``, this safety net stays. v2.1 task: audit and
161
- remove.
162
-
163
- ``contextlib.suppress(Exception)`` is deliberate — ``__del__``
164
- must never raise. The GC timing and shutdown-ordering edges
165
- are explicitly silenced; this is exactly the
166
- "if you must keep ``__del__``, make absolutely sure it can
167
- never raise" mitigation the audit recommended.
168
- """
169
- with contextlib.suppress(Exception):
170
- self.close()
171
-
172
143
  def __enter__(self) -> Annotator:
173
144
  """Return self for `with` usage."""
174
145
  return self
@@ -24,7 +24,7 @@ from __future__ import annotations
24
24
 
25
25
  import logging
26
26
  import sqlite3
27
- from typing import TYPE_CHECKING, ClassVar
27
+ from typing import TYPE_CHECKING, Any, ClassVar
28
28
 
29
29
  from allelix.annotators.base import Annotator, LicenseDescriptor
30
30
  from allelix.databases._versions import CADD_SCHEMA_VERSION
@@ -90,9 +90,11 @@ class CaddAnnotator(Annotator):
90
90
  self._conn: sqlite3.Connection | None = None
91
91
  self._full_mode = full_mode
92
92
  self._tabix_path = data_dir / CADD_FULL_FILENAME
93
- self._tabix: object | None = None
93
+ # pysam ships no type stubs (see [tool.mypy.overrides]); the tabix
94
+ # handle is held as Any so call sites can use `.fetch()` / `.close()`.
95
+ self._tabix: Any = None
94
96
  self._indel_tabix_path = data_dir / CADD_INDEL_FILENAME
95
- self._indel_tabix: object | None = None
97
+ self._indel_tabix: Any = None
96
98
 
97
99
  def _connection(self) -> sqlite3.Connection:
98
100
  if self._conn is None:
@@ -104,11 +106,11 @@ class CaddAnnotator(Annotator):
104
106
  self._conn = sqlite3.connect(self._db_path)
105
107
  return self._conn
106
108
 
107
- def _open_tabix(self) -> object:
109
+ def _open_tabix(self) -> Any: # noqa: ANN401 — pysam ships no type stubs
108
110
  """Open the tabix file for full-mode queries."""
109
111
  if self._tabix is None:
110
112
  try:
111
- import pysam # type: ignore[import-untyped]
113
+ import pysam
112
114
  except ImportError:
113
115
  raise ImportError(
114
116
  "Full CADD mode requires pysam. Install with: pip install 'allelix[cadd]'"
@@ -122,7 +124,7 @@ class CaddAnnotator(Annotator):
122
124
  self._tabix = pysam.TabixFile(str(self._tabix_path))
123
125
  return self._tabix
124
126
 
125
- def _open_indel_tabix(self) -> object | None:
127
+ def _open_indel_tabix(self) -> Any: # noqa: ANN401 — pysam ships no type stubs
126
128
  """Open the indel tabix file. Returns None if file doesn't exist."""
127
129
  if self._indel_tabix is None:
128
130
  if not self._indel_tabix_path.exists():
@@ -131,7 +133,7 @@ class CaddAnnotator(Annotator):
131
133
  if not tbi.exists():
132
134
  return None
133
135
  try:
134
- import pysam # type: ignore[import-untyped]
136
+ import pysam
135
137
  except ImportError:
136
138
  return None
137
139
  self._indel_tabix = pysam.TabixFile(str(self._indel_tabix_path))
@@ -500,9 +500,10 @@ class ClinVarAnnotator(Annotator):
500
500
  for rows in clinvar_rows.values():
501
501
  rows.sort()
502
502
  for v in build_variants:
503
- rows = clinvar_rows.get((v.chromosome, v.position))
504
- if not rows:
503
+ pos_rows = clinvar_rows.get((v.chromosome, v.position))
504
+ if not pos_rows:
505
505
  continue
506
+ rows = pos_rows
506
507
  user_alleles = {v.allele1, v.allele2}
507
508
  # Stamp only on a UNIQUE consistent match. Multiple matches
508
509
  # mean the Variant model can't distinguish them (the 1/1
@@ -16,7 +16,7 @@ import logging
16
16
  import re
17
17
  import sys
18
18
  import time
19
- from typing import TYPE_CHECKING, NamedTuple
19
+ from typing import TYPE_CHECKING, NamedTuple, cast
20
20
 
21
21
  import click
22
22
  from rich.console import Console
@@ -34,7 +34,10 @@ from allelix.reports.terminal import render_terminal, render_terminal_diff
34
34
  if TYPE_CHECKING:
35
35
  from pathlib import Path
36
36
 
37
+ from allelix.annotators.alphamissense import AlphaMissenseAnnotator
37
38
  from allelix.annotators.base import Annotator
39
+ from allelix.annotators.cadd import CaddAnnotator
40
+ from allelix.annotators.gnomad import GnomadAnnotator
38
41
  from allelix.parsers.base import GenotypeParser
39
42
 
40
43
  console = Console()
@@ -489,9 +492,9 @@ def _run_analysis_command(
489
492
  ready,
490
493
  skipped_count_provider=lambda: counter.count,
491
494
  build_override=build,
492
- gnomad=gnomad_annotator,
493
- alphamissense=am_annotator,
494
- cadd=cadd_annotator,
495
+ gnomad=cast("GnomadAnnotator | None", gnomad_annotator),
496
+ alphamissense=cast("AlphaMissenseAnnotator | None", am_annotator),
497
+ cadd=cast("CaddAnnotator | None", cadd_annotator),
495
498
  high_value_rsids=hv_rsids,
496
499
  )
497
500
  finally: