# V2 — PGx DB and star-allele calling (PGX-001 … PGX-021) Status: **implemented for CPIC star-allele genes, interim validation** (2026-09-29). * `scripts/pgx/fetch_sources.sh` → CPIC API tables (CC0, data version 2026-08-03). * `simurg pgx db build` → deterministic SQLite: 22 genes, 1354 alleles, 1897 normalised variant rows; GSA v4 PGx probe map 5002 links; two builds byte-identical. * `simurg pgx star-allele call` → `diplotype_summary.tsv`, `pgx_calls.json` (11 star-allele genes). * `simurg pgx phenotype` (alias `annotate`) → per-sample `.pgx.json` (`simurg.pgx/1`) and `diplotype_annotations.tsv` with `#` metadata lines (PGX-024 … PGX-028); CPIC activity scores (xN = N × activity, *5/*0 = 0) and function-count lookups; disagreeing feasible solutions → `Indeterminate` plus per-solution phenotypes. `tests/unit/test_pgx_phenotype.py`. * Unit tests: `tests/unit/test_pgx_db.py`, `tests/unit/test_pgx_star.py`. ## Data and truth * Arrays: the GSA v4 PGx demo (36 arrays = 30 CEPH/1000G individuals, `scripts/pgx/demo36_identity.tsv`), SNV VCFs and PGx CN VCFs from SimurgArray. * Star-allele truth: GeT-RM consolidated PGx/HLA table (CDC, 2025 V4) — 16 of the 30 individuals (`scripts/pgx/getrm_truth.py`). * Probe mask (PGX-010): array vs 1000G 30x (NYGC phased panel, fetched per gene over HTTPS) for the **other 14 individuals** (`scripts/pgx/derive_masks.py`), so the mask is independent of the GeT-RM validation set: 8 of 658 assessed records (23 probe names) below 95 % concordance — e.g. CYP2C19 rs367543002 and CYP2D6 rs1135836 called homozygous ALT in every sample, the UGT1A1 `C>CATAT` TA-repeat probe. Regenerate with the command in the script header (the mask lists vendor probe names and is not committed). ## Result (16 individuals; replicate arrays counted once) | Gene | Top solution = GeT-RM | Truth among array-equivalent feasible solutions | |---|---|---| | CYP2C9 | **15/15** | 15/15 | | CYP2C19 | **14/15** | 15/15 | | CYP2D6 | **12/15** | 14/15 | | CYP3A5 | 15/15 | 15/15 | | TPMT | 15/16 | 15/16 | | CYP2B6 | 12/14 | 12/14 | | CYP4F2 | 8/9 | 8/9 | | NAT2 | 8/9 | 8/9 | | NUDT15 | 1/1 | 1/1 | | **total** | **100/109 (91.7 %)** | **103/109 (94.5 %)** | PGX-032 target (≥ 90 % for CYP2D6/CYP2C19/CYP2C9 against truth): **41/45 = 91 %** (top solution). Details and every discordant call: `V2_pgx_star_getrm16.json`. Not in the total (GeT-RM uses pre-2023 names the CPIC definitions no longer carry): UGT1A1 (GeT-RM `*60`; 0/9 top) and SLCO1B1 (`*1B/*17/*21` vs CPIC `*37/*15/*20`; 9/15 top). Remaining discordances: CYP2D6 `*3/(*68)+*4` (NA12878; *68 hybrids not modelled, *3 frameshift probe), CYP2D6 *2 vs *1 where the array cannot separate them (prior picks *1), CYP2C19 *8, TPMT *21, CYP2B6 *27, CYP4F2 *2 — rare alleles whose defining variants are absent or masked on this array. ## Error model (K-029) After masking, 0 of 9 740 array calls at DB variants disagreed with 1000G 30x in any GenCall-score bin (0.15–1.0), so the per-call error is 1e-3 (GS ≥ 0.15) and errors are handled by the probe mask. The first model, ε = 0.5·(1 − GS)², let correct low-GS calls lose to the prior (CYP2C9 7/15 → 15/15 after the change). ## Caveats * 16 individuals, one array type, European (CEPH) only; masks from 14 individuals (spec: ≥ 100 samples). * CPIC now defines the GRCh38 reference as its own allele where it differs from *1 (CYP2C19 *38): a sample heterozygous at the *1-defining rs3758581 fits *1/*38 best (raw score 0.98), but *38 has no CPIC frequency, so the prior keeps *1/*1 on top — which is what legacy GeT-RM reports. * Not yet: named-haplotype (G6PD), variant-based and tagging genes; CYP2D6 hybrids (*13, *36, *68); PharmVar-only genes (CYP2A6, CYP3A4, CYP1A2, …); DPWG tables; the full per-allele supporting/unresolved-variant detail of PGX-027/028 (`supporting_variants.tsv`, `unresolved_variants.tsv`, `variants_not_covered.tsv`).