afquery 0.1.4__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. afquery-0.2.0/.dockerignore +11 -0
  2. {afquery-0.1.4 → afquery-0.2.0}/.github/workflows/release.yml +55 -1
  3. {afquery-0.1.4 → afquery-0.2.0}/.gitignore +1 -0
  4. afquery-0.2.0/Dockerfile +6 -0
  5. afquery-0.2.0/PKG-INFO +142 -0
  6. afquery-0.2.0/README.md +115 -0
  7. {afquery-0.1.4 → afquery-0.2.0}/docs/advanced/benchmarking.md +66 -4
  8. afquery-0.2.0/docs/advanced/debugging-results.md +138 -0
  9. afquery-0.2.0/docs/advanced/filter-pass-tracking.md +100 -0
  10. afquery-0.2.0/docs/advanced/multi-cohort-strategies.md +160 -0
  11. {afquery-0.1.4 → afquery-0.2.0}/docs/advanced/performance.md +49 -7
  12. afquery-0.2.0/docs/advanced/pipeline-integration.md +184 -0
  13. afquery-0.2.0/docs/advanced/ploidy-and-sex-chroms.md +149 -0
  14. afquery-0.2.0/docs/faq.md +233 -0
  15. afquery-0.2.0/docs/getting-started/concepts.md +215 -0
  16. {afquery-0.1.4 → afquery-0.2.0}/docs/getting-started/installation.md +22 -16
  17. afquery-0.2.0/docs/getting-started/motivation.md +140 -0
  18. afquery-0.2.0/docs/getting-started/preprocessing.md +35 -0
  19. afquery-0.2.0/docs/getting-started/quickstart.md +121 -0
  20. afquery-0.2.0/docs/getting-started/understanding-output.md +147 -0
  21. {afquery-0.1.4 → afquery-0.2.0}/docs/guides/annotate-vcf.md +9 -29
  22. {afquery-0.1.4 → afquery-0.2.0}/docs/guides/create-database.md +7 -11
  23. afquery-0.2.0/docs/guides/dump-export.md +133 -0
  24. {afquery-0.1.4 → afquery-0.2.0}/docs/guides/manifest-format.md +28 -31
  25. afquery-0.2.0/docs/guides/query.md +196 -0
  26. {afquery-0.1.4 → afquery-0.2.0}/docs/guides/sample-filtering.md +46 -8
  27. {afquery-0.1.4 → afquery-0.2.0}/docs/guides/update-database.md +74 -11
  28. afquery-0.2.0/docs/index.md +75 -0
  29. {afquery-0.1.4 → afquery-0.2.0}/docs/reference/cli.md +19 -12
  30. {afquery-0.1.4 → afquery-0.2.0}/docs/reference/data-model.md +8 -19
  31. afquery-0.2.0/docs/reference/glossary.md +65 -0
  32. {afquery-0.1.4 → afquery-0.2.0}/docs/reference/python-api.md +116 -4
  33. {afquery-0.1.4 → afquery-0.2.0}/docs/troubleshooting.md +22 -2
  34. afquery-0.2.0/docs/use-cases/acmg-use-cases.md +226 -0
  35. afquery-0.2.0/docs/use-cases/clinical-prioritization.md +119 -0
  36. afquery-0.2.0/docs/use-cases/cohort-stratification.md +113 -0
  37. afquery-0.2.0/docs/use-cases/population-specific-af.md +79 -0
  38. afquery-0.2.0/docs/use-cases/pseudo-controls.md +131 -0
  39. afquery-0.2.0/docs/use-cases/sex-specific-af.md +111 -0
  40. afquery-0.2.0/docs/use-cases/technology-integration.md +166 -0
  41. afquery-0.2.0/mkdocs.yml +99 -0
  42. afquery-0.2.0/plans/issue-01-remove-publication-section.md +27 -0
  43. afquery-0.2.0/plans/issue-02-drop-schema-v1.md +52 -0
  44. afquery-0.2.0/plans/issue-03-query-syntax-chrom-pos.md +83 -0
  45. afquery-0.2.0/plans/issue-04-wes-bed-validation.md +62 -0
  46. afquery-0.2.0/plans/issue-05-update-sample-metadata.md +79 -0
  47. afquery-0.2.0/plans/issue-06-multi-chromosome-query.md +55 -0
  48. afquery-0.2.0/plans/issue-07-doc-formatting-fixes.md +71 -0
  49. afquery-0.2.0/plans/issue-08-index-feature-list.md +49 -0
  50. afquery-0.2.0/plans/issue-09-doc-content-improvements.md +97 -0
  51. afquery-0.2.0/src/afquery/__init__.py +5 -0
  52. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/_version.py +2 -2
  53. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/annotate.py +7 -1
  54. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/cli.py +154 -41
  55. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/constants.py +1 -0
  56. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/database.py +73 -1
  57. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/dump.py +15 -21
  58. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/models.py +4 -0
  59. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/preprocess/update.py +210 -0
  60. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/query.py +236 -58
  61. afquery-0.2.0/tests/test_batch.py +435 -0
  62. {afquery-0.1.4 → afquery-0.2.0}/tests/test_benchmark.py +2 -2
  63. {afquery-0.1.4 → afquery-0.2.0}/tests/test_cli.py +21 -21
  64. afquery-0.2.0/tests/test_cli_docs_consistency.py +87 -0
  65. {afquery-0.1.4 → afquery-0.2.0}/tests/test_dump.py +71 -1
  66. {afquery-0.1.4 → afquery-0.2.0}/tests/test_sample_filter.py +6 -6
  67. afquery-0.2.0/tests/test_update_metadata.py +436 -0
  68. afquery-0.2.0/tests/test_warnings.py +100 -0
  69. afquery-0.1.4/PKG-INFO +0 -371
  70. afquery-0.1.4/README.md +0 -344
  71. afquery-0.1.4/docs/advanced/filter-pass-tracking.md +0 -111
  72. afquery-0.1.4/docs/advanced/ploidy-and-sex-chroms.md +0 -100
  73. afquery-0.1.4/docs/faq.md +0 -129
  74. afquery-0.1.4/docs/getting-started/concepts.md +0 -110
  75. afquery-0.1.4/docs/getting-started/quickstart.md +0 -145
  76. afquery-0.1.4/docs/guides/dump-export.md +0 -152
  77. afquery-0.1.4/docs/guides/query.md +0 -161
  78. afquery-0.1.4/docs/index.md +0 -94
  79. afquery-0.1.4/docs/webhook_schema.md +0 -106
  80. afquery-0.1.4/mkdocs.yml +0 -74
  81. afquery-0.1.4/src/afquery/__init__.py +0 -5
  82. afquery-0.1.4/tests/test_batch.py +0 -196
  83. {afquery-0.1.4 → afquery-0.2.0}/.github/workflows/ci.yml +0 -0
  84. {afquery-0.1.4 → afquery-0.2.0}/.github/workflows/docs.yml +0 -0
  85. {afquery-0.1.4 → afquery-0.2.0}/LICENSE +0 -0
  86. {afquery-0.1.4 → afquery-0.2.0}/pyproject.toml +0 -0
  87. {afquery-0.1.4 → afquery-0.2.0}/recipes/afquery/meta.yaml +0 -0
  88. {afquery-0.1.4 → afquery-0.2.0}/resources/normalize_vcf.sh +0 -0
  89. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/benchmark.py +0 -0
  90. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/bitmaps.py +0 -0
  91. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/capture.py +0 -0
  92. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/cli.pyr +0 -0
  93. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/ploidy.py +0 -0
  94. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/preprocess/__init__.py +0 -0
  95. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/preprocess/build.py +0 -0
  96. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/preprocess/compact.py +0 -0
  97. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/preprocess/ingest.py +0 -0
  98. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/preprocess/manifest.py +0 -0
  99. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/preprocess/regions.py +0 -0
  100. {afquery-0.1.4 → afquery-0.2.0}/src/afquery/preprocess/synth.py +0 -0
  101. {afquery-0.1.4 → afquery-0.2.0}/tests/conftest.py +0 -0
  102. {afquery-0.1.4 → afquery-0.2.0}/tests/data/annotate_input.vcf +0 -0
  103. {afquery-0.1.4 → afquery-0.2.0}/tests/data/annotate_multi_bucket.vcf +0 -0
  104. {afquery-0.1.4 → afquery-0.2.0}/tests/data/annotate_multi_chrom.vcf +0 -0
  105. {afquery-0.1.4 → afquery-0.2.0}/tests/data/beds/wes_kit_a.bed +0 -0
  106. {afquery-0.1.4 → afquery-0.2.0}/tests/data/beds/wes_kit_b.bed +0 -0
  107. {afquery-0.1.4 → afquery-0.2.0}/tests/data/expected_results.json +0 -0
  108. {afquery-0.1.4 → afquery-0.2.0}/tests/data/manifest.tsv +0 -0
  109. {afquery-0.1.4 → afquery-0.2.0}/tests/data/vcfs/S00.vcf +0 -0
  110. {afquery-0.1.4 → afquery-0.2.0}/tests/data/vcfs/S01.vcf +0 -0
  111. {afquery-0.1.4 → afquery-0.2.0}/tests/data/vcfs/S02.vcf +0 -0
  112. {afquery-0.1.4 → afquery-0.2.0}/tests/data/vcfs/S03.vcf +0 -0
  113. {afquery-0.1.4 → afquery-0.2.0}/tests/data/vcfs/S04.vcf +0 -0
  114. {afquery-0.1.4 → afquery-0.2.0}/tests/data/vcfs/S05.vcf +0 -0
  115. {afquery-0.1.4 → afquery-0.2.0}/tests/data/vcfs/S06.vcf +0 -0
  116. {afquery-0.1.4 → afquery-0.2.0}/tests/data/vcfs/S07.vcf +0 -0
  117. {afquery-0.1.4 → afquery-0.2.0}/tests/data/vcfs/S08.vcf +0 -0
  118. {afquery-0.1.4 → afquery-0.2.0}/tests/data/vcfs/S09.vcf +0 -0
  119. {afquery-0.1.4 → afquery-0.2.0}/tests/test_annotate.py +0 -0
  120. {afquery-0.1.4 → afquery-0.2.0}/tests/test_bitmaps.py +0 -0
  121. {afquery-0.1.4 → afquery-0.2.0}/tests/test_capture.py +0 -0
  122. {afquery-0.1.4 → afquery-0.2.0}/tests/test_compact.py +0 -0
  123. {afquery-0.1.4 → afquery-0.2.0}/tests/test_constants.py +0 -0
  124. {afquery-0.1.4 → afquery-0.2.0}/tests/test_haploid_stats.py +0 -0
  125. {afquery-0.1.4 → afquery-0.2.0}/tests/test_info.py +0 -0
  126. {afquery-0.1.4 → afquery-0.2.0}/tests/test_pass_filter.py +0 -0
  127. {afquery-0.1.4 → afquery-0.2.0}/tests/test_ploidy.py +0 -0
  128. {afquery-0.1.4 → afquery-0.2.0}/tests/test_preprocess.py +0 -0
  129. {afquery-0.1.4 → afquery-0.2.0}/tests/test_query.py +0 -0
  130. {afquery-0.1.4 → afquery-0.2.0}/tests/test_synth.py +0 -0
  131. {afquery-0.1.4 → afquery-0.2.0}/tests/test_synthetic_stats.py +0 -0
  132. {afquery-0.1.4 → afquery-0.2.0}/tests/test_update.py +0 -0
@@ -0,0 +1,11 @@
1
+ .git
2
+ .github
3
+ docs/
4
+ tests/
5
+ recipes/
6
+ *.md
7
+ *.yml
8
+ __pycache__
9
+ *.pyc
10
+ .venv
11
+ dist/
@@ -73,7 +73,61 @@ jobs:
73
73
  files: dist/*
74
74
  generate_release_notes: true
75
75
 
76
- # ── 5. Bioconda PR (final releases only — no "rc" in tag) ────────────
76
+ # ── 5. Docker image → ghcr.io (final releases only) ─────────────────
77
+ docker:
78
+ needs: publish-pypi
79
+ runs-on: ubuntu-latest
80
+ if: ${{ !contains(github.ref_name, 'rc') }} # skip RC tags
81
+ permissions:
82
+ packages: write
83
+ steps:
84
+ - uses: actions/checkout@v4
85
+
86
+ - name: Extract version
87
+ id: version
88
+ run: echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
89
+
90
+ - name: Set up QEMU
91
+ uses: docker/setup-qemu-action@v3
92
+
93
+ - name: Set up Docker Buildx
94
+ uses: docker/setup-buildx-action@v3
95
+
96
+ - name: Login to GHCR
97
+ uses: docker/login-action@v3
98
+ with:
99
+ registry: ghcr.io
100
+ username: ${{ github.actor }}
101
+ password: ${{ secrets.GITHUB_TOKEN }}
102
+
103
+ - name: Wait for PyPI propagation
104
+ run: |
105
+ VERSION=${{ steps.version.outputs.version }}
106
+ echo "Waiting for afquery==${VERSION} on PyPI..."
107
+ for i in $(seq 1 12); do
108
+ STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
109
+ "https://pypi.org/pypi/afquery/${VERSION}/json")
110
+ if [ "$STATUS" = "200" ]; then
111
+ echo "Package available."
112
+ exit 0
113
+ fi
114
+ echo "Attempt ${i}/12: HTTP ${STATUS} — retrying in 30 s..."
115
+ sleep 30
116
+ done
117
+ echo "Not available after 6 min, proceeding anyway."
118
+
119
+ - name: Build and push
120
+ uses: docker/build-push-action@v6
121
+ with:
122
+ context: .
123
+ push: true
124
+ platforms: linux/amd64,linux/arm64
125
+ build-args: VERSION=${{ steps.version.outputs.version }}
126
+ tags: |
127
+ ghcr.io/${{ github.repository }}:${{ steps.version.outputs.version }}
128
+ ghcr.io/${{ github.repository }}:latest
129
+
130
+ # ── 6. Bioconda PR (final releases only — no "rc" in tag) ────────────
77
131
  bioconda-pr:
78
132
  needs: publish-pypi
79
133
  runs-on: ubuntu-latest
@@ -59,3 +59,4 @@ src/afquery/_version.py
59
59
  *.duckdb.wal
60
60
  *.parquet
61
61
  db/*
62
+ site
@@ -0,0 +1,6 @@
1
+ FROM python:3.11-slim
2
+
3
+ ARG VERSION
4
+ RUN pip install --no-cache-dir afquery==${VERSION}
5
+
6
+ ENTRYPOINT ["afquery"]
afquery-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: afquery
3
+ Version: 0.2.0
4
+ Summary: Genomic allele frequency query engine with bitmap-encoded genotypes
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: click>=8.1
13
+ Requires-Dist: cyvcf2>=0.30
14
+ Requires-Dist: duckdb>=0.10
15
+ Requires-Dist: pyarrow>=14.0
16
+ Requires-Dist: pyranges<0.2,>=0.1.2
17
+ Requires-Dist: pyroaring>=0.4.8
18
+ Requires-Dist: tqdm>=4.60
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
21
+ Requires-Dist: pytest>=8.0; extra == 'dev'
22
+ Provides-Extra: docs
23
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
24
+ Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
25
+ Requires-Dist: pymdown-extensions>=10.7; extra == 'docs'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # AFQuery
29
+
30
+ [![CI](https://github.com/dlopez-bioinfo/afquery/actions/workflows/ci.yml/badge.svg)](https://github.com/dlopez-bioinfo/afquery/actions/workflows/ci.yml)
31
+ [![Coverage](https://codecov.io/gh/dlopez-bioinfo/afquery/graph/badge.svg)](https://codecov.io/gh/dlopez-bioinfo/afquery)
32
+ [![Docs](https://img.shields.io/website?url=https%3A%2F%2Fdlopez-bioinfo.github.io%2Fafquery%2F&label=docs)](https://dlopez-bioinfo.github.io/afquery/)
33
+ <br>
34
+ [![PyPI](https://img.shields.io/pypi/v/afquery.svg?color=blue)](https://pypi.org/project/afquery/)
35
+ [![Bioconda](https://img.shields.io/conda/vn/bioconda/afquery.svg)](https://anaconda.org/bioconda/afquery)
36
+ [![Docker](https://img.shields.io/badge/ghcr.io-afquery-blue?logo=docker)](https://github.com/dlopez-bioinfo/afquery/pkgs/container/afquery)
37
+ [![Python](https://img.shields.io/pypi/pyversions/afquery.svg)](https://pypi.org/project/afquery/)
38
+ [![License: MIT](https://img.shields.io/github/license/dlopez-bioinfo/afquery)](https://github.com/dlopez-bioinfo/afquery/blob/master/LICENSE)
39
+
40
+ AFQuery enables fast allele frequency queries on user-defined subsets of local genomic cohorts, without rescanning VCFs.
41
+
42
+ AFQuery is a bitmap-indexed engine that efficiently recomputes AC/AN/AF for dynamically defined subcohorts (e.g., by phenotype, sex, or sequencing technology), a common requirement in ACMG/AMP variant classification. It stores per-variant genotype data as Roaring Bitmaps in Parquet files and resolves sample filters into bitmaps that can be intersected in microseconds, enabling sub-100 ms queries on large cohorts. The system accounts for ploidy in sex chromosomes, adjusts AN based on sequencing technology, supports incremental updates, and runs locally using a file-based setup (Parquet + SQLite) without requiring server or cloud infrastructure.
43
+
44
+ [Full Documentation→](https://dlopez-bioinfo.github.io/afquery/)
45
+
46
+ ## When to use AFQuery
47
+
48
+ - You need allele frequencies for phenotype or user-defined subcohorts
49
+ - You work with mixed sequencing technologies or capture kits versions (WGS, WES, targeted panels)
50
+ - You require fast, repeated queries without rescanning VCFs
51
+ - You want a local, reproducible workflow without cloud or cluster dependencies
52
+
53
+ ## Features
54
+
55
+ - **Dynamic subcohort queries (<100 ms)** — bitmap intersections at query time; no VCF re-scan required
56
+ - **Technology-aware** — avoids bias when mixing WGS, WES, and panels using different BED capture indexes
57
+ - **Ploidy-aware** — correct handling of sex chromosomes (PAR/non-PAR, chrX, chrY)
58
+ - **ACMG-compatible allele counting** — AC/AN/AF computed per standard definitions
59
+ - **Flexible metadata filtering** — arbitrary labels (ICD-10, HPO, custom fields) with inclusion/exclusion rules
60
+ - **Incremental updates** — add or remove samples and update metadata without rebuilding the database
61
+ - **VCF annotation** — annotate variants using subcohort-specific frequencies
62
+ - **FILTER/call quality tracking** — failed calls (FILTER!=PASS) tracked per variant and reported as N_FAIL
63
+ - **Batch and region queries** — query a single locus, a genomic region, or a list of variants from a file
64
+ - **Bulk CSV export** — export all variant frequencies with optional disaggregation by sex, technology, or phenotype
65
+ - **Audit changelog** — all database operations logged with timestamps and operator notes
66
+ - **Database validation** — integrity checks with scripted exit codes
67
+ - **Portable and serverless** — file-based system, no infrastructure required
68
+
69
+ ## Performance
70
+
71
+ - Query latency: <100 ms (tested up to 50,000 samples)
72
+ - Storage: ~2 bytes/sample/variant
73
+ - Scales to millions of variants per chromosome
74
+
75
+ ## Comparison with Alternative Tools
76
+
77
+ | | AFQuery | bcftools | GATK GenomicsDB | Hail |
78
+ |---|---|---|---|---|
79
+ | Technology-aware AN | **Yes** | No | No | No |
80
+ | Metadata filtering | **Arbitrary labels** | No | No | Custom code |
81
+ | Ploidy-aware sex chromosomes | **Yes** | Manual | No | Manual |
82
+ | Dynamic subcohort queries | **Yes** | No | Limited | Requires code |
83
+ | FILTER/call quality tracking | **Per variant** | Manual | No | Manual |
84
+ | Incremental updates | **Yes** | No | **Yes** | No |
85
+ | Infrastructure required | **None** | **None** | Java/server | Spark cluster |
86
+ | Query latency (50K samples) | **<100 ms** | ~5 min | <1 min | 1–2 min |
87
+
88
+ ## Algorithm Overview
89
+
90
+ AFQuery pre-indexes per-variant genotype data as [Roaring Bitmaps](https://roaringbitmap.org/) stored in Parquet files. Each variant row holds three bitmaps: heterozygous carriers, homozygous alt carriers, and samples with FILTER!=PASS. Sample metadata (sex, phenotype, technology) is pre-serialized as bitmaps in SQLite.
91
+
92
+ At query time, the requested sample filter is resolved to a single candidate bitmap via bitmap intersections and differences — taking microseconds regardless of cohort size. For each variant, the candidate bitmap is intersected with the genotype bitmaps to compute AC/AN/AF. AN accounts for WES capture regions (via BED-indexed interval trees) and for ploidy on sex chromosomes (males are haploid on non-PAR chrX and chrY).
93
+
94
+ ## Input Requirements
95
+
96
+ - VCF files: normalized and consistent with the selected genome build (GRCh37 or GRCh38)
97
+ - Sample metadata: must include sex, sequencing technology, and any fields used for filtering (e.g., phenotype)
98
+ - BED files (optional): define capture regions for each sequencing technology
99
+
100
+ ## Quick Start
101
+
102
+ Example workflow from raw VCFs to query, export, and annotation:
103
+
104
+ ```bash
105
+ pip install afquery
106
+ # Docker: see Installation docs for docker pull / run usage
107
+
108
+ # Build the database
109
+ afquery create-db --manifest samples.tsv --output-dir ./db/ --genome-build GRCh38
110
+
111
+ # Inspect the database
112
+ afquery info --db ./db/
113
+
114
+ # Query a single position, filtered to a phenotype
115
+ afquery query --db ./db/ --locus chr1:925952 --phenotype E11.9 --sex female
116
+
117
+ # Query a genomic region
118
+ afquery query --db ./db/ --region chr1:900000-1000000
119
+
120
+ # Export BRCA1 variant frequencies to CSV
121
+ afquery dump --db ./db/ --output all_variants.csv --chrom chr17 --start 43044292 --end 43170327
122
+
123
+ # Annotate a VCF with cohort frequencies
124
+ afquery annotate --db ./db/ --input patient.vcf --output annotated.vcf --threads 12
125
+
126
+ # Add new samples to an existing database
127
+ afquery update-db --db ./db/ --add-samples new_samples.tsv
128
+ ```
129
+
130
+ ## Documentation
131
+
132
+ - [Full Documentation](https://dlopez-bioinfo.github.io/afquery/)
133
+ - [Getting Started](https://dlopez-bioinfo.github.io/afquery/getting-started/)
134
+ - [Why local allele frequencies?](https://dlopez-bioinfo.github.io/afquery/getting-started/motivation/)
135
+ - [CLI Reference](https://dlopez-bioinfo.github.io/afquery/reference/cli/)
136
+
137
+ ## Citation
138
+
139
+ If you use AFQuery, please cite:
140
+
141
+ > AFQuery: fast, metadata-aware allele frequency queries on local genomic cohorts.
142
+ > *(manuscript in preparation)*
@@ -0,0 +1,115 @@
1
+ # AFQuery
2
+
3
+ [![CI](https://github.com/dlopez-bioinfo/afquery/actions/workflows/ci.yml/badge.svg)](https://github.com/dlopez-bioinfo/afquery/actions/workflows/ci.yml)
4
+ [![Coverage](https://codecov.io/gh/dlopez-bioinfo/afquery/graph/badge.svg)](https://codecov.io/gh/dlopez-bioinfo/afquery)
5
+ [![Docs](https://img.shields.io/website?url=https%3A%2F%2Fdlopez-bioinfo.github.io%2Fafquery%2F&label=docs)](https://dlopez-bioinfo.github.io/afquery/)
6
+ <br>
7
+ [![PyPI](https://img.shields.io/pypi/v/afquery.svg?color=blue)](https://pypi.org/project/afquery/)
8
+ [![Bioconda](https://img.shields.io/conda/vn/bioconda/afquery.svg)](https://anaconda.org/bioconda/afquery)
9
+ [![Docker](https://img.shields.io/badge/ghcr.io-afquery-blue?logo=docker)](https://github.com/dlopez-bioinfo/afquery/pkgs/container/afquery)
10
+ [![Python](https://img.shields.io/pypi/pyversions/afquery.svg)](https://pypi.org/project/afquery/)
11
+ [![License: MIT](https://img.shields.io/github/license/dlopez-bioinfo/afquery)](https://github.com/dlopez-bioinfo/afquery/blob/master/LICENSE)
12
+
13
+ AFQuery enables fast allele frequency queries on user-defined subsets of local genomic cohorts, without rescanning VCFs.
14
+
15
+ AFQuery is a bitmap-indexed engine that efficiently recomputes AC/AN/AF for dynamically defined subcohorts (e.g., by phenotype, sex, or sequencing technology), a common requirement in ACMG/AMP variant classification. It stores per-variant genotype data as Roaring Bitmaps in Parquet files and resolves sample filters into bitmaps that can be intersected in microseconds, enabling sub-100 ms queries on large cohorts. The system accounts for ploidy in sex chromosomes, adjusts AN based on sequencing technology, supports incremental updates, and runs locally using a file-based setup (Parquet + SQLite) without requiring server or cloud infrastructure.
16
+
17
+ [Full Documentation→](https://dlopez-bioinfo.github.io/afquery/)
18
+
19
+ ## When to use AFQuery
20
+
21
+ - You need allele frequencies for phenotype or user-defined subcohorts
22
+ - You work with mixed sequencing technologies or capture kits versions (WGS, WES, targeted panels)
23
+ - You require fast, repeated queries without rescanning VCFs
24
+ - You want a local, reproducible workflow without cloud or cluster dependencies
25
+
26
+ ## Features
27
+
28
+ - **Dynamic subcohort queries (<100 ms)** — bitmap intersections at query time; no VCF re-scan required
29
+ - **Technology-aware** — avoids bias when mixing WGS, WES, and panels using different BED capture indexes
30
+ - **Ploidy-aware** — correct handling of sex chromosomes (PAR/non-PAR, chrX, chrY)
31
+ - **ACMG-compatible allele counting** — AC/AN/AF computed per standard definitions
32
+ - **Flexible metadata filtering** — arbitrary labels (ICD-10, HPO, custom fields) with inclusion/exclusion rules
33
+ - **Incremental updates** — add or remove samples and update metadata without rebuilding the database
34
+ - **VCF annotation** — annotate variants using subcohort-specific frequencies
35
+ - **FILTER/call quality tracking** — failed calls (FILTER!=PASS) tracked per variant and reported as N_FAIL
36
+ - **Batch and region queries** — query a single locus, a genomic region, or a list of variants from a file
37
+ - **Bulk CSV export** — export all variant frequencies with optional disaggregation by sex, technology, or phenotype
38
+ - **Audit changelog** — all database operations logged with timestamps and operator notes
39
+ - **Database validation** — integrity checks with scripted exit codes
40
+ - **Portable and serverless** — file-based system, no infrastructure required
41
+
42
+ ## Performance
43
+
44
+ - Query latency: <100 ms (tested up to 50,000 samples)
45
+ - Storage: ~2 bytes/sample/variant
46
+ - Scales to millions of variants per chromosome
47
+
48
+ ## Comparison with Alternative Tools
49
+
50
+ | | AFQuery | bcftools | GATK GenomicsDB | Hail |
51
+ |---|---|---|---|---|
52
+ | Technology-aware AN | **Yes** | No | No | No |
53
+ | Metadata filtering | **Arbitrary labels** | No | No | Custom code |
54
+ | Ploidy-aware sex chromosomes | **Yes** | Manual | No | Manual |
55
+ | Dynamic subcohort queries | **Yes** | No | Limited | Requires code |
56
+ | FILTER/call quality tracking | **Per variant** | Manual | No | Manual |
57
+ | Incremental updates | **Yes** | No | **Yes** | No |
58
+ | Infrastructure required | **None** | **None** | Java/server | Spark cluster |
59
+ | Query latency (50K samples) | **<100 ms** | ~5 min | <1 min | 1–2 min |
60
+
61
+ ## Algorithm Overview
62
+
63
+ AFQuery pre-indexes per-variant genotype data as [Roaring Bitmaps](https://roaringbitmap.org/) stored in Parquet files. Each variant row holds three bitmaps: heterozygous carriers, homozygous alt carriers, and samples with FILTER!=PASS. Sample metadata (sex, phenotype, technology) is pre-serialized as bitmaps in SQLite.
64
+
65
+ At query time, the requested sample filter is resolved to a single candidate bitmap via bitmap intersections and differences — taking microseconds regardless of cohort size. For each variant, the candidate bitmap is intersected with the genotype bitmaps to compute AC/AN/AF. AN accounts for WES capture regions (via BED-indexed interval trees) and for ploidy on sex chromosomes (males are haploid on non-PAR chrX and chrY).
66
+
67
+ ## Input Requirements
68
+
69
+ - VCF files: normalized and consistent with the selected genome build (GRCh37 or GRCh38)
70
+ - Sample metadata: must include sex, sequencing technology, and any fields used for filtering (e.g., phenotype)
71
+ - BED files (optional): define capture regions for each sequencing technology
72
+
73
+ ## Quick Start
74
+
75
+ Example workflow from raw VCFs to query, export, and annotation:
76
+
77
+ ```bash
78
+ pip install afquery
79
+ # Docker: see Installation docs for docker pull / run usage
80
+
81
+ # Build the database
82
+ afquery create-db --manifest samples.tsv --output-dir ./db/ --genome-build GRCh38
83
+
84
+ # Inspect the database
85
+ afquery info --db ./db/
86
+
87
+ # Query a single position, filtered to a phenotype
88
+ afquery query --db ./db/ --locus chr1:925952 --phenotype E11.9 --sex female
89
+
90
+ # Query a genomic region
91
+ afquery query --db ./db/ --region chr1:900000-1000000
92
+
93
+ # Export BRCA1 variant frequencies to CSV
94
+ afquery dump --db ./db/ --output all_variants.csv --chrom chr17 --start 43044292 --end 43170327
95
+
96
+ # Annotate a VCF with cohort frequencies
97
+ afquery annotate --db ./db/ --input patient.vcf --output annotated.vcf --threads 12
98
+
99
+ # Add new samples to an existing database
100
+ afquery update-db --db ./db/ --add-samples new_samples.tsv
101
+ ```
102
+
103
+ ## Documentation
104
+
105
+ - [Full Documentation](https://dlopez-bioinfo.github.io/afquery/)
106
+ - [Getting Started](https://dlopez-bioinfo.github.io/afquery/getting-started/)
107
+ - [Why local allele frequencies?](https://dlopez-bioinfo.github.io/afquery/getting-started/motivation/)
108
+ - [CLI Reference](https://dlopez-bioinfo.github.io/afquery/reference/cli/)
109
+
110
+ ## Citation
111
+
112
+ If you use AFQuery, please cite:
113
+
114
+ > AFQuery: fast, metadata-aware allele frequency queries on local genomic cohorts.
115
+ > *(manuscript in preparation)*
@@ -23,14 +23,14 @@ This generates 1,000 synthetic samples and 10,000 variants per chromosome, build
23
23
  | `--n-samples` | `1000` | Number of synthetic samples |
24
24
  | `--n-variants` | `10000` | Number of variants per chromosome |
25
25
  | `--output` | `benchmark_report.json` | Output path for JSON report |
26
- | `--db-dir` | None | Run against an existing database instead of synthetic data |
26
+ | `--db` | None | Run against an existing database instead of synthetic data |
27
27
 
28
28
  ---
29
29
 
30
30
  ## Benchmark Against a Real Database
31
31
 
32
32
  ```bash
33
- afquery benchmark --db-dir ./my_db/ --output my_db_benchmark.json
33
+ afquery benchmark --db ./my_db/ --output my_db_benchmark.json
34
34
  ```
35
35
 
36
36
  This uses your actual Parquet files and sample metadata, giving realistic performance numbers for your specific cohort size and variant density.
@@ -90,11 +90,11 @@ Run the benchmark after major updates to detect regressions:
90
90
 
91
91
  ```bash
92
92
  # Before update
93
- afquery benchmark --db-dir ./db/ --output before.json
93
+ afquery benchmark --db ./db/ --output before.json
94
94
 
95
95
  # After adding 500 samples
96
96
  afquery update-db --db ./db/ --add-samples new_batch.tsv
97
- afquery benchmark --db-dir ./db/ --output after.json
97
+ afquery benchmark --db ./db/ --output after.json
98
98
 
99
99
  # Compare
100
100
  python3 -c "
@@ -106,3 +106,65 @@ for k in before:
106
106
  print(f'{k}: {before[k]:.1f} → {after[k]:.1f} ms ({delta:+.1f})')
107
107
  "
108
108
  ```
109
+
110
+ ---
111
+
112
+ ## Comparison with Alternative Approaches
113
+
114
+ ### Feature Comparison
115
+
116
+ | Feature | AFQuery | bcftools stats | VCFtools --freq | GATK GenomicsDB | Hail |
117
+ |---------|---------|---------------|----------------|----------------|------|
118
+ | **Query latency** | <100 ms | Minutes (VCF scan) | Minutes (VCF scan) | Seconds | Seconds–minutes |
119
+ | **Dynamic subcohort queries** | Yes | No | No | Partial | Yes (programmatic) |
120
+ | **Metadata filtering** | Arbitrary labels | No | No | No | User-defined |
121
+ | **Sex-stratified AF** | Yes (auto ploidy) | Manual | Manual | No | Manual |
122
+ | **Technology-aware AN** | Yes (BED capture) | No | No | No | No |
123
+ | **Incremental updates** | Yes (no rebuild) | N/A | N/A | Yes (import) | Rebuild |
124
+ | **Infrastructure required** | None (file-based) | None | None | Java/server | Spark cluster |
125
+ | **Input format** | Single-sample VCFs | Any VCF | VCF | gVCF | VCF/BGEN |
126
+ | **Output format** | JSON/TSV/VCF annotation | Stats text | Freq file | Merged gVCF | Table/VCF |
127
+ | **Max cohort size (tested)** | 50,000 | 100,000+ | 100,000+ | 100,000+ | 1,000,000+ |
128
+ | **Bitmap compression** | Yes (Roaring) | No | No | Yes (GenomicsDB) | No |
129
+ | **VCF annotation** | Yes | No | No | No | Yes |
130
+ | **Python API** | Yes | No | No | No | Yes |
131
+
132
+ ### Query Latency Summary
133
+
134
+ Brief summary of query latency comparison:
135
+
136
+ | Tool | 50K samples, chr1, point query |
137
+ |------|-------------------------------|
138
+ | AFQuery (cold) | <100 ms |
139
+ | AFQuery (warm) | ~10 ms |
140
+ | bcftools stats (VCF scan) | ~5 minutes |
141
+ | VCFtools --freq (VCF scan) | ~5 minutes |
142
+
143
+ AFQuery achieves this speed advantage because queries access only the bitmaps for a single 1-Mbp bucket (one Parquet file) rather than scanning the entire dataset.
144
+
145
+ ---
146
+
147
+ ## Regression Testing
148
+
149
+ Run the benchmark after each major update to detect performance regressions:
150
+
151
+ ```bash
152
+ # Save baseline before changes
153
+ afquery benchmark --db ./db/ --output baseline.json
154
+
155
+ # After update
156
+ afquery benchmark --db ./db/ --output after_update.json
157
+
158
+ # Compare
159
+ python3 - <<'EOF'
160
+ import json
161
+ b = json.load(open('baseline.json'))['queries']
162
+ a = json.load(open('after_update.json'))['queries']
163
+ for k in b:
164
+ pct = (a[k] - b[k]) / b[k] * 100
165
+ flag = " ⚠" if pct > 20 else ""
166
+ print(f"{k}: {b[k]:.1f} → {a[k]:.1f} ms ({pct:+.1f}%){flag}")
167
+ EOF
168
+ ```
169
+
170
+ A regression of >20% on `point_query_cold_ms` warrants investigation.
@@ -0,0 +1,138 @@
1
+ # Debugging Results
2
+
3
+ When AFQuery returns unexpected results — AN=0, surprising AF values, or missing variants — use this diagnostic checklist to identify the root cause.
4
+
5
+ ---
6
+
7
+ ## Diagnostic Checklist
8
+
9
+ ### 1. Unexpected AN=0
10
+
11
+ AN=0 means no eligible samples at the queried position. Work through these checks in order:
12
+
13
+ | Check | Command | What to look for |
14
+ |-------|---------|-----------------|
15
+ | Chromosome normalization | `afquery query --db ./db/ --chrom 1 ...` vs `--chrom chr1` | Database may use `chr1` while you're querying `1` (or vice versa). Check `manifest.json` for `genome_build`. |
16
+ | Position exists in database | `afquery query --db ./db/ --locus chr1:12345678` | If no result at all, the variant was not observed in any sample during ingestion. |
17
+ | BED coverage (WES) | `afquery info --db ./db/` | If all eligible samples are WES and the position is outside capture regions, AN=0 is correct. |
18
+ | Sample filter too restrictive | Remove `--phenotype` and `--sex` filters | Query with no filters first. If AN>0 without filters, the filter is excluding all samples. |
19
+ | Technology filter | Remove `--tech` filter | Check if any samples match the requested technology. |
20
+
21
+ ### 2. Unexpected AF Value
22
+
23
+ | Symptom | Likely Cause | Resolution |
24
+ |---------|-------------|------------|
25
+ | AF higher than expected | Cohort enriched for disease with this variant | Compare with `--phenotype ^disease_code` to get control AF |
26
+ | AF lower than expected | Many WES samples without coverage at this position → diluted AN | Filter by `--tech wgs` to check WGS-only AF |
27
+ | AF=1.0 | All eligible samples carry the variant | Check if AN is very small (e.g., AN=2 means just 1 sample) |
28
+ | AF differs from gnomAD | Expected — local cohort AF ≠ global population AF | This is by design; see [Why Cohort-Specific AF Matters](../getting-started/concepts.md#why-cohort-specific-af-matters) |
29
+
30
+ ### 3. Missing Variants
31
+
32
+ A variant you expect to find is not in the database:
33
+
34
+ | Check | Details |
35
+ |-------|---------|
36
+ | Was it in the source VCFs? | AFQuery only stores variants present in ingested VCFs |
37
+ | Was it FILTER=PASS? | Default ingestion skips non-PASS variants. Check with `afquery info` for `pass_only_filter` |
38
+ | Multiallelic sites | AFQuery stores each ALT separately. Query the specific ALT allele, not just position |
39
+ | Chromosome naming | Ensure consistent `chr` prefix usage |
40
+
41
+ ### 4. Unexpected N_FAIL > 0
42
+
43
+ `N_FAIL > 0` means some eligible samples had the alt allele called but with `FILTER≠PASS`. These samples are excluded from AC/AN. This is usually benign (1–2 samples), but a high N_FAIL warrants investigation:
44
+
45
+ | N_FAIL relative to n_eligible | Likely cause |
46
+ |---|---|
47
+ | 1–2 samples | Isolated low-quality calls — not concerning |
48
+ | > 5% of n_eligible | Systematic sequencing artifact at this site |
49
+ | All eligible samples | Site-wide QC failure — AF=0 but variant is present in source VCFs |
50
+
51
+ To inspect a site with high N_FAIL, query with `--format json` to see all fields:
52
+
53
+ ```bash
54
+ afquery query --db ./db/ --locus chr1:12345678 --format json
55
+ ```
56
+
57
+ If N_FAIL is consistently high across many sites, check the variant calling pipeline and FILTER field settings in your VCFs.
58
+
59
+ ---
60
+
61
+ ## Diagnostic Commands
62
+
63
+ ### Database Info
64
+
65
+ ```bash
66
+ afquery info --db ./db/
67
+ ```
68
+
69
+ Shows: sample count, technology list, schema version, genome build, PASS-only status.
70
+
71
+ ### Check Database Integrity
72
+
73
+ ```bash
74
+ afquery check --db ./db/
75
+ ```
76
+
77
+ Validates: manifest consistency, Parquet file integrity, capture index presence.
78
+
79
+ ### Query with Full Output
80
+
81
+ ```bash
82
+ # Point query with all fields visible
83
+ afquery query --db ./db/ --locus chr1:12345678 --format json
84
+ ```
85
+
86
+ JSON format shows all fields including N_HET, N_HOM_ALT, N_HOM_REF, and N_FAIL — useful for understanding the composition of the result.
87
+
88
+
89
+ ### Direct Metadata Inspection
90
+
91
+ ```python
92
+ import sqlite3
93
+
94
+ conn = sqlite3.connect("./db/metadata.sqlite")
95
+
96
+ # List all phenotype codes and sample counts
97
+ cursor = conn.execute("""
98
+ SELECT code, COUNT(*) as n_samples
99
+ FROM sample_phenotypes
100
+ GROUP BY code
101
+ ORDER BY n_samples DESC
102
+ """)
103
+ for row in cursor:
104
+ print(f"{row[0]:30s} {row[1]} samples")
105
+
106
+ # List technologies
107
+ cursor = conn.execute("""
108
+ SELECT technology, COUNT(*) as n_samples
109
+ FROM samples
110
+ GROUP BY technology
111
+ """)
112
+ for row in cursor:
113
+ print(f"{row[0]:20s} {row[1]} samples")
114
+
115
+ conn.close()
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Common Root Causes
121
+
122
+ | Symptom | Root Cause | Fix |
123
+ |---------|-----------|-----|
124
+ | AN=0 for all queries | Wrong `--db` path or empty database | Verify path; run `afquery info` |
125
+ | AN=0 for specific region | WES-only cohort, position outside capture | Check BED file coverage |
126
+ | AN much lower than sample count | Mixed WGS/WES, position outside WES capture | Filter by `--tech wgs` to isolate |
127
+ | AF=None in output | AN=0 (division by zero) | See AN=0 diagnosis above |
128
+ | Different AF between `query` and `annotate` | Different default filters or phenotype context | Ensure same `--phenotype`, `--sex`, `--tech` flags |
129
+ | N_FAIL high at a site | Systematic QC failure in source VCFs at this position | Inspect site with `--format json`; check VCF FILTER annotations |
130
+
131
+ ---
132
+
133
+ ## Related Pages
134
+
135
+ - [Understanding Output](../getting-started/understanding-output.md) — what each field means
136
+ - [FAQ](../faq.md) — common questions and answers
137
+ - [Troubleshooting](../troubleshooting.md) — error messages and solutions
138
+ - [FILTER=PASS Tracking](filter-pass-tracking.md) — how PASS filtering works