allelix 2.1.1__tar.gz → 2.2.0__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 (85) hide show
  1. {allelix-2.1.1 → allelix-2.2.0}/PKG-INFO +56 -38
  2. {allelix-2.1.1 → allelix-2.2.0}/README.md +53 -37
  3. {allelix-2.1.1 → allelix-2.2.0}/allelix/annotators/__init__.py +10 -12
  4. {allelix-2.1.1 → allelix-2.2.0}/allelix/annotators/base.py +0 -38
  5. {allelix-2.1.1 → allelix-2.2.0}/allelix/annotators/cadd.py +9 -7
  6. {allelix-2.1.1 → allelix-2.2.0}/allelix/annotators/clinvar.py +122 -57
  7. {allelix-2.1.1 → allelix-2.2.0}/allelix/cli/_helpers.py +66 -4
  8. {allelix-2.1.1 → allelix-2.2.0}/allelix/cli/_options.py +19 -0
  9. {allelix-2.1.1 → allelix-2.2.0}/allelix/cli/config.py +10 -18
  10. {allelix-2.1.1 → allelix-2.2.0}/allelix/cli/db.py +231 -0
  11. {allelix-2.1.1 → allelix-2.2.0}/allelix/cli/focused.py +3 -3
  12. {allelix-2.1.1 → allelix-2.2.0}/allelix/cli/utility.py +6 -36
  13. {allelix-2.1.1 → allelix-2.2.0}/allelix/config.py +34 -14
  14. {allelix-2.1.1 → allelix-2.2.0}/allelix/data/clinvar_clnsig_snapshot.yaml +13 -1
  15. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/_versions.py +7 -1
  16. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/cpic_loader.py +2 -2
  17. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/gwas_loader.py +12 -2
  18. allelix-2.2.0/allelix/databases/manager.py +758 -0
  19. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/snpedia_parser.py +6 -4
  20. {allelix-2.1.1 → allelix-2.2.0}/allelix/exporters/plink.py +50 -1
  21. {allelix-2.1.1 → allelix-2.2.0}/allelix/parsers/__init__.py +2 -0
  22. allelix-2.2.0/allelix/parsers/ftdna_famfinder.py +175 -0
  23. {allelix-2.1.1 → allelix-2.2.0}/allelix/reports/__init__.py +3 -3
  24. {allelix-2.1.1 → allelix-2.2.0}/allelix/reports/_pipeline.py +121 -5
  25. {allelix-2.1.1 → allelix-2.2.0}/allelix/reports/diff.py +1 -1
  26. {allelix-2.1.1 → allelix-2.2.0}/allelix/reports/html.py +54 -1
  27. {allelix-2.1.1 → allelix-2.2.0}/allelix/reports/json_report.py +10 -2
  28. {allelix-2.1.1 → allelix-2.2.0}/allelix/reports/terminal.py +38 -0
  29. {allelix-2.1.1 → allelix-2.2.0}/allelix/utils/allele.py +18 -0
  30. {allelix-2.1.1 → allelix-2.2.0}/allelix.egg-info/PKG-INFO +56 -38
  31. {allelix-2.1.1 → allelix-2.2.0}/allelix.egg-info/SOURCES.txt +1 -0
  32. {allelix-2.1.1 → allelix-2.2.0}/allelix.egg-info/requires.txt +2 -0
  33. allelix-2.2.0/pyproject.toml +156 -0
  34. {allelix-2.1.1 → allelix-2.2.0}/tests/test_cli.py +474 -41
  35. {allelix-2.1.1 → allelix-2.2.0}/tests/test_config.py +23 -0
  36. {allelix-2.1.1 → allelix-2.2.0}/tests/test_mock_data_invariants.py +0 -137
  37. allelix-2.1.1/allelix/databases/manager.py +0 -532
  38. allelix-2.1.1/pyproject.toml +0 -104
  39. {allelix-2.1.1 → allelix-2.2.0}/LICENSE +0 -0
  40. {allelix-2.1.1 → allelix-2.2.0}/allelix/__init__.py +0 -0
  41. {allelix-2.1.1 → allelix-2.2.0}/allelix/annotators/alphamissense.py +0 -0
  42. {allelix-2.1.1 → allelix-2.2.0}/allelix/annotators/gnomad.py +0 -0
  43. {allelix-2.1.1 → allelix-2.2.0}/allelix/annotators/gwas.py +0 -0
  44. {allelix-2.1.1 → allelix-2.2.0}/allelix/annotators/pharmgkb.py +0 -0
  45. {allelix-2.1.1 → allelix-2.2.0}/allelix/annotators/snpedia.py +0 -0
  46. {allelix-2.1.1 → allelix-2.2.0}/allelix/cli/__init__.py +0 -0
  47. {allelix-2.1.1 → allelix-2.2.0}/allelix/cli/analyze.py +0 -0
  48. {allelix-2.1.1 → allelix-2.2.0}/allelix/compare.py +0 -0
  49. {allelix-2.1.1 → allelix-2.2.0}/allelix/data/__init__.py +0 -0
  50. {allelix-2.1.1 → allelix-2.2.0}/allelix/data/high_value_snps.yaml +0 -0
  51. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/__init__.py +0 -0
  52. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/alphamissense_loader.py +0 -0
  53. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/cadd_loader.py +0 -0
  54. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/gnomad_loader.py +0 -0
  55. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/loader_utils.py +0 -0
  56. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/pharmgkb_loader.py +0 -0
  57. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/schema.py +0 -0
  58. {allelix-2.1.1 → allelix-2.2.0}/allelix/databases/snpedia_loader.py +0 -0
  59. {allelix-2.1.1 → allelix-2.2.0}/allelix/exporters/__init__.py +0 -0
  60. {allelix-2.1.1 → allelix-2.2.0}/allelix/models.py +0 -0
  61. {allelix-2.1.1 → allelix-2.2.0}/allelix/parsers/_helpers.py +0 -0
  62. {allelix-2.1.1 → allelix-2.2.0}/allelix/parsers/ancestrydna.py +0 -0
  63. {allelix-2.1.1 → allelix-2.2.0}/allelix/parsers/base.py +0 -0
  64. {allelix-2.1.1 → allelix-2.2.0}/allelix/parsers/ftdna.py +0 -0
  65. {allelix-2.1.1 → allelix-2.2.0}/allelix/parsers/ftdna_illumina.py +0 -0
  66. {allelix-2.1.1 → allelix-2.2.0}/allelix/parsers/livingdna.py +0 -0
  67. {allelix-2.1.1 → allelix-2.2.0}/allelix/parsers/myhappygenes.py +0 -0
  68. {allelix-2.1.1 → allelix-2.2.0}/allelix/parsers/myheritage.py +0 -0
  69. {allelix-2.1.1 → allelix-2.2.0}/allelix/parsers/twentythreeandme.py +0 -0
  70. {allelix-2.1.1 → allelix-2.2.0}/allelix/parsers/vcf.py +0 -0
  71. {allelix-2.1.1 → allelix-2.2.0}/allelix/py.typed +0 -0
  72. {allelix-2.1.1 → allelix-2.2.0}/allelix/reports/high_value.py +0 -0
  73. {allelix-2.1.1 → allelix-2.2.0}/allelix/reports/methylation.py +0 -0
  74. {allelix-2.1.1 → allelix-2.2.0}/allelix/utils/__init__.py +0 -0
  75. {allelix-2.1.1 → allelix-2.2.0}/allelix/utils/build_detect.py +0 -0
  76. {allelix-2.1.1 → allelix-2.2.0}/allelix.egg-info/dependency_links.txt +0 -0
  77. {allelix-2.1.1 → allelix-2.2.0}/allelix.egg-info/entry_points.txt +0 -0
  78. {allelix-2.1.1 → allelix-2.2.0}/allelix.egg-info/top_level.txt +0 -0
  79. {allelix-2.1.1 → allelix-2.2.0}/setup.cfg +0 -0
  80. {allelix-2.1.1 → allelix-2.2.0}/tests/test_cli_helpers.py +0 -0
  81. {allelix-2.1.1 → allelix-2.2.0}/tests/test_compare.py +0 -0
  82. {allelix-2.1.1 → allelix-2.2.0}/tests/test_end_to_end.py +0 -0
  83. {allelix-2.1.1 → allelix-2.2.0}/tests/test_models.py +0 -0
  84. {allelix-2.1.1 → allelix-2.2.0}/tests/test_registry.py +0 -0
  85. {allelix-2.1.1 → allelix-2.2.0}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: allelix
3
- Version: 2.1.1
3
+ Version: 2.2.0
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,35 +29,33 @@ 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.1.0** —
51
- > ADR-0035 'Cluster B' coordinated model change: `Variant.ref` carries
52
- > the forward-strand reference allele, per-Annotation `alt` threading
53
- > on GWAS / SNPedia / ClinPGx enables allele-specific gnomAD /
54
- > AlphaMissense / CADD enrichment on array inputs, structured GWAS
55
- > fields (`trait` / `p_value` / `phecode`) replace prose re-parsing in
56
- > the rollup, and ClinVar / ClinPGx carrier matching is strand-aware
57
- > with multi-allelic safety guard. JSON `schema_version` bumped 4 → 5
58
- > (additive, v4 readers unaffected).
59
- > Release notes:
60
- > [`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.
61
59
 
62
60
  ## Quickstart
63
61
 
@@ -97,20 +95,22 @@ See [Development](#development) for source installs and running tests, [Managing
97
95
  | MyHeritage DNA | ✓ | CSV, same structure as FTDNA. Detected by "MyHeritage" in comment header. Handles double-double-quoted field variant. |
98
96
  | Living DNA | ✓ | Tab-delimited despite `.csv` extension. Handles AX-, AFFX-prefixed and CHR:POS positional SNP IDs. |
99
97
  | FTDNA Illumina raw | ✓ | Tab-delimited variant of the FTDNA export (distinct from the CSV format above). `RSID/CHROMOSOME/POSITION/RESULT` columns. Build 37 default. |
98
+ | FTDNA FamFinder | ✓ | Third FTDNA file shape: tab-delimited with **separate `ALLELE1` / `ALLELE2` columns** instead of a concatenated `RESULT`. Detected by the `famfinder` substring in the file header plus the 5-column canonical header. Build 37 default. |
100
99
  | VCF / gVCF | ✓ | REF/ALT encoding, `0/1` genotype notation. Plain VCF: absence at a position means reference. gVCF: explicit reference blocks (lines with `<NON_REF>` ALT and `END=` INFO) are skipped — they match nothing in any annotation database. Multi-sample files require `--sample <ID>`. Streams via stdlib; `.vcf.gz` handled transparently. Optional `pip install allelix[vcf-index]` enables pysam-backed tabix random access for fast `extract --snps` on huge VCFs. |
101
100
 
102
101
  Adding a new format means adding one file to `allelix/parsers/` and registering an instance in the `PARSERS` list in `allelix/parsers/__init__.py`.
103
102
 
104
- ### v2.1+ roadmap
103
+ ### Roadmap
105
104
 
106
- | Feature | Notes |
105
+ | Release | Theme |
107
106
  |---|---|
108
- | Per-source scoring | Magnitude breakdown by database. Users see which source drove the composite score. |
109
- | 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. |
110
- | 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. |
111
- | PLINK import | Read .bed/.bim/.fam as an input format (complement to the v1.7.0 export). |
112
- | PharmCAT integration | Wrap CPIC's PharmCAT as an optional external engine for star-allele / diplotype calling. Requires VCF input (shipped in v2.0.0). |
113
- | 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. |
107
+ | **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`). |
108
+ | **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. |
109
+ | **v2.4** | ACMG/AMP automated classification engine, variant consequence annotation (VEP-equivalent). |
110
+ | **v2.5+** | CNVs and repeat-expansion support, dbSNP resolution for rsID-less VCFs, gnomAD genomes (non-coding population frequencies), supplemental genotype file merging. |
111
+ | **v3.0** | Ancestry estimation (PCA + 1KG/HGDP reference panels), Polygenic Risk Score (PRS) integration. |
112
+
113
+ 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.
114
114
 
115
115
  ## Supported Databases
116
116
 
@@ -254,28 +254,44 @@ Allelix stores reference database caches at `~/.local/share/allelix/` (Linux/mac
254
254
  **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):
255
255
 
256
256
  ```bash
257
- # Reclaim all ~16-22 GB
258
- rm -rf ~/.local/share/allelix/
257
+ # Reclaim all ~16-22 GB, with size report and confirmation prompt
258
+ allelix db clean
259
+
260
+ # Preserve the CADD cache (avoids re-downloading the slowest one, ~5.8 GB)
261
+ allelix db clean --keep-cadd
262
+
263
+ # Show what would be deleted without acting
264
+ allelix db clean --dry-run
259
265
 
260
- # Reclaim everything except CADD (avoids re-downloading the slowest one)
261
- find ~/.local/share/allelix/ -mindepth 1 ! -name 'cadd*.sqlite*' -delete
266
+ # Scripted use — skip the confirmation prompt
267
+ allelix db clean --yes
262
268
 
263
269
  # Re-populate from scratch next time you analyze a file
264
270
  allelix db update
265
271
  ```
266
272
 
267
- **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.
273
+ `db clean` refuses to act on a directory that doesn't contain any recognized allelix cache files — a typo'd `--data-dir ~/Documents` won't result in `rm -rf` against an unrelated tree, even with `--yes`. Pass `--force` if you've staged caches under a non-standard name and want to delete them anyway.
274
+
275
+ For scripting and backup integration, `allelix db path` prints the resolved data directory:
276
+
277
+ ```bash
278
+ ALLELIX_DATA=$(allelix db path)
279
+ du -sh "$ALLELIX_DATA"
280
+
281
+ # --check additionally verifies the path is writable; exit non-zero if not
282
+ allelix db path --check
283
+ ```
284
+
285
+ **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. `allelix db clean` preserves `config.toml` even when it lives inside the data directory (older installs).
268
286
 
269
287
  To fully uninstall allelix and reclaim everything:
270
288
 
271
289
  ```bash
272
290
  pip uninstall allelix
273
- rm -rf ~/.local/share/allelix/ # reference database caches
291
+ allelix db clean --yes # reference database caches
274
292
  rm -rf ~/.config/allelix/ # license + source-toggle config
275
293
  ```
276
294
 
277
- 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).
278
-
279
295
  ## Data Sources & Licensing
280
296
 
281
297
  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:
@@ -384,6 +400,8 @@ pytest
384
400
 
385
401
  The pre-commit hook enforces `ruff check` + `ruff format --check`. If a commit is blocked, fix the underlying problem rather than skipping the hook.
386
402
 
403
+ For the full real-data release-validation battery — every parser format, VCF/gVCF, PLINK round-trip, edge cases, the GIAB/1000G corpus — see [`test_data/FULL_TEST_PROTOCOL.md`](test_data/FULL_TEST_PROTOCOL.md). That document is the project's real-data testing story; running `pytest` covers the fast suite that gates every commit.
404
+
387
405
  ## License
388
406
 
389
407
  GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See `LICENSE`.
@@ -1,26 +1,22 @@
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.1.0** —
14
- > ADR-0035 'Cluster B' coordinated model change: `Variant.ref` carries
15
- > the forward-strand reference allele, per-Annotation `alt` threading
16
- > on GWAS / SNPedia / ClinPGx enables allele-specific gnomAD /
17
- > AlphaMissense / CADD enrichment on array inputs, structured GWAS
18
- > fields (`trait` / `p_value` / `phecode`) replace prose re-parsing in
19
- > the rollup, and ClinVar / ClinPGx carrier matching is strand-aware
20
- > with multi-allelic safety guard. JSON `schema_version` bumped 4 → 5
21
- > (additive, v4 readers unaffected).
22
- > Release notes:
23
- > [`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.
24
20
 
25
21
  ## Quickstart
26
22
 
@@ -60,20 +56,22 @@ See [Development](#development) for source installs and running tests, [Managing
60
56
  | MyHeritage DNA | ✓ | CSV, same structure as FTDNA. Detected by "MyHeritage" in comment header. Handles double-double-quoted field variant. |
61
57
  | Living DNA | ✓ | Tab-delimited despite `.csv` extension. Handles AX-, AFFX-prefixed and CHR:POS positional SNP IDs. |
62
58
  | FTDNA Illumina raw | ✓ | Tab-delimited variant of the FTDNA export (distinct from the CSV format above). `RSID/CHROMOSOME/POSITION/RESULT` columns. Build 37 default. |
59
+ | FTDNA FamFinder | ✓ | Third FTDNA file shape: tab-delimited with **separate `ALLELE1` / `ALLELE2` columns** instead of a concatenated `RESULT`. Detected by the `famfinder` substring in the file header plus the 5-column canonical header. Build 37 default. |
63
60
  | VCF / gVCF | ✓ | REF/ALT encoding, `0/1` genotype notation. Plain VCF: absence at a position means reference. gVCF: explicit reference blocks (lines with `<NON_REF>` ALT and `END=` INFO) are skipped — they match nothing in any annotation database. Multi-sample files require `--sample <ID>`. Streams via stdlib; `.vcf.gz` handled transparently. Optional `pip install allelix[vcf-index]` enables pysam-backed tabix random access for fast `extract --snps` on huge VCFs. |
64
61
 
65
62
  Adding a new format means adding one file to `allelix/parsers/` and registering an instance in the `PARSERS` list in `allelix/parsers/__init__.py`.
66
63
 
67
- ### v2.1+ roadmap
64
+ ### Roadmap
68
65
 
69
- | Feature | Notes |
66
+ | Release | Theme |
70
67
  |---|---|
71
- | Per-source scoring | Magnitude breakdown by database. Users see which source drove the composite score. |
72
- | 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. |
73
- | 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. |
74
- | PLINK import | Read .bed/.bim/.fam as an input format (complement to the v1.7.0 export). |
75
- | PharmCAT integration | Wrap CPIC's PharmCAT as an optional external engine for star-allele / diplotype calling. Requires VCF input (shipped in v2.0.0). |
76
- | 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. |
68
+ | **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`). |
69
+ | **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. |
70
+ | **v2.4** | ACMG/AMP automated classification engine, variant consequence annotation (VEP-equivalent). |
71
+ | **v2.5+** | CNVs and repeat-expansion support, dbSNP resolution for rsID-less VCFs, gnomAD genomes (non-coding population frequencies), supplemental genotype file merging. |
72
+ | **v3.0** | Ancestry estimation (PCA + 1KG/HGDP reference panels), Polygenic Risk Score (PRS) integration. |
73
+
74
+ 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.
77
75
 
78
76
  ## Supported Databases
79
77
 
@@ -217,28 +215,44 @@ Allelix stores reference database caches at `~/.local/share/allelix/` (Linux/mac
217
215
  **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):
218
216
 
219
217
  ```bash
220
- # Reclaim all ~16-22 GB
221
- rm -rf ~/.local/share/allelix/
218
+ # Reclaim all ~16-22 GB, with size report and confirmation prompt
219
+ allelix db clean
220
+
221
+ # Preserve the CADD cache (avoids re-downloading the slowest one, ~5.8 GB)
222
+ allelix db clean --keep-cadd
223
+
224
+ # Show what would be deleted without acting
225
+ allelix db clean --dry-run
222
226
 
223
- # Reclaim everything except CADD (avoids re-downloading the slowest one)
224
- find ~/.local/share/allelix/ -mindepth 1 ! -name 'cadd*.sqlite*' -delete
227
+ # Scripted use — skip the confirmation prompt
228
+ allelix db clean --yes
225
229
 
226
230
  # Re-populate from scratch next time you analyze a file
227
231
  allelix db update
228
232
  ```
229
233
 
230
- **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.
234
+ `db clean` refuses to act on a directory that doesn't contain any recognized allelix cache files — a typo'd `--data-dir ~/Documents` won't result in `rm -rf` against an unrelated tree, even with `--yes`. Pass `--force` if you've staged caches under a non-standard name and want to delete them anyway.
235
+
236
+ For scripting and backup integration, `allelix db path` prints the resolved data directory:
237
+
238
+ ```bash
239
+ ALLELIX_DATA=$(allelix db path)
240
+ du -sh "$ALLELIX_DATA"
241
+
242
+ # --check additionally verifies the path is writable; exit non-zero if not
243
+ allelix db path --check
244
+ ```
245
+
246
+ **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. `allelix db clean` preserves `config.toml` even when it lives inside the data directory (older installs).
231
247
 
232
248
  To fully uninstall allelix and reclaim everything:
233
249
 
234
250
  ```bash
235
251
  pip uninstall allelix
236
- rm -rf ~/.local/share/allelix/ # reference database caches
252
+ allelix db clean --yes # reference database caches
237
253
  rm -rf ~/.config/allelix/ # license + source-toggle config
238
254
  ```
239
255
 
240
- 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).
241
-
242
256
  ## Data Sources & Licensing
243
257
 
244
258
  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:
@@ -347,6 +361,8 @@ pytest
347
361
 
348
362
  The pre-commit hook enforces `ruff check` + `ruff format --check`. If a commit is blocked, fix the underlying problem rather than skipping the hook.
349
363
 
364
+ For the full real-data release-validation battery — every parser format, VCF/gVCF, PLINK round-trip, edge cases, the GIAB/1000G corpus — see [`test_data/FULL_TEST_PROTOCOL.md`](test_data/FULL_TEST_PROTOCOL.md). That document is the project's real-data testing story; running `pytest` covers the fast suite that gates every commit.
365
+
350
366
  ## License
351
367
 
352
368
  GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See `LICENSE`.
@@ -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,43 +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
- Status (v2.1.1 audit, GH #36):
148
- - **Production callsites are clean.** ``run_analysis`` wires every
149
- primary + enrichment annotator into ``contextlib.ExitStack``
150
- (v2.0.2 fix). ``allelix/cli/utility.py`` lines 221 and 476 each
151
- construct a ``GnomadAnnotator`` inside ``try / finally
152
- gnomad.close()``. ``allelix/annotators/__init__.py`` returns a
153
- list that the pipeline immediately wraps in ``ExitStack``.
154
- - **Test callsites NOT yet clean.** ~27 sites in
155
- ``tests/reports/test_pipeline.py`` instantiate annotators
156
- directly without a context manager or explicit close. The
157
- pipeline closes them via its own ``ExitStack`` during
158
- ``run_analysis``, but the test still holds the closed-instance
159
- reference, and the underlying sqlite3 ``Connection``'s GC
160
- finalizer surfaces a ``ResourceWarning`` during the next test's
161
- startup. pytest's ``error::PytestUnraisableExceptionWarning``
162
- filter then converts it to a hard failure on the gate.
163
-
164
- Removing ``__del__`` without first refactoring the test sites
165
- therefore fails the gate (verified — 3 failures, all in
166
- ``tests/reports/test_pipeline.py``, triggered by leaks from
167
- earlier tests in the same file). The test-site refactor is
168
- tracked separately; ``__del__`` stays as the safety net until
169
- that lands. v2.1.x task — see the follow-up issue filed at
170
- ``#36``-closure-time.
171
-
172
- ``contextlib.suppress(Exception)`` is deliberate — ``__del__``
173
- must never raise. GC timing and shutdown-ordering edges are
174
- explicitly silenced; this is exactly the "if you must keep
175
- ``__del__``, make absolutely sure it can never raise"
176
- mitigation the audit recommended.
177
- """
178
- with contextlib.suppress(Exception):
179
- self.close()
180
-
181
143
  def __enter__(self) -> Annotator:
182
144
  """Return self for `with` usage."""
183
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))