PGx: full call detail (PGX-016..020, 027/028, 031), pgx coverage (PGX-009/035), full HGVS
ci / test (3.12) (push) Failing after 9s
ci / test (3.13) (push) Failing after 11s

- supporting_variants.tsv, unresolved_variants.tsv, variants_not_covered.tsv with PGX-026 metadata; per-allele
  supporting/unresolved sites for both alleles of every candidate; variant_change vs top, distant_alternative,
  unexplained_variants, alleles_impacted_by_no_call, alleles_tested.
- simurg pgx coverage: callable / not_covered / masked / equivalent_to per allele and manifest.
- HGVS on GRCh38 RefSeq accessions (NC_...:g.).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01814GKVzZVL3FuF3DeiZXhv
This commit is contained in:
kemalandClaude Opus 5.5 committed 2026-09-29 16:16:39 +01:00
1 parent 0215af9c9c
commit 4dafce3f18
10 files changed
+352 -40

No files matched your search

+71 -3
View File
@@ -113,7 +113,7 @@ def run_db_build(o: PgxDbOptions, rep: Reporter) -> int:
"""``simurg pgx db build`` (CLI-035)."""
from simurg.formats.bpm import read_bpm
from simurg.formats.fasta import ContigMap, Genome
from simurg.pgx.db import cpic_definitions, result_basis
from simurg.pgx.db import cpic_definitions, full_hgvs, result_basis
cdir = _latest(o.sources, "cpic")
if cdir is None:
@@ -145,14 +145,14 @@ def run_db_build(o: PgxDbOptions, rep: Reporter) -> int:
continue
pos, ref, alt = s.alleles[val]
rows["allele_variants"].append(
(gene, d.name, "GRCh38", s.chrom, pos, ref, alt, s.hgvs, s.rsid, lid, val)
(gene, d.name, "GRCh38", s.chrom, pos, ref, alt, full_hgvs(s.chrom, s.hgvs), s.rsid, lid, val)
)
for lid in d.wildcards:
rows["allele_wildcards"].append((gene, d.name, lid))
for pop, f in sorted(d.frequencies.items()):
rows["frequencies"].append((gene, d.name, pop, f))
for lid, s in sorted(sites.items()):
rows["sites"].append((s.gene, lid, s.chrom, s.hgvs, s.rsid, s.ref_value))
rows["sites"].append((s.gene, lid, s.chrom, full_hgvs(s.chrom, s.hgvs), s.rsid, s.ref_value))
for val, (pos, ref, alt) in sorted(s.alleles.items()):
rows["site_values"].append((s.gene, lid, val, pos, ref, alt))
results = {r["id"]: r for r in t["gene_result"]}
@@ -221,3 +221,71 @@ def run_db_build(o: PgxDbOptions, rep: Reporter) -> int:
rep.info(f"PGx DB: {len(defs)} genes, {len(rows['alleles'])} alleles, {len(rows['allele_variants'])} variant "
f"rows, {len(rows['probe_map'])} probe links → {final} (content sha256 {sha})") # fmt: skip
return ExitCode.OK
# ------------------------------------------------------------------------------------------------ coverage
def coverage_rows(db: Path, manifest: str) -> list[dict[str, Any]]:
"""PGX-009 / PGX-035: per allele, callable | not_covered | masked | equivalent_to:<allele> on a manifest."""
import numpy as np
from simurg.pipeline.pgx_star import load_models
con = sqlite3.connect(db)
links: dict[tuple[str, int, str, str], list[str]] = {}
for probe, c, p, r, a in con.execute("SELECT probe_name, chrom, pos, ref, alt FROM probe_map WHERE manifest = ?",
(manifest,)): # fmt: skip
links.setdefault((c, p, r, a), []).append(probe)
masked = {p for (p,) in con.execute("SELECT probe_name FROM masks WHERE manifest = ?", (manifest,))}
basis = dict(con.execute("SELECT gene, result_basis FROM genes").fetchall())
kinds = {(g, a): (k, d) for g, a, k, d in con.execute("SELECT gene, allele, kind, defined FROM alleles")}
con.close()
out = []
for gene, m in sorted(load_models(db, "global").items()):
usable = np.array([bool(links.get(v)) and not all(pr in masked for pr in links[v]) for v in m.variants])
masked_v = np.array([bool(links.get(v)) and all(pr in masked for pr in links[v]) for v in m.variants])
sig: dict[bytes, list[int]] = {}
for h in range(len(m.haplotypes)):
sig.setdefault(np.where(usable, m.carries[h], -9).tobytes(), []).append(h)
rep = {}
for members in sig.values():
best = max(members, key=lambda h: (m.freq[h], -h))
for h in members:
rep[h] = best
for h, name in enumerate(m.haplotypes):
defining = m.carries[h] == 1
probes = sorted({pr for k in np.flatnonzero(defining).tolist() for pr in links.get(m.variants[k], [])})
if defining.any() and not (defining & usable).any():
status = "masked" if (defining & masked_v).any() else "not_covered"
elif rep[h] != h:
status = f"equivalent_to:{m.haplotypes[rep[h]]}"
else:
status = "callable"
out.append({"gene": gene, "allele": name, "allele_kind": kinds.get((gene, name), ("", 1))[0],
"result_basis": basis.get(gene, ""), "status": status,
"covering_probes": ";".join(probes)}) # fmt: skip
con = sqlite3.connect(db)
for gene, allele, kind in con.execute("SELECT gene, allele, kind FROM alleles WHERE defined = 0 ORDER BY 1, 2"):
out.append({"gene": gene, "allele": allele, "allele_kind": kind, "result_basis": basis.get(gene, ""),
"status": "not_covered", "covering_probes": ""}) # fmt: skip
con.close()
return out
def run_coverage(bpm: Path, db: Path, out: Path, rep: Reporter) -> int:
"""``simurg pgx coverage --bpm --db`` (PGX-035)."""
from simurg.formats.bpm import read_bpm
from simurg.pipeline.pgx_pheno import db_meta, tsv_header, write_meta_tsv
name = read_bpm(bpm).manifest_name
rows = coverage_rows(db, name)
if not any(r["covering_probes"] for r in rows):
rep.warn("W_PGX_NO_PROBE_MAP", f"the PGx DB has no probe map for {name}; rebuild it with --manifests")
out.mkdir(parents=True, exist_ok=True)
cols = ["gene", "allele", "allele_kind", "result_basis", "status", "covering_probes"]
write_meta_tsv(out / "pgx_coverage.tsv", tsv_header(db, db_meta(db), "none")[:-1], cols, rows)
counts: dict[str, int] = {}
for r in rows:
k = r["status"].split(":")[0]
counts[k] = counts.get(k, 0) + 1
rep.info(f"{name}: " + ", ".join(f"{k} {v}" for k, v in sorted(counts.items())))
return ExitCode.OK