allelix 2.5.1__tar.gz → 2.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. {allelix-2.5.1/allelix.egg-info → allelix-2.6.0}/PKG-INFO +83 -54
  2. {allelix-2.5.1 → allelix-2.6.0}/README.md +79 -50
  3. allelix-2.6.0/allelix/annotators/__init__.py +191 -0
  4. allelix-2.6.0/allelix/annotators/_placement.py +153 -0
  5. {allelix-2.5.1 → allelix-2.6.0}/allelix/annotators/_resolution.py +77 -1
  6. {allelix-2.5.1 → allelix-2.6.0}/allelix/annotators/alphamissense.py +12 -12
  7. {allelix-2.5.1 → allelix-2.6.0}/allelix/annotators/base.py +27 -3
  8. {allelix-2.5.1 → allelix-2.6.0}/allelix/annotators/cadd.py +12 -14
  9. {allelix-2.5.1 → allelix-2.6.0}/allelix/annotators/clinvar.py +115 -18
  10. {allelix-2.5.1 → allelix-2.6.0}/allelix/annotators/gnomad.py +38 -14
  11. {allelix-2.5.1 → allelix-2.6.0}/allelix/annotators/gwas.py +88 -44
  12. {allelix-2.5.1 → allelix-2.6.0}/allelix/annotators/pharmgkb.py +120 -41
  13. allelix-2.6.0/allelix/annotators/reference_fallback.py +238 -0
  14. {allelix-2.5.1 → allelix-2.6.0}/allelix/annotators/rsid_identity.py +155 -39
  15. {allelix-2.5.1 → allelix-2.6.0}/allelix/annotators/snpedia.py +58 -45
  16. allelix-2.6.0/allelix/attribution.py +99 -0
  17. {allelix-2.5.1 → allelix-2.6.0}/allelix/cli/__init__.py +11 -3
  18. {allelix-2.5.1 → allelix-2.6.0}/allelix/cli/_helpers.py +267 -44
  19. {allelix-2.5.1 → allelix-2.6.0}/allelix/cli/_options.py +99 -28
  20. {allelix-2.5.1 → allelix-2.6.0}/allelix/cli/analyze.py +1 -0
  21. {allelix-2.5.1 → allelix-2.6.0}/allelix/cli/config.py +17 -21
  22. {allelix-2.5.1 → allelix-2.6.0}/allelix/cli/db.py +103 -25
  23. {allelix-2.5.1 → allelix-2.6.0}/allelix/cli/utility.py +506 -104
  24. allelix-2.6.0/allelix/compare.py +328 -0
  25. {allelix-2.5.1 → allelix-2.6.0}/allelix/config.py +30 -6
  26. {allelix-2.5.1 → allelix-2.6.0}/allelix/data/high_value_snps.yaml +6 -0
  27. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/__init__.py +12 -2
  28. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/gwas_loader.py +50 -18
  29. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/loader_utils.py +24 -23
  30. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/manager.py +362 -89
  31. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/pharmgkb_loader.py +61 -20
  32. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/rsid_identity_loader.py +35 -27
  33. allelix-2.6.0/allelix/databases/staging.py +72 -0
  34. {allelix-2.5.1 → allelix-2.6.0}/allelix/exporters/plink.py +133 -21
  35. {allelix-2.5.1 → allelix-2.6.0}/allelix/models.py +27 -1
  36. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/__init__.py +4 -0
  37. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/_helpers.py +56 -1
  38. allelix-2.6.0/allelix/parsers/rsidlist.py +158 -0
  39. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/twentythreeandme.py +37 -10
  40. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/vcf.py +659 -36
  41. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/__init__.py +105 -6
  42. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/_pipeline.py +960 -97
  43. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/association_text.py +2 -0
  44. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/build_text.py +30 -1
  45. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/clinvar_text.py +50 -0
  46. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/diff.py +107 -46
  47. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/high_value.py +96 -0
  48. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/html.py +66 -8
  49. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/json_report.py +54 -6
  50. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/terminal.py +56 -30
  51. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/vcf.py +175 -43
  52. {allelix-2.5.1 → allelix-2.6.0}/allelix/utils/allele.py +28 -11
  53. {allelix-2.5.1 → allelix-2.6.0}/allelix/utils/build_detect.py +57 -9
  54. {allelix-2.5.1 → allelix-2.6.0/allelix.egg-info}/PKG-INFO +83 -54
  55. {allelix-2.5.1 → allelix-2.6.0}/allelix.egg-info/SOURCES.txt +5 -0
  56. {allelix-2.5.1 → allelix-2.6.0}/allelix.egg-info/requires.txt +1 -0
  57. {allelix-2.5.1 → allelix-2.6.0}/pyproject.toml +9 -9
  58. allelix-2.5.1/allelix/annotators/__init__.py +0 -111
  59. allelix-2.5.1/allelix/compare.py +0 -215
  60. {allelix-2.5.1 → allelix-2.6.0}/LICENSE +0 -0
  61. {allelix-2.5.1 → allelix-2.6.0}/MANIFEST.in +0 -0
  62. {allelix-2.5.1 → allelix-2.6.0}/allelix/__init__.py +0 -0
  63. {allelix-2.5.1 → allelix-2.6.0}/allelix/cli/focused.py +0 -0
  64. {allelix-2.5.1 → allelix-2.6.0}/allelix/data/__init__.py +0 -0
  65. {allelix-2.5.1 → allelix-2.6.0}/allelix/data/clinvar_clnsig_snapshot.yaml +0 -0
  66. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/_versions.py +0 -0
  67. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/alphamissense_loader.py +0 -0
  68. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/cache_state.py +0 -0
  69. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/cadd_loader.py +0 -0
  70. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/cpic_loader.py +0 -0
  71. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/gnomad_loader.py +0 -0
  72. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/schema.py +0 -0
  73. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/snpedia_loader.py +0 -0
  74. {allelix-2.5.1 → allelix-2.6.0}/allelix/databases/snpedia_parser.py +0 -0
  75. {allelix-2.5.1 → allelix-2.6.0}/allelix/exporters/__init__.py +0 -0
  76. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/ancestrydna.py +0 -0
  77. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/base.py +0 -0
  78. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/decodeme.py +0 -0
  79. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/ftdna.py +0 -0
  80. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/ftdna_famfinder.py +0 -0
  81. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/ftdna_illumina.py +0 -0
  82. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/genesforgood.py +0 -0
  83. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/livingdna.py +0 -0
  84. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/myhappygenes.py +0 -0
  85. {allelix-2.5.1 → allelix-2.6.0}/allelix/parsers/myheritage.py +0 -0
  86. {allelix-2.5.1 → allelix-2.6.0}/allelix/py.typed +0 -0
  87. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/_sources.py +0 -0
  88. {allelix-2.5.1 → allelix-2.6.0}/allelix/reports/methylation.py +0 -0
  89. {allelix-2.5.1 → allelix-2.6.0}/allelix/utils/__init__.py +0 -0
  90. {allelix-2.5.1 → allelix-2.6.0}/allelix/utils/plausibility.py +0 -0
  91. {allelix-2.5.1 → allelix-2.6.0}/allelix.egg-info/dependency_links.txt +0 -0
  92. {allelix-2.5.1 → allelix-2.6.0}/allelix.egg-info/entry_points.txt +0 -0
  93. {allelix-2.5.1 → allelix-2.6.0}/allelix.egg-info/top_level.txt +0 -0
  94. {allelix-2.5.1 → allelix-2.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: allelix
3
- Version: 2.5.1
3
+ Version: 2.6.0
4
4
  Summary: Open-source genotype analysis toolkit. Format-agnostic ingestion, database-agnostic annotation, offline-first.
5
5
  Author: Allelix
6
6
  Maintainer-email: dial481 <dial481@users.noreply.github.com>
@@ -14,11 +14,10 @@ Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Intended Audience :: Science/Research
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
20
19
  Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
21
- Requires-Python: >=3.11
20
+ Requires-Python: >=3.12
22
21
  Description-Content-Type: text/markdown
23
22
  License-File: LICENSE
24
23
  Requires-Dist: click>=8.2
@@ -34,6 +33,7 @@ Requires-Dist: mypy>=1.11; extra == "dev"
34
33
  Requires-Dist: pre-commit>=3.7; extra == "dev"
35
34
  Requires-Dist: pytest>=8.0; extra == "dev"
36
35
  Requires-Dist: pytest-cov<8,>=7; extra == "dev"
36
+ Requires-Dist: pytest-xdist>=3.6; extra == "dev"
37
37
  Requires-Dist: ruff>=0.6; extra == "dev"
38
38
  Requires-Dist: types-PyYAML>=6.0; extra == "dev"
39
39
  Dynamic: license-file
@@ -56,11 +56,13 @@ Dynamic: license-file
56
56
 
57
57
  # Allelix
58
58
 
59
- > **Status:** Production. Reads eleven file formats, including VCF and gVCF. Reports what ClinVar, ClinPGx, the GWAS Catalog and SNPedia say about your variants, with frequencies and scores from gnomAD, AlphaMissense and CADD. It decides the genome build from the file itself and stops rather than guess. HTML, JSON, terminal or annotated VCF. Everything runs on your machine. The [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) tracks every release.
59
+ > **Status:** Production. Reads twelve file formats, including VCF and gVCF and lists of rsIDs and genotypes. Reports what ClinVar, ClinPGx, the GWAS Catalog and SNPedia say about your variants, with frequencies and scores from gnomAD, AlphaMissense and CADD. It decides the genome build from the file itself and stops rather than guess. HTML, JSON, terminal or annotated VCF. Everything runs on your machine. The [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) tracks every release.
60
60
 
61
61
  ## Quickstart
62
62
 
63
- **Requires Python 3.11 or newer.** On Python 3.10 or older pip cannot install this release (it may fall back to an old one, which then fails with `ImportError: cannot import name 'UTC' from 'datetime'`); install on 3.11+. Check with `python --version`.
63
+ **Requires Python 3.12 or newer** (tested on 3.12 and 3.14). On Python 3.11 or older pip cannot install this release (it may fall back to an old one, which on 3.10 or older then fails with `ImportError: cannot import name 'UTC' from 'datetime'`); install on 3.12+. Check with `python --version`.
64
+
65
+ On Python 3.14 or newer, pip builds the SNPedia parser dependency (mwparserfromhell) from source, which needs a C compiler until that package publishes wheels for these versions. Without one, install with `WITH_EXTENSION=0 pip install allelix` to use its pure-Python parser.
64
66
 
65
67
  ```bash
66
68
  pip install allelix
@@ -80,8 +82,8 @@ allelix analyze your_wgs.vcf.gz --output report.html
80
82
  # Multi-sample VCF — pick which sample to analyze
81
83
  allelix analyze trio.vcf.gz --sample HG002 --output report.html
82
84
 
83
- # VCF quality gate: rows the variant caller filtered (FILTER not PASS) are
84
- # skipped and counted; calls with GQ below 20 or no reads become no-calls.
85
+ # VCF quality gate: rows the variant caller filtered (FILTER, or the sample's
86
+ # FORMAT FT, neither PASS nor '.') are skipped and counted; calls with GQ below 20 or no reads become no-calls.
85
87
  allelix analyze your_wgs.vcf.gz --keep-filtered --min-gq 10 --output report.html
86
88
 
87
89
  # Filter to a custom panel (rsIDs and gene names, one per line; a # starts a comment, on its own line or after an entry; blank lines are ignored)
@@ -105,32 +107,36 @@ See [Development](#development) for source installs and running tests, [Managing
105
107
  | Format | Status | Notes |
106
108
  |---|---|---|
107
109
  | 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). |
108
- | 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). |
110
+ | 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 are passed through but not matched against ClinVar, and `D`/`I` indel calls (mixed calls such as `AD` too) are matched by no source (ADR-0070); the report counts both (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. Such calls are never interpreted, also at known variants such as BRCA1 5382insC, BRCA2 6174delT and CFTR F508del; carrier status for these needs clinical testing ([docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#d-and-i-calls)). |
109
111
  | 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). |
112
+ | rsID and genotype list | ✓ | Two tab-separated columns, an rsID and a call, with no chromosome, position or build (IYG files, for one). Detected strictly: every one of the first 50 data lines is `ID<TAB>call`, and at least three of their IDs, and at least 80%, are rsIDs. Each row is placed on GRCh38 from the rsID identity table when exactly one strand reading of its call fits the site, and is otherwise counted by reason and kept without a locus for ClinPGx, SNPedia and the GWAS Catalog (ADR-0069). Needs the identity table; only `analyze`, `methylation` and `pharmacogenomics` read it. See [docs/formats.md](https://github.com/allelix/allelix/blob/main/docs/formats.md#rsid-and-genotype-lists). |
110
113
  | AncestryDNA | ✓ | Tab-delimited, 5 columns. Chromosome mapping: 23→X, 24→Y, 25→X (PAR), 26→MT. V1 and V2 chip layouts. |
111
- | 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). |
114
+ | 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). |
112
115
  | 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). |
113
116
  | Living DNA | ✓ | Tab-delimited despite `.csv` extension. Handles AX-, AFFX-prefixed and CHR:POS positional SNP IDs. |
114
117
  | 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). |
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. |
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. |
117
- | 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. |
118
+ | 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)). |
119
+ | 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)). |
120
+ | VCF / gVCF | ✓ | REF/ALT encoding, `0/1` genotype notation. A confident homozygous-reference call (`0/0`, `0|0`, haploid `0`) is read by ClinPGx, SNPedia and the GWAS Catalog as the same chip call would be (ADR-0064). A gVCF reference block (lines with `<NON_REF>` ALT and `END=` INFO) is not expanded for them. For ClinVar, a reference call matches only 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 `.`, or a selected sample whose FORMAT `FT` is not `PASS` or `.`) are skipped and counted in the report under each filter code; `--keep-filtered` keeps them (ADR-0060). 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. |
118
121
 
119
122
  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.
120
123
 
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; 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)).
124
+ 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)).
122
125
 
123
126
  Adding a new format means adding one file to `allelix/parsers/` and registering an instance in the `PARSERS` list in `allelix/parsers/__init__.py`.
124
127
 
125
128
  ### Roadmap
126
129
 
130
+ Shipped releases are listed for history; the [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) has each release's changes.
131
+
127
132
  | Release | Theme |
128
133
  |---|---|
129
- | **v2.2** | Custom-panel UX improvements, methylation panel retune + citations, ClinVar source switch (VCF → `variant_summary.txt`), CLI cleanups (`db clean` / `db path`). |
130
- | **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. |
131
- | **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. |
132
- | **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. |
133
- | **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. |
134
+ | **v2.2** (shipped) | Custom-panel UX improvements, methylation panel retune + citations, ClinVar source switch (VCF → `variant_summary.txt`), CLI cleanups (`db clean` / `db path`). |
135
+ | **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. |
136
+ | **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. |
137
+ | **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. |
138
+ | **v2.6** | Validated calls: the sample's FT filter, padded calls without an rsID, one record per finding, identity contradictions, explicit reference calls for every genotype source, both strand readings together; a switched-off source fully off; rsID and genotype lists; Python 3.12 and newer. |
139
+ | **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. |
134
140
 
135
141
  Issues and milestones are tracked at [github.com/allelix/allelix/issues](https://github.com/allelix/allelix/issues).
136
142
 
@@ -138,15 +144,17 @@ Issues and milestones are tracked at [github.com/allelix/allelix/issues](https:/
138
144
 
139
145
  | Database | Status | Notes |
140
146
  |---|---|---|
141
- | 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). |
142
- | 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. |
147
+ | 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 scored down by ClinVar's Conflicting or Benign classification, or as an outranked Pathogenic / Likely pathogenic submission (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). |
148
+ | 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, written on the input build's strand, 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 are decided: by the other build's ClinVar, written on the input build's strand, else by gnomAD's major allele. Where the strand relation between the builds cannot be told (F5 rs6025 is such a site), the other build's ClinVar is not used: a GRCh38 call is decided by gnomAD alone, and a GRCh37 call is not decided. gnomAD is GRCh38's own reading: a GRCh38 call can use its evidence as it is, and a GRCh37 call only written on its own strand. Where none of these can tell (no evidence, a tie, or, for a GRCh37 call, a strand relation between the builds that cannot be told), 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. |
143
149
  | 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. |
144
- | 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`. |
145
- | 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). |
146
- | 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. |
150
+ | 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 that states its orientation is kept wherever its genotype is among them, A/T and C/G sites included. A page without one is read on its own: as written when only that fits, complemented when only the complement fits. It abstains when both fit (an A/T or C/G site, or a site with more alleles) or neither does (ADR-0066). 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`. |
151
+ | 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. A risk allele is read as written when only that fits and complemented when only the complement fits. It abstains when both fit (an A/T or C/G site, or a site with more alleles), when neither fits, or when the table does not place the rsID at the call, and the report counts each abstention (ADR-0066). 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). |
152
+ | 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. `--no-gnomad` switches it off for the run (see below). |
147
153
  | 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. |
148
154
  | 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. |
149
- | 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). |
155
+ | 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). |
156
+
157
+ A single-base call whose rsID the identity table places only elsewhere on the analysis build (a stale rsID, or coordinates from another build) is given to no source, ClinPGx included, and the report counts it in a warning, `identity_locus_conflict`. Indels and chip `D`/`I` calls are not checked this way, and an rsID the table does not place is not affected (ADR-0063).
150
158
 
151
159
  ### CADD modes
152
160
 
@@ -157,26 +165,34 @@ CADD ranks how deleterious any single-nucleotide variant is using 100+ annotatio
157
165
  Two modes:
158
166
 
159
167
  - **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.
160
- - **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.
168
+ - **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`.
161
169
 
162
170
  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.
163
171
 
164
172
  ### Build coverage asymmetry (GRCh37 vs GRCh38)
165
173
 
166
- 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.
174
+ 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.
167
175
 
168
176
  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.
169
177
 
170
- ### Known ClinPGx limitation: reference-genotype rows where ClinVar and CPIC both lack data
178
+ ### Known ClinPGx limitation: normal-genotype rows without usable normal-allele evidence
171
179
 
172
- 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:
180
+ 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:
173
181
 
174
- - **ClinVar's REF allele** (the primary filter — see ADR-0023). Covers any rsID ClinVar catalogs.
182
+ - **ClinVar's normal allele** (the primary filter — see ADR-0023): usually its single-base REF, or the other build's REF, written on this build's strand, where this build's reference carries ClinVar's variant allele (F5 rs6025 on GRCh37). With only one ClinVar build installed, a GRCh37 rs6025 call is not decided: neither the identity table (with only GRCh37's ClinVar; it lists every base there) nor GRCh38's ClinVar or gnomAD (with only GRCh38's) can tell its strand, so a CC call's ClinPGx and SNPedia rows show. A GRCh38 call is decided by its own ClinVar where installed, else by gnomAD where its evidence is decisive; otherwise its CC rows show too. 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, written on the input build's strand; where the strand relation between the builds cannot be told, a GRCh37 call is not decided and a GRCh38 call asks gnomAD alone), and anything else shows.
175
183
  - **CPIC's per-allele function table** (the secondary fallback — see ADR-0020). Covers rsIDs CPIC has classified.
176
184
 
177
- 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).
185
+ So without ClinVar or CPIC data, gnomAD can still decide a site whose reference differs between the builds, at that placement only: for a GRCh38 call, or for a GRCh37 call where the strand relation between the builds is known. 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, and for a GRCh37 call on a strand relation between the builds that cannot be told), 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).
186
+
187
+ 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.
188
+
189
+ ### Why some Pathogenic ClinVar findings are scored down
178
190
 
179
- 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.
191
+ ClinVar publishes its own classification of each variant, made from all the submissions it counts. When submitters disagree, ClinVar calls the variant "Conflicting classifications of pathogenicity". Allelix follows that classification. A Pathogenic or Likely pathogenic submission for a Conflicting variant is scored down to 4.0, below the default report's floor, even when most submitters say Pathogenic. Under a Benign or Likely benign classification it is scored lower still. An outranked Pathogenic or Likely pathogenic submission (one ClinVar's review tiers rank below the classification) is also scored down to 4.0 where ClinVar's classification is "Likely risk allele" or "other", or where its own review tier holds other disease classifications (ADR-0045).
192
+
193
+ Allelix does not count votes to override ClinVar. There is no accepted standard for weighing submitters, and a single newer submission with stronger evidence can be right against many older ones. Allelix reports ClinVar; it does not classify variants.
194
+
195
+ The report lists every variant scored down this way, with its gene, rsID, submitter split ("40 of 41 submitters Pathogenic") and ClinVar's classification. The list is in the terminal output before the findings table, in the HTML report's ClinVar notice (the line above "Technical details" says how many ClinVar rows were scored down; a variant can have several), and in JSON `clinvar.scored_down_variants`. To see the rows themselves, use `--min-magnitude 0`. For a clinical question about one of them, check the variant in ClinVar and confirm it with clinical testing.
180
196
 
181
197
  ### Known ClinVar upstream data quality issues
182
198
 
@@ -196,7 +212,7 @@ This is not a disclaimer afterthought. It is a design constraint that affects mo
196
212
 
197
213
  - No data leaves your machine. No telemetry. No uploads. No analytics.
198
214
  - Reference databases are downloaded via `allelix db update` and cached locally.
199
- - Analysis runs offline against local database caches. A brief freshness check runs before analysis by default (skipped with `--no-update`).
215
+ - Analysis runs offline against local database caches. A brief freshness check of the switched-on sources runs before analysis by default (skipped with `--no-update`).
200
216
 
201
217
  ### Output files contain real annotations of your genome
202
218
 
@@ -238,7 +254,7 @@ allelix config set plausibility.homozygous_max_af 0.0001
238
254
  allelix config set plausibility.heterozygous_max_af 0
239
255
  ```
240
256
 
241
- CLI flags (`--no-gnomad`, `--no-alphamissense`, `--no-cadd`, `--exclude-snpedia`) turn a source off for a single run. The config sets the baseline; flags override per-invocation.
257
+ CLI flags (`--no-gnomad`, `--no-alphamissense`, `--no-cadd`, `--exclude-snpedia`) turn a source off for a single run. The config sets the baseline; flags override per-invocation. A source turned off is fully off: no finding comes from it, and no other source uses it either. For example, with ClinVar or gnomAD off, ClinPGx and SNPedia cannot use them to tell a normal allele. The report says what ran without it. `stats`, `extract` and `export plink` follow the config the same way.
242
258
 
243
259
  ### Plausibility flag
244
260
 
@@ -252,9 +268,9 @@ Genotyping arrays sometimes miscall very rare variants, most visibly as a homozy
252
268
 
253
269
  The HTML report shows a ⚠ icon next to a flagged genotype, with the reason on hover and in the detail panel, and a muted ○ with the reason when the call was not assessed. The terminal table marks implausible calls with ⚠, suspicious ones with ? and not-assessed ones with ○. JSON reports carry `plausibility` on every annotation (`plausible`, `suspicious`, `implausible` or `not_assessed`) and, when not assessed, `plausibility_reason` (`no_frequency`, `gnomad_not_run`, `chromosome`, `half_call` or `no_allele`; ADR-0050).
254
270
 
255
- 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.
271
+ 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. When records at one position disagree on ploidy (one copy in one record, two in another), each finding keeps its own record's genotype and zygosity, and the report counts them in a warning, `contradictory_calls` (ADR-0062). Confirm a flagged call with a clinical-grade test before relying on it.
256
272
 
257
- 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).
273
+ 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).
258
274
 
259
275
  ### Database sizes and download times
260
276
 
@@ -272,7 +288,7 @@ Not all databases are equal in size. `allelix db update` downloads them all by d
272
288
 
273
289
  gnomAD and AlphaMissense are the largest but add the most interpretive context. gnomAD answers "is this variant rare or common?" — a pathogenic variant carried by 35% of the population reads very differently from one seen in 3 people. AlphaMissense answers "does this missense change likely damage the protein?" — especially valuable for the thousands of variants ClinVar hasn't reviewed yet.
274
290
 
275
- To skip either during download: `allelix db update --no-gnomad --no-alphamissense`. To disable permanently: `allelix config set sources.gnomad false`.
291
+ To skip either during download: `allelix db update --no-gnomad --no-alphamissense`. To stop using one: `allelix config set sources.gnomad false`. Reports then neither use it nor refresh it, and no other source consults it (ADR-0067); an explicit `allelix db update` still downloads it unless you pass `--no-gnomad`.
276
292
 
277
293
  ### Managing your data
278
294
 
@@ -285,6 +301,7 @@ Allelix keeps its caches in `~/.local/share/allelix/`, or `$XDG_DATA_HOME/alleli
285
301
  ├── cadd.sqlite ~5.8 GB (only if you opted into --cadd)
286
302
  ├── clinvar.GRCh37.sqlite ~1.2 GB
287
303
  ├── clinvar.GRCh38.sqlite ~1.2 GB
304
+ ├── clinvar-build.log each ClinVar build's source hashes and counts
288
305
  ├── gwas_catalog_associations.tsv ~745 MB
289
306
  ├── rsid_identity.sqlite ~457 MB
290
307
  ├── gwas.sqlite ~202 MB
@@ -292,6 +309,8 @@ Allelix keeps its caches in `~/.local/share/allelix/`, or `$XDG_DATA_HOME/alleli
292
309
  └── pharmgkb.sqlite ~6 MB
293
310
  ```
294
311
 
312
+ Every cache is written beside its final name under a staging name of its own and moved into place only when complete, so a failed or interrupted update keeps the cache you had, and two updates running at once cannot install each other's files. A staging file left by a killed process (a hidden `.<name>…` file or directory) is never removed by another run; `db clean` removes it (with `--force` when no finished cache is left beside it, since `db clean` otherwise refuses a directory it does not recognise).
313
+
295
314
  **The caches are disposable and re-downloadable.** Everything here was fetched by `allelix db update` and can be re-fetched at any time. There's no per-user state in the cache — your genotype files never enter this directory. Safe to delete to reclaim space (or to force a full refresh if you suspect a cache went stale):
296
315
 
297
316
  ```bash
@@ -323,7 +342,7 @@ du -sh "$ALLELIX_DATA"
323
342
  allelix db path --check
324
343
  ```
325
344
 
326
- **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.
345
+ **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.
327
346
 
328
347
  To fully uninstall allelix and reclaim everything:
329
348
 
@@ -383,12 +402,14 @@ The v2.3.1 cache format also preserves each page's SNPedia orientation. Known mi
383
402
 
384
403
  ### `ImportError: cannot import name 'UTC' from 'datetime'`
385
404
 
386
- You're on Python 3.10 or older, where pip cannot install this release and may fall back to an old one. Allelix requires 3.11+. `python --version` to check; upgrade and reinstall.
405
+ You're on Python 3.10 or older, where pip cannot install this release and may fall back to an old one. Allelix requires 3.12+. `python --version` to check; upgrade and reinstall.
387
406
 
388
407
  ### `allelix db update` failed partway through
389
408
 
390
409
  Run it again. The update is idempotent — completed downloads are skipped (signal-matched against the upstream freshness probe), so re-running picks up where it left off. If a specific optional source keeps failing, use its `--no-*` flag to finish the other sources, then remove the skip flag and re-run the full update later. `--only clinvar` exists for the ClinVar-only CI and recovery path; other sources do not currently have standalone `--only` selectors.
391
410
 
411
+ If ClinVar is refused because the new release has fewer than 90% of the current cache's rows, the existing caches are kept. When the drop is expected, `allelix db update --allow-clinvar-shrink` accepts it for that one run; the fixed row floor still applies, and the old and new counts are printed and written to `clinvar-build.log` (ADR-0053).
412
+
392
413
  If the failure is a network timeout on the same source repeatedly, the issue is likely on the source's end (HuggingFace / NCBI / EBI / etc. transient outage). Wait an hour and retry.
393
414
 
394
415
  ### `db status` shows an annotator as "not ready" but the cache files exist on disk
@@ -398,7 +419,7 @@ If the failure is a network timeout on the same source repeatedly, the issue is
398
419
  - **out of date:** almost always a schema or interpreter version bump in a recent allelix release that invalidates the pre-bump cache. The fix is `allelix db update`; it re-ingests only the affected annotators (everything else signal-skips). See the relevant `CHANGELOG.md` entry for "Cache invalidation" notes per release.
399
420
  - **cannot read:** the file's permissions do not let you read it.
400
421
  - **damaged:** the file is not an SQLite database, lacks a table, or its main table has no rows; `allelix db update` replaces that source only (`--force` would download every source again).
401
- - **WAL mode from an older Allelix:** gnomAD, AlphaMissense and CADD caches installed by earlier releases are in WAL mode. Reading one leaves `-wal` and `-shm` files beside it, and in a directory you cannot write SQLite cannot read it at all, so `analyze --no-update` skips it. Run `allelix db update` once (with write access to the data directory); it converts them without downloading. `analyze` without `--no-update` converts them too.
422
+ - **WAL mode from an older Allelix:** gnomAD, AlphaMissense and CADD caches installed by earlier releases are in WAL mode. Reading one leaves `-wal` and `-shm` files beside it, and in a directory you cannot write SQLite cannot read it at all, so `analyze --no-update` skips it. Run `allelix db update` once (with write access to the data directory); it converts them without downloading. `analyze` without `--no-update` converts them too, except a source switched off for that run (ADR-0067).
402
423
 
403
424
  The report records the same reasons: the JSON `sources` block lists every source with its part in the run, and the HTML lists the sources that could not be used. A report without ClinVar carries a warning at the top (ADR-0049).
404
425
 
@@ -408,9 +429,9 @@ With `--no-update`, `analyze` writes nothing to the data directory, so a data di
408
429
 
409
430
  Before anything is annotated, Allelix decides the file's genome build from the evidence the file itself carries (ADR-0044):
410
431
 
411
- - **VCF contig lengths.** Every nuclear chromosome has a different length in GRCh37 and GRCh38, so the `##contig` lengths settle the build whatever the contig names look like (`chr1` or `1`). If chrM has the old hg19 length (16,571 bp), no mitochondrial record is annotated and a note says so, also when the build comes from `--build` or the file's assembly header. In a GRCh37 VCF, mitochondrial records named `chrM` or `M` (UCSC hg19 names) are left out, counted and noted, unless the file declares that name with the 16,569 bp reference length; `MT` and `chrMT` are read.
432
+ - **VCF contig lengths.** Every nuclear chromosome has a different length in GRCh37 and GRCh38, so the `##contig` lengths settle the build whatever the contig names look like (`chr1` or `1`). If chrM has the old hg19 length (16,571 bp), no mitochondrial record is annotated or compared and a note says so, also when the build comes from `--build` or the file's assembly header. In a GRCh37 VCF, mitochondrial records named `chrM` or `M` (UCSC hg19 names) are left out (of `compare` too), counted and noted, unless the file declares that name with the 16,569 bp reference length; `MT` and `chrMT` are read.
412
433
  - **Known-SNP positions across the whole file.** A set of well-known SNPs sits at different positions in each build. Allelix reads the whole file for them before annotating (a few seconds on a full chip). At least three concordant positions and an 80% majority make the evidence confident (ADR-0037). A VCF record without an rsID counts when it sits at such a position.
413
- - **The file's own label** (a build line in an array header, or a VCF `assembly=`) is used only when the evidence does not contradict it and at least one known-SNP position is there to check it.
434
+ - **The file's own label** (a build line in an array header, or a VCF `assembly=`) is used only when the evidence does not contradict it and at least one known-SNP position is there to check it. A chip labelled GRCh36 is the exception: its label is used without one, because GRCh36 turns position-keyed lookups off.
414
435
 
415
436
  The banner says where the build came from, for example `Build: GRCh38 (from the contig lengths: 24 contig length(s) match GRCh38)`. If confident positions contradict the label (real-world MHG / Tempus exports say 37 and ship 38), Allelix uses the positions and prints a **Build mismatch** notice. An unlabelled chip whose known-SNP positions do not decide uses its format's documented default, with a visible note, provided at least one of those positions matches a build and the build most of them match is the default (a few that disagree do not stop it); the note gives the counts. A tentative GRCh36 winner is the safety exception: Allelix uses GRCh36 so it cannot query a modern position cache (see [`docs/grch36-liftover.md`](https://github.com/allelix/allelix/blob/main/docs/grch36-liftover.md)).
416
437
 
@@ -421,12 +442,12 @@ The banner says where the build came from, for example `Build: GRCh38 (from the
421
442
  - the known-SNP positions are tied between builds and the build that would otherwise decide (the label, the format default, or the contig-length build) is not one of them. When it is one of them, Allelix proceeds on it and prints an "inconclusive" warning, also shown in the HTML report. Without contig lengths, a tie that includes GRCh36 instead takes the GRCh36 safety exception described above, with the same warning;
422
443
  - a VCF has no contig lengths, no assembly and too few known-SNP positions;
423
444
  - 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);
424
- - 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);
425
- - 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);
445
+ - 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), unless it is a chip labelled GRCh36;
446
+ - 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`);
426
447
  - 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;
427
448
  - `--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.
428
449
 
429
- 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.
450
+ When the file's label is its only evidence (no contig lengths decide) and it carries no known-SNP position (a chip labelled GRCh36 excepted), the message names the labelled build: "If the file is GRCh37, run it again with --build grch37". 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.
430
451
 
431
452
  ### Keeping input and output files safe
432
453
 
@@ -435,22 +456,28 @@ must differ from the input, including symlink and hard-link aliases. These
435
456
  checks run before cache setup or output creation. Inputs that alias the former
436
457
  fixed report or PLINK `.tmp` path are also refused; unrelated old temp files
437
458
  are left untouched. Annotated VCF still refuses any existing output at the CLI.
459
+ Two outputs of one run must name different files too: `--output` and
460
+ `--vcf-out`, and PLINK's `.bed`, `.bim` and `.fam`, are refused before any work
461
+ when they resolve to one file (another spelling, a symlink, a linked folder or a
462
+ hardlink).
438
463
 
439
464
  Writers use unique temporary files in the destination directory and publish
440
465
  only complete, closed content. Failure before publication preserves previous
441
466
  outputs. PLINK stages all three files first, then publishes each atomically;
442
467
  a later publication failure can leave complete new files next to unchanged old
443
468
  ones. Existing ordinary permission bits are retained when replacement is
444
- permitted; new staging files start private. Ownership, ACLs, extended attributes,
469
+ permitted; new staging files start private, and a new output file gets the mode
470
+ your umask gives any new file (`0666 & ~umask`, so `0644` under `022`).
471
+ Ownership, ACLs, extended attributes,
445
472
  crash durability and concurrent path swaps are outside this guarantee.
446
473
 
447
474
  ### Is my database stale? How fresh is the data?
448
475
 
449
- `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.
476
+ `allelix db status` shows the ingestion date of each cache. The freshness model is signal-driven: `allelix db update` queries an upstream freshness signal per source (an md5 checksum, or the ETag and Last-Modified headers) and re-ingests only when the signal changed. If a server stops sending the header the cache recorded and sends only the other one, the cache is kept with a note; `--force` refreshes it. For ClinPGx, a changed signal downloads the archive, and the cache is rebuilt only if what it is built from (the two annotation files and CPIC's allele-function data) differs from the last build; otherwise `db update` says "content unchanged" and keeps the cache. There's no time-based staleness — a 6-month-old cache against an unchanged source IS current.
450
477
 
451
478
  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.
452
479
 
453
- 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`.
480
+ A ClinPGx cache built while CPIC's allele-function data could not be fetched is marked as such: `db status` shows "(no CPIC)", a report made with it says so (console, HTML and JSON warning `cpic_unavailable`), 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`.
454
481
 
455
482
  ### Output reports contain real genome annotations
456
483
 
@@ -475,17 +502,18 @@ Notable load-bearing ADRs:
475
502
  - **ADR-0037 — Conservative position-based build confidence.** A winning build needs at least three matches and an 80% ratio; weaker signals remain diagnostic only.
476
503
  - **ADR-0038 — GWAS effect-modifier disambiguation.** Only positively identified odds ratios receive an effect-size modifier; beta and ambiguous effects abstain.
477
504
  - **ADR-0041 — Plausibility flag.** Very rare homozygous calls and heterozygous calls with no gnomAD carriers are flagged for review, never suppressed.
478
- - **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.
505
+ - **ADR-0042 — VCF quality gate.** The variant caller's FILTER, the sample's FT and genotype quality are honoured, low-quality reference blocks are no-calls, and every exclusion is counted in the report.
479
506
  - **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.
480
507
  - **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.
481
- - **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.
508
+ - **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, except under "Likely risk allele" or "other", where they take the Conflicting cap (4.0).
482
509
  - **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`.
483
- - **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).
510
+ - **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).
484
511
  - **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.
485
512
  - **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.
486
513
  - **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.
487
- - **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.
514
+ - **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.
488
515
  - **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.
516
+ - **ADR-0070: A chip D/I call is interpreted by no source.** A `D` or `I` code does not say which indel was called, so ClinVar, SNPedia, the GWAS Catalog and ClinPGx skip it; it is counted, and named at high-value and P/LP rsIDs as not checked.
489
517
 
490
518
  Release history: see [`CHANGELOG.md`](https://github.com/allelix/allelix/blob/main/CHANGELOG.md).
491
519
 
@@ -500,12 +528,13 @@ pre-commit install --hook-type pre-commit
500
528
 
501
529
  ruff check .
502
530
  ruff format --check .
503
- pytest
531
+ pytest -m "not slow and not integration" # the fast suite CI runs
532
+ pytest # every test, including slow and integration
504
533
  ```
505
534
 
506
535
  The pre-commit hook enforces `ruff check` + `ruff format --check`. If a commit is blocked, fix the underlying problem rather than skipping the hook.
507
536
 
508
- 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.
537
+ 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.
509
538
 
510
539
  ## License
511
540