allelix 2.1.0__tar.gz → 2.1.1__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.1}/PKG-INFO +106 -8
  2. {allelix-2.1.0 → allelix-2.1.1}/README.md +105 -7
  3. {allelix-2.1.0 → allelix-2.1.1}/allelix/annotators/base.py +28 -19
  4. {allelix-2.1.0 → allelix-2.1.1}/allelix/models.py +36 -1
  5. {allelix-2.1.0 → allelix-2.1.1}/allelix/reports/diff.py +4 -2
  6. {allelix-2.1.0 → allelix-2.1.1}/allelix/reports/json_report.py +3 -5
  7. {allelix-2.1.0 → allelix-2.1.1}/allelix.egg-info/PKG-INFO +106 -8
  8. {allelix-2.1.0 → allelix-2.1.1}/pyproject.toml +1 -1
  9. {allelix-2.1.0 → allelix-2.1.1}/tests/test_models.py +46 -0
  10. {allelix-2.1.0 → allelix-2.1.1}/LICENSE +0 -0
  11. {allelix-2.1.0 → allelix-2.1.1}/allelix/__init__.py +0 -0
  12. {allelix-2.1.0 → allelix-2.1.1}/allelix/annotators/__init__.py +0 -0
  13. {allelix-2.1.0 → allelix-2.1.1}/allelix/annotators/alphamissense.py +0 -0
  14. {allelix-2.1.0 → allelix-2.1.1}/allelix/annotators/cadd.py +0 -0
  15. {allelix-2.1.0 → allelix-2.1.1}/allelix/annotators/clinvar.py +0 -0
  16. {allelix-2.1.0 → allelix-2.1.1}/allelix/annotators/gnomad.py +0 -0
  17. {allelix-2.1.0 → allelix-2.1.1}/allelix/annotators/gwas.py +0 -0
  18. {allelix-2.1.0 → allelix-2.1.1}/allelix/annotators/pharmgkb.py +0 -0
  19. {allelix-2.1.0 → allelix-2.1.1}/allelix/annotators/snpedia.py +0 -0
  20. {allelix-2.1.0 → allelix-2.1.1}/allelix/cli/__init__.py +0 -0
  21. {allelix-2.1.0 → allelix-2.1.1}/allelix/cli/_helpers.py +0 -0
  22. {allelix-2.1.0 → allelix-2.1.1}/allelix/cli/_options.py +0 -0
  23. {allelix-2.1.0 → allelix-2.1.1}/allelix/cli/analyze.py +0 -0
  24. {allelix-2.1.0 → allelix-2.1.1}/allelix/cli/config.py +0 -0
  25. {allelix-2.1.0 → allelix-2.1.1}/allelix/cli/db.py +0 -0
  26. {allelix-2.1.0 → allelix-2.1.1}/allelix/cli/focused.py +0 -0
  27. {allelix-2.1.0 → allelix-2.1.1}/allelix/cli/utility.py +0 -0
  28. {allelix-2.1.0 → allelix-2.1.1}/allelix/compare.py +0 -0
  29. {allelix-2.1.0 → allelix-2.1.1}/allelix/config.py +0 -0
  30. {allelix-2.1.0 → allelix-2.1.1}/allelix/data/__init__.py +0 -0
  31. {allelix-2.1.0 → allelix-2.1.1}/allelix/data/clinvar_clnsig_snapshot.yaml +0 -0
  32. {allelix-2.1.0 → allelix-2.1.1}/allelix/data/high_value_snps.yaml +0 -0
  33. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/__init__.py +0 -0
  34. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/_versions.py +0 -0
  35. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/alphamissense_loader.py +0 -0
  36. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/cadd_loader.py +0 -0
  37. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/cpic_loader.py +0 -0
  38. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/gnomad_loader.py +0 -0
  39. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/gwas_loader.py +0 -0
  40. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/loader_utils.py +0 -0
  41. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/manager.py +0 -0
  42. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/pharmgkb_loader.py +0 -0
  43. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/schema.py +0 -0
  44. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/snpedia_loader.py +0 -0
  45. {allelix-2.1.0 → allelix-2.1.1}/allelix/databases/snpedia_parser.py +0 -0
  46. {allelix-2.1.0 → allelix-2.1.1}/allelix/exporters/__init__.py +0 -0
  47. {allelix-2.1.0 → allelix-2.1.1}/allelix/exporters/plink.py +0 -0
  48. {allelix-2.1.0 → allelix-2.1.1}/allelix/parsers/__init__.py +0 -0
  49. {allelix-2.1.0 → allelix-2.1.1}/allelix/parsers/_helpers.py +0 -0
  50. {allelix-2.1.0 → allelix-2.1.1}/allelix/parsers/ancestrydna.py +0 -0
  51. {allelix-2.1.0 → allelix-2.1.1}/allelix/parsers/base.py +0 -0
  52. {allelix-2.1.0 → allelix-2.1.1}/allelix/parsers/ftdna.py +0 -0
  53. {allelix-2.1.0 → allelix-2.1.1}/allelix/parsers/ftdna_illumina.py +0 -0
  54. {allelix-2.1.0 → allelix-2.1.1}/allelix/parsers/livingdna.py +0 -0
  55. {allelix-2.1.0 → allelix-2.1.1}/allelix/parsers/myhappygenes.py +0 -0
  56. {allelix-2.1.0 → allelix-2.1.1}/allelix/parsers/myheritage.py +0 -0
  57. {allelix-2.1.0 → allelix-2.1.1}/allelix/parsers/twentythreeandme.py +0 -0
  58. {allelix-2.1.0 → allelix-2.1.1}/allelix/parsers/vcf.py +0 -0
  59. {allelix-2.1.0 → allelix-2.1.1}/allelix/py.typed +0 -0
  60. {allelix-2.1.0 → allelix-2.1.1}/allelix/reports/__init__.py +0 -0
  61. {allelix-2.1.0 → allelix-2.1.1}/allelix/reports/_pipeline.py +0 -0
  62. {allelix-2.1.0 → allelix-2.1.1}/allelix/reports/high_value.py +0 -0
  63. {allelix-2.1.0 → allelix-2.1.1}/allelix/reports/html.py +0 -0
  64. {allelix-2.1.0 → allelix-2.1.1}/allelix/reports/methylation.py +0 -0
  65. {allelix-2.1.0 → allelix-2.1.1}/allelix/reports/terminal.py +0 -0
  66. {allelix-2.1.0 → allelix-2.1.1}/allelix/utils/__init__.py +0 -0
  67. {allelix-2.1.0 → allelix-2.1.1}/allelix/utils/allele.py +0 -0
  68. {allelix-2.1.0 → allelix-2.1.1}/allelix/utils/build_detect.py +0 -0
  69. {allelix-2.1.0 → allelix-2.1.1}/allelix.egg-info/SOURCES.txt +0 -0
  70. {allelix-2.1.0 → allelix-2.1.1}/allelix.egg-info/dependency_links.txt +0 -0
  71. {allelix-2.1.0 → allelix-2.1.1}/allelix.egg-info/entry_points.txt +0 -0
  72. {allelix-2.1.0 → allelix-2.1.1}/allelix.egg-info/requires.txt +0 -0
  73. {allelix-2.1.0 → allelix-2.1.1}/allelix.egg-info/top_level.txt +0 -0
  74. {allelix-2.1.0 → allelix-2.1.1}/setup.cfg +0 -0
  75. {allelix-2.1.0 → allelix-2.1.1}/tests/test_cli.py +0 -0
  76. {allelix-2.1.0 → allelix-2.1.1}/tests/test_cli_helpers.py +0 -0
  77. {allelix-2.1.0 → allelix-2.1.1}/tests/test_compare.py +0 -0
  78. {allelix-2.1.0 → allelix-2.1.1}/tests/test_config.py +0 -0
  79. {allelix-2.1.0 → allelix-2.1.1}/tests/test_end_to_end.py +0 -0
  80. {allelix-2.1.0 → allelix-2.1.1}/tests/test_mock_data_invariants.py +0 -0
  81. {allelix-2.1.0 → allelix-2.1.1}/tests/test_registry.py +0 -0
  82. {allelix-2.1.0 → allelix-2.1.1}/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.1
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>
@@ -47,19 +47,26 @@ Open-source command-line toolkit for analyzing raw genotype files from consumer
47
47
  > HTML/JSON/terminal reports, methylation + pharmacogenomics focused
48
48
  > commands, report diffing, persistent config with commercial-mode
49
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.
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).
54
59
  > Release notes:
55
60
  > [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/main/CHANGELOG.md).
56
61
 
57
62
  ## Quickstart
58
63
 
64
+ **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`.
65
+
59
66
  ```bash
60
67
  pip install allelix
61
68
 
62
- # Download reference databases (~15GB with all sources).
69
+ # Download reference databases (~16 GB default install; ~22 GB if you opt into CADD).
63
70
  # Use --no-gnomad / --no-alphamissense to skip the large ones.
64
71
  # CADD is opt-in: allelix db update --cadd
65
72
  allelix db update
@@ -77,7 +84,7 @@ allelix analyze trio.vcf.gz --sample HG002 --output report.html
77
84
  allelix analyze your_genotype_file.txt --filter-file my_panel.txt --output report.html
78
85
  ```
79
86
 
80
- Requires Python 3.11+. See [Development](#development) for source installs and running tests.
87
+ 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
88
 
82
89
  ## Supported Formats
83
90
 
@@ -116,7 +123,20 @@ Adding a new format means adding one file to `allelix/parsers/` and registering
116
123
  | 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
124
  | 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
125
  | 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. |
126
+ | 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. |
127
+
128
+ ### CADD modes
129
+
130
+ 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%.
131
+
132
+ **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.
133
+
134
+ Two modes:
135
+
136
+ - **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.
137
+ - **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.
138
+
139
+ If your input is a genotyping chip file, cache mode is all you need.
120
140
 
121
141
  ### Build coverage asymmetry (GRCh37 vs GRCh38)
122
142
 
@@ -214,6 +234,48 @@ gnomAD and AlphaMissense are the largest but add the most interpretive context.
214
234
 
215
235
  To skip either during download: `allelix db update --no-gnomad --no-alphamissense`. To disable permanently: `allelix config set sources.gnomad false`.
216
236
 
237
+ ### Managing your data
238
+
239
+ 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.
240
+
241
+ ```
242
+ ~/.local/share/allelix/
243
+ ├── alphamissense.sqlite ~7.8 GB
244
+ ├── gnomad.sqlite ~6.1 GB
245
+ ├── cadd.sqlite ~5.8 GB (only if you opted into --cadd)
246
+ ├── clinvar.GRCh37.sqlite ~470 MB
247
+ ├── clinvar.GRCh38.sqlite ~480 MB
248
+ ├── gwas_catalog_associations.tsv ~700 MB
249
+ ├── snpedia.sqlite ~390 MB
250
+ ├── gwas.sqlite ~190 MB
251
+ └── pharmgkb.sqlite ~14 MB
252
+ ```
253
+
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
+
256
+ ```bash
257
+ # Reclaim all ~16-22 GB
258
+ rm -rf ~/.local/share/allelix/
259
+
260
+ # Reclaim everything except CADD (avoids re-downloading the slowest one)
261
+ find ~/.local/share/allelix/ -mindepth 1 ! -name 'cadd*.sqlite*' -delete
262
+
263
+ # Re-populate from scratch next time you analyze a file
264
+ allelix db update
265
+ ```
266
+
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.
268
+
269
+ To fully uninstall allelix and reclaim everything:
270
+
271
+ ```bash
272
+ pip uninstall allelix
273
+ rm -rf ~/.local/share/allelix/ # reference database caches
274
+ rm -rf ~/.config/allelix/ # license + source-toggle config
275
+ ```
276
+
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
+
217
279
  ## Data Sources & Licensing
218
280
 
219
281
  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 +318,42 @@ Of the 104,806 genotype pages in the archive:
256
318
 
257
319
  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
320
 
321
+ ## Troubleshooting
322
+
323
+ ### `ImportError: cannot import name 'UTC' from 'datetime'`
324
+
325
+ You're on Python 3.10 or older. Allelix requires 3.11+. `python --version` to check; upgrade and reinstall.
326
+
327
+ ### `allelix db update` failed partway through
328
+
329
+ 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.
330
+
331
+ 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.
332
+
333
+ ### `db status` shows an annotator as "not ready" but the cache files exist on disk
334
+
335
+ 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.
336
+
337
+ ### Build mismatch warning when analyzing a known-good file
338
+
339
+ 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).
340
+
341
+ 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.
342
+
343
+ ### Is my database stale? How fresh is the data?
344
+
345
+ `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.
346
+
347
+ If you want to force a refresh: `allelix db update --force` re-downloads every cache regardless of freshness signal.
348
+
349
+ ### Output reports contain real genome annotations
350
+
351
+ 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.
352
+
353
+ ### Where does allelix store its data?
354
+
355
+ See [Managing your data](#managing-your-data) above for cache locations, the disposability rule, what to back up, and the uninstall procedure.
356
+
259
357
  ## Architecture & Design Decisions
260
358
 
261
359
  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.
@@ -10,19 +10,26 @@ Open-source command-line toolkit for analyzing raw genotype files from consumer
10
10
  > HTML/JSON/terminal reports, methylation + pharmacogenomics focused
11
11
  > commands, report diffing, persistent config with commercial-mode
12
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.
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).
17
22
  > Release notes:
18
23
  > [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/main/CHANGELOG.md).
19
24
 
20
25
  ## Quickstart
21
26
 
27
+ **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`.
28
+
22
29
  ```bash
23
30
  pip install allelix
24
31
 
25
- # Download reference databases (~15GB with all sources).
32
+ # Download reference databases (~16 GB default install; ~22 GB if you opt into CADD).
26
33
  # Use --no-gnomad / --no-alphamissense to skip the large ones.
27
34
  # CADD is opt-in: allelix db update --cadd
28
35
  allelix db update
@@ -40,7 +47,7 @@ allelix analyze trio.vcf.gz --sample HG002 --output report.html
40
47
  allelix analyze your_genotype_file.txt --filter-file my_panel.txt --output report.html
41
48
  ```
42
49
 
43
- Requires Python 3.11+. See [Development](#development) for source installs and running tests.
50
+ 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
51
 
45
52
  ## Supported Formats
46
53
 
@@ -79,7 +86,20 @@ Adding a new format means adding one file to `allelix/parsers/` and registering
79
86
  | 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
87
  | 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
88
  | 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. |
89
+ | 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. |
90
+
91
+ ### CADD modes
92
+
93
+ 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%.
94
+
95
+ **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.
96
+
97
+ Two modes:
98
+
99
+ - **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.
100
+ - **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.
101
+
102
+ If your input is a genotyping chip file, cache mode is all you need.
83
103
 
84
104
  ### Build coverage asymmetry (GRCh37 vs GRCh38)
85
105
 
@@ -177,6 +197,48 @@ gnomAD and AlphaMissense are the largest but add the most interpretive context.
177
197
 
178
198
  To skip either during download: `allelix db update --no-gnomad --no-alphamissense`. To disable permanently: `allelix config set sources.gnomad false`.
179
199
 
200
+ ### Managing your data
201
+
202
+ 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.
203
+
204
+ ```
205
+ ~/.local/share/allelix/
206
+ ├── alphamissense.sqlite ~7.8 GB
207
+ ├── gnomad.sqlite ~6.1 GB
208
+ ├── cadd.sqlite ~5.8 GB (only if you opted into --cadd)
209
+ ├── clinvar.GRCh37.sqlite ~470 MB
210
+ ├── clinvar.GRCh38.sqlite ~480 MB
211
+ ├── gwas_catalog_associations.tsv ~700 MB
212
+ ├── snpedia.sqlite ~390 MB
213
+ ├── gwas.sqlite ~190 MB
214
+ └── pharmgkb.sqlite ~14 MB
215
+ ```
216
+
217
+ **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
+
219
+ ```bash
220
+ # Reclaim all ~16-22 GB
221
+ rm -rf ~/.local/share/allelix/
222
+
223
+ # Reclaim everything except CADD (avoids re-downloading the slowest one)
224
+ find ~/.local/share/allelix/ -mindepth 1 ! -name 'cadd*.sqlite*' -delete
225
+
226
+ # Re-populate from scratch next time you analyze a file
227
+ allelix db update
228
+ ```
229
+
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.
231
+
232
+ To fully uninstall allelix and reclaim everything:
233
+
234
+ ```bash
235
+ pip uninstall allelix
236
+ rm -rf ~/.local/share/allelix/ # reference database caches
237
+ rm -rf ~/.config/allelix/ # license + source-toggle config
238
+ ```
239
+
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
+
180
242
  ## Data Sources & Licensing
181
243
 
182
244
  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 +281,42 @@ Of the 104,806 genotype pages in the archive:
219
281
 
220
282
  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
283
 
284
+ ## Troubleshooting
285
+
286
+ ### `ImportError: cannot import name 'UTC' from 'datetime'`
287
+
288
+ You're on Python 3.10 or older. Allelix requires 3.11+. `python --version` to check; upgrade and reinstall.
289
+
290
+ ### `allelix db update` failed partway through
291
+
292
+ 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.
293
+
294
+ 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.
295
+
296
+ ### `db status` shows an annotator as "not ready" but the cache files exist on disk
297
+
298
+ 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.
299
+
300
+ ### Build mismatch warning when analyzing a known-good file
301
+
302
+ 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).
303
+
304
+ 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.
305
+
306
+ ### Is my database stale? How fresh is the data?
307
+
308
+ `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.
309
+
310
+ If you want to force a refresh: `allelix db update --force` re-downloads every cache regardless of freshness signal.
311
+
312
+ ### Output reports contain real genome annotations
313
+
314
+ 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.
315
+
316
+ ### Where does allelix store its data?
317
+
318
+ See [Managing your data](#managing-your-data) above for cache locations, the disposability rule, what to back up, and the uninstall procedure.
319
+
222
320
  ## Architecture & Design Decisions
223
321
 
224
322
  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.
@@ -144,27 +144,36 @@ class Annotator(ABC):
144
144
  def __del__(self) -> None:
145
145
  """Safety-net resource release on GC. Deliberately retained.
146
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.
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.
162
171
 
163
172
  ``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.
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.
168
177
  """
169
178
  with contextlib.suppress(Exception):
170
179
  self.close()
@@ -11,7 +11,7 @@ the validation.
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
- from dataclasses import dataclass, field
14
+ from dataclasses import asdict, dataclass, field
15
15
 
16
16
  NO_CALL_MARKER = "-"
17
17
  DEFAULT_BUILD = "GRCh37"
@@ -138,6 +138,10 @@ class Annotation:
138
138
  gene: str = ""
139
139
  review_status: str = ""
140
140
  alt: str = ""
141
+ # Internal flag — see ``_INTERNAL_ANNOTATION_FIELDS`` below. Excluded
142
+ # from every public serialization path (JSON report, diff entries,
143
+ # diff change records). If you add another internal-only field,
144
+ # extend the frozenset; the three call sites all read from it.
141
145
  is_must_include: bool = False
142
146
  allele_frequency: float | None = None
143
147
  am_pathogenicity: float | None = None
@@ -162,3 +166,34 @@ class Annotation:
162
166
  if len(parts) != 2:
163
167
  return "Homozygous" if len(set(self.genotype_match)) == 1 else "Heterozygous"
164
168
  return "Heterozygous" if parts[0] != parts[1] else "Homozygous"
169
+
170
+
171
+ # Annotation fields that are internal-only and MUST be stripped from every
172
+ # public serialization path (JSON report annotations, JSON diff entries,
173
+ # diff change records). Adding a new internal field? Extend this frozenset
174
+ # and every existing serializer picks up the exclusion automatically — no
175
+ # new "remember to strip the field" obligation across N call sites.
176
+ #
177
+ # GH allelix-dev #3 (the v2.1.1 "is_must_include leak by omission risk"
178
+ # cleanup) eliminated the three duplicated inline filters that previously
179
+ # carried this rule. If you find yourself writing
180
+ # ``{k: v for k, v in asdict(a).items() if k != "<some_field>"}`` in a
181
+ # new public-output path, you're re-introducing the bug — add the field
182
+ # here and use ``annotation_to_public_dict`` instead.
183
+ _INTERNAL_ANNOTATION_FIELDS: frozenset[str] = frozenset({"is_must_include"})
184
+
185
+
186
+ def annotation_to_public_dict(a: Annotation) -> dict:
187
+ """Serialize an Annotation for public output, stripping internal fields.
188
+
189
+ Canonical helper for every public Annotation serialization path. The
190
+ JSON report's main annotation list, JSON diff entries, and diff change
191
+ records all route through this — so any future internal-only field
192
+ just needs adding to ``_INTERNAL_ANNOTATION_FIELDS`` and every consumer
193
+ inherits the exclusion.
194
+
195
+ Does NOT add display-derived fields (``zygosity``, ``am_caveat``,
196
+ diff-specific ``previous_*``) — those are layered by the calling
197
+ serializer after this helper has stripped internal state.
198
+ """
199
+ return {k: v for k, v in asdict(a).items() if k not in _INTERNAL_ANNOTATION_FIELDS}
@@ -18,9 +18,11 @@ from __future__ import annotations
18
18
  import json
19
19
  import math
20
20
  from collections import Counter
21
- from dataclasses import asdict, dataclass, field
21
+ from dataclasses import dataclass, field
22
22
  from typing import TYPE_CHECKING
23
23
 
24
+ from allelix.models import annotation_to_public_dict
25
+
24
26
  if TYPE_CHECKING:
25
27
  from pathlib import Path
26
28
 
@@ -202,7 +204,7 @@ def summarize_diff(diff: DiffResult) -> str:
202
204
 
203
205
  def diff_annotation_to_dict(a: ChangedAnnotation) -> dict:
204
206
  """Serialize a ChangedAnnotation for JSON output."""
205
- d = {k: v for k, v in asdict(a.current).items() if k != "is_must_include"}
207
+ d = annotation_to_public_dict(a.current)
206
208
  d["previous_significance"] = a.previous_significance
207
209
  d["previous_magnitude"] = a.previous_magnitude
208
210
  return d
@@ -35,11 +35,11 @@ or strips that field.
35
35
  from __future__ import annotations
36
36
 
37
37
  import json
38
- from dataclasses import asdict
39
38
  from datetime import UTC, datetime
40
39
  from typing import TYPE_CHECKING
41
40
 
42
41
  from allelix import __version__
42
+ from allelix.models import annotation_to_public_dict
43
43
  from allelix.reports import REGULATORY_NOTICE, atomic_write_text
44
44
  from allelix.reports._pipeline import rollup_gwas_duplicates
45
45
 
@@ -61,7 +61,7 @@ SCHEMA_VERSION = "5"
61
61
 
62
62
  def _annotation_dict(a: Annotation) -> dict:
63
63
  """Serialize an annotation, adding AM caveat for non-protein sources."""
64
- d = {k: v for k, v in asdict(a).items() if k != "is_must_include"}
64
+ d = annotation_to_public_dict(a)
65
65
  d["zygosity"] = a.zygosity
66
66
  if a.am_pathogenicity is not None and a.source == "pharmgkb":
67
67
  d["am_caveat"] = "protein structure impact only"
@@ -159,9 +159,7 @@ def render_json(
159
159
  payload["diff"] = {
160
160
  "previous_report": diff.previous_generated_at,
161
161
  "summary": summarize_diff(diff),
162
- "new": [
163
- {k: v for k, v in asdict(a).items() if k != "is_must_include"} for a in diff.new
164
- ],
162
+ "new": [annotation_to_public_dict(a) for a in diff.new],
165
163
  "changed": [diff_annotation_to_dict(c) for c in diff.changed],
166
164
  "removed": diff.removed,
167
165
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: allelix
3
- Version: 2.1.0
3
+ Version: 2.1.1
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>
@@ -47,19 +47,26 @@ Open-source command-line toolkit for analyzing raw genotype files from consumer
47
47
  > HTML/JSON/terminal reports, methylation + pharmacogenomics focused
48
48
  > commands, report diffing, persistent config with commercial-mode
49
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.
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).
54
59
  > Release notes:
55
60
  > [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/main/CHANGELOG.md).
56
61
 
57
62
  ## Quickstart
58
63
 
64
+ **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`.
65
+
59
66
  ```bash
60
67
  pip install allelix
61
68
 
62
- # Download reference databases (~15GB with all sources).
69
+ # Download reference databases (~16 GB default install; ~22 GB if you opt into CADD).
63
70
  # Use --no-gnomad / --no-alphamissense to skip the large ones.
64
71
  # CADD is opt-in: allelix db update --cadd
65
72
  allelix db update
@@ -77,7 +84,7 @@ allelix analyze trio.vcf.gz --sample HG002 --output report.html
77
84
  allelix analyze your_genotype_file.txt --filter-file my_panel.txt --output report.html
78
85
  ```
79
86
 
80
- Requires Python 3.11+. See [Development](#development) for source installs and running tests.
87
+ 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
88
 
82
89
  ## Supported Formats
83
90
 
@@ -116,7 +123,20 @@ Adding a new format means adding one file to `allelix/parsers/` and registering
116
123
  | 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
124
  | 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
125
  | 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. |
126
+ | 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. |
127
+
128
+ ### CADD modes
129
+
130
+ 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%.
131
+
132
+ **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.
133
+
134
+ Two modes:
135
+
136
+ - **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.
137
+ - **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.
138
+
139
+ If your input is a genotyping chip file, cache mode is all you need.
120
140
 
121
141
  ### Build coverage asymmetry (GRCh37 vs GRCh38)
122
142
 
@@ -214,6 +234,48 @@ gnomAD and AlphaMissense are the largest but add the most interpretive context.
214
234
 
215
235
  To skip either during download: `allelix db update --no-gnomad --no-alphamissense`. To disable permanently: `allelix config set sources.gnomad false`.
216
236
 
237
+ ### Managing your data
238
+
239
+ 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.
240
+
241
+ ```
242
+ ~/.local/share/allelix/
243
+ ├── alphamissense.sqlite ~7.8 GB
244
+ ├── gnomad.sqlite ~6.1 GB
245
+ ├── cadd.sqlite ~5.8 GB (only if you opted into --cadd)
246
+ ├── clinvar.GRCh37.sqlite ~470 MB
247
+ ├── clinvar.GRCh38.sqlite ~480 MB
248
+ ├── gwas_catalog_associations.tsv ~700 MB
249
+ ├── snpedia.sqlite ~390 MB
250
+ ├── gwas.sqlite ~190 MB
251
+ └── pharmgkb.sqlite ~14 MB
252
+ ```
253
+
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
+
256
+ ```bash
257
+ # Reclaim all ~16-22 GB
258
+ rm -rf ~/.local/share/allelix/
259
+
260
+ # Reclaim everything except CADD (avoids re-downloading the slowest one)
261
+ find ~/.local/share/allelix/ -mindepth 1 ! -name 'cadd*.sqlite*' -delete
262
+
263
+ # Re-populate from scratch next time you analyze a file
264
+ allelix db update
265
+ ```
266
+
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.
268
+
269
+ To fully uninstall allelix and reclaim everything:
270
+
271
+ ```bash
272
+ pip uninstall allelix
273
+ rm -rf ~/.local/share/allelix/ # reference database caches
274
+ rm -rf ~/.config/allelix/ # license + source-toggle config
275
+ ```
276
+
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
+
217
279
  ## Data Sources & Licensing
218
280
 
219
281
  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 +318,42 @@ Of the 104,806 genotype pages in the archive:
256
318
 
257
319
  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
320
 
321
+ ## Troubleshooting
322
+
323
+ ### `ImportError: cannot import name 'UTC' from 'datetime'`
324
+
325
+ You're on Python 3.10 or older. Allelix requires 3.11+. `python --version` to check; upgrade and reinstall.
326
+
327
+ ### `allelix db update` failed partway through
328
+
329
+ 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.
330
+
331
+ 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.
332
+
333
+ ### `db status` shows an annotator as "not ready" but the cache files exist on disk
334
+
335
+ 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.
336
+
337
+ ### Build mismatch warning when analyzing a known-good file
338
+
339
+ 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).
340
+
341
+ 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.
342
+
343
+ ### Is my database stale? How fresh is the data?
344
+
345
+ `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.
346
+
347
+ If you want to force a refresh: `allelix db update --force` re-downloads every cache regardless of freshness signal.
348
+
349
+ ### Output reports contain real genome annotations
350
+
351
+ 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.
352
+
353
+ ### Where does allelix store its data?
354
+
355
+ See [Managing your data](#managing-your-data) above for cache locations, the disposability rule, what to back up, and the uninstall procedure.
356
+
259
357
  ## Architecture & Design Decisions
260
358
 
261
359
  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.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "allelix"
7
- version = "2.1.0"
7
+ version = "2.1.1"
8
8
  description = "Open-source genotype analysis toolkit. Format-agnostic ingestion, database-agnostic annotation, offline-first."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -130,6 +130,52 @@ class TestAnnotation:
130
130
  assert a.condition == ""
131
131
  assert a.gene == ""
132
132
 
133
+ def test_annotation_to_public_dict_strips_internal_fields(self):
134
+ """GH allelix-dev #3 — every internal-only field is excluded.
135
+
136
+ The contract: any field name in ``_INTERNAL_ANNOTATION_FIELDS`` MUST NOT
137
+ appear in the dict returned by ``annotation_to_public_dict``. If a new
138
+ internal field is added without registering it, this test fires.
139
+ """
140
+ from allelix.models import (
141
+ _INTERNAL_ANNOTATION_FIELDS,
142
+ annotation_to_public_dict,
143
+ )
144
+
145
+ a = self._minimal(is_must_include=True)
146
+ d = annotation_to_public_dict(a)
147
+ for field_name in _INTERNAL_ANNOTATION_FIELDS:
148
+ assert field_name not in d, (
149
+ f"{field_name!r} declared internal but leaked into public output dict"
150
+ )
151
+ # And spot-check a public field IS present (sanity for the inverse).
152
+ assert d["source"] == "clinvar"
153
+ assert d["rsid"] == "rs1"
154
+
155
+ def test_annotation_to_public_dict_used_by_all_three_serializers(self):
156
+ """GH allelix-dev #3 — production serializers route through the
157
+ canonical helper, not inline filters.
158
+
159
+ If any future code path adds a 4th 'asdict(a) minus is_must_include'
160
+ inline filter, this test won't catch it directly — but the central
161
+ helper is the only place to remember the rule, and grep-ing for
162
+ ``annotation_to_public_dict`` shows every consumer that respects it.
163
+ """
164
+ from allelix.models import annotation_to_public_dict
165
+ from allelix.reports import json_report
166
+ from allelix.reports.diff import diff_annotation_to_dict
167
+
168
+ # All three production sites import the helper:
169
+ assert annotation_to_public_dict in vars(json_report).values()
170
+ # diff.py imports it at module scope too:
171
+ from allelix.reports import diff as diff_module
172
+
173
+ assert annotation_to_public_dict in vars(diff_module).values()
174
+ # Sanity: diff_annotation_to_dict exists and is callable
175
+ # (its body is exercised by tests/reports/test_diff.py — this is just
176
+ # the import-shape pin).
177
+ assert callable(diff_annotation_to_dict)
178
+
133
179
  def test_default_references_independent_per_instance(self):
134
180
  a1 = self._minimal()
135
181
  a2 = self._minimal()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes