allelix 2.2.3__tar.gz → 2.3.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.
- {allelix-2.2.3 → allelix-2.3.0}/PKG-INFO +8 -3
- {allelix-2.2.3 → allelix-2.3.0}/README.md +7 -2
- {allelix-2.2.3 → allelix-2.3.0}/allelix/cli/_helpers.py +38 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/cli/_options.py +14 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/cli/analyze.py +4 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/reports/_pipeline.py +53 -1
- allelix-2.3.0/allelix/reports/vcf.py +579 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix.egg-info/PKG-INFO +8 -3
- {allelix-2.2.3 → allelix-2.3.0}/allelix.egg-info/SOURCES.txt +2 -0
- {allelix-2.2.3 → allelix-2.3.0}/pyproject.toml +1 -1
- allelix-2.3.0/tests/test_cli_vcf_out.py +369 -0
- {allelix-2.2.3 → allelix-2.3.0}/LICENSE +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/__init__.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/annotators/__init__.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/annotators/alphamissense.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/annotators/base.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/annotators/cadd.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/annotators/clinvar.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/annotators/gnomad.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/annotators/gwas.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/annotators/pharmgkb.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/annotators/snpedia.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/cli/__init__.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/cli/config.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/cli/db.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/cli/focused.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/cli/utility.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/compare.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/config.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/data/__init__.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/data/clinvar_clnsig_snapshot.yaml +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/data/high_value_snps.yaml +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/__init__.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/_versions.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/alphamissense_loader.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/cadd_loader.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/cpic_loader.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/gnomad_loader.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/gwas_loader.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/loader_utils.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/manager.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/pharmgkb_loader.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/schema.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/snpedia_loader.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/databases/snpedia_parser.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/exporters/__init__.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/exporters/plink.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/models.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/__init__.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/_helpers.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/ancestrydna.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/base.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/ftdna.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/ftdna_famfinder.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/ftdna_illumina.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/livingdna.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/myhappygenes.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/myheritage.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/twentythreeandme.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/parsers/vcf.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/py.typed +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/reports/__init__.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/reports/diff.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/reports/high_value.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/reports/html.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/reports/json_report.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/reports/methylation.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/reports/terminal.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/utils/__init__.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/utils/allele.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix/utils/build_detect.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix.egg-info/dependency_links.txt +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix.egg-info/entry_points.txt +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix.egg-info/requires.txt +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/allelix.egg-info/top_level.txt +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/setup.cfg +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_changelog_sanitize.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_cli.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_cli_helpers.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_compare.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_config.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_end_to_end.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_extract_rsidless_gh128.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_mock_data_invariants.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_models.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_original_rsid_never_serializes.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_registry.py +0 -0
- {allelix-2.2.3 → allelix-2.3.0}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: allelix
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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>
|
|
@@ -43,7 +43,7 @@ Dynamic: license-file
|
|
|
43
43
|
|
|
44
44
|
**Open-source genotype analysis. Format-agnostic. Offline-first.**
|
|
45
45
|
|
|
46
|
-
[Website](https://allelix.io) · [Quickstart](#quickstart) · [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md)
|
|
46
|
+
[Website](https://allelix.io) · [Demo](https://analyze.allelix.io) · [Quickstart](#quickstart) · [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md)
|
|
47
47
|
|
|
48
48
|
[](https://www.python.org/downloads/)
|
|
49
49
|
[](https://pypi.org/project/allelix/)
|
|
@@ -55,7 +55,7 @@ Dynamic: license-file
|
|
|
55
55
|
|
|
56
56
|
# Allelix
|
|
57
57
|
|
|
58
|
-
> **Status:** Production. Eight parser formats (including VCF + gVCF), four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three enrichment sources (gnomAD + AlphaMissense + CADD), licensable-source gating for commercial users, dual-build ClinVar caches (GRCh37 + GRCh38), HTML/JSON/terminal reports, methylation + pharmacogenomics focused commands, report diffing, persistent config with commercial-mode safety switch. Build auto-detection from position data (ADR-0021). No regex on prose anywhere in production. The [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) tracks every release.
|
|
58
|
+
> **Status:** Production. Eight parser formats (including VCF + gVCF), four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three enrichment sources (gnomAD + AlphaMissense + CADD), licensable-source gating for commercial users, dual-build ClinVar caches (GRCh37 + GRCh38), HTML/JSON/terminal reports and annotated VCF output for pipeline integration (v2.3.0+), methylation + pharmacogenomics focused commands, report diffing, persistent config with commercial-mode safety switch. Build auto-detection from position data (ADR-0021). No regex on prose anywhere in production. The [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) tracks every release.
|
|
59
59
|
|
|
60
60
|
## Quickstart
|
|
61
61
|
|
|
@@ -80,6 +80,11 @@ allelix analyze trio.vcf.gz --sample HG002 --output report.html
|
|
|
80
80
|
|
|
81
81
|
# Filter to a custom panel (rsIDs + gene names, one per line; '#' comments and blank lines ignored)
|
|
82
82
|
allelix analyze your_genotype_file.txt --filter-file my_panel.txt --output report.html
|
|
83
|
+
|
|
84
|
+
# Annotated VCF output — VCF-in / VCF-out for pipeline integration
|
|
85
|
+
# Writes a valid VCF with annotations as INFO fields + full provenance in the header.
|
|
86
|
+
# Refuses to overwrite; requires VCF or gVCF input.
|
|
87
|
+
allelix analyze your_wgs.vcf.gz --vcf-out annotated.vcf
|
|
83
88
|
```
|
|
84
89
|
|
|
85
90
|
See [Development](#development) for source installs and running tests, [Managing your data](#managing-your-data) for cache locations and cleanup, and [Troubleshooting](#troubleshooting) for common failure states.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Open-source genotype analysis. Format-agnostic. Offline-first.**
|
|
6
6
|
|
|
7
|
-
[Website](https://allelix.io) · [Quickstart](#quickstart) · [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md)
|
|
7
|
+
[Website](https://allelix.io) · [Demo](https://analyze.allelix.io) · [Quickstart](#quickstart) · [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md)
|
|
8
8
|
|
|
9
9
|
[](https://www.python.org/downloads/)
|
|
10
10
|
[](https://pypi.org/project/allelix/)
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
# Allelix
|
|
18
18
|
|
|
19
|
-
> **Status:** Production. Eight parser formats (including VCF + gVCF), four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three enrichment sources (gnomAD + AlphaMissense + CADD), licensable-source gating for commercial users, dual-build ClinVar caches (GRCh37 + GRCh38), HTML/JSON/terminal reports, methylation + pharmacogenomics focused commands, report diffing, persistent config with commercial-mode safety switch. Build auto-detection from position data (ADR-0021). No regex on prose anywhere in production. The [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) tracks every release.
|
|
19
|
+
> **Status:** Production. Eight parser formats (including VCF + gVCF), four annotators (ClinVar + ClinPGx + GWAS Catalog + SNPedia), three enrichment sources (gnomAD + AlphaMissense + CADD), licensable-source gating for commercial users, dual-build ClinVar caches (GRCh37 + GRCh38), HTML/JSON/terminal reports and annotated VCF output for pipeline integration (v2.3.0+), methylation + pharmacogenomics focused commands, report diffing, persistent config with commercial-mode safety switch. Build auto-detection from position data (ADR-0021). No regex on prose anywhere in production. The [Changelog](https://github.com/allelix/allelix/blob/main/CHANGELOG.md) tracks every release.
|
|
20
20
|
|
|
21
21
|
## Quickstart
|
|
22
22
|
|
|
@@ -41,6 +41,11 @@ allelix analyze trio.vcf.gz --sample HG002 --output report.html
|
|
|
41
41
|
|
|
42
42
|
# Filter to a custom panel (rsIDs + gene names, one per line; '#' comments and blank lines ignored)
|
|
43
43
|
allelix analyze your_genotype_file.txt --filter-file my_panel.txt --output report.html
|
|
44
|
+
|
|
45
|
+
# Annotated VCF output — VCF-in / VCF-out for pipeline integration
|
|
46
|
+
# Writes a valid VCF with annotations as INFO fields + full provenance in the header.
|
|
47
|
+
# Refuses to overwrite; requires VCF or gVCF input.
|
|
48
|
+
allelix analyze your_wgs.vcf.gz --vcf-out annotated.vcf
|
|
44
49
|
```
|
|
45
50
|
|
|
46
51
|
See [Development](#development) for source installs and running tests, [Managing your data](#managing-your-data) for cache locations and cleanup, and [Troubleshooting](#troubleshooting) for common failure states.
|
|
@@ -496,12 +496,29 @@ def _run_analysis_command(
|
|
|
496
496
|
no_alphamissense: bool = False,
|
|
497
497
|
no_cadd: bool = False,
|
|
498
498
|
sample: str | None = None,
|
|
499
|
+
vcf_out: Path | None = None,
|
|
499
500
|
) -> None:
|
|
500
501
|
resolved = resolve_data_dir(data_dir)
|
|
501
502
|
if not no_update:
|
|
502
503
|
_maybe_refresh_databases(resolved)
|
|
503
504
|
parser = _resolve_parser(file_path, fmt, sample=sample)
|
|
504
505
|
|
|
506
|
+
# #150: --vcf-out preflight. Bail before any expensive setup so a
|
|
507
|
+
# user who mistyped the flag or aimed it at a 23andMe file finds
|
|
508
|
+
# out immediately, not after the analyze pipeline runs.
|
|
509
|
+
if vcf_out is not None:
|
|
510
|
+
if parser.name != "vcf":
|
|
511
|
+
raise click.ClickException(
|
|
512
|
+
f"--vcf-out requires VCF or gVCF input; got {parser.display_name} "
|
|
513
|
+
f"({parser.name!r}). Convert to VCF first, or drop --vcf-out to "
|
|
514
|
+
"produce the default report."
|
|
515
|
+
)
|
|
516
|
+
if vcf_out.exists():
|
|
517
|
+
raise click.ClickException(
|
|
518
|
+
f"--vcf-out refuses to overwrite an existing file: {vcf_out}. "
|
|
519
|
+
"Move or delete the existing file, or choose a different path."
|
|
520
|
+
)
|
|
521
|
+
|
|
505
522
|
from allelix.config import load_config
|
|
506
523
|
|
|
507
524
|
cfg = load_config(resolved)
|
|
@@ -554,6 +571,7 @@ def _run_analysis_command(
|
|
|
554
571
|
cadd=cast("CaddAnnotator | None", cadd_annotator),
|
|
555
572
|
high_value_rsids=hv_rsids,
|
|
556
573
|
panel_rsids=rsids,
|
|
574
|
+
collect_vcf_write_inputs=vcf_out is not None,
|
|
557
575
|
)
|
|
558
576
|
finally:
|
|
559
577
|
_unwire_parser_logging(counter, stderr_handler, snapshot)
|
|
@@ -650,6 +668,26 @@ def _run_analysis_command(
|
|
|
650
668
|
)
|
|
651
669
|
console.print(f"[green]Wrote {rendered:,} annotation(s) to {output}[/green]")
|
|
652
670
|
|
|
671
|
+
if vcf_out is not None:
|
|
672
|
+
from allelix.reports.vcf import render_vcf
|
|
673
|
+
|
|
674
|
+
# Pipeline populates result.vcf_write_inputs whenever the
|
|
675
|
+
# ``collect_vcf_write_inputs`` flag is set — which the preflight
|
|
676
|
+
# above guarantees for this branch — so the None case here is
|
|
677
|
+
# a defensive assert, not a runtime path.
|
|
678
|
+
assert result.vcf_write_inputs is not None
|
|
679
|
+
stamped = render_vcf(
|
|
680
|
+
input_path=file_path,
|
|
681
|
+
output_path=vcf_out,
|
|
682
|
+
build=result.build,
|
|
683
|
+
annotators_used=result.annotators_used,
|
|
684
|
+
annotations_by_key=result.vcf_write_inputs.annotations_by_key,
|
|
685
|
+
recovered_rsids=result.vcf_write_inputs.recovered_rsids,
|
|
686
|
+
)
|
|
687
|
+
console.print(
|
|
688
|
+
f"[green]Wrote annotated VCF with {stamped:,} stamped row(s) to {vcf_out}[/green]"
|
|
689
|
+
)
|
|
690
|
+
|
|
653
691
|
console.print(
|
|
654
692
|
f"[dim]{len(result.annotations):,} total annotation(s) from {len(ready)} "
|
|
655
693
|
f"database(s) across {result.total_variants:,} variant(s).[/dim]"
|
|
@@ -169,3 +169,17 @@ _SAMPLE_OPT = click.option(
|
|
|
169
169
|
"or any plain VCF tool to list available samples."
|
|
170
170
|
),
|
|
171
171
|
)
|
|
172
|
+
# #150: annotated VCF output. Requires VCF/gVCF input (any other format
|
|
173
|
+
# errors out early). Existing output paths are refused (no clobber, no
|
|
174
|
+
# --force in v2.3.0). See ADR-0036 for the output contract.
|
|
175
|
+
_VCF_OUT_OPT = click.option(
|
|
176
|
+
"--vcf-out",
|
|
177
|
+
"vcf_out",
|
|
178
|
+
type=click.Path(dir_okay=False, path_type=Path),
|
|
179
|
+
default=None,
|
|
180
|
+
help=(
|
|
181
|
+
"Write an annotated VCF at this path (v2.3.0+; experimental output "
|
|
182
|
+
"contract, ADR-0036). Requires VCF or gVCF input. Refuses to "
|
|
183
|
+
"overwrite an existing file."
|
|
184
|
+
),
|
|
185
|
+
)
|
|
@@ -29,6 +29,7 @@ from allelix.cli._options import (
|
|
|
29
29
|
_REPORT_FORMAT_OPT,
|
|
30
30
|
_SAMPLE_OPT,
|
|
31
31
|
_SNPEDIA_MIN_MAG_OPT,
|
|
32
|
+
_VCF_OUT_OPT,
|
|
32
33
|
)
|
|
33
34
|
|
|
34
35
|
if TYPE_CHECKING:
|
|
@@ -61,6 +62,7 @@ if TYPE_CHECKING:
|
|
|
61
62
|
@_NO_ALPHAMISSENSE_OPT
|
|
62
63
|
@_NO_CADD_OPT
|
|
63
64
|
@_SAMPLE_OPT
|
|
65
|
+
@_VCF_OUT_OPT
|
|
64
66
|
def analyze(
|
|
65
67
|
file_path: Path,
|
|
66
68
|
fmt: str | None,
|
|
@@ -82,6 +84,7 @@ def analyze(
|
|
|
82
84
|
no_alphamissense: bool,
|
|
83
85
|
no_cadd: bool,
|
|
84
86
|
sample: str | None,
|
|
87
|
+
vcf_out: Path | None,
|
|
85
88
|
) -> None:
|
|
86
89
|
"""Annotate a genotype file against all ready reference databases."""
|
|
87
90
|
filter_genes: frozenset[str] | None = None
|
|
@@ -113,4 +116,5 @@ def analyze(
|
|
|
113
116
|
no_alphamissense=no_alphamissense,
|
|
114
117
|
no_cadd=no_cadd,
|
|
115
118
|
sample=sample,
|
|
119
|
+
vcf_out=vcf_out,
|
|
116
120
|
)
|
|
@@ -39,6 +39,7 @@ if TYPE_CHECKING:
|
|
|
39
39
|
from allelix.annotators.gnomad import GnomadAnnotator
|
|
40
40
|
from allelix.models import Annotation, Variant
|
|
41
41
|
from allelix.parsers.base import GenotypeParser
|
|
42
|
+
from allelix.reports.vcf import VcfWriteInputs
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
# How many input variants to buffer while waiting for detection to
|
|
@@ -155,6 +156,12 @@ class AnalysisResult:
|
|
|
155
156
|
# Used by the CLI to surface the GRCh38 rsID-less undercount
|
|
156
157
|
# warning until #62's dbSNP resolution lands.
|
|
157
158
|
rsidless_variant_count: int = 0
|
|
159
|
+
# #150: pipeline-produced inputs for the annotated VCF writer.
|
|
160
|
+
# Populated only when ``run_analysis`` is called with
|
|
161
|
+
# ``collect_vcf_write_inputs=True`` — the ``--vcf-out`` code path.
|
|
162
|
+
# None on every other analyze run (zero memory / CPU overhead on the
|
|
163
|
+
# default path). Consumed by ``allelix.reports.vcf.render_vcf``.
|
|
164
|
+
vcf_write_inputs: VcfWriteInputs | None = None
|
|
158
165
|
|
|
159
166
|
def panel_coverage(
|
|
160
167
|
self, filtered_annotations: list[Annotation] | None = None
|
|
@@ -439,6 +446,7 @@ def run_analysis(
|
|
|
439
446
|
cadd: CaddAnnotator | None = None,
|
|
440
447
|
high_value_rsids: set[str] | None = None,
|
|
441
448
|
panel_rsids: frozenset[str] | None = None,
|
|
449
|
+
collect_vcf_write_inputs: bool = False,
|
|
442
450
|
) -> AnalysisResult:
|
|
443
451
|
"""Stream the file once; batch-annotate; return a fully-populated result.
|
|
444
452
|
|
|
@@ -515,6 +523,13 @@ def run_analysis(
|
|
|
515
523
|
# back to position-keyed gnomAD / AlphaMissense lookups for resolved
|
|
516
524
|
# rsIDs that don't appear in those caches' rsid index.
|
|
517
525
|
resolved_coords: dict[str, tuple[str, int, str, str]] = {}
|
|
526
|
+
# #150: annotation dict keyed by (chrom, pos, ref, alt) for the
|
|
527
|
+
# annotated-VCF writer. Populated per batch inside ``_flush`` when
|
|
528
|
+
# ``collect_vcf_write_inputs`` is True. Values are references to the
|
|
529
|
+
# Annotation objects in ``annotations`` — enrichment mutations
|
|
530
|
+
# (gnomAD AF, AlphaMissense, CADD) that happen after streaming are
|
|
531
|
+
# visible through these refs without a per-annotation copy.
|
|
532
|
+
vcf_annotations_by_key: dict[tuple[str, int, str, str], list[Annotation]] = {}
|
|
518
533
|
|
|
519
534
|
with contextlib.ExitStack() as stack:
|
|
520
535
|
bound = [stack.enter_context(a) for a in annotators]
|
|
@@ -617,8 +632,29 @@ def run_analysis(
|
|
|
617
632
|
nonlocal no_ref_total, rsidless_total
|
|
618
633
|
no_ref_total += sum(1 for v in batch_buf if v.ref is None)
|
|
619
634
|
rsidless_total += sum(1 for v in batch_buf if not v.rsid.startswith("rs"))
|
|
635
|
+
batch_new: list[Annotation] = []
|
|
620
636
|
for annotator in bound:
|
|
621
|
-
|
|
637
|
+
batch_new.extend(annotator.batch_annotate(batch_buf))
|
|
638
|
+
annotations.extend(batch_new)
|
|
639
|
+
# #150: collect writer inputs for this batch. Only touches
|
|
640
|
+
# variants whose ``ref`` is populated (VCF inputs; array
|
|
641
|
+
# data with ref=None can't produce a well-formed VCF key).
|
|
642
|
+
# Skips annotations with empty ``alt`` — GWAS rows keyed by
|
|
643
|
+
# rsID alone can't be VCF-stamped without allele context.
|
|
644
|
+
if collect_vcf_write_inputs:
|
|
645
|
+
rsid_to_coords: dict[str, tuple[str, int, str]] = {
|
|
646
|
+
v.rsid: (v.chromosome, v.position, v.ref)
|
|
647
|
+
for v in batch_buf
|
|
648
|
+
if v.ref is not None
|
|
649
|
+
}
|
|
650
|
+
for a in batch_new:
|
|
651
|
+
if not a.alt:
|
|
652
|
+
continue
|
|
653
|
+
coords = rsid_to_coords.get(a.rsid)
|
|
654
|
+
if coords is None:
|
|
655
|
+
continue
|
|
656
|
+
chrom, pos, ref = coords
|
|
657
|
+
vcf_annotations_by_key.setdefault((chrom, pos, ref, a.alt), []).append(a)
|
|
622
658
|
batch_buf.clear()
|
|
623
659
|
|
|
624
660
|
def _accept(v: Variant) -> None:
|
|
@@ -733,6 +769,21 @@ def run_analysis(
|
|
|
733
769
|
result_annotated = None
|
|
734
770
|
result_genotypes = None
|
|
735
771
|
|
|
772
|
+
vcf_write_inputs: VcfWriteInputs | None = None
|
|
773
|
+
if collect_vcf_write_inputs:
|
|
774
|
+
# Import here to avoid a top-level cycle with allelix.reports.vcf
|
|
775
|
+
# (which reaches into the annotator registry for attribution).
|
|
776
|
+
from allelix.reports.vcf import VcfWriteInputs as _VcfWriteInputs
|
|
777
|
+
|
|
778
|
+
vcf_recovered_rsids = {
|
|
779
|
+
(chrom, pos, ref, alt): rsid
|
|
780
|
+
for rsid, (chrom, pos, ref, alt) in resolved_coords.items()
|
|
781
|
+
}
|
|
782
|
+
vcf_write_inputs = _VcfWriteInputs(
|
|
783
|
+
annotations_by_key=vcf_annotations_by_key,
|
|
784
|
+
recovered_rsids=vcf_recovered_rsids,
|
|
785
|
+
)
|
|
786
|
+
|
|
736
787
|
return AnalysisResult(
|
|
737
788
|
file_path=file_path,
|
|
738
789
|
parser_name=parser.name,
|
|
@@ -751,6 +802,7 @@ def run_analysis(
|
|
|
751
802
|
panel_genotypes=result_genotypes,
|
|
752
803
|
no_ref_variant_count=no_ref_total,
|
|
753
804
|
rsidless_variant_count=rsidless_total,
|
|
805
|
+
vcf_write_inputs=vcf_write_inputs,
|
|
754
806
|
)
|
|
755
807
|
|
|
756
808
|
|