allelix 2.5.0__tar.gz → 2.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. {allelix-2.5.0/allelix.egg-info → allelix-2.5.2}/PKG-INFO +64 -33
  2. {allelix-2.5.0 → allelix-2.5.2}/README.md +62 -32
  3. {allelix-2.5.0 → allelix-2.5.2}/allelix/annotators/__init__.py +12 -5
  4. {allelix-2.5.0 → allelix-2.5.2}/allelix/annotators/_resolution.py +2 -1
  5. {allelix-2.5.0 → allelix-2.5.2}/allelix/annotators/base.py +17 -3
  6. {allelix-2.5.0 → allelix-2.5.2}/allelix/annotators/clinvar.py +187 -77
  7. {allelix-2.5.0 → allelix-2.5.2}/allelix/annotators/gnomad.py +26 -0
  8. {allelix-2.5.0 → allelix-2.5.2}/allelix/annotators/gwas.py +9 -3
  9. {allelix-2.5.0 → allelix-2.5.2}/allelix/annotators/pharmgkb.py +54 -5
  10. allelix-2.5.2/allelix/annotators/reference_fallback.py +209 -0
  11. {allelix-2.5.0 → allelix-2.5.2}/allelix/annotators/rsid_identity.py +29 -6
  12. {allelix-2.5.0 → allelix-2.5.2}/allelix/annotators/snpedia.py +12 -2
  13. allelix-2.5.2/allelix/attribution.py +99 -0
  14. {allelix-2.5.0 → allelix-2.5.2}/allelix/cli/__init__.py +11 -3
  15. {allelix-2.5.0 → allelix-2.5.2}/allelix/cli/_helpers.py +213 -37
  16. {allelix-2.5.0 → allelix-2.5.2}/allelix/cli/_options.py +42 -11
  17. {allelix-2.5.0 → allelix-2.5.2}/allelix/cli/analyze.py +1 -0
  18. {allelix-2.5.0 → allelix-2.5.2}/allelix/cli/config.py +15 -19
  19. {allelix-2.5.0 → allelix-2.5.2}/allelix/cli/db.py +68 -18
  20. allelix-2.5.2/allelix/cli/utility.py +1236 -0
  21. {allelix-2.5.0 → allelix-2.5.2}/allelix/compare.py +76 -10
  22. {allelix-2.5.0 → allelix-2.5.2}/allelix/config.py +30 -6
  23. {allelix-2.5.0 → allelix-2.5.2}/allelix/data/high_value_snps.yaml +6 -0
  24. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/__init__.py +12 -2
  25. allelix-2.5.2/allelix/databases/cache_state.py +359 -0
  26. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/loader_utils.py +10 -1
  27. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/manager.py +252 -74
  28. allelix-2.5.2/allelix/exporters/plink.py +463 -0
  29. {allelix-2.5.0 → allelix-2.5.2}/allelix/models.py +9 -0
  30. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/__init__.py +4 -1
  31. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/_helpers.py +149 -17
  32. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/twentythreeandme.py +37 -10
  33. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/vcf.py +398 -36
  34. allelix-2.5.2/allelix/reports/__init__.py +431 -0
  35. {allelix-2.5.0 → allelix-2.5.2}/allelix/reports/_pipeline.py +623 -231
  36. {allelix-2.5.0 → allelix-2.5.2}/allelix/reports/build_text.py +27 -1
  37. {allelix-2.5.0 → allelix-2.5.2}/allelix/reports/clinvar_text.py +50 -0
  38. allelix-2.5.2/allelix/reports/diff.py +332 -0
  39. {allelix-2.5.0 → allelix-2.5.2}/allelix/reports/high_value.py +96 -0
  40. {allelix-2.5.0 → allelix-2.5.2}/allelix/reports/html.py +121 -35
  41. {allelix-2.5.0 → allelix-2.5.2}/allelix/reports/json_report.py +40 -5
  42. {allelix-2.5.0 → allelix-2.5.2}/allelix/reports/terminal.py +20 -2
  43. {allelix-2.5.0 → allelix-2.5.2}/allelix/reports/vcf.py +225 -69
  44. {allelix-2.5.0 → allelix-2.5.2}/allelix/utils/build_detect.py +15 -4
  45. {allelix-2.5.0 → allelix-2.5.2/allelix.egg-info}/PKG-INFO +64 -33
  46. {allelix-2.5.0 → allelix-2.5.2}/allelix.egg-info/SOURCES.txt +2 -0
  47. {allelix-2.5.0 → allelix-2.5.2}/pyproject.toml +4 -3
  48. allelix-2.5.0/allelix/cli/utility.py +0 -748
  49. allelix-2.5.0/allelix/databases/cache_state.py +0 -161
  50. allelix-2.5.0/allelix/exporters/plink.py +0 -243
  51. allelix-2.5.0/allelix/reports/__init__.py +0 -227
  52. allelix-2.5.0/allelix/reports/diff.py +0 -212
  53. {allelix-2.5.0 → allelix-2.5.2}/LICENSE +0 -0
  54. {allelix-2.5.0 → allelix-2.5.2}/MANIFEST.in +0 -0
  55. {allelix-2.5.0 → allelix-2.5.2}/allelix/__init__.py +0 -0
  56. {allelix-2.5.0 → allelix-2.5.2}/allelix/annotators/alphamissense.py +0 -0
  57. {allelix-2.5.0 → allelix-2.5.2}/allelix/annotators/cadd.py +0 -0
  58. {allelix-2.5.0 → allelix-2.5.2}/allelix/cli/focused.py +0 -0
  59. {allelix-2.5.0 → allelix-2.5.2}/allelix/data/__init__.py +0 -0
  60. {allelix-2.5.0 → allelix-2.5.2}/allelix/data/clinvar_clnsig_snapshot.yaml +0 -0
  61. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/_versions.py +0 -0
  62. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/alphamissense_loader.py +0 -0
  63. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/cadd_loader.py +0 -0
  64. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/cpic_loader.py +0 -0
  65. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/gnomad_loader.py +0 -0
  66. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/gwas_loader.py +0 -0
  67. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/pharmgkb_loader.py +0 -0
  68. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/rsid_identity_loader.py +0 -0
  69. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/schema.py +0 -0
  70. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/snpedia_loader.py +0 -0
  71. {allelix-2.5.0 → allelix-2.5.2}/allelix/databases/snpedia_parser.py +0 -0
  72. {allelix-2.5.0 → allelix-2.5.2}/allelix/exporters/__init__.py +0 -0
  73. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/ancestrydna.py +0 -0
  74. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/base.py +0 -0
  75. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/decodeme.py +0 -0
  76. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/ftdna.py +0 -0
  77. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/ftdna_famfinder.py +0 -0
  78. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/ftdna_illumina.py +0 -0
  79. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/genesforgood.py +0 -0
  80. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/livingdna.py +0 -0
  81. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/myhappygenes.py +0 -0
  82. {allelix-2.5.0 → allelix-2.5.2}/allelix/parsers/myheritage.py +0 -0
  83. {allelix-2.5.0 → allelix-2.5.2}/allelix/py.typed +0 -0
  84. {allelix-2.5.0 → allelix-2.5.2}/allelix/reports/_sources.py +0 -0
  85. {allelix-2.5.0 → allelix-2.5.2}/allelix/reports/association_text.py +0 -0
  86. {allelix-2.5.0 → allelix-2.5.2}/allelix/reports/methylation.py +0 -0
  87. {allelix-2.5.0 → allelix-2.5.2}/allelix/utils/__init__.py +0 -0
  88. {allelix-2.5.0 → allelix-2.5.2}/allelix/utils/allele.py +0 -0
  89. {allelix-2.5.0 → allelix-2.5.2}/allelix/utils/plausibility.py +0 -0
  90. {allelix-2.5.0 → allelix-2.5.2}/allelix.egg-info/dependency_links.txt +0 -0
  91. {allelix-2.5.0 → allelix-2.5.2}/allelix.egg-info/entry_points.txt +0 -0
  92. {allelix-2.5.0 → allelix-2.5.2}/allelix.egg-info/requires.txt +0 -0
  93. {allelix-2.5.0 → allelix-2.5.2}/allelix.egg-info/top_level.txt +0 -0
  94. {allelix-2.5.0 → allelix-2.5.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: allelix
3
- Version: 2.5.0
3
+ Version: 2.5.2
4
4
  Summary: Open-source genotype analysis toolkit. Format-agnostic ingestion, database-agnostic annotation, offline-first.
5
5
  Author: Allelix
6
6
  Maintainer-email: dial481 <dial481@users.noreply.github.com>
@@ -16,6 +16,7 @@ Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
19
20
  Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
20
21
  Requires-Python: >=3.11
21
22
  Description-Content-Type: text/markdown
@@ -87,6 +88,8 @@ allelix analyze your_wgs.vcf.gz --keep-filtered --min-gq 10 --output report.html
87
88
  allelix analyze your_genotype_file.txt --filter-file my_panel.txt --output report.html
88
89
  # Panel coverage puts each requested rsID in one state: annotated, typed (no annotation),
89
90
  # no call, or not on chip ("not in file" for a VCF)
91
+ # VCF coverage includes explicit REF/REF calls and explains rejected/excluded records.
92
+ # Gene entries match constituent symbols in lists; hyphens inside symbols stay intact.
90
93
 
91
94
  # Annotated VCF output — VCF-in / VCF-out for pipeline integration
92
95
  # Writes a valid VCF with annotations as INFO fields + full provenance in the header.
@@ -102,32 +105,34 @@ See [Development](#development) for source installs and running tests, [Managing
102
105
  | Format | Status | Notes |
103
106
  |---|---|---|
104
107
  | MyHappyGenes (Tempus) | ✓ | Tab-delimited, 5 columns. **Build is auto-detected** — real-world MHG exports mislabel the header as "build 37.1" while shipping GRCh38 coordinates. Allelix decides the build from position data and warns on header/data disagreement (ADR-0021, ADR-0044). See [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#myhappygenes-tempus). |
105
- | 23andMe | ✓ | Tab-delimited, 4 columns, concatenated genotype. Reads build 36, 37 or 38; the header's build is used when the known-SNP positions agree. I-prefixed probe IDs passed through; they and `D`/`I` indel calls are not matched against ClinVar, and the report counts them (ADR-0051). |
108
+ | 23andMe | ✓ | Tab-delimited, 4 columns, concatenated genotype; a copy a spreadsheet re-saved as CSV (quoted or not) or with spaces is read too ([docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#23andme)). Reads build 36, 37 or 38; the header's build is used when the known-SNP positions agree. I-prefixed probe IDs passed through; they and `D`/`I` indel calls are not matched against ClinVar, and the report counts them (ADR-0051). A `D`/`I` call at a high-value rsID, or at one ClinVar classifies Pathogenic or Likely pathogenic, is also named as not checked (gene, variant name, rsID and call) on the console, in HTML and in JSON. |
106
109
  | Genes for Good | ✓ | The 23andMe-style `.txt` from a Genes for Good download (23andMe's columns under a PLINK banner). The imputed text file (about 7.7 million rows) and the imputed VCF are labelled imputed in every report; the measured files are not. The download's VCFs are read as VCF; see [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#genes-for-good). |
107
110
  | AncestryDNA | ✓ | Tab-delimited, 5 columns. Chromosome mapping: 23→X, 24→Y, 25→X (PAR), 26→MT. V1 and V2 chip layouts. |
108
- | Family Tree DNA | ✓ | CSV, double-quoted fields, concatenated genotype. Build 37 default when known-SNP positions are present but do not decide; with none at all and no build line, the run stops (ADR-0044). |
111
+ | Family Tree DNA | ✓ | CSV, double-quoted fields, concatenated genotype. Build 37 default when known-SNP positions are present but do not decide (subject to the stops and the GRCh36 exception in [How the genome build is decided](#how-the-genome-build-is-decided-and-why-a-run-can-stop)); with none at all and no build line, the run stops (ADR-0044). |
109
112
  | MyHeritage DNA | ✓ | CSV, same structure as FTDNA. Detected by "MyHeritage" in the file's preamble. Handles double-double-quoted fields and files re-saved through a spreadsheet (padded rows, signature not on line 1, tab-delimited copies). Two exports: the **array** export (pre-2026 kits) and the **low-pass WGS** export (kits from January 2026; genotypes imputed from about 2x coverage), which the report labels as imputed and whose `##reference=build37` line is used as the header build. See [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#myheritage). |
110
113
  | Living DNA | ✓ | Tab-delimited despite `.csv` extension. Handles AX-, AFFX-prefixed and CHR:POS positional SNP IDs. |
111
114
  | deCODEme | ✓ | `deCODEme_scan.csv` (deCODE genetics, service closed 2013): comma-separated, a per-row `Strand` column; minus-strand calls are complemented to the plus strand. Build 36: decided from known-SNP positions, then run with position lookups off (ADR-0025). See [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#decodeme). |
112
- | FTDNA Illumina raw | ✓ | Tab-delimited variant of the FTDNA export (distinct from the CSV format above). `RSID/CHROMOSOME/POSITION/RESULT` columns. Build 37 default when known-SNP positions are present but do not decide. |
113
- | 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 when known-SNP positions are present but do not decide. |
114
- | 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 not annotated — they match nothing in any annotation database — but they are read at high-value positions: a block with low genotype quality or no reads there makes the position a no-call, and so does a gVCF position no record covers. High-value no-calls list the reason (for example `low_gq_block`, `no_coverage`, `filtered`, `low_gq_call`). A plain VCF has no reference blocks, so a position without a record is reported as unknown rather than as reference or as a no-call. Multi-sample files require `--sample <ID>`. Rows the variant caller rejected (FILTER other than `PASS` or `.`) are skipped and counted in the report; `--keep-filtered` keeps them. A call with genotype quality below `--min-gq` (default 20) or with no reads (DP 0) is reported as a no-call. The spanning-deletion allele `*` is treated as symbolic: such calls are not assessed, and the report counts them and lists their positions. A half call (`./1`) is assessed on its called allele by ClinVar and the GWAS Catalog and shown as `T/.` with zygosity "Half call"; ClinPGx and SNPedia, which need both alleles, skip it. Malformed rows, rows without a genotype, genotypes naming an allele the record lacks and genotypes naming a symbolic allele such as `<NON_REF>` beside a real ALT are counted in the report; a no-call written `./.` stays a no-call (ADR-0047). A VCF that holds only structural-variant or copy-number records (such as a provider's separate `sv` / `cnv` file) is rejected with a message naming the file to use instead; see [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#sequencingcom-30x-whole-genome) for per-provider deliverables. A VCF whose header declares an imputation tool's fields (Minimac, Beagle, GLIMPSE) is labelled imputed in every report; see [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#imputed-vcfs). 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. |
115
+ | FTDNA Illumina raw | ✓ | Tab-delimited variant of the FTDNA export (distinct from the CSV format above). `RSID/CHROMOSOME/POSITION/RESULT` columns. Build 37 default when known-SNP positions are present but do not decide (subject to the stops and the GRCh36 exception in [How the genome build is decided](#how-the-genome-build-is-decided-and-why-a-run-can-stop)). |
116
+ | 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 when known-SNP positions are present but do not decide (subject to the stops and the GRCh36 exception in [How the genome build is decided](#how-the-genome-build-is-decided-and-why-a-run-can-stop)). |
117
+ | VCF / gVCF | ✓ | REF/ALT encoding, `0/1` genotype notation. A homozygous-reference call (`0/0`) or a gVCF reference block (lines with `<NON_REF>` ALT and `END=` INFO) carries no variant allele, so it usually matches nothing. The exception is a site where the reference genome itself carries a ClinVar allele (Factor V Leiden on GRCh37, for example): there a `0/0` call, or a reference block that passes the genotype-quality gate, is assessed as homozygous for that allele. See [docs/formats.md, Reference calls in VCFs](https://github.com/allelix/allelix/blob/main/docs/formats.md#reference-calls-in-vcfs) (ADR-0039, ADR-0059). At high-value positions, a block with low genotype quality or no reads makes the position a no-call, and so does a gVCF position no record covers. High-value no-calls list the reason (for example `low_gq_block`, `no_coverage`, `filtered`, `low_gq_call`). A record counts for a high-value or panel rsID when it is at one of the rsID's identity placements with that placement's REF, whatever its ID or ALT; an rsID the identity table does not place is matched by ID (ADR-0055). A plain VCF has no reference blocks, so a position without a record is reported as unknown rather than as reference or as a no-call; at such a ClinVar site the report counts the positions no record covers, and names the high-value ones (such as CYP3A5*3 rs776746 on GRCh37) as not checked. Multi-sample files require `--sample <ID>`. Rows the variant caller rejected (FILTER other than `PASS` or `.`) are skipped and counted in the report; `--keep-filtered` keeps them. A call with genotype quality below `--min-gq` (default 20) or with no reads (DP 0) is reported as a no-call. The spanning-deletion allele `*` is treated as symbolic: such calls are not assessed, and the report counts them and lists their positions. A half call (`./1`) is assessed on its called allele by ClinVar and the GWAS Catalog and shown as `T/.` with zygosity "Half call"; ClinPGx and SNPedia, which need both alleles, skip it. Malformed rows, rows without a genotype, genotypes naming an allele the record lacks and genotypes naming a symbolic allele such as `<NON_REF>` beside a real ALT are counted in the report; a no-call written `./.` stays a no-call (ADR-0047). A VCF that holds only structural-variant or copy-number records (such as a provider's separate `sv` / `cnv` file) is rejected with a message naming the file to use instead; see [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#sequencingcom-30x-whole-genome) for per-provider deliverables. A VCF whose header declares an imputation tool's fields (Minimac, Beagle, GLIMPSE) is labelled imputed in every report; see [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#imputed-vcfs). 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. |
115
118
 
116
119
  Files can be passed as delivered: gzipped (Family Tree DNA's `.csv.gz`, for example), or a zip holding one genotype file. A zip with several files, a password or an unusual compression stops with a message to unzip it first. Windows, Linux and old-Mac line endings are read; a leading byte order mark or blank line is ignored.
117
120
 
118
- If Allelix does not recognise a file, use the original file from your testing company. `--format <name>` forces a reader at your own risk; every report then carries a warning (see [docs/formats.md, Forcing a format](https://github.com/allelix/allelix/blob/main/docs/formats.md#forcing-a-format)).
121
+ If Allelix does not recognise a file, use the original file from your testing company. `--format <name>` forces a reader at your own risk; when that reader does not recognise the file, every report carries a warning (see [docs/formats.md, Forcing a format](https://github.com/allelix/allelix/blob/main/docs/formats.md#forcing-a-format)).
119
122
 
120
123
  Adding a new format means adding one file to `allelix/parsers/` and registering an instance in the `PARSERS` list in `allelix/parsers/__init__.py`.
121
124
 
122
125
  ### Roadmap
123
126
 
127
+ Shipped releases are listed for history; the [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) has each release's changes.
128
+
124
129
  | Release | Theme |
125
130
  |---|---|
126
- | **v2.2** | Custom-panel UX improvements, methylation panel retune + citations, ClinVar source switch (VCF → `variant_summary.txt`), CLI cleanups (`db clean` / `db path`). |
127
- | **v2.3** | Annotated VCF output plus v2.3.1 correctness hardening: cross-build enrichment guards, GWAS effect-type disambiguation, conservative build confidence, SNPedia orientation handling, and strict slow-suite drift verification. |
128
- | **v2.4** | Correctness hardening: cross-build rsID identity table with explicit allele translation, plausibility flag for genotyping-artifact detection, VCF quality gate honouring FILTER and genotype quality, unified rsID-recovery step across all commands, and parser/format hygiene. |
129
- | **v2.5** | ClinVar's own classification caps each row; ClinVar variants without an rsID; VCF calls counted, not dropped; read-only caches and named sources in every report; zip input; Genes for Good and deCODEme; imputed input labelled. |
130
- | **Later** | Variant consequences, CNVs and repeat expansions, gnomAD genomes, supplemental genotype-file merging, ancestry estimation, and PRS support. These are directional themes rather than release commitments. |
131
+ | **v2.2** (shipped) | Custom-panel UX improvements, methylation panel retune + citations, ClinVar source switch (VCF → `variant_summary.txt`), CLI cleanups (`db clean` / `db path`). |
132
+ | **v2.3** (shipped) | Annotated VCF output plus v2.3.1 correctness hardening: cross-build enrichment guards, GWAS effect-type disambiguation, conservative build confidence, SNPedia orientation handling, and strict slow-suite drift verification. |
133
+ | **v2.4** (shipped) | Correctness hardening: cross-build rsID identity table with explicit allele translation, plausibility flag for genotyping-artifact detection, VCF quality gate honouring FILTER and genotype quality, unified rsID-recovery step across all commands, and parser/format hygiene. |
134
+ | **v2.5** (shipped) | ClinVar's own classification caps each row; ClinVar variants without an rsID; VCF calls counted, not dropped; read-only caches and named sources in every report; zip input; Genes for Good and deCODEme; imputed input labelled. |
135
+ | **Later** (planned) | Variant consequences, CNVs and repeat expansions, gnomAD genomes, supplemental genotype-file merging, ancestry estimation, and PRS support. These are directional themes rather than release commitments. |
131
136
 
132
137
  Issues and milestones are tracked at [github.com/allelix/allelix/issues](https://github.com/allelix/allelix/issues).
133
138
 
@@ -135,15 +140,15 @@ Issues and milestones are tracked at [github.com/allelix/allelix/issues](https:/
135
140
 
136
141
  | Database | Status | Notes |
137
142
  |---|---|---|
138
- | ClinVar (GRCh37 + GRCh38) | ✓ | Public domain (NCBI). SNVs + indels + multi-allelic sites. **Both builds cached**; `analyze` dispatches by the decided build (ADR-0044). Rows are matched by chromosome, position, REF and ALT, never by rsID alone, and reference-allele records (REF = ALT) are never reported (ADR-0039). Carrier rule (ADR-0007) requires the user to carry the ALT allele. Indel-anchor protection (ADR-0011) prevents single-base array readouts from matching anchor-base indels. Array calls ClinVar cannot match are counted in the report: vendor probes without an rsID at a locus ClinVar classifies as Pathogenic or Likely pathogenic, and `D`/`I` calls (ADR-0051). ClinVar variants without an rsID are matched for VCF input only, where REF comes from the file (ADR-0046). A VCF allele written with bases it shares with REF (`GTT>GT,G`, `CA>TA`) is trimmed per carried allele before matching, and a VCF homozygous deletion to one base matches; indels are not left-aligned, so one the file places at a different position from ClinVar's still does not match (ADR-0047). |
139
- | ClinPGx (formerly PharmGKB) | ✓ | CC BY-SA 4.0. Clinical annotations only — single-rsid SNVs; star alleles and haplotypes deferred (ADR-0009). **Primary non-finding filter is the ClinVar REF carrier rule (ADR-0023):** if ClinVar publishes a single-base REF for the rsid and the user is homozygous for it, the row is suppressed. CPIC's `(rsid, base) → function_class` join (ADR-0020) survives as a secondary tier for rsids ClinVar doesn't catalog. Earlier prose tiers (ADR-0013, ADR-0017, ADR-0018) are superseded. |
143
+ | ClinVar (GRCh37 + GRCh38) | ✓ | Public domain (NCBI). SNVs + indels + multi-allelic sites. **Both builds cached**; `analyze` dispatches by the decided build (ADR-0044). Rows are matched by chromosome, position, REF and ALT, never by rsID alone. A record written REF = ALT, where this build's reference itself carries the record's allele (Factor V Leiden on GRCh37, or a record for the normal allele), is reported for a user who carries that allele; Pathogenic and Likely pathogenic classifications of a normal allele are dropped (ADR-0039). Carrier rule (ADR-0007) requires the user to carry the ALT allele. Indel-anchor protection (ADR-0011) prevents single-base array readouts from matching anchor-base indels. Array calls ClinVar cannot match are counted in the report: vendor probes without an rsID at a locus ClinVar classifies as Pathogenic or Likely pathogenic, and `D`/`I` calls (ADR-0051). A `D`/`I` call at a high-value or Pathogenic / Likely pathogenic rsID is named as not checked, not only counted; it is still not interpreted. Variants that ClinVar's Conflicting or Benign classification scored down (ADR-0045) are named with their submitter split. ClinVar variants without an rsID are matched for VCF input only, where REF comes from the file (ADR-0046). A VCF allele written with bases it shares with REF (`GTT>GT,G`, `CA>TA`) is trimmed per carried allele before matching, by ClinPGx, SNPedia and the GWAS Catalog as well as ClinVar, and a VCF homozygous deletion to one base matches; indels are not left-aligned, so one the file places at a different position from ClinVar's still does not match (ADR-0047). |
144
+ | ClinPGx (formerly PharmGKB) | ✓ | CC BY-SA 4.0. Clinical annotations only — single-rsid SNVs; star alleles and haplotypes deferred (ADR-0009). **Primary non-finding filter is the ClinVar REF carrier rule (ADR-0023):** where ClinVar for the input's build is installed and places the rsID at the call's position, a user homozygous for the normal allele ClinVar resolves there is not reported. That is usually ClinVar's single-base REF. The exception is a site where this build's reference itself carries ClinVar's variant allele (a REF = ALT row, F5 rs6025 on GRCh37): there the normal allele is the other build's REF, so a TT call is reported as a carrier and CC is the non-finding. Where that normal allele cannot be told, nothing is suppressed. Without that cache, only sites where the two builds' references differ (F5 rs6025) are decided: by the other build's ClinVar, else by gnomAD's major allele; if neither can tell, the row shows. Every other site is then not suppressed. CPIC's `(rsid, base) → function_class` join (ADR-0020) survives as a secondary tier for rsids ClinVar doesn't catalog. Earlier prose tiers (ADR-0013, ADR-0017, ADR-0018) are superseded. |
140
145
  | CPIC (per-allele function table) | ✓ | Internal data source for the ClinPGx filter. Fetched from `api.cpicpgx.org` at `db update` time. Used to populate the `pharmgkb_allele_function` table — not surfaced to end users as its own annotator. |
141
146
  | SNPedia | ✓ | CC BY-NC-SA 3.0 US. Pre-built cache downloaded via `db update` (~216K wiki pages, ~105K genotype rows). The cache carries SNPedia's orientation metadata: minus-strand alleles are complemented before matching. Single-base genotypes at rsIDs are then checked against the site's forward alleles in the rsID identity table: a page's stated orientation must put the genotype among them, an unknown orientation is read directly or else complemented, and A/T and C/G sites abstain. Sites that abstain are counted in the report. If the database is absent, analysis runs without it. For commercial use, pass `--exclude-snpedia`. The cache can also be rebuilt from source via `scripts/scrape_snpedia.py` + `scripts/parse_snpedia.py`. |
142
147
  | 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. The reported allele is the study's effect (risk) allele, which can be the reference allele (ADR-0039). P-value magnitude scoring maps continuous p-values to the 0–10 scale; only a confidence-interval field that positively identifies a bare odds ratio receives the odds-ratio modifier. Beta coefficients and ambiguous or unit-bearing effects receive no modifier (ADR-0038). Unknown-risk-allele entries fire on rsID match alone but are capped at 3.0. Risk alleles are read on the forward strand against the site's alleles in the rsID identity table: as written, else complemented; A/T and C/G sites and rsIDs the table does not place at the call abstain, and the report counts them. Every row says whether the effect allele raises or lowers the odds or the measured value (JSON `effect_direction`). GWAS rows must meet the larger of `--min-magnitude` and `--gwas-min-magnitude` (default 9.0); the must-include rsIDs skip the GWAS floor (ADR-0024). |
143
148
  | gnomAD | ✓ | ODbL v1.0. **GRCh38-only enrichment annotator** — adds population allele frequency context to existing annotations. Shows how common each variant is in the general population (~57M exome variants with an rsID, 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` (~7 GB on disk). GRCh37 input is enriched only through the rsID identity table's explicit allele translation (ADR-0040); gnomAD's own rsID resolution stays GRCh38-only, and GRCh36 input receives nothing. Use `--no-gnomad` to skip. |
144
149
  | AlphaMissense | ✓ | CC BY 4.0. **GRCh38-only 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). GRCh37 input is enriched only through the rsID identity table (ADR-0040); GRCh36 input receives nothing. Use `--no-alphamissense` to skip. |
145
150
  | CADD | ✓ | LicenseRef-CADD (non-commercial). **GRCh38-only enrichment annotator** — adds PHRED-scaled deleteriousness scores from CADD v1.7. **Opt-in**, disabled by default; GRCh37 input is scored only through the rsID identity table (ADR-0040) and GRCh36 input never; see [CADD modes](#cadd-modes) below for details. |
146
- | dbSNP rsID identity | ✓ | Public domain (NCBI dbSNP). Not an annotator: a cross-build table of each annotator rsID's GRCh37 and GRCh38 placement and alleles (ADR-0040). Supplies the reference allele for GRCh37 and GRCh38 array data, recovers rsIDs for rsID-less VCFs on both builds, and translates a GRCh37 allele to its GRCh38 coordinates so gnomAD, AlphaMissense and CADD can be looked up. Strand-inverted loci are translated explicitly; palindromic sites (A/T, C/G, or a site whose dbSNP alleles include all four bases) and alleles that became the GRCh38 reference abstain. Pre-built cache downloaded via `db update` (~4.9M rows covering ~3.0M rsIDs, from dbSNP b157). |
151
+ | dbSNP rsID identity | ✓ | Public domain (NCBI dbSNP). Not an annotator: a cross-build table of each annotator rsID's GRCh37 and GRCh38 placement and alleles (ADR-0040). Supplies the reference allele for GRCh37 and GRCh38 array data, recovers rsIDs for rsID-less VCFs on both builds, and translates a GRCh37 allele to its GRCh38 coordinates so gnomAD, AlphaMissense and CADD can be looked up. Strand-inverted loci are translated explicitly. A row the table flags palindromic (its dbSNP allele set equals its own complement: A/T, C/G, or all four bases) abstains where the GRCh37 and GRCh38 reference bases differ, and translates as written where they are equal; rows the table does not flag translate by their alleles. Alleles that became the GRCh38 reference abstain. Pre-built cache downloaded via `db update` (~4.9M rows covering ~3.0M rsIDs, from dbSNP b157). |
147
152
 
148
153
  ### CADD modes
149
154
 
@@ -154,26 +159,26 @@ CADD ranks how deleterious any single-nucleotide variant is using 100+ annotatio
154
159
  Two modes:
155
160
 
156
161
  - **Cache mode** (default): pre-built ~5.8 GB SQLite cache (~120M GRCh38 variant keys). Covers nearly every GRCh38 position allelix can annotate from its other databases (gnomAD, AlphaMissense, ClinVar). GRCh37 input is scored only after translation through the rsID identity table; GRCh36 input never is.
157
- - **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.
162
+ - **Full mode** (`options.cadd_full = true`): tabix queries against the complete CADD v1.7 file via pysam (GRCh38 only). It scores variants the cache lacks, such as novel or private variants in WGS/WES data. CADD adds scores to findings from the other sources; it never makes a finding of its own, so a variant no other source reports gains nothing. Requires `pip install allelix[cadd]` for the pysam dependency, and CADD's files, downloaded separately from [cadd.gs.washington.edu/download](https://cadd.gs.washington.edu/download) (`db update --cadd` installs only the SQLite cache): `whole_genome_SNVs.tsv.gz` (about 81 GB) and its `.tbi` index in the data directory, plus, optionally for indels, `gnomad.genomes.r4.0.indel.tsv.gz` (about 1.2 GB) and its `.tbi`.
158
163
 
159
164
  If your input is a genotyping chip file, cache mode is normally all you need. GRCh37 chip files are scored through the rsID identity table; GRCh36 files are not scored.
160
165
 
161
166
  ### Build coverage asymmetry (GRCh37 vs GRCh38)
162
167
 
163
- ClinVar dispatches per build and ships with both GRCh37 and GRCh38 caches. gnomAD v4.1 exomes, AlphaMissense, and CADD v1.7 are GRCh38 datasets. Every enrichment lookup is made at GRCh38 coordinates by chromosome, position, reference and alternate allele, never by rsID. For GRCh37 input, the cross-build rsID identity table (ADR-0040) translates the allele the user carries at its GRCh37 position into the matching GRCh38 allele, written explicitly on the GRCh38 forward strand where the reference strand was inverted between assemblies. Palindromic sites (A/T, C/G, or all four bases in dbSNP, whose strand cannot be told from sequence) and alleles that are the GRCh38 reference receive no values rather than a guess. Without the identity table installed, GRCh37 analyses receive no gnomAD, AlphaMissense or CADD values; GRCh36 analyses never do.
168
+ ClinVar dispatches per build and ships with both GRCh37 and GRCh38 caches. gnomAD v4.1 exomes, AlphaMissense, and CADD v1.7 are GRCh38 datasets. Every enrichment lookup is made at GRCh38 coordinates by chromosome, position, reference and alternate allele, never by rsID. For GRCh37 input, the cross-build rsID identity table (ADR-0040) translates the allele the user carries at its GRCh37 position into the matching GRCh38 allele, written explicitly on the GRCh38 forward strand where the reference strand was inverted between assemblies. A row the identity table flags palindromic (A/T, C/G, or all four bases in dbSNP, whose strand cannot be told from sequence) whose reference base differs between the builds, and an allele that is the GRCh38 reference, receive no values rather than a guess. Without the identity table installed, GRCh37 analyses receive no gnomAD, AlphaMissense or CADD values; GRCh36 analyses never do.
164
169
 
165
170
  Position-based rsID recovery for rsID-less VCFs uses ClinVar (on the selected build), the identity table (on GRCh37 and GRCh38), and gnomAD (GRCh38 only). The same recovery step runs for `analyze`, `stats`, `extract` and `export plink`, so high-value warnings, extracted genotypes and PLINK allele coding all see the recovered rsIDs (ADR-0043). The identity table covers the rsIDs the annotators hold, so an rsID-less GRCh37 VCF now recovers ClinPGx, GWAS Catalog and SNPedia rsIDs as well as ClinVar ones. If you convert between assemblies, re-normalize REF/ALT as part of the conversion and pass `--build` explicitly when the file does not carry a trustworthy build signal.
166
171
 
167
- ### Known ClinPGx limitation: reference-genotype rows where ClinVar and CPIC both lack data
172
+ ### Known ClinPGx limitation: normal-genotype rows without usable normal-allele evidence
168
173
 
169
- ADR-0022 + ADR-0023: a tiny residual of ClinPGx rows may appear in reports even when the user is homozygous reference. ClinPGx publishes one annotation per genotype including the reference homozygote, and for the reference-homozygote row to be suppressed Allelix needs structured data on the variant from either:
174
+ ADR-0022 + ADR-0023: a tiny residual of ClinPGx rows may appear in reports even when the user carries the normal genotype. ClinPGx publishes one annotation per genotype including the normal homozygote, and for that row to be suppressed Allelix needs usable evidence of the normal allele from one of:
170
175
 
171
- - **ClinVar's REF allele** (the primary filter — see ADR-0023). Covers any rsID ClinVar catalogs.
176
+ - **ClinVar's normal allele** (the primary filter — see ADR-0023): usually its single-base REF, or the other build's REF where this build's reference carries ClinVar's variant allele (F5 rs6025 on GRCh37). Covers an rsID that ClinVar for the input's build places at the call's position. Without that cache, only a site whose reference differs between the builds is decided (by the other build's ClinVar, else gnomAD's major allele), and anything else shows.
172
177
  - **CPIC's per-allele function table** (the secondary fallback — see ADR-0020). Covers rsIDs CPIC has classified.
173
178
 
174
- For the rare rsID where ClinPGx has an annotation but *neither* ClinVar nor CPIC has data, the row emits. These are identifiable by a homozygous-reference genotype combined with "decreased risk," "may have a typical response," or similar comparative language. They are an upstream data gap, not an Allelix bug — we surface them honestly rather than hide them behind a curated exclusion list (which would recreate the maintenance trap the v0.5–v0.7 prose filters were trying to escape).
179
+ So without ClinVar or CPIC data, gnomAD can still decide a site whose reference differs between the builds, at that placement only. For the rare rsID where ClinPGx has an annotation and none of these gives a usable normal allele (no ClinVar or CPIC data, and no decision from the other build's ClinVar or gnomAD, which abstains on a tie, an unknown frequency or an unlisted site), the row emits: Allelix shows it rather than guess. These are identifiable by a homozygous-reference genotype combined with "decreased risk," "may have a typical response," or similar comparative language. They are an upstream data gap, not an Allelix bug — we surface them honestly rather than hide them behind a curated exclusion list (which would recreate the maintenance trap the v0.5–v0.7 prose filters were trying to escape).
175
180
 
176
- The CFTR × ivacaftor leak (~30+ rows on real data, pre-v0.7.3) is fixed by the ADR-0023 ClinVar REF check: CPIC's CFTR vocabulary (`"ivacaftor responsive"`) doesn't match the four-class enum the secondary tier expects, but ClinVar publishes REF for every CFTR rsID, so the primary tier catches them universally.
181
+ The CFTR × ivacaftor leak (~30+ rows on real data, pre-v0.7.3) is fixed by the ADR-0023 ClinVar REF check: CPIC's CFTR vocabulary (`"ivacaftor responsive"`) doesn't match the four-class enum the secondary tier expects, but ClinVar publishes REF for the CFTR rsIDs, so with ClinVar for the input's build installed the primary tier catches them.
177
182
 
178
183
  ### Known ClinVar upstream data quality issues
179
184
 
@@ -251,7 +256,7 @@ The HTML report shows a ⚠ icon next to a flagged genotype, with the reason on
251
256
 
252
257
  A call is not assessed when gnomAD has no frequency for the allele. Allelix's gnomAD cache covers exome sites with an rsID, so a missing value says nothing about rarity. Calls are also not assessed when the gnomAD database is skipped. Homozygous calls on X are not assessed because a male's single X reads as homozygous; heterozygous X calls are. A call the file itself declares as one copy (a one-letter array call such as 23andMe's male X calls, or a one-index VCF genotype) is shown as Hemizygous and assessed as one copy. Y and mitochondrial calls are not assessed. Confirm a flagged call with a clinical-grade test before relying on it.
253
258
 
254
- When three or more variants on chromosomes 1 to 22 are called homozygous for an allele ClinVar classifies as Pathogenic or Likely pathogenic, and are rare in gnomAD or have no gnomAD frequency, the report warns at the top. This is counted before any display filter, so `--min-magnitude` cannot hide it (ADR-0050).
259
+ When three or more variants on chromosomes 1 to 22 are called homozygous for an allele ClinVar classifies as Pathogenic or Likely pathogenic, and are rare in gnomAD or were looked up there and are absent, the report warns at the top. A call gnomAD was not asked about (gnomAD not installed, or a GRCh37 allele the identity table cannot translate) does not count. This is counted before any display filter, so `--min-magnitude` cannot hide it (ADR-0050).
255
260
 
256
261
  ### Database sizes and download times
257
262
 
@@ -282,6 +287,7 @@ Allelix keeps its caches in `~/.local/share/allelix/`, or `$XDG_DATA_HOME/alleli
282
287
  ├── cadd.sqlite ~5.8 GB (only if you opted into --cadd)
283
288
  ├── clinvar.GRCh37.sqlite ~1.2 GB
284
289
  ├── clinvar.GRCh38.sqlite ~1.2 GB
290
+ ├── clinvar-build.log each ClinVar build's source hashes and counts
285
291
  ├── gwas_catalog_associations.tsv ~745 MB
286
292
  ├── rsid_identity.sqlite ~457 MB
287
293
  ├── gwas.sqlite ~202 MB
@@ -320,7 +326,7 @@ du -sh "$ALLELIX_DATA"
320
326
  allelix db path --check
321
327
  ```
322
328
 
323
- **The one thing worth backing up: `config.toml`.** It lives inside the resolved data directory (typically `~/.local/share/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` always preserves it.
329
+ **The one thing worth backing up: `config.toml`.** It lives inside the resolved data directory (typically `~/.local/share/allelix/config.toml`) and stores your license assertions (commercial-mode toggle, CADD opt-in confirmation) and per-source enable/disable settings. The caches will rebuild on the next `db update`; `config.toml` won't. `allelix db clean` always preserves it. `clinvar-build.log` (the source hashes and counts of past ClinVar builds) is not reproducible either, and `db clean` removes it: copy it elsewhere first if you want to keep that history.
324
330
 
325
331
  To fully uninstall allelix and reclaim everything:
326
332
 
@@ -419,17 +425,41 @@ The banner says where the build came from, for example `Build: GRCh38 (from the
419
425
  - a VCF has no contig lengths, no assembly and too few known-SNP positions;
420
426
  - a chip states no build and carries none of the known-SNP positions at all (for example an X-chromosome-only export, since they are all on autosomes);
421
427
  - a file states a build but carries none of the known-SNP positions to check it (for example an mtDNA-only export, or a small panel VCF without contig lengths);
422
- - the file has known-SNP positions but none of them sits at any supported build's coordinate, whether or not it states a build (contig lengths, when a VCF declares them, still decide);
428
+ - the file has known-SNP positions but none of them sits at any supported build's coordinate, whether or not it states a build (contig lengths, when a VCF declares them, still decide). Choosing a build is not the fix here: the message says the positions cannot be trusted on any build and asks you to check that this is the original file from the testing company. If you force a build anyway with `--build`, the run goes ahead but prints a **Build warning** naming the evidence (0 of N known-SNP positions match), and every report keeps it (JSON `report_warnings` code `build_override_unmatched`);
423
429
  - contig lengths decided, but the known-SNP positions met while annotating point to the other build (for example a lifted-over file that kept its old header). The message says the annotation pass ran; nothing is written;
424
430
  - `--build grch37` or `--build grch38` was given, but the file's contig lengths, or three or more known-SNP positions with an 80% majority, say the other build. The message names both builds. Contrary positions that fall short of that (fewer than three, or under an 80% majority) only print a **Build warning**, and so does a tie that leaves out the forced build. `--build grch36` is never checked, because it turns position-keyed lookups off.
425
431
 
426
- The message names the evidence and asks you to say which build the file uses: `--build grch37` or `--build grch38`, or `--build grch36` for an old GRCh36 / hg18 file, which then runs with position-keyed lookups off (see [`docs/grch36-liftover.md`](https://github.com/allelix/allelix/blob/main/docs/grch36-liftover.md)). For `compare`, use `--build1` / `--build2`. A stop leaves no report, annotated VCF, diff, PLINK files or extract table behind. A forced build is recorded as such in every report.
432
+ Otherwise the message names the evidence and asks you to say which build the file uses: `--build grch37` or `--build grch38`, or `--build grch36` for an old GRCh36 / hg18 file, which then runs with position-keyed lookups off (see [`docs/grch36-liftover.md`](https://github.com/allelix/allelix/blob/main/docs/grch36-liftover.md)). For `compare`, use `--build1` / `--build2`. A stop leaves no report, annotated VCF, diff, PLINK files or extract table behind. A forced build is recorded as such in every report.
433
+
434
+ ### Keeping input and output files safe
435
+
436
+ Report `--output`, annotated `--vcf-out` and all three actual PLINK output paths
437
+ must differ from the input, including symlink and hard-link aliases. These
438
+ checks run before cache setup or output creation. Inputs that alias the former
439
+ fixed report or PLINK `.tmp` path are also refused; unrelated old temp files
440
+ are left untouched. Annotated VCF still refuses any existing output at the CLI.
441
+ Two outputs of one run must name different files too: `--output` and
442
+ `--vcf-out`, and PLINK's `.bed`, `.bim` and `.fam`, are refused before any work
443
+ when they resolve to one file (another spelling, a symlink, a linked folder or a
444
+ hardlink).
445
+
446
+ Writers use unique temporary files in the destination directory and publish
447
+ only complete, closed content. Failure before publication preserves previous
448
+ outputs. PLINK stages all three files first, then publishes each atomically;
449
+ a later publication failure can leave complete new files next to unchanged old
450
+ ones. Existing ordinary permission bits are retained when replacement is
451
+ permitted; new staging files start private, and a new output file gets the mode
452
+ your umask gives any new file (`0666 & ~umask`, so `0644` under `022`).
453
+ Ownership, ACLs, extended attributes,
454
+ crash durability and concurrent path swaps are outside this guarantee.
427
455
 
428
456
  ### Is my database stale? How fresh is the data?
429
457
 
430
458
  `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.
431
459
 
432
- If you want to force a refresh: `allelix db update --force` re-downloads every cache regardless of freshness signal.
460
+ If you want to force a refresh: `allelix db update --force` re-downloads every cache regardless of freshness signal. ClinVar is never refreshed unverified: its freshness signal is NCBI's checksum for the submission summary, which also verifies the download. If that checksum cannot be fetched (after one retry), ClinVar is not refreshed, with or without `--force`; the existing caches are kept, and a later `db update` tries again. The GWAS Catalog and ClinPGx publish no checksum, so `--force` refreshes them even when their freshness signal cannot be fetched; it stores no signal, and the next ordinary `db update` refreshes them once more when the signal is available again.
461
+
462
+ A ClinPGx cache built while CPIC's allele-function data could not be fetched is marked as such: `db status` shows "(no CPIC)", and the next `db update` tries CPIC again. A cache from 2.5.0 that claims CPIC data but holds none is repaired the same way by an ordinary `db update`.
433
463
 
434
464
  ### Output reports contain real genome annotations
435
465
 
@@ -457,13 +487,13 @@ Notable load-bearing ADRs:
457
487
  - **ADR-0042 — VCF quality gate.** The variant caller's FILTER and genotype quality are honoured, low-quality reference blocks are no-calls, and every exclusion is counted in the report.
458
488
  - **ADR-0043 — One rsID-recovery step.** Every command that needs rsIDs recovers them for rsID-less input through the same shared step; build detection votes by position instead.
459
489
  - **ADR-0044 — Evidence before labels.** The build is decided before annotation from contig lengths or whole-file known-SNP positions; evidence that contradicts the label, or no usable evidence, stops the run instead of producing a report on the wrong build.
460
- - **ADR-0045: ClinVar's own classification.** A ClinVar row is capped by ClinVar's aggregate classification of the variant (Conflicting, Benign, Likely benign), with the submitter split shown; outranked P/LP submissions are kept by ClinVar's tiered rule where the aggregate is not a disease call.
490
+ - **ADR-0045: ClinVar's own classification.** A ClinVar row is capped by ClinVar's aggregate classification of the variant (Conflicting, Benign, Likely benign), with the submitter split shown, and the variants scored down are named on the console, in HTML and in JSON (`clinvar.scored_down_variants`); outranked P/LP submissions are kept by ClinVar's tiered rule where the aggregate is not a disease call.
461
491
  - **ADR-0046: ClinVar variants without an rsID.** Kept in the cache and matched for VCF input only, by locus and the file's REF; reports name such a finding by `chrom:pos ref>alt`.
462
- - **ADR-0047: VCF calls no longer dropped silently.** Untrimmed alleles are trimmed per carried allele, half calls are assessed by the sources that need only a carried allele, VCF homozygous deletions to one base match ClinVar, and dropped calls are counted with their reason (a genotype on a gVCF reference block's own `<NON_REF>` is not checked).
492
+ - **ADR-0047: VCF calls no longer dropped silently.** Untrimmed alleles are trimmed per carried allele (by every annotator since 2.5.2), half calls are assessed by the sources that need only a carried allele, VCF homozygous deletions to one base match ClinVar, and dropped calls are counted with their reason (a genotype on a gVCF reference block's own `<NON_REF>` is not checked).
463
493
  - **ADR-0048: Caches are read-only at analyze time.** With `--no-update`, `analyze` writes nothing to the data directory; every cache opens read-only, installs in rollback-journal mode, and a skipped source is named with its reason (missing, outdated, unreadable, damaged, or WAL mode from an older Allelix). Local migrations run in the update step.
464
494
  - **ADR-0049: Every report names its sources.** JSON `sources` gives every registered source's part in the run (used, ready but not used, or skipped with a reason); `report_warnings` carries report-level warnings, starting with ClinVar not used; the annotated VCF names skipped sources.
465
495
  - **ADR-0050: Zygosity codes and haploid calls.** JSON zygosity and plausibility are codes, a call the file declares haploid is shown as hemizygous, "not assessed" carries a reason, and three rare homozygous P/LP calls give a report warning.
466
- - **ADR-0051: Array calls ClinVar cannot match are counted.** Vendor probes at P/LP loci and `D`/`I` calls are counted in the report, and panel coverage puts each requested rsID in one of four states.
496
+ - **ADR-0051: Array calls ClinVar cannot match are counted.** Vendor probes at P/LP loci and `D`/`I` calls are counted in the report; since 2.5.2, a `D`/`I` call at a high-value or P/LP rsID is also named as not checked (JSON `high_value_unchecked` and `report_warnings` code `di_calls_not_checked`), and panel coverage puts each requested rsID in one of four states.
467
497
  - **ADR-0052: Zero-star P/LP on a common allele.** A ClinVar P/LP row without assertion criteria whose allele has a gnomAD frequency of 5% or more is scored down, marked and counted.
468
498
 
469
499
  Release history: see [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/main/CHANGELOG.md).
@@ -472,19 +502,20 @@ Release history: see [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/ma
472
502
 
473
503
  ```bash
474
504
  source .venv/bin/activate
475
- pip install -e ".[dev]"
505
+ pip install -e ".[dev,vcf-index]" # the CI install: vcf-index (pysam) runs the indexed-VCF tests
476
506
 
477
507
  # One-time: install pre-commit hooks
478
508
  pre-commit install --hook-type pre-commit
479
509
 
480
510
  ruff check .
481
511
  ruff format --check .
482
- pytest
512
+ pytest -m "not slow and not integration" # the fast suite CI runs
513
+ pytest # every test, including slow and integration
483
514
  ```
484
515
 
485
516
  The pre-commit hook enforces `ruff check` + `ruff format --check`. If a commit is blocked, fix the underlying problem rather than skipping the hook.
486
517
 
487
- 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`](https://github.com/allelix/allelix/blob/main/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.
518
+ 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`](https://github.com/allelix/allelix/blob/main/test_data/FULL_TEST_PROTOCOL.md). That document is the project's real-data testing story; `pytest -m "not slow and not integration"` is the fast suite that gates every commit, as in CI.
488
519
 
489
520
  ## License
490
521
 
@@ -48,6 +48,8 @@ allelix analyze your_wgs.vcf.gz --keep-filtered --min-gq 10 --output report.html
48
48
  allelix analyze your_genotype_file.txt --filter-file my_panel.txt --output report.html
49
49
  # Panel coverage puts each requested rsID in one state: annotated, typed (no annotation),
50
50
  # no call, or not on chip ("not in file" for a VCF)
51
+ # VCF coverage includes explicit REF/REF calls and explains rejected/excluded records.
52
+ # Gene entries match constituent symbols in lists; hyphens inside symbols stay intact.
51
53
 
52
54
  # Annotated VCF output — VCF-in / VCF-out for pipeline integration
53
55
  # Writes a valid VCF with annotations as INFO fields + full provenance in the header.
@@ -63,32 +65,34 @@ See [Development](#development) for source installs and running tests, [Managing
63
65
  | Format | Status | Notes |
64
66
  |---|---|---|
65
67
  | MyHappyGenes (Tempus) | ✓ | Tab-delimited, 5 columns. **Build is auto-detected** — real-world MHG exports mislabel the header as "build 37.1" while shipping GRCh38 coordinates. Allelix decides the build from position data and warns on header/data disagreement (ADR-0021, ADR-0044). See [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#myhappygenes-tempus). |
66
- | 23andMe | ✓ | Tab-delimited, 4 columns, concatenated genotype. Reads build 36, 37 or 38; the header's build is used when the known-SNP positions agree. I-prefixed probe IDs passed through; they and `D`/`I` indel calls are not matched against ClinVar, and the report counts them (ADR-0051). |
68
+ | 23andMe | ✓ | Tab-delimited, 4 columns, concatenated genotype; a copy a spreadsheet re-saved as CSV (quoted or not) or with spaces is read too ([docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#23andme)). Reads build 36, 37 or 38; the header's build is used when the known-SNP positions agree. I-prefixed probe IDs passed through; they and `D`/`I` indel calls are not matched against ClinVar, and the report counts them (ADR-0051). A `D`/`I` call at a high-value rsID, or at one ClinVar classifies Pathogenic or Likely pathogenic, is also named as not checked (gene, variant name, rsID and call) on the console, in HTML and in JSON. |
67
69
  | Genes for Good | ✓ | The 23andMe-style `.txt` from a Genes for Good download (23andMe's columns under a PLINK banner). The imputed text file (about 7.7 million rows) and the imputed VCF are labelled imputed in every report; the measured files are not. The download's VCFs are read as VCF; see [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#genes-for-good). |
68
70
  | AncestryDNA | ✓ | Tab-delimited, 5 columns. Chromosome mapping: 23→X, 24→Y, 25→X (PAR), 26→MT. V1 and V2 chip layouts. |
69
- | Family Tree DNA | ✓ | CSV, double-quoted fields, concatenated genotype. Build 37 default when known-SNP positions are present but do not decide; with none at all and no build line, the run stops (ADR-0044). |
71
+ | Family Tree DNA | ✓ | CSV, double-quoted fields, concatenated genotype. Build 37 default when known-SNP positions are present but do not decide (subject to the stops and the GRCh36 exception in [How the genome build is decided](#how-the-genome-build-is-decided-and-why-a-run-can-stop)); with none at all and no build line, the run stops (ADR-0044). |
70
72
  | MyHeritage DNA | ✓ | CSV, same structure as FTDNA. Detected by "MyHeritage" in the file's preamble. Handles double-double-quoted fields and files re-saved through a spreadsheet (padded rows, signature not on line 1, tab-delimited copies). Two exports: the **array** export (pre-2026 kits) and the **low-pass WGS** export (kits from January 2026; genotypes imputed from about 2x coverage), which the report labels as imputed and whose `##reference=build37` line is used as the header build. See [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#myheritage). |
71
73
  | Living DNA | ✓ | Tab-delimited despite `.csv` extension. Handles AX-, AFFX-prefixed and CHR:POS positional SNP IDs. |
72
74
  | deCODEme | ✓ | `deCODEme_scan.csv` (deCODE genetics, service closed 2013): comma-separated, a per-row `Strand` column; minus-strand calls are complemented to the plus strand. Build 36: decided from known-SNP positions, then run with position lookups off (ADR-0025). See [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#decodeme). |
73
- | FTDNA Illumina raw | ✓ | Tab-delimited variant of the FTDNA export (distinct from the CSV format above). `RSID/CHROMOSOME/POSITION/RESULT` columns. Build 37 default when known-SNP positions are present but do not decide. |
74
- | 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 when known-SNP positions are present but do not decide. |
75
- | 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 not annotated — they match nothing in any annotation database — but they are read at high-value positions: a block with low genotype quality or no reads there makes the position a no-call, and so does a gVCF position no record covers. High-value no-calls list the reason (for example `low_gq_block`, `no_coverage`, `filtered`, `low_gq_call`). A plain VCF has no reference blocks, so a position without a record is reported as unknown rather than as reference or as a no-call. Multi-sample files require `--sample <ID>`. Rows the variant caller rejected (FILTER other than `PASS` or `.`) are skipped and counted in the report; `--keep-filtered` keeps them. A call with genotype quality below `--min-gq` (default 20) or with no reads (DP 0) is reported as a no-call. The spanning-deletion allele `*` is treated as symbolic: such calls are not assessed, and the report counts them and lists their positions. A half call (`./1`) is assessed on its called allele by ClinVar and the GWAS Catalog and shown as `T/.` with zygosity "Half call"; ClinPGx and SNPedia, which need both alleles, skip it. Malformed rows, rows without a genotype, genotypes naming an allele the record lacks and genotypes naming a symbolic allele such as `<NON_REF>` beside a real ALT are counted in the report; a no-call written `./.` stays a no-call (ADR-0047). A VCF that holds only structural-variant or copy-number records (such as a provider's separate `sv` / `cnv` file) is rejected with a message naming the file to use instead; see [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#sequencingcom-30x-whole-genome) for per-provider deliverables. A VCF whose header declares an imputation tool's fields (Minimac, Beagle, GLIMPSE) is labelled imputed in every report; see [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#imputed-vcfs). 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. |
75
+ | FTDNA Illumina raw | ✓ | Tab-delimited variant of the FTDNA export (distinct from the CSV format above). `RSID/CHROMOSOME/POSITION/RESULT` columns. Build 37 default when known-SNP positions are present but do not decide (subject to the stops and the GRCh36 exception in [How the genome build is decided](#how-the-genome-build-is-decided-and-why-a-run-can-stop)). |
76
+ | 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 when known-SNP positions are present but do not decide (subject to the stops and the GRCh36 exception in [How the genome build is decided](#how-the-genome-build-is-decided-and-why-a-run-can-stop)). |
77
+ | VCF / gVCF | ✓ | REF/ALT encoding, `0/1` genotype notation. A homozygous-reference call (`0/0`) or a gVCF reference block (lines with `<NON_REF>` ALT and `END=` INFO) carries no variant allele, so it usually matches nothing. The exception is a site where the reference genome itself carries a ClinVar allele (Factor V Leiden on GRCh37, for example): there a `0/0` call, or a reference block that passes the genotype-quality gate, is assessed as homozygous for that allele. See [docs/formats.md, Reference calls in VCFs](https://github.com/allelix/allelix/blob/main/docs/formats.md#reference-calls-in-vcfs) (ADR-0039, ADR-0059). At high-value positions, a block with low genotype quality or no reads makes the position a no-call, and so does a gVCF position no record covers. High-value no-calls list the reason (for example `low_gq_block`, `no_coverage`, `filtered`, `low_gq_call`). A record counts for a high-value or panel rsID when it is at one of the rsID's identity placements with that placement's REF, whatever its ID or ALT; an rsID the identity table does not place is matched by ID (ADR-0055). A plain VCF has no reference blocks, so a position without a record is reported as unknown rather than as reference or as a no-call; at such a ClinVar site the report counts the positions no record covers, and names the high-value ones (such as CYP3A5*3 rs776746 on GRCh37) as not checked. Multi-sample files require `--sample <ID>`. Rows the variant caller rejected (FILTER other than `PASS` or `.`) are skipped and counted in the report; `--keep-filtered` keeps them. A call with genotype quality below `--min-gq` (default 20) or with no reads (DP 0) is reported as a no-call. The spanning-deletion allele `*` is treated as symbolic: such calls are not assessed, and the report counts them and lists their positions. A half call (`./1`) is assessed on its called allele by ClinVar and the GWAS Catalog and shown as `T/.` with zygosity "Half call"; ClinPGx and SNPedia, which need both alleles, skip it. Malformed rows, rows without a genotype, genotypes naming an allele the record lacks and genotypes naming a symbolic allele such as `<NON_REF>` beside a real ALT are counted in the report; a no-call written `./.` stays a no-call (ADR-0047). A VCF that holds only structural-variant or copy-number records (such as a provider's separate `sv` / `cnv` file) is rejected with a message naming the file to use instead; see [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#sequencingcom-30x-whole-genome) for per-provider deliverables. A VCF whose header declares an imputation tool's fields (Minimac, Beagle, GLIMPSE) is labelled imputed in every report; see [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#imputed-vcfs). 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. |
76
78
 
77
79
  Files can be passed as delivered: gzipped (Family Tree DNA's `.csv.gz`, for example), or a zip holding one genotype file. A zip with several files, a password or an unusual compression stops with a message to unzip it first. Windows, Linux and old-Mac line endings are read; a leading byte order mark or blank line is ignored.
78
80
 
79
- If Allelix does not recognise a file, use the original file from your testing company. `--format <name>` forces a reader at your own risk; every report then carries a warning (see [docs/formats.md, Forcing a format](https://github.com/allelix/allelix/blob/main/docs/formats.md#forcing-a-format)).
81
+ If Allelix does not recognise a file, use the original file from your testing company. `--format <name>` forces a reader at your own risk; when that reader does not recognise the file, every report carries a warning (see [docs/formats.md, Forcing a format](https://github.com/allelix/allelix/blob/main/docs/formats.md#forcing-a-format)).
80
82
 
81
83
  Adding a new format means adding one file to `allelix/parsers/` and registering an instance in the `PARSERS` list in `allelix/parsers/__init__.py`.
82
84
 
83
85
  ### Roadmap
84
86
 
87
+ Shipped releases are listed for history; the [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) has each release's changes.
88
+
85
89
  | Release | Theme |
86
90
  |---|---|
87
- | **v2.2** | Custom-panel UX improvements, methylation panel retune + citations, ClinVar source switch (VCF → `variant_summary.txt`), CLI cleanups (`db clean` / `db path`). |
88
- | **v2.3** | Annotated VCF output plus v2.3.1 correctness hardening: cross-build enrichment guards, GWAS effect-type disambiguation, conservative build confidence, SNPedia orientation handling, and strict slow-suite drift verification. |
89
- | **v2.4** | Correctness hardening: cross-build rsID identity table with explicit allele translation, plausibility flag for genotyping-artifact detection, VCF quality gate honouring FILTER and genotype quality, unified rsID-recovery step across all commands, and parser/format hygiene. |
90
- | **v2.5** | ClinVar's own classification caps each row; ClinVar variants without an rsID; VCF calls counted, not dropped; read-only caches and named sources in every report; zip input; Genes for Good and deCODEme; imputed input labelled. |
91
- | **Later** | Variant consequences, CNVs and repeat expansions, gnomAD genomes, supplemental genotype-file merging, ancestry estimation, and PRS support. These are directional themes rather than release commitments. |
91
+ | **v2.2** (shipped) | Custom-panel UX improvements, methylation panel retune + citations, ClinVar source switch (VCF → `variant_summary.txt`), CLI cleanups (`db clean` / `db path`). |
92
+ | **v2.3** (shipped) | Annotated VCF output plus v2.3.1 correctness hardening: cross-build enrichment guards, GWAS effect-type disambiguation, conservative build confidence, SNPedia orientation handling, and strict slow-suite drift verification. |
93
+ | **v2.4** (shipped) | Correctness hardening: cross-build rsID identity table with explicit allele translation, plausibility flag for genotyping-artifact detection, VCF quality gate honouring FILTER and genotype quality, unified rsID-recovery step across all commands, and parser/format hygiene. |
94
+ | **v2.5** (shipped) | ClinVar's own classification caps each row; ClinVar variants without an rsID; VCF calls counted, not dropped; read-only caches and named sources in every report; zip input; Genes for Good and deCODEme; imputed input labelled. |
95
+ | **Later** (planned) | Variant consequences, CNVs and repeat expansions, gnomAD genomes, supplemental genotype-file merging, ancestry estimation, and PRS support. These are directional themes rather than release commitments. |
92
96
 
93
97
  Issues and milestones are tracked at [github.com/allelix/allelix/issues](https://github.com/allelix/allelix/issues).
94
98
 
@@ -96,15 +100,15 @@ Issues and milestones are tracked at [github.com/allelix/allelix/issues](https:/
96
100
 
97
101
  | Database | Status | Notes |
98
102
  |---|---|---|
99
- | ClinVar (GRCh37 + GRCh38) | ✓ | Public domain (NCBI). SNVs + indels + multi-allelic sites. **Both builds cached**; `analyze` dispatches by the decided build (ADR-0044). Rows are matched by chromosome, position, REF and ALT, never by rsID alone, and reference-allele records (REF = ALT) are never reported (ADR-0039). Carrier rule (ADR-0007) requires the user to carry the ALT allele. Indel-anchor protection (ADR-0011) prevents single-base array readouts from matching anchor-base indels. Array calls ClinVar cannot match are counted in the report: vendor probes without an rsID at a locus ClinVar classifies as Pathogenic or Likely pathogenic, and `D`/`I` calls (ADR-0051). ClinVar variants without an rsID are matched for VCF input only, where REF comes from the file (ADR-0046). A VCF allele written with bases it shares with REF (`GTT>GT,G`, `CA>TA`) is trimmed per carried allele before matching, and a VCF homozygous deletion to one base matches; indels are not left-aligned, so one the file places at a different position from ClinVar's still does not match (ADR-0047). |
100
- | ClinPGx (formerly PharmGKB) | ✓ | CC BY-SA 4.0. Clinical annotations only — single-rsid SNVs; star alleles and haplotypes deferred (ADR-0009). **Primary non-finding filter is the ClinVar REF carrier rule (ADR-0023):** if ClinVar publishes a single-base REF for the rsid and the user is homozygous for it, the row is suppressed. CPIC's `(rsid, base) → function_class` join (ADR-0020) survives as a secondary tier for rsids ClinVar doesn't catalog. Earlier prose tiers (ADR-0013, ADR-0017, ADR-0018) are superseded. |
103
+ | ClinVar (GRCh37 + GRCh38) | ✓ | Public domain (NCBI). SNVs + indels + multi-allelic sites. **Both builds cached**; `analyze` dispatches by the decided build (ADR-0044). Rows are matched by chromosome, position, REF and ALT, never by rsID alone. A record written REF = ALT, where this build's reference itself carries the record's allele (Factor V Leiden on GRCh37, or a record for the normal allele), is reported for a user who carries that allele; Pathogenic and Likely pathogenic classifications of a normal allele are dropped (ADR-0039). Carrier rule (ADR-0007) requires the user to carry the ALT allele. Indel-anchor protection (ADR-0011) prevents single-base array readouts from matching anchor-base indels. Array calls ClinVar cannot match are counted in the report: vendor probes without an rsID at a locus ClinVar classifies as Pathogenic or Likely pathogenic, and `D`/`I` calls (ADR-0051). A `D`/`I` call at a high-value or Pathogenic / Likely pathogenic rsID is named as not checked, not only counted; it is still not interpreted. Variants that ClinVar's Conflicting or Benign classification scored down (ADR-0045) are named with their submitter split. ClinVar variants without an rsID are matched for VCF input only, where REF comes from the file (ADR-0046). A VCF allele written with bases it shares with REF (`GTT>GT,G`, `CA>TA`) is trimmed per carried allele before matching, by ClinPGx, SNPedia and the GWAS Catalog as well as ClinVar, and a VCF homozygous deletion to one base matches; indels are not left-aligned, so one the file places at a different position from ClinVar's still does not match (ADR-0047). |
104
+ | ClinPGx (formerly PharmGKB) | ✓ | CC BY-SA 4.0. Clinical annotations only — single-rsid SNVs; star alleles and haplotypes deferred (ADR-0009). **Primary non-finding filter is the ClinVar REF carrier rule (ADR-0023):** where ClinVar for the input's build is installed and places the rsID at the call's position, a user homozygous for the normal allele ClinVar resolves there is not reported. That is usually ClinVar's single-base REF. The exception is a site where this build's reference itself carries ClinVar's variant allele (a REF = ALT row, F5 rs6025 on GRCh37): there the normal allele is the other build's REF, so a TT call is reported as a carrier and CC is the non-finding. Where that normal allele cannot be told, nothing is suppressed. Without that cache, only sites where the two builds' references differ (F5 rs6025) are decided: by the other build's ClinVar, else by gnomAD's major allele; if neither can tell, the row shows. Every other site is then not suppressed. CPIC's `(rsid, base) → function_class` join (ADR-0020) survives as a secondary tier for rsids ClinVar doesn't catalog. Earlier prose tiers (ADR-0013, ADR-0017, ADR-0018) are superseded. |
101
105
  | CPIC (per-allele function table) | ✓ | Internal data source for the ClinPGx filter. Fetched from `api.cpicpgx.org` at `db update` time. Used to populate the `pharmgkb_allele_function` table — not surfaced to end users as its own annotator. |
102
106
  | SNPedia | ✓ | CC BY-NC-SA 3.0 US. Pre-built cache downloaded via `db update` (~216K wiki pages, ~105K genotype rows). The cache carries SNPedia's orientation metadata: minus-strand alleles are complemented before matching. Single-base genotypes at rsIDs are then checked against the site's forward alleles in the rsID identity table: a page's stated orientation must put the genotype among them, an unknown orientation is read directly or else complemented, and A/T and C/G sites abstain. Sites that abstain are counted in the report. If the database is absent, analysis runs without it. For commercial use, pass `--exclude-snpedia`. The cache can also be rebuilt from source via `scripts/scrape_snpedia.py` + `scripts/parse_snpedia.py`. |
103
107
  | 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. The reported allele is the study's effect (risk) allele, which can be the reference allele (ADR-0039). P-value magnitude scoring maps continuous p-values to the 0–10 scale; only a confidence-interval field that positively identifies a bare odds ratio receives the odds-ratio modifier. Beta coefficients and ambiguous or unit-bearing effects receive no modifier (ADR-0038). Unknown-risk-allele entries fire on rsID match alone but are capped at 3.0. Risk alleles are read on the forward strand against the site's alleles in the rsID identity table: as written, else complemented; A/T and C/G sites and rsIDs the table does not place at the call abstain, and the report counts them. Every row says whether the effect allele raises or lowers the odds or the measured value (JSON `effect_direction`). GWAS rows must meet the larger of `--min-magnitude` and `--gwas-min-magnitude` (default 9.0); the must-include rsIDs skip the GWAS floor (ADR-0024). |
104
108
  | gnomAD | ✓ | ODbL v1.0. **GRCh38-only enrichment annotator** — adds population allele frequency context to existing annotations. Shows how common each variant is in the general population (~57M exome variants with an rsID, 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` (~7 GB on disk). GRCh37 input is enriched only through the rsID identity table's explicit allele translation (ADR-0040); gnomAD's own rsID resolution stays GRCh38-only, and GRCh36 input receives nothing. Use `--no-gnomad` to skip. |
105
109
  | AlphaMissense | ✓ | CC BY 4.0. **GRCh38-only 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). GRCh37 input is enriched only through the rsID identity table (ADR-0040); GRCh36 input receives nothing. Use `--no-alphamissense` to skip. |
106
110
  | CADD | ✓ | LicenseRef-CADD (non-commercial). **GRCh38-only enrichment annotator** — adds PHRED-scaled deleteriousness scores from CADD v1.7. **Opt-in**, disabled by default; GRCh37 input is scored only through the rsID identity table (ADR-0040) and GRCh36 input never; see [CADD modes](#cadd-modes) below for details. |
107
- | dbSNP rsID identity | ✓ | Public domain (NCBI dbSNP). Not an annotator: a cross-build table of each annotator rsID's GRCh37 and GRCh38 placement and alleles (ADR-0040). Supplies the reference allele for GRCh37 and GRCh38 array data, recovers rsIDs for rsID-less VCFs on both builds, and translates a GRCh37 allele to its GRCh38 coordinates so gnomAD, AlphaMissense and CADD can be looked up. Strand-inverted loci are translated explicitly; palindromic sites (A/T, C/G, or a site whose dbSNP alleles include all four bases) and alleles that became the GRCh38 reference abstain. Pre-built cache downloaded via `db update` (~4.9M rows covering ~3.0M rsIDs, from dbSNP b157). |
111
+ | dbSNP rsID identity | ✓ | Public domain (NCBI dbSNP). Not an annotator: a cross-build table of each annotator rsID's GRCh37 and GRCh38 placement and alleles (ADR-0040). Supplies the reference allele for GRCh37 and GRCh38 array data, recovers rsIDs for rsID-less VCFs on both builds, and translates a GRCh37 allele to its GRCh38 coordinates so gnomAD, AlphaMissense and CADD can be looked up. Strand-inverted loci are translated explicitly. A row the table flags palindromic (its dbSNP allele set equals its own complement: A/T, C/G, or all four bases) abstains where the GRCh37 and GRCh38 reference bases differ, and translates as written where they are equal; rows the table does not flag translate by their alleles. Alleles that became the GRCh38 reference abstain. Pre-built cache downloaded via `db update` (~4.9M rows covering ~3.0M rsIDs, from dbSNP b157). |
108
112
 
109
113
  ### CADD modes
110
114
 
@@ -115,26 +119,26 @@ CADD ranks how deleterious any single-nucleotide variant is using 100+ annotatio
115
119
  Two modes:
116
120
 
117
121
  - **Cache mode** (default): pre-built ~5.8 GB SQLite cache (~120M GRCh38 variant keys). Covers nearly every GRCh38 position allelix can annotate from its other databases (gnomAD, AlphaMissense, ClinVar). GRCh37 input is scored only after translation through the rsID identity table; GRCh36 input never is.
118
- - **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.
122
+ - **Full mode** (`options.cadd_full = true`): tabix queries against the complete CADD v1.7 file via pysam (GRCh38 only). It scores variants the cache lacks, such as novel or private variants in WGS/WES data. CADD adds scores to findings from the other sources; it never makes a finding of its own, so a variant no other source reports gains nothing. Requires `pip install allelix[cadd]` for the pysam dependency, and CADD's files, downloaded separately from [cadd.gs.washington.edu/download](https://cadd.gs.washington.edu/download) (`db update --cadd` installs only the SQLite cache): `whole_genome_SNVs.tsv.gz` (about 81 GB) and its `.tbi` index in the data directory, plus, optionally for indels, `gnomad.genomes.r4.0.indel.tsv.gz` (about 1.2 GB) and its `.tbi`.
119
123
 
120
124
  If your input is a genotyping chip file, cache mode is normally all you need. GRCh37 chip files are scored through the rsID identity table; GRCh36 files are not scored.
121
125
 
122
126
  ### Build coverage asymmetry (GRCh37 vs GRCh38)
123
127
 
124
- ClinVar dispatches per build and ships with both GRCh37 and GRCh38 caches. gnomAD v4.1 exomes, AlphaMissense, and CADD v1.7 are GRCh38 datasets. Every enrichment lookup is made at GRCh38 coordinates by chromosome, position, reference and alternate allele, never by rsID. For GRCh37 input, the cross-build rsID identity table (ADR-0040) translates the allele the user carries at its GRCh37 position into the matching GRCh38 allele, written explicitly on the GRCh38 forward strand where the reference strand was inverted between assemblies. Palindromic sites (A/T, C/G, or all four bases in dbSNP, whose strand cannot be told from sequence) and alleles that are the GRCh38 reference receive no values rather than a guess. Without the identity table installed, GRCh37 analyses receive no gnomAD, AlphaMissense or CADD values; GRCh36 analyses never do.
128
+ ClinVar dispatches per build and ships with both GRCh37 and GRCh38 caches. gnomAD v4.1 exomes, AlphaMissense, and CADD v1.7 are GRCh38 datasets. Every enrichment lookup is made at GRCh38 coordinates by chromosome, position, reference and alternate allele, never by rsID. For GRCh37 input, the cross-build rsID identity table (ADR-0040) translates the allele the user carries at its GRCh37 position into the matching GRCh38 allele, written explicitly on the GRCh38 forward strand where the reference strand was inverted between assemblies. A row the identity table flags palindromic (A/T, C/G, or all four bases in dbSNP, whose strand cannot be told from sequence) whose reference base differs between the builds, and an allele that is the GRCh38 reference, receive no values rather than a guess. Without the identity table installed, GRCh37 analyses receive no gnomAD, AlphaMissense or CADD values; GRCh36 analyses never do.
125
129
 
126
130
  Position-based rsID recovery for rsID-less VCFs uses ClinVar (on the selected build), the identity table (on GRCh37 and GRCh38), and gnomAD (GRCh38 only). The same recovery step runs for `analyze`, `stats`, `extract` and `export plink`, so high-value warnings, extracted genotypes and PLINK allele coding all see the recovered rsIDs (ADR-0043). The identity table covers the rsIDs the annotators hold, so an rsID-less GRCh37 VCF now recovers ClinPGx, GWAS Catalog and SNPedia rsIDs as well as ClinVar ones. If you convert between assemblies, re-normalize REF/ALT as part of the conversion and pass `--build` explicitly when the file does not carry a trustworthy build signal.
127
131
 
128
- ### Known ClinPGx limitation: reference-genotype rows where ClinVar and CPIC both lack data
132
+ ### Known ClinPGx limitation: normal-genotype rows without usable normal-allele evidence
129
133
 
130
- ADR-0022 + ADR-0023: a tiny residual of ClinPGx rows may appear in reports even when the user is homozygous reference. ClinPGx publishes one annotation per genotype including the reference homozygote, and for the reference-homozygote row to be suppressed Allelix needs structured data on the variant from either:
134
+ ADR-0022 + ADR-0023: a tiny residual of ClinPGx rows may appear in reports even when the user carries the normal genotype. ClinPGx publishes one annotation per genotype including the normal homozygote, and for that row to be suppressed Allelix needs usable evidence of the normal allele from one of:
131
135
 
132
- - **ClinVar's REF allele** (the primary filter — see ADR-0023). Covers any rsID ClinVar catalogs.
136
+ - **ClinVar's normal allele** (the primary filter — see ADR-0023): usually its single-base REF, or the other build's REF where this build's reference carries ClinVar's variant allele (F5 rs6025 on GRCh37). Covers an rsID that ClinVar for the input's build places at the call's position. Without that cache, only a site whose reference differs between the builds is decided (by the other build's ClinVar, else gnomAD's major allele), and anything else shows.
133
137
  - **CPIC's per-allele function table** (the secondary fallback — see ADR-0020). Covers rsIDs CPIC has classified.
134
138
 
135
- For the rare rsID where ClinPGx has an annotation but *neither* ClinVar nor CPIC has data, the row emits. These are identifiable by a homozygous-reference genotype combined with "decreased risk," "may have a typical response," or similar comparative language. They are an upstream data gap, not an Allelix bug — we surface them honestly rather than hide them behind a curated exclusion list (which would recreate the maintenance trap the v0.5–v0.7 prose filters were trying to escape).
139
+ So without ClinVar or CPIC data, gnomAD can still decide a site whose reference differs between the builds, at that placement only. For the rare rsID where ClinPGx has an annotation and none of these gives a usable normal allele (no ClinVar or CPIC data, and no decision from the other build's ClinVar or gnomAD, which abstains on a tie, an unknown frequency or an unlisted site), the row emits: Allelix shows it rather than guess. These are identifiable by a homozygous-reference genotype combined with "decreased risk," "may have a typical response," or similar comparative language. They are an upstream data gap, not an Allelix bug — we surface them honestly rather than hide them behind a curated exclusion list (which would recreate the maintenance trap the v0.5–v0.7 prose filters were trying to escape).
136
140
 
137
- The CFTR × ivacaftor leak (~30+ rows on real data, pre-v0.7.3) is fixed by the ADR-0023 ClinVar REF check: CPIC's CFTR vocabulary (`"ivacaftor responsive"`) doesn't match the four-class enum the secondary tier expects, but ClinVar publishes REF for every CFTR rsID, so the primary tier catches them universally.
141
+ The CFTR × ivacaftor leak (~30+ rows on real data, pre-v0.7.3) is fixed by the ADR-0023 ClinVar REF check: CPIC's CFTR vocabulary (`"ivacaftor responsive"`) doesn't match the four-class enum the secondary tier expects, but ClinVar publishes REF for the CFTR rsIDs, so with ClinVar for the input's build installed the primary tier catches them.
138
142
 
139
143
  ### Known ClinVar upstream data quality issues
140
144
 
@@ -212,7 +216,7 @@ The HTML report shows a ⚠ icon next to a flagged genotype, with the reason on
212
216
 
213
217
  A call is not assessed when gnomAD has no frequency for the allele. Allelix's gnomAD cache covers exome sites with an rsID, so a missing value says nothing about rarity. Calls are also not assessed when the gnomAD database is skipped. Homozygous calls on X are not assessed because a male's single X reads as homozygous; heterozygous X calls are. A call the file itself declares as one copy (a one-letter array call such as 23andMe's male X calls, or a one-index VCF genotype) is shown as Hemizygous and assessed as one copy. Y and mitochondrial calls are not assessed. Confirm a flagged call with a clinical-grade test before relying on it.
214
218
 
215
- When three or more variants on chromosomes 1 to 22 are called homozygous for an allele ClinVar classifies as Pathogenic or Likely pathogenic, and are rare in gnomAD or have no gnomAD frequency, the report warns at the top. This is counted before any display filter, so `--min-magnitude` cannot hide it (ADR-0050).
219
+ When three or more variants on chromosomes 1 to 22 are called homozygous for an allele ClinVar classifies as Pathogenic or Likely pathogenic, and are rare in gnomAD or were looked up there and are absent, the report warns at the top. A call gnomAD was not asked about (gnomAD not installed, or a GRCh37 allele the identity table cannot translate) does not count. This is counted before any display filter, so `--min-magnitude` cannot hide it (ADR-0050).
216
220
 
217
221
  ### Database sizes and download times
218
222
 
@@ -243,6 +247,7 @@ Allelix keeps its caches in `~/.local/share/allelix/`, or `$XDG_DATA_HOME/alleli
243
247
  ├── cadd.sqlite ~5.8 GB (only if you opted into --cadd)
244
248
  ├── clinvar.GRCh37.sqlite ~1.2 GB
245
249
  ├── clinvar.GRCh38.sqlite ~1.2 GB
250
+ ├── clinvar-build.log each ClinVar build's source hashes and counts
246
251
  ├── gwas_catalog_associations.tsv ~745 MB
247
252
  ├── rsid_identity.sqlite ~457 MB
248
253
  ├── gwas.sqlite ~202 MB
@@ -281,7 +286,7 @@ du -sh "$ALLELIX_DATA"
281
286
  allelix db path --check
282
287
  ```
283
288
 
284
- **The one thing worth backing up: `config.toml`.** It lives inside the resolved data directory (typically `~/.local/share/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` always preserves it.
289
+ **The one thing worth backing up: `config.toml`.** It lives inside the resolved data directory (typically `~/.local/share/allelix/config.toml`) and stores your license assertions (commercial-mode toggle, CADD opt-in confirmation) and per-source enable/disable settings. The caches will rebuild on the next `db update`; `config.toml` won't. `allelix db clean` always preserves it. `clinvar-build.log` (the source hashes and counts of past ClinVar builds) is not reproducible either, and `db clean` removes it: copy it elsewhere first if you want to keep that history.
285
290
 
286
291
  To fully uninstall allelix and reclaim everything:
287
292
 
@@ -380,17 +385,41 @@ The banner says where the build came from, for example `Build: GRCh38 (from the
380
385
  - a VCF has no contig lengths, no assembly and too few known-SNP positions;
381
386
  - a chip states no build and carries none of the known-SNP positions at all (for example an X-chromosome-only export, since they are all on autosomes);
382
387
  - a file states a build but carries none of the known-SNP positions to check it (for example an mtDNA-only export, or a small panel VCF without contig lengths);
383
- - the file has known-SNP positions but none of them sits at any supported build's coordinate, whether or not it states a build (contig lengths, when a VCF declares them, still decide);
388
+ - the file has known-SNP positions but none of them sits at any supported build's coordinate, whether or not it states a build (contig lengths, when a VCF declares them, still decide). Choosing a build is not the fix here: the message says the positions cannot be trusted on any build and asks you to check that this is the original file from the testing company. If you force a build anyway with `--build`, the run goes ahead but prints a **Build warning** naming the evidence (0 of N known-SNP positions match), and every report keeps it (JSON `report_warnings` code `build_override_unmatched`);
384
389
  - contig lengths decided, but the known-SNP positions met while annotating point to the other build (for example a lifted-over file that kept its old header). The message says the annotation pass ran; nothing is written;
385
390
  - `--build grch37` or `--build grch38` was given, but the file's contig lengths, or three or more known-SNP positions with an 80% majority, say the other build. The message names both builds. Contrary positions that fall short of that (fewer than three, or under an 80% majority) only print a **Build warning**, and so does a tie that leaves out the forced build. `--build grch36` is never checked, because it turns position-keyed lookups off.
386
391
 
387
- The message names the evidence and asks you to say which build the file uses: `--build grch37` or `--build grch38`, or `--build grch36` for an old GRCh36 / hg18 file, which then runs with position-keyed lookups off (see [`docs/grch36-liftover.md`](https://github.com/allelix/allelix/blob/main/docs/grch36-liftover.md)). For `compare`, use `--build1` / `--build2`. A stop leaves no report, annotated VCF, diff, PLINK files or extract table behind. A forced build is recorded as such in every report.
392
+ Otherwise the message names the evidence and asks you to say which build the file uses: `--build grch37` or `--build grch38`, or `--build grch36` for an old GRCh36 / hg18 file, which then runs with position-keyed lookups off (see [`docs/grch36-liftover.md`](https://github.com/allelix/allelix/blob/main/docs/grch36-liftover.md)). For `compare`, use `--build1` / `--build2`. A stop leaves no report, annotated VCF, diff, PLINK files or extract table behind. A forced build is recorded as such in every report.
393
+
394
+ ### Keeping input and output files safe
395
+
396
+ Report `--output`, annotated `--vcf-out` and all three actual PLINK output paths
397
+ must differ from the input, including symlink and hard-link aliases. These
398
+ checks run before cache setup or output creation. Inputs that alias the former
399
+ fixed report or PLINK `.tmp` path are also refused; unrelated old temp files
400
+ are left untouched. Annotated VCF still refuses any existing output at the CLI.
401
+ Two outputs of one run must name different files too: `--output` and
402
+ `--vcf-out`, and PLINK's `.bed`, `.bim` and `.fam`, are refused before any work
403
+ when they resolve to one file (another spelling, a symlink, a linked folder or a
404
+ hardlink).
405
+
406
+ Writers use unique temporary files in the destination directory and publish
407
+ only complete, closed content. Failure before publication preserves previous
408
+ outputs. PLINK stages all three files first, then publishes each atomically;
409
+ a later publication failure can leave complete new files next to unchanged old
410
+ ones. Existing ordinary permission bits are retained when replacement is
411
+ permitted; new staging files start private, and a new output file gets the mode
412
+ your umask gives any new file (`0666 & ~umask`, so `0644` under `022`).
413
+ Ownership, ACLs, extended attributes,
414
+ crash durability and concurrent path swaps are outside this guarantee.
388
415
 
389
416
  ### Is my database stale? How fresh is the data?
390
417
 
391
418
  `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.
392
419
 
393
- If you want to force a refresh: `allelix db update --force` re-downloads every cache regardless of freshness signal.
420
+ If you want to force a refresh: `allelix db update --force` re-downloads every cache regardless of freshness signal. ClinVar is never refreshed unverified: its freshness signal is NCBI's checksum for the submission summary, which also verifies the download. If that checksum cannot be fetched (after one retry), ClinVar is not refreshed, with or without `--force`; the existing caches are kept, and a later `db update` tries again. The GWAS Catalog and ClinPGx publish no checksum, so `--force` refreshes them even when their freshness signal cannot be fetched; it stores no signal, and the next ordinary `db update` refreshes them once more when the signal is available again.
421
+
422
+ A ClinPGx cache built while CPIC's allele-function data could not be fetched is marked as such: `db status` shows "(no CPIC)", and the next `db update` tries CPIC again. A cache from 2.5.0 that claims CPIC data but holds none is repaired the same way by an ordinary `db update`.
394
423
 
395
424
  ### Output reports contain real genome annotations
396
425
 
@@ -418,13 +447,13 @@ Notable load-bearing ADRs:
418
447
  - **ADR-0042 — VCF quality gate.** The variant caller's FILTER and genotype quality are honoured, low-quality reference blocks are no-calls, and every exclusion is counted in the report.
419
448
  - **ADR-0043 — One rsID-recovery step.** Every command that needs rsIDs recovers them for rsID-less input through the same shared step; build detection votes by position instead.
420
449
  - **ADR-0044 — Evidence before labels.** The build is decided before annotation from contig lengths or whole-file known-SNP positions; evidence that contradicts the label, or no usable evidence, stops the run instead of producing a report on the wrong build.
421
- - **ADR-0045: ClinVar's own classification.** A ClinVar row is capped by ClinVar's aggregate classification of the variant (Conflicting, Benign, Likely benign), with the submitter split shown; outranked P/LP submissions are kept by ClinVar's tiered rule where the aggregate is not a disease call.
450
+ - **ADR-0045: ClinVar's own classification.** A ClinVar row is capped by ClinVar's aggregate classification of the variant (Conflicting, Benign, Likely benign), with the submitter split shown, and the variants scored down are named on the console, in HTML and in JSON (`clinvar.scored_down_variants`); outranked P/LP submissions are kept by ClinVar's tiered rule where the aggregate is not a disease call.
422
451
  - **ADR-0046: ClinVar variants without an rsID.** Kept in the cache and matched for VCF input only, by locus and the file's REF; reports name such a finding by `chrom:pos ref>alt`.
423
- - **ADR-0047: VCF calls no longer dropped silently.** Untrimmed alleles are trimmed per carried allele, half calls are assessed by the sources that need only a carried allele, VCF homozygous deletions to one base match ClinVar, and dropped calls are counted with their reason (a genotype on a gVCF reference block's own `<NON_REF>` is not checked).
452
+ - **ADR-0047: VCF calls no longer dropped silently.** Untrimmed alleles are trimmed per carried allele (by every annotator since 2.5.2), half calls are assessed by the sources that need only a carried allele, VCF homozygous deletions to one base match ClinVar, and dropped calls are counted with their reason (a genotype on a gVCF reference block's own `<NON_REF>` is not checked).
424
453
  - **ADR-0048: Caches are read-only at analyze time.** With `--no-update`, `analyze` writes nothing to the data directory; every cache opens read-only, installs in rollback-journal mode, and a skipped source is named with its reason (missing, outdated, unreadable, damaged, or WAL mode from an older Allelix). Local migrations run in the update step.
425
454
  - **ADR-0049: Every report names its sources.** JSON `sources` gives every registered source's part in the run (used, ready but not used, or skipped with a reason); `report_warnings` carries report-level warnings, starting with ClinVar not used; the annotated VCF names skipped sources.
426
455
  - **ADR-0050: Zygosity codes and haploid calls.** JSON zygosity and plausibility are codes, a call the file declares haploid is shown as hemizygous, "not assessed" carries a reason, and three rare homozygous P/LP calls give a report warning.
427
- - **ADR-0051: Array calls ClinVar cannot match are counted.** Vendor probes at P/LP loci and `D`/`I` calls are counted in the report, and panel coverage puts each requested rsID in one of four states.
456
+ - **ADR-0051: Array calls ClinVar cannot match are counted.** Vendor probes at P/LP loci and `D`/`I` calls are counted in the report; since 2.5.2, a `D`/`I` call at a high-value or P/LP rsID is also named as not checked (JSON `high_value_unchecked` and `report_warnings` code `di_calls_not_checked`), and panel coverage puts each requested rsID in one of four states.
428
457
  - **ADR-0052: Zero-star P/LP on a common allele.** A ClinVar P/LP row without assertion criteria whose allele has a gnomAD frequency of 5% or more is scored down, marked and counted.
429
458
 
430
459
  Release history: see [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/main/CHANGELOG.md).
@@ -433,19 +462,20 @@ Release history: see [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/ma
433
462
 
434
463
  ```bash
435
464
  source .venv/bin/activate
436
- pip install -e ".[dev]"
465
+ pip install -e ".[dev,vcf-index]" # the CI install: vcf-index (pysam) runs the indexed-VCF tests
437
466
 
438
467
  # One-time: install pre-commit hooks
439
468
  pre-commit install --hook-type pre-commit
440
469
 
441
470
  ruff check .
442
471
  ruff format --check .
443
- pytest
472
+ pytest -m "not slow and not integration" # the fast suite CI runs
473
+ pytest # every test, including slow and integration
444
474
  ```
445
475
 
446
476
  The pre-commit hook enforces `ruff check` + `ruff format --check`. If a commit is blocked, fix the underlying problem rather than skipping the hook.
447
477
 
448
- 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`](https://github.com/allelix/allelix/blob/main/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.
478
+ 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`](https://github.com/allelix/allelix/blob/main/test_data/FULL_TEST_PROTOCOL.md). That document is the project's real-data testing story; `pytest -m "not slow and not integration"` is the fast suite that gates every commit, as in CI.
449
479
 
450
480
  ## License
451
481