PGx: full call detail (PGX-016..020, 027/028, 031), pgx coverage (PGX-009/035), full HGVS
- 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.).
This commit is contained in:
1 parent
9d3242828e
commit
abea028818
10 files changed
+352
-40
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user