afquery 0.1.2__tar.gz → 0.1.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. afquery-0.1.2.2/.github/workflows/docs.yml +38 -0
  2. afquery-0.1.2.2/PKG-INFO +395 -0
  3. {afquery-0.1.2 → afquery-0.1.2.2}/README.md +31 -12
  4. afquery-0.1.2.2/docs/advanced/benchmarking.md +108 -0
  5. afquery-0.1.2.2/docs/advanced/filter-pass-tracking.md +111 -0
  6. afquery-0.1.2.2/docs/advanced/performance.md +143 -0
  7. afquery-0.1.2.2/docs/advanced/ploidy-and-sex-chroms.md +100 -0
  8. afquery-0.1.2.2/docs/faq.md +129 -0
  9. afquery-0.1.2.2/docs/getting-started/concepts.md +110 -0
  10. afquery-0.1.2.2/docs/getting-started/installation.md +78 -0
  11. afquery-0.1.2.2/docs/getting-started/quickstart.md +145 -0
  12. afquery-0.1.2.2/docs/guides/annotate-vcf.md +132 -0
  13. afquery-0.1.2.2/docs/guides/create-database.md +139 -0
  14. afquery-0.1.2.2/docs/guides/dump-export.md +152 -0
  15. afquery-0.1.2.2/docs/guides/manifest-format.md +99 -0
  16. afquery-0.1.2.2/docs/guides/query.md +161 -0
  17. afquery-0.1.2.2/docs/guides/sample-filtering.md +158 -0
  18. afquery-0.1.2.2/docs/guides/update-database.md +162 -0
  19. afquery-0.1.2.2/docs/index.md +94 -0
  20. afquery-0.1.2.2/docs/reference/cli.md +215 -0
  21. afquery-0.1.2.2/docs/reference/data-model.md +174 -0
  22. afquery-0.1.2.2/docs/reference/python-api.md +379 -0
  23. afquery-0.1.2.2/docs/troubleshooting.md +131 -0
  24. afquery-0.1.2.2/mkdocs.yml +74 -0
  25. {afquery-0.1.2 → afquery-0.1.2.2}/pyproject.toml +6 -0
  26. {afquery-0.1.2 → afquery-0.1.2.2}/recipes/afquery/meta.yaml +10 -3
  27. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/_version.py +2 -2
  28. afquery-0.1.2/PKG-INFO +0 -15
  29. {afquery-0.1.2 → afquery-0.1.2.2}/.github/workflows/ci.yml +0 -0
  30. {afquery-0.1.2 → afquery-0.1.2.2}/.github/workflows/release.yml +0 -0
  31. {afquery-0.1.2 → afquery-0.1.2.2}/.gitignore +0 -0
  32. {afquery-0.1.2 → afquery-0.1.2.2}/docs/webhook_schema.md +0 -0
  33. {afquery-0.1.2 → afquery-0.1.2.2}/resources/normalize_vcf.sh +0 -0
  34. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/__init__.py +0 -0
  35. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/annotate.py +0 -0
  36. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/benchmark.py +0 -0
  37. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/bitmaps.py +0 -0
  38. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/capture.py +0 -0
  39. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/cli.py +0 -0
  40. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/cli.pyr +0 -0
  41. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/constants.py +0 -0
  42. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/database.py +0 -0
  43. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/dump.py +0 -0
  44. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/models.py +0 -0
  45. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/ploidy.py +0 -0
  46. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/__init__.py +0 -0
  47. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/build.py +0 -0
  48. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/compact.py +0 -0
  49. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/ingest.py +0 -0
  50. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/manifest.py +0 -0
  51. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/regions.py +0 -0
  52. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/synth.py +0 -0
  53. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/preprocess/update.py +0 -0
  54. {afquery-0.1.2 → afquery-0.1.2.2}/src/afquery/query.py +0 -0
  55. {afquery-0.1.2 → afquery-0.1.2.2}/tests/conftest.py +0 -0
  56. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/annotate_input.vcf +0 -0
  57. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/annotate_multi_bucket.vcf +0 -0
  58. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/annotate_multi_chrom.vcf +0 -0
  59. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/beds/wes_kit_a.bed +0 -0
  60. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/beds/wes_kit_b.bed +0 -0
  61. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/expected_results.json +0 -0
  62. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/manifest.tsv +0 -0
  63. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S00.vcf +0 -0
  64. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S01.vcf +0 -0
  65. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S02.vcf +0 -0
  66. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S03.vcf +0 -0
  67. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S04.vcf +0 -0
  68. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S05.vcf +0 -0
  69. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S06.vcf +0 -0
  70. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S07.vcf +0 -0
  71. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S08.vcf +0 -0
  72. {afquery-0.1.2 → afquery-0.1.2.2}/tests/data/vcfs/S09.vcf +0 -0
  73. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_annotate.py +0 -0
  74. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_batch.py +0 -0
  75. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_benchmark.py +0 -0
  76. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_bitmaps.py +0 -0
  77. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_capture.py +0 -0
  78. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_cli.py +0 -0
  79. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_compact.py +0 -0
  80. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_constants.py +0 -0
  81. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_dump.py +0 -0
  82. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_haploid_stats.py +0 -0
  83. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_info.py +0 -0
  84. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_pass_filter.py +0 -0
  85. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_ploidy.py +0 -0
  86. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_preprocess.py +0 -0
  87. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_query.py +0 -0
  88. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_sample_filter.py +0 -0
  89. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_synth.py +0 -0
  90. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_synthetic_stats.py +0 -0
  91. {afquery-0.1.2 → afquery-0.1.2.2}/tests/test_update.py +0 -0
@@ -0,0 +1,38 @@
1
+ name: Deploy Documentation
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ paths:
7
+ - 'docs/**'
8
+ - 'mkdocs.yml'
9
+ - 'src/**'
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+ pages: write
15
+ id-token: write
16
+
17
+ concurrency:
18
+ group: pages
19
+ cancel-in-progress: false
20
+
21
+ jobs:
22
+ deploy:
23
+ runs-on: ubuntu-latest
24
+ environment:
25
+ name: github-pages
26
+ url: ${{ steps.deployment.outputs.page_url }}
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+
30
+ - uses: actions/setup-python@v5
31
+ with:
32
+ python-version: '3.11'
33
+
34
+ - name: Install docs dependencies
35
+ run: pip install -e ".[docs]"
36
+
37
+ - name: Build and deploy docs
38
+ run: mkdocs gh-deploy --force
@@ -0,0 +1,395 @@
1
+ Metadata-Version: 2.4
2
+ Name: afquery
3
+ Version: 0.1.2.2
4
+ Summary: Genomic allele frequency query engine with bitmap-encoded genotypes
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: click>=8.1
7
+ Requires-Dist: cyvcf2>=0.30
8
+ Requires-Dist: duckdb>=0.10
9
+ Requires-Dist: pyarrow>=14.0
10
+ Requires-Dist: pyranges<0.2,>=0.1.2
11
+ Requires-Dist: pyroaring>=0.4.8
12
+ Requires-Dist: tqdm>=4.60
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
15
+ Requires-Dist: pytest>=8.0; extra == 'dev'
16
+ Provides-Extra: docs
17
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
18
+ Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
19
+ Requires-Dist: pymdown-extensions>=10.7; extra == 'docs'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # AFQuery
23
+
24
+ Fast, file-based genomic allele frequency queries for large cohorts (10K–50K samples).
25
+ No server, no cloud — just files. Sub-100ms point queries with flexible filtering
26
+ by sex, phenotype (ICD codes), and sequencing technology.
27
+
28
+ ## Quick Example
29
+
30
+ ```bash
31
+ pip install afquery
32
+
33
+ # Build database from your VCFs
34
+ afquery create-db --manifest samples.tsv --output-dir ./db/ --genome-build GRCh38
35
+
36
+ # Query allele frequency
37
+ afquery query --db ./db/ --chrom chr1 --pos 123456 --phenotype E11.9 --sex female
38
+
39
+ # Annotate a VCF
40
+ afquery annotate --db ./db/ --input variants.vcf --output annotated.vcf
41
+ ```
42
+
43
+ **[Full documentation →](https://davidlaewen.github.io/afquery/)**
44
+
45
+ ## Features
46
+
47
+ - Sub-100ms point queries on 50K-sample cohorts
48
+ - Filter by sex, phenotype (ICD codes), and sequencing technology
49
+ - Bitmap-compressed storage (Roaring Bitmaps + Parquet)
50
+ - Incremental updates (add/remove samples without full rebuild)
51
+ - VCF annotation with custom sample subsets
52
+ - Ploidy-aware AN for sex chromosomes (X/Y/MT)
53
+ - Zero infrastructure — purely file-based
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ pip install afquery
59
+ # or
60
+ conda install -c bioconda -c conda-forge afquery
61
+ ```
62
+
63
+ Requires Python 3.10+.
64
+
65
+ ## Quick Start
66
+
67
+ ### 1. Create a Database
68
+
69
+ First, prepare a manifest TSV with sample metadata:
70
+
71
+ ```tsv
72
+ sample_name sex tech_name vcf_path phenotype_codes
73
+ sample_1 male wgs vcfs/sample_1.vcf E11.9,I10
74
+ sample_2 female wes_kit_a vcfs/sample_2.vcf E11.9
75
+ sample_3 male wgs vcfs/sample_3.vcf I10
76
+ ```
77
+
78
+ **Key points about the manifest:**
79
+ - `tech_name`: Either `wgs` (case-insensitive) for whole genome, or a custom technology name
80
+ - `vcf_path`: Path to the single-sample VCF file (relative to manifest directory, or absolute)
81
+ - For WES/exome technologies, capture regions are loaded from `--bed-dir/{tech_name}.bed`
82
+ - Example: `tech_name=wes_kit_a` → loads `beds/wes_kit_a.bed`
83
+
84
+ Organize your files:
85
+ ```
86
+ project/
87
+ ├── manifest.tsv
88
+ ├── vcfs/
89
+ │ ├── sample_1.vcf
90
+ │ ├── sample_2.vcf
91
+ │ └── sample_3.vcf
92
+ └── beds/ # Required for non-WGS technologies
93
+ └── wes_kit_a.bed # BED file for WES kit A
94
+ ```
95
+
96
+ Then preprocess your VCF files:
97
+
98
+ ```bash
99
+ afquery preprocess \
100
+ --manifest manifest.tsv \
101
+ --bed-dir ./beds/ \
102
+ --output-dir ./my_db/ \
103
+ --genome-build GRCh38
104
+ ```
105
+
106
+ This creates:
107
+ - `my_db/manifest.json` — database metadata
108
+ - `my_db/metadata.sqlite` — samples, technologies, phenotype codes, precomputed bitmaps
109
+ - `my_db/variants/{chrom}.parquet` — variant data with encoded genotypes
110
+ - `my_db/capture/` — capture regions for each technology
111
+
112
+ ### 2. Query Allele Frequencies
113
+
114
+ ```bash
115
+ # Point query
116
+ afquery query --db my_db --chrom chr1 --pos 1000 --alt G
117
+
118
+ # Batch query (100 positions)
119
+ afquery query-batch --db my_db --positions positions.tsv --phenotype E11.9
120
+
121
+ # Region query
122
+ afquery query --db my_db --chrom chr1 --start 1000 --end 10000 --phenotype E11.9 --sex M
123
+ ```
124
+
125
+ ### 3. Annotate VCF Files
126
+
127
+ ```bash
128
+ afquery annotate \
129
+ --db my_db \
130
+ --vcf input.vcf \
131
+ --output annotated.vcf \
132
+ --phenotype E11.9 \
133
+ --tech WGS
134
+ ```
135
+
136
+ Adds `AFQUERY_AC`, `AFQUERY_AN`, `AFQUERY_AF` and genotype fields to your VCF.
137
+
138
+ ## Python API
139
+
140
+ ```python
141
+ from afquery import Database
142
+
143
+ db = Database("/path/to/db")
144
+
145
+ # Single position query
146
+ # Automatically filters samples by: sex + phenotype codes + capture coverage
147
+ result = db.query(
148
+ chrom="chr1",
149
+ pos=1000,
150
+ alt="G",
151
+ phenotype_codes=["E11.9"],
152
+ sex="both"
153
+ )
154
+ print(f"AC={result.ac}, AN={result.an}, AF={result.af}")
155
+
156
+ # Batch query (multi-variant)
157
+ results = db.query_batch(
158
+ "chr1",
159
+ variants=[(1500, "A", "T"), (3500, "G", "C")],
160
+ phenotype=["E11.9"],
161
+ )
162
+
163
+ # Region query (genomic range)
164
+ results = db.query_region(
165
+ chrom="chr1",
166
+ start=1000,
167
+ end=10000,
168
+ phenotype_codes=["E11.9", "I10"]
169
+ )
170
+
171
+ # Annotate VCF with allele frequencies
172
+ # Note: tech_name filters annotation to samples of that technology
173
+ db.annotate_vcf(
174
+ vcf_path="input.vcf",
175
+ output_path="annotated.vcf",
176
+ phenotype_codes=["E11.9"],
177
+ tech_name="wgs" # Only annotate using WGS samples
178
+ )
179
+ ```
180
+
181
+ **How samples are filtered in queries**:
182
+ - Sex filter: `male`, `female`, or `both`
183
+ - phenotype filter: All codes must match
184
+ - Capture filter: Automatic—only samples whose tech's BED covers the position
185
+
186
+ ## Database Structure
187
+
188
+ ```
189
+ my_db/
190
+ ├── manifest.json # Metadata: genome_build, sample_count, schema_version
191
+ ├── metadata.sqlite # SQLite: samples, technologies, phenotype codes, bitmaps
192
+ ├── variants/
193
+ │ ├── chr1.parquet
194
+ │ ├── chr2.parquet
195
+ │ └── ...
196
+ └── capture/
197
+ ├── tech_0.pickle # WGS capture region (always covered)
198
+ └── tech_1.pickle # WES kit capture region
199
+ ```
200
+
201
+ Each variant row contains:
202
+ - `pos` — 1-based genomic position
203
+ - `ref` — reference allele
204
+ - `alt` — alternate allele
205
+ - `het_bitmap` — Roaring Bitmap of heterozygous samples
206
+ - `hom_bitmap` — Roaring Bitmap of homozygous samples
207
+
208
+ ## How Capture BED Files Are Associated with Samples
209
+
210
+ Samples are linked to capture regions through their **technology**:
211
+
212
+ 1. **Manifest specifies technology**: Each sample lists `tech_name` (e.g., `wgs`, `wes_kit_a`)
213
+ 2. **Technology maps to BED file**:
214
+ - **WGS**: No BED file needed (always fully covered)
215
+ - **WES/Custom**: BED file loaded from `{bed_dir}/{tech_name}.bed`
216
+ 3. **Storage in database**:
217
+ - `metadata.sqlite::technologies` stores tech_id, tech_name, and bed_path
218
+ - `metadata.sqlite::samples` stores sample_id, sample_name, and tech_id (foreign key)
219
+ 4. **Query-time filtering**: When querying, samples are filtered by:
220
+ - Sex (male/female/both)
221
+ - phenotype diagnosis codes
222
+ - Capture region coverage (via tech's BED file)
223
+
224
+ **Example**: If you have samples on two exome kits:
225
+ ```tsv
226
+ sample_name sex tech_name vcf_path phenotype_codes
227
+ S001 male exome_v1 vcfs/S001.vcf E11.9
228
+ S002 female exome_v1 vcfs/S002.vcf E11.9
229
+ S003 male exome_v2 vcfs/S003.vcf I10
230
+ ```
231
+
232
+ Then provide:
233
+ ```
234
+ beds/
235
+ ├── exome_v1.bed # Coverage for samples S001, S002
236
+ └── exome_v2.bed # Coverage for sample S003
237
+ ```
238
+
239
+ At query time, each sample's eligible regions are determined by its tech's BED file.
240
+
241
+ ## Advanced Features
242
+
243
+ ### Incremental Updates (add_samples)
244
+
245
+ ```bash
246
+ afquery add-samples \
247
+ --db my_db \
248
+ --manifest new_samples.tsv \
249
+ --vcf-dir ./new_vcfs/
250
+ ```
251
+
252
+ Adds new samples without rebuilding the entire database.
253
+
254
+ ### Compact Database
255
+
256
+ Remove samples and reclaim disk space:
257
+
258
+ ```bash
259
+ afquery compact --db my_db --samples-to-remove sample_1,sample_2
260
+ ```
261
+
262
+ ### Run Benchmarks
263
+
264
+ ```bash
265
+ afquery benchmark --db my_db --n-queries 1000 --query-type point
266
+ ```
267
+
268
+ ### Generate Synthetic Data
269
+
270
+ ```bash
271
+ afquery synth --output synthetic_db/ --n-samples 5000 --n-variants 100000
272
+ ```
273
+
274
+ ## Ploidy Rules
275
+
276
+ AF computation respects chromosome-specific ploidy:
277
+
278
+ | Chromosome | Formula |
279
+ |---|---|
280
+ | Autosomes | `AN = 2 × eligible_samples` |
281
+ | chrM | `AN = 1 × eligible_samples` |
282
+ | chrY | `AN = 1 × eligible_males` |
283
+ | chrX (PAR) | `AN = 2 × eligible_samples` |
284
+ | chrX (non-PAR) | `AN = 2 × eligible_females + 1 × eligible_males` |
285
+
286
+ Where `eligible` = samples matching sex, phenotype, and technology capture filters.
287
+
288
+ ## Performance Targets
289
+
290
+ - **Point query (cold)**: <100 ms
291
+ - **Point query (warm)**: ~10 ms
292
+ - **Batch 100 positions**: ~200 ms
293
+ - **VCF annotation (5K variants)**: ~30 s
294
+ - **VCF annotation (5M variants)**: ~30 min
295
+
296
+ ## Command Reference
297
+
298
+ ```
299
+ afquery query Query single position
300
+ afquery query-batch Batch query multiple positions
301
+ afquery annotate Annotate VCF file
302
+ afquery info Show database info
303
+ afquery preprocess Build database from VCFs
304
+ afquery add-samples Add new samples to database
305
+ afquery compact Remove samples and reclaim space
306
+ afquery synth Generate synthetic test database
307
+ afquery benchmark Run performance benchmarks
308
+ ```
309
+
310
+ Run `afquery --help` for full options.
311
+
312
+ ## Development
313
+
314
+ ### Running Tests
315
+
316
+ ```bash
317
+ # All 190 tests
318
+ python3 -m pytest --tb=short -q
319
+
320
+ # Specific test module
321
+ python3 -m pytest tests/test_query.py -v
322
+ ```
323
+
324
+ ### Key Modules
325
+
326
+ - `afquery.query` — QueryEngine, point/batch/region queries
327
+ - `afquery.annotate` — VCF annotation pipeline
328
+ - `afquery.database` — Database wrapper (public API)
329
+ - `afquery.preprocess` — Manifest parsing, VCF ingestion, Parquet building
330
+ - `afquery.bitmaps` — Roaring Bitmap encoding/decoding
331
+ - `afquery.ploidy` — Chromosome-specific ploidy rules
332
+ - `afquery.models` — Data classes (QueryResult, ParsedSample, etc.)
333
+
334
+ ### Architecture
335
+
336
+ See `brain/architecture.md` for detailed system design, data flow, and query algorithm.
337
+
338
+ ## Genome Builds
339
+
340
+ - **GRCh37** (hg19) — PAR regions: chrX:1-2649520
341
+ - **GRCh38** (hg38) — PAR regions: chrX:1-3099677
342
+
343
+ ## Technologies Supported
344
+
345
+ - **WGS** — Whole genome sequencing (always fully covered, no BED file needed)
346
+ - Manifest: `tech_name = wgs` (case-insensitive)
347
+ - Query-time: All positions in genome considered covered
348
+
349
+ - **WES** — Whole exome sequencing (coverage defined by BED file)
350
+ - Manifest: `tech_name = wes_kit_a` (or any custom name)
351
+ - Preprocessing: Loads `{bed_dir}/wes_kit_a.bed` (0-based half-open BED format)
352
+ - Query-time: Only positions within BED intervals considered covered
353
+
354
+ - **Custom** — Any technology with a BED file (e.g., gene panels, targeted sequencing)
355
+ - Manifest: Use any `tech_name`
356
+ - Preprocessing: Loads `{bed_dir}/{tech_name}.bed`
357
+ - Query-time: Respects BED file coverage
358
+
359
+ ## Troubleshooting
360
+
361
+ ### ImportError: `cyvcf2`
362
+
363
+ `cyvcf2` import happens inside worker processes during preprocessing. Do not import at module level.
364
+
365
+ ### DuckDB Temp Files
366
+
367
+ Uses Parquet format (not Arrow IPC) for compatibility. Set `DUCKDB_TEMP_DIRECTORY` if needed.
368
+
369
+ ### Sample IDs
370
+
371
+ Sample IDs are 0-indexed and monotonically increasing. Never reuse removed IDs—use `compact` to reclaim space.
372
+
373
+ ## Contributing
374
+
375
+ 1. Read `brain/project_state.json` for current phase and test count
376
+ 2. Read `brain/architecture.md` for system design
377
+ 3. Follow code conventions in `CLAUDE.md`
378
+ 4. Update `brain/` docs after architectural changes
379
+ 5. Run tests before submitting
380
+
381
+ ## License
382
+
383
+ (Add your license here)
384
+
385
+ ## Citation
386
+
387
+ If you use afquery in research, please cite:
388
+
389
+ ```
390
+ (Citation format to be determined)
391
+ ```
392
+
393
+ ---
394
+
395
+ **Status**: Phase 5 complete (190 tests passing). Active development.
@@ -1,23 +1,42 @@
1
- # afquery
1
+ # AFQuery
2
2
 
3
- Genomic allele frequency query engine with bitmap-encoded genotypes. Fast, file-based queries over 10K-50K samples at sub-100ms latency.
3
+ Fast, file-based genomic allele frequency queries for large cohorts (10K–50K samples).
4
+ No server, no cloud — just files. Sub-100ms point queries with flexible filtering
5
+ by sex, phenotype (ICD codes), and sequencing technology.
6
+
7
+ ## Quick Example
8
+
9
+ ```bash
10
+ pip install afquery
11
+
12
+ # Build database from your VCFs
13
+ afquery create-db --manifest samples.tsv --output-dir ./db/ --genome-build GRCh38
14
+
15
+ # Query allele frequency
16
+ afquery query --db ./db/ --chrom chr1 --pos 123456 --phenotype E11.9 --sex female
17
+
18
+ # Annotate a VCF
19
+ afquery annotate --db ./db/ --input variants.vcf --output annotated.vcf
20
+ ```
21
+
22
+ **[Full documentation →](https://davidlaewen.github.io/afquery/)**
4
23
 
5
24
  ## Features
6
25
 
7
- - **Fast point queries**: <100ms cold start, ~10ms warm queries on single positions
8
- - **Batch queries**: Multi-position queries via SQL IN clauses or temporary tables
9
- - **Region queries**: Genomic range queries with automatic partitioning
10
- - **VCF annotation**: Annotate VCF files with computed allele frequencies and sample genotypes
11
- - **Ploidy-aware**: Correct AN/AC computation for autosomes, chrX, chrY, and chrM
12
- - **Bitmap compression**: Roaring Bitmaps for efficient genotype storage
13
- - **In-process**: No server process—queries run locally with DuckDB
14
- - **Incremental updates**: Add new samples to existing databases
15
- - **Multiple genome builds**: Support for GRCh37 and GRCh38
26
+ - Sub-100ms point queries on 50K-sample cohorts
27
+ - Filter by sex, phenotype (ICD codes), and sequencing technology
28
+ - Bitmap-compressed storage (Roaring Bitmaps + Parquet)
29
+ - Incremental updates (add/remove samples without full rebuild)
30
+ - VCF annotation with custom sample subsets
31
+ - Ploidy-aware AN for sex chromosomes (X/Y/MT)
32
+ - Zero infrastructure — purely file-based
16
33
 
17
34
  ## Installation
18
35
 
19
36
  ```bash
20
- pip install -e /home/dan/projects/afquery/ --break-system-packages
37
+ pip install afquery
38
+ # or
39
+ conda install -c bioconda -c conda-forge afquery
21
40
  ```
22
41
 
23
42
  Requires Python 3.10+.
@@ -0,0 +1,108 @@
1
+ # Benchmarking
2
+
3
+ `afquery benchmark` measures query performance on synthetic or real data and produces a JSON report.
4
+
5
+ ---
6
+
7
+ ## Quick Benchmark
8
+
9
+ Run against synthetic data (no real database required):
10
+
11
+ ```bash
12
+ afquery benchmark
13
+ ```
14
+
15
+ This generates 1,000 synthetic samples and 10,000 variants per chromosome, builds an in-memory database, runs a suite of query types, and writes results to `benchmark_report.json`.
16
+
17
+ ---
18
+
19
+ ## Options
20
+
21
+ | Option | Default | Description |
22
+ |--------|---------|-------------|
23
+ | `--n-samples` | `1000` | Number of synthetic samples |
24
+ | `--n-variants` | `10000` | Number of variants per chromosome |
25
+ | `--output` | `benchmark_report.json` | Output path for JSON report |
26
+ | `--db-dir` | None | Run against an existing database instead of synthetic data |
27
+
28
+ ---
29
+
30
+ ## Benchmark Against a Real Database
31
+
32
+ ```bash
33
+ afquery benchmark --db-dir ./my_db/ --output my_db_benchmark.json
34
+ ```
35
+
36
+ This uses your actual Parquet files and sample metadata, giving realistic performance numbers for your specific cohort size and variant density.
37
+
38
+ ---
39
+
40
+ ## Report Format
41
+
42
+ The JSON report contains timing results for each query type:
43
+
44
+ ```json
45
+ {
46
+ "n_samples": 1000,
47
+ "n_variants": 10000,
48
+ "genome_build": "GRCh38",
49
+ "queries": {
50
+ "point_query_cold_ms": 87.3,
51
+ "point_query_warm_ms": 9.1,
52
+ "region_query_1mbp_ms": 312.4,
53
+ "batch_query_100_ms": 198.7,
54
+ "annotation_100_variants_ms": 523.1
55
+ },
56
+ "timestamp": "2026-03-16T10:00:00Z"
57
+ }
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Interpreting Results
63
+
64
+ | Metric | Target | Notes |
65
+ |--------|--------|-------|
66
+ | `point_query_cold_ms` | < 100 ms | First query after cold start; includes Parquet I/O |
67
+ | `point_query_warm_ms` | < 20 ms | Subsequent queries; OS page cache active |
68
+ | `region_query_1mbp_ms` | < 500 ms | Depends on variant density in region |
69
+ | `batch_query_100_ms` | < 300 ms | 100 pre-specified variants on same chromosome |
70
+ | `annotation_100_variants_ms` | < 2000 ms | VCF annotation, single-threaded |
71
+
72
+ If `point_query_cold_ms` exceeds 500 ms, check disk I/O performance. If `point_query_warm_ms` is slow, check available RAM for OS page cache.
73
+
74
+ ---
75
+
76
+ ## Synthetic Data Generation
77
+
78
+ The benchmark generates:
79
+ - `N` samples with random sex (50/50) and random technology assignment
80
+ - `M` variants per chromosome with uniformly random positions
81
+ - Random genotypes with configurable carrier rates
82
+
83
+ Synthetic data is written to a temporary directory and cleaned up after the benchmark completes.
84
+
85
+ ---
86
+
87
+ ## Tracking Performance Over Time
88
+
89
+ Run the benchmark after major updates to detect regressions:
90
+
91
+ ```bash
92
+ # Before update
93
+ afquery benchmark --db-dir ./db/ --output before.json
94
+
95
+ # After adding 500 samples
96
+ afquery update-db --db ./db/ --add-samples new_batch.tsv
97
+ afquery benchmark --db-dir ./db/ --output after.json
98
+
99
+ # Compare
100
+ python3 -c "
101
+ import json
102
+ before = json.load(open('before.json'))['queries']
103
+ after = json.load(open('after.json'))['queries']
104
+ for k in before:
105
+ delta = after[k] - before[k]
106
+ print(f'{k}: {before[k]:.1f} → {after[k]:.1f} ms ({delta:+.1f})')
107
+ "
108
+ ```
@@ -0,0 +1,111 @@
1
+ # FILTER=PASS Tracking
2
+
3
+ Schema version 2 introduces tracking of variants that are called but fail quality filters (`FILTER≠PASS`). This allows distinguishing between "variant not present" and "variant present but low quality" in your cohort.
4
+
5
+ ---
6
+
7
+ ## Background: VCF FILTER Field
8
+
9
+ In VCF format, the FILTER column indicates whether a variant call passed quality filters:
10
+ - `PASS` or `.` (missing) — the variant passed all filters
11
+ - Any other value (e.g., `LowQual`, `VQSRTrancheSNP99.90to100.00`) — the variant failed one or more filters
12
+
13
+ AFQuery default behavior:
14
+ - **PASS-only** (default): only `FILTER=PASS` variants are counted in AC/AN
15
+ - **All filters** (`--include-all-filters`): all variant calls are counted regardless of FILTER status
16
+
17
+ ---
18
+
19
+ ## Schema v2: fail_bitmap
20
+
21
+ In schema v2, AFQuery stores a third bitmap per variant alongside `het_bitmap` and `hom_bitmap`:
22
+
23
+ - **`fail_bitmap`** — bit set for each sample that has a non-ref genotype (AC>0) AND `FILTER≠PASS`
24
+
25
+ This means:
26
+ - A sample in `fail_bitmap` was genotyped with the alt allele but the call failed QC
27
+ - Such samples are **not** counted in AC/AN (they don't affect AF)
28
+ - Their count is exposed as `FAIL_SAMPLES` / `N_FAIL`
29
+
30
+ ---
31
+
32
+ ## Enabling Schema v2
33
+
34
+ Schema v2 is the default for new databases. The `fail_bitmap` is always written; its content depends on the `--include-all-filters` flag:
35
+
36
+ ```bash
37
+ # Default: PASS-only ingestion (fail_bitmap tracks failed calls)
38
+ afquery create-db --manifest manifest.tsv --output-dir ./db/ --genome-build GRCh38
39
+
40
+ # Include all filters: AC/AN counts all calls; fail_bitmap is all-zeros
41
+ afquery create-db --manifest manifest.tsv --output-dir ./db/ --genome-build GRCh38 \
42
+ --include-all-filters
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Querying FAIL_SAMPLES
48
+
49
+ ### CLI
50
+
51
+ The `FAIL_SAMPLES` count is shown automatically in query output for v2 databases:
52
+
53
+ ```bash
54
+ afquery query --db ./db/ --chrom chr1 --pos 925952
55
+ ```
56
+
57
+ ```
58
+ chr1:925952
59
+ REF=G ALT=A AC=142 AN=2742 AF=0.0518 N_HET=138 N_HOM_ALT=2 FAIL=7
60
+ ```
61
+
62
+ `FAIL=7` means 7 eligible samples had the alt allele called but with FILTER≠PASS.
63
+
64
+ ### Python API
65
+
66
+ ```python
67
+ results = db.query("chr1", pos=925952)
68
+ for r in results:
69
+ print(f"AC={r.AC} AN={r.AN} FAIL={r.N_FAIL}")
70
+ if r.N_FAIL is not None and r.N_FAIL > 0:
71
+ print(f" Warning: {r.N_FAIL} samples have low-quality calls at this site")
72
+ ```
73
+
74
+ `N_FAIL` is `0` for v1 databases (no fail tracking), not `None`. Use `QueryResult.N_FAIL` directly.
75
+
76
+ ---
77
+
78
+ ## VCF Annotation
79
+
80
+ Schema v2 databases add an additional INFO field to annotated VCFs:
81
+
82
+ | Field | Type | Description |
83
+ |-------|------|-------------|
84
+ | `AFQUERY_N_FAIL` | Integer | Eligible samples with FILTER≠PASS at this variant |
85
+
86
+ ```bash
87
+ afquery annotate --db ./db/ --input variants.vcf --output annotated.vcf
88
+ ```
89
+
90
+ v1 databases do not write `AFQUERY_N_FAIL`.
91
+
92
+ ---
93
+
94
+ ## Migration from v1
95
+
96
+ v1 databases (no `schema_version` in `manifest.json`, or `schema_version < 2.0`) are fully compatible with AFQuery v2 code. They return `N_FAIL=0` in query results and do not write `AFQUERY_N_FAIL` in annotation.
97
+
98
+ There is no automatic migration from v1 to v2. To gain fail tracking, rebuild the database with `create-db`.
99
+
100
+ ---
101
+
102
+ ## When to Use --include-all-filters
103
+
104
+ By default (PASS-only), AF reflects the quality-filtered allele frequency — the frequency of the alt allele among high-quality calls. This is the recommended setting for most clinical and research use cases.
105
+
106
+ Use `--include-all-filters` when:
107
+ - You want to count all called genotypes regardless of quality
108
+ - You are studying filter performance or variant calling artifacts
109
+ - Your upstream pipeline handles filtering separately
110
+
111
+ With `--include-all-filters`, `fail_bitmap` stores zeros for all variants (since all calls are counted in AC/AN).