PGx: all CPIC gene kinds — G6PD, variant-based, HLA tags, *0 CN genes (PGX-006/007/021)
- DB build resolves the PGX-007 tag variants from the manifest's probes; coverage over all gene kinds. - Variant-based genes reported per variant (rs.. REF/ALT | Reference) with the CPIC lowest-activity diplotype; tagging genes as positive/negative with the population caveat; GSTM1/GSTT1/UGT2B17 *0 from targeted CN. - Case-insensitive phenotype keys (MT-RNR1). GeT-RM check extended to VKORC1 and the CN genes: 131/144 gene calls (docs/validation/V2_pgx_star.md). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01814GKVzZVL3FuF3DeiZXhv
This commit is contained in:
1 parent
4dafce3f18
commit
58dbd8f514
10 files changed
+336
-23
No files matched your search
@@ -48,16 +48,24 @@ def build_table(gene: str, rows: list[tuple[str, str | None, str]], alleles: lis
|
||||
by_key: dict[str, tuple[str, str | None]] = {}
|
||||
totals: dict[float, str] = {}
|
||||
for key, total, pheno in rows:
|
||||
k = json.dumps(json.loads(key), sort_keys=True)
|
||||
k = _canon(json.loads(key))
|
||||
by_key.setdefault(k, (pheno, total))
|
||||
t = _num(total)
|
||||
if t is not None:
|
||||
totals.setdefault(t, pheno)
|
||||
func = {a: f for a, f, _ in alleles}
|
||||
func: dict[str, str | None] = {a: f.lower() for a, f, _ in alleles if f}
|
||||
act = {a: _num(v) for a, _, v in alleles}
|
||||
return GuidelineTable(gene, by_key, sorted(totals.items()), func, act)
|
||||
|
||||
|
||||
def _canon(counts: dict[str, int]) -> str:
|
||||
"""Case-insensitive lookup key (CPIC mixes "Normal risk …" and "normal risk …", MT-RNR1)."""
|
||||
merged: dict[str, int] = {}
|
||||
for k, v in counts.items():
|
||||
merged[k.lower()] = merged.get(k.lower(), 0) + v
|
||||
return json.dumps(merged, sort_keys=True)
|
||||
|
||||
|
||||
def _split(allele: str) -> tuple[str, int]:
|
||||
m = re.fullmatch(r"(.+?)x(\d+|N)", allele)
|
||||
if not m:
|
||||
@@ -80,7 +88,7 @@ def phenotype(t: GuidelineTable, haplotypes: tuple[str, ...]) -> PhenotypeResult
|
||||
for v in vals:
|
||||
s = f"{v:.1f}" if abs(v * 10 - round(v * 10)) < 1e-9 else f"{v:g}"
|
||||
counts[s] = counts.get(s, 0) + 1
|
||||
key = json.dumps(counts, sort_keys=True)
|
||||
key = _canon(counts)
|
||||
total = sum(vals)
|
||||
hit = t.by_key.get(key)
|
||||
if hit is not None:
|
||||
@@ -92,13 +100,13 @@ def phenotype(t: GuidelineTable, haplotypes: tuple[str, ...]) -> PhenotypeResult
|
||||
funcs: dict[str, int] = {}
|
||||
for h in haplotypes:
|
||||
core, n = _split(h)
|
||||
f = "No function" if core in deletion else t.function.get(core)
|
||||
f = "no function" if core in deletion else t.function.get(core)
|
||||
if f is None:
|
||||
return PhenotypeResult("Indeterminate", None, ["phenotype_not_in_guideline"])
|
||||
if n > 1:
|
||||
return PhenotypeResult("Indeterminate", None, ["phenotype_not_in_guideline"]) # no CPIC rule for xN here
|
||||
funcs[f] = funcs.get(f, 0) + 1
|
||||
hit = t.by_key.get(json.dumps(funcs, sort_keys=True))
|
||||
hit = t.by_key.get(_canon(funcs))
|
||||
if hit is None:
|
||||
return PhenotypeResult("Indeterminate", None, ["phenotype_not_in_guideline"])
|
||||
return PhenotypeResult(hit[0], None)
|
||||
|
||||
@@ -40,6 +40,9 @@ class GeneModel:
|
||||
freq: F64 # (H,) population frequency of each haplotype (prior)
|
||||
deletion_allele: str | None = None # named whole-gene deletion (CYP2D6 *5), or "*0"
|
||||
reference: str = ""
|
||||
basis: str = "star_allele" # PGX-002 result basis: star_allele | haplotype_named | variant_based | tagging_variant
|
||||
activity: dict[str, float] = field(default_factory=dict) # allele → activity value (variant-based ordering)
|
||||
function: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
Reference in new issue
Block a user