gnomancer 0.3.1__py3-none-any.whl

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.
gnomancer/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ """gnomancer: vendor-agnostic analysis of consumer raw DNA files."""
2
+ __version__ = "0.3.1"
gnomancer/__main__.py ADDED
@@ -0,0 +1,2 @@
1
+ from .cli import main
2
+ main()
gnomancer/cli.py ADDED
@@ -0,0 +1,155 @@
1
+ """Command-line entry point: python -m gnomancer <raw file> [--out DIR]"""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from . import __version__
10
+ from .load import load
11
+ from .qc import chromosome_table, summary
12
+ from .roh import find_roh, interpret
13
+ from .panel import load_markers, evaluate, combos
14
+ from .haplogroups import call_y, call_mt
15
+ from .report import write
16
+ from . import clinvar as clinvar_mod
17
+ from . import pharmcat as pharmcat_mod
18
+ from . import yleaf as yleaf_mod
19
+ from . import summary as summary_mod
20
+ from .report_html import write_html
21
+
22
+ from .paths import DATA, ancestry_python
23
+
24
+
25
+ def run_ezancestry(inp: Path, loaded) -> dict | None:
26
+ """Run ezancestry in a separate interpreter (it needs Python <=3.11) if .venv-ancestry exists, else in-process."""
27
+ import json as _json
28
+ import subprocess
29
+ code = ("import json,sys\nfrom snps import SNPs\nr=SNPs(sys.argv[1]).predict_ancestry()\n"
30
+ "print(json.dumps({k:v for k,v in r.items() if k!='ezancestry_df'}, default=str))")
31
+ venv_py = ancestry_python()
32
+ py = str(venv_py) if venv_py else sys.executable
33
+ try:
34
+ p = subprocess.run([py, "-c", code, str(inp)], capture_output=True, text=True, timeout=1800)
35
+ if p.returncode != 0:
36
+ raise RuntimeError(p.stderr.strip().splitlines()[-1] if p.stderr.strip() else "unknown error")
37
+ return _json.loads(p.stdout.strip().splitlines()[-1])
38
+ except Exception as e: # noqa: BLE001
39
+ loaded.notes.append(f"ezancestry failed: {e}")
40
+ return None
41
+
42
+
43
+ def main(argv=None):
44
+ argv = list(sys.argv[1:] if argv is None else argv)
45
+ if argv[:1] == ["serve"]:
46
+ from .mcp_server import main as serve
47
+ return serve()
48
+ if argv[:1] == ["setup"]:
49
+ from .setup_cmd import run as setup
50
+ return setup(argv[1:])
51
+ ap = argparse.ArgumentParser(prog="gnomancer", description="Analyze a consumer raw DNA file locally. Subcommands: `gnomancer setup` (download tools), `gnomancer serve` (MCP server).")
52
+ ap.add_argument("input", help="raw data file (.txt/.csv/.tsv/.vcf, optionally .zip/.gz)")
53
+ ap.add_argument("--out", default=None, help="output directory (default: <input>_analysis)")
54
+ ap.add_argument("--markers", default=str(DATA / "markers.tsv"), help="marker panel TSV")
55
+ ap.add_argument("--no-y", action="store_true", help="skip Y haplogroup")
56
+ ap.add_argument("--no-mt", action="store_true", help="skip mtDNA haplogroup")
57
+ ap.add_argument("--ancestry", action="store_true", help="run ezancestry population prediction (uses .venv-ancestry if present)")
58
+ ap.add_argument("--no-clinvar", action="store_true", help="skip the ClinVar scan")
59
+ ap.add_argument("--no-yleaf", action="store_true", help="skip the Yleaf second-opinion Y call")
60
+ ap.add_argument("--no-pharmcat", action="store_true", help="skip PharmCAT")
61
+ ap.add_argument("--summary", default=None, help="Markdown/text file with a narrative summary (e.g. written by your AI agent) to show at the top of the report")
62
+ ap.add_argument("--ai", action="store_true", help="have Claude write the narrative summary (sends the structured results, not the raw file, to the Anthropic API; needs credentials)")
63
+ ap.add_argument("--version", action="version", version=__version__)
64
+ a = ap.parse_args(argv)
65
+ run_args = argv
66
+
67
+ inp = Path(a.input)
68
+ out = Path(a.out) if a.out else inp.with_name(inp.stem.replace(".", "_") + "_analysis")
69
+ out.mkdir(parents=True, exist_ok=True)
70
+
71
+ print(f"[1/6] loading {inp} ...", file=sys.stderr)
72
+ loaded = load(str(inp))
73
+ df = loaded.df
74
+ print(f" {loaded.source}, build {loaded.build}, {loaded.count:,} SNPs, sex {loaded.sex or '?'}", file=sys.stderr)
75
+
76
+ print("[2/6] quality metrics ...", file=sys.stderr)
77
+ ct = chromosome_table(df)
78
+ qc = summary(df, ct, loaded.sex)
79
+ ct.to_csv(out / "chromosome-qc.tsv", sep="\t", index=False)
80
+
81
+ print("[3/6] runs of homozygosity ...", file=sys.stderr)
82
+ roh = find_roh(df)
83
+ roh.to_csv(out / "roh.tsv", sep="\t", index=False)
84
+ roh_text = interpret(roh)
85
+
86
+ print("[4/6] marker panel ...", file=sys.stderr)
87
+ markers = load_markers(Path(a.markers))
88
+ panel = evaluate(df, markers)
89
+ panel.to_csv(out / "marker-panel.tsv", sep="\t", index=False)
90
+ combo = combos(df)
91
+
92
+ is_male = qc["sex_inferred"] == "Male"
93
+ y = dict(error="skipped (sample is not male)" if not is_male else "skipped (--no-y)")
94
+ if is_male and not a.no_y:
95
+ print("[5/6] Y haplogroup (yhaplo) ...", file=sys.stderr)
96
+ y = call_y(df, out)
97
+ if not a.no_yleaf:
98
+ print(" second opinion on current trees (Yleaf) ...", file=sys.stderr)
99
+ y["yleaf"] = yleaf_mod.run(df, out)
100
+ mt = dict(error="skipped (--no-mt)")
101
+ if not a.no_mt:
102
+ print("[5/6] mtDNA haplogroup (haplogrep3) ...", file=sys.stderr)
103
+ mt = call_mt(df, out, loaded.build_original)
104
+
105
+ cv = dict(available=False, hits=None, note="skipped (--no-clinvar)")
106
+ if not a.no_clinvar:
107
+ print("[6/8] ClinVar scan of every typed site ...", file=sys.stderr)
108
+ cv = clinvar_mod.scan(df)
109
+ if cv["available"] and cv["hits"] is not None and not cv["hits"].empty:
110
+ cv["hits"].to_csv(out / "clinvar-hits.tsv", sep="\t", index=False)
111
+ cv_text = clinvar_mod.interpret(cv) if not a.no_clinvar else cv["note"]
112
+ pc = dict(available=False, calls=None, error="skipped (--no-pharmcat)", stats={})
113
+ if not a.no_pharmcat:
114
+ print("[7/8] PharmCAT pharmacogenomics ...", file=sys.stderr)
115
+ pc = pharmcat_mod.run(df, out)
116
+ if pc.get("calls") is not None and not pc["calls"].empty:
117
+ pc["calls"].to_csv(out / "pharmcat-calls.tsv", sep="\t", index=False)
118
+ pc_text = pharmcat_mod.interpret(pc) if not a.no_pharmcat else pc["error"]
119
+ ancestry = None
120
+ if a.ancestry:
121
+ print("[8/8] ancestry prediction (ezancestry) ...", file=sys.stderr)
122
+ ancestry = run_ezancestry(inp, loaded)
123
+
124
+ glance = summary_mod.at_a_glance(loaded, qc, roh, roh_text, combo, y, mt, ancestry, panel, cv, pc)
125
+ prompt = summary_mod.build_prompt(glance, combo, y, mt, ancestry, panel, cv, pc, qc)
126
+ summary_mod.write_prompt_file(out, prompt)
127
+ narrative = None
128
+ if a.summary:
129
+ narrative = Path(a.summary).read_text().strip()
130
+ elif a.ai:
131
+ print(" narrative summary (Claude) ...", file=sys.stderr)
132
+ try:
133
+ narrative = summary_mod.ai_summary(prompt)
134
+ (out / "summary.md").write_text(narrative + "\n")
135
+ except Exception as e: # noqa: BLE001
136
+ loaded.notes.append(f"AI summary failed: {e}")
137
+ extra = dict(clinvar=cv, clinvar_text=cv_text, pharmcat=pc, pharmcat_text=pc_text, glance=glance, narrative=narrative)
138
+ rep = write(out / "REPORT.md", loaded, qc, ct, roh, roh_text, panel, combo, y, mt, ancestry, extra)
139
+ html = write_html(out / "REPORT.html", loaded, qc, ct, roh, roh_text, panel, combo, y, mt, ancestry, input_name=inp.name, extra=extra)
140
+ json.dump(dict(input=str(inp.resolve()), args=[str(Path(x).resolve()) if Path(x).exists() else x for x in run_args],
141
+ report_html=str((out / "REPORT.html").resolve()), report_md=str((out / "REPORT.md").resolve()), glance=glance,
142
+ source=loaded.source, build=loaded.build, count=loaded.count, qc=qc,
143
+ roh_total_mb=float(roh.length_mb.sum()) if not roh.empty else 0.0, y=y, mt=mt,
144
+ combos=combo, ancestry=ancestry, notes=loaded.notes,
145
+ clinvar=dict(sites_in_clinvar=cv.get("sites_in_clinvar"), pathogenic_sites_typed=cv.get("pathogenic_sites_typed"),
146
+ hits=(cv["hits"].to_dict("records") if cv.get("hits") is not None and not cv["hits"].empty else [])),
147
+ pharmcat=dict(stats={k: v for k, v in pc.get("stats", {}).items() if k != "genes"}, error=pc.get("error"),
148
+ calls=(pc["calls"].to_dict("records") if pc.get("calls") is not None and not pc["calls"].empty else []))),
149
+ open(out / "summary.json", "w"), indent=1, default=str)
150
+ print(f"\nReport: {html}\n {rep}", file=sys.stderr)
151
+ if y.get("haplogroup"):
152
+ print(f"Y: {y['haplogroup']} ", end="", file=sys.stderr)
153
+ if mt.get("haplogroup"):
154
+ print(f"MT: {mt['haplogroup']}", end="", file=sys.stderr)
155
+ print(file=sys.stderr)
gnomancer/clinvar.py ADDED
@@ -0,0 +1,133 @@
1
+ """Scan every typed site against ClinVar (GRCh37 VCF) and report pathogenic / likely pathogenic alleles carried.
2
+
3
+ Why this matters: the curated panel covers ~110 sites; consumer chips type several thousand sites that ClinVar
4
+ classifies. This finds the rest. Why to be careful: arrays have a high false-positive rate at rare sites
5
+ (Tandy-Connor et al. 2018, Genetics in Medicine, doi:10.1038/gim.2018.38: 40% of variants from consumer raw data
6
+ were false positives on clinical confirmation), so every hit here is "worth confirming with a clinical test",
7
+ never a diagnosis.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import gzip
12
+ from pathlib import Path
13
+
14
+ import pandas as pd
15
+
16
+ from .paths import tool
17
+
18
+ DEFAULT_VCF = tool("clinvar", "clinvar_GRCh37.vcf.gz")
19
+ PATHOGENIC = ("Pathogenic", "Likely_pathogenic")
20
+ STAR = {"practice_guideline": 4, "reviewed_by_expert_panel": 3, "criteria_provided,_multiple_submitters,_no_conflicts": 2,
21
+ "criteria_provided,_single_submitter": 1, "criteria_provided,_conflicting_classifications": 1,
22
+ "criteria_provided,_conflicting_interpretations": 1, "no_assertion_criteria_provided": 0, "no_classification_provided": 0}
23
+
24
+
25
+ def _conditions(raw: str, limit: int = 4) -> str:
26
+ """'Disease_A|not_provided|Disease_B' -> 'Disease A; Disease B' (deduplicated, generic entries dropped, at most `limit`)."""
27
+ seen, out = set(), []
28
+ for c in raw.replace("_", " ").split("|"):
29
+ c = c.strip()
30
+ if not c or c.lower() in ("not provided", "not specified", "see cases") or c in seen:
31
+ continue
32
+ seen.add(c)
33
+ out.append(c)
34
+ return "; ".join(out[:limit]) + (f" (+{len(out) - limit} more)" if len(out) > limit else "")
35
+
36
+
37
+ def _info(s: str) -> dict:
38
+ d = {}
39
+ for kv in s.split(";"):
40
+ if "=" in kv:
41
+ k, v = kv.split("=", 1)
42
+ d[k] = v
43
+ return d
44
+
45
+
46
+ def scan(df: pd.DataFrame, vcf: Path = DEFAULT_VCF) -> dict:
47
+ """df: normalized SNP table (chrom, pos, genotype). Returns dict with hits table and coverage stats."""
48
+ res = dict(available=vcf.exists(), vcf=str(vcf), sites_in_clinvar=0, pathogenic_sites_typed=0, hits=pd.DataFrame(), note="")
49
+ if not vcf.exists():
50
+ res["note"] = "ClinVar VCF not found; run `gnomancer setup` (downloads ~200 MB from NCBI)."
51
+ return res
52
+ called = df[df.genotype.notna()]
53
+ key = {}
54
+ for chrom, pos, gt in zip(called.chrom, called.pos, called.genotype):
55
+ key[(chrom, int(pos))] = gt
56
+ hits = []
57
+ n_in = n_path = 0
58
+ with gzip.open(vcf, "rt") as f:
59
+ for line in f:
60
+ if line[0] == "#":
61
+ continue
62
+ p = line.rstrip("\n").split("\t", 8)
63
+ chrom = p[0]
64
+ if chrom == "MT":
65
+ chrom = "MT"
66
+ k = (chrom, int(p[1]))
67
+ gt = key.get(k)
68
+ if gt is None:
69
+ continue
70
+ n_in += 1
71
+ info = _info(p[7])
72
+ sig = info.get("CLNSIG", "")
73
+ if not any(s in sig for s in PATHOGENIC) or sig.startswith("Benign") or sig.startswith("Likely_benign"):
74
+ continue
75
+ n_path += 1
76
+ ref, alts = p[3], p[4].split(",")
77
+ if len(ref) != 1 or any(len(a) != 1 for a in alts):
78
+ continue # indels are not reliably comparable to array I/D calls
79
+ carried = [a for a in gt if a in alts]
80
+ if not carried:
81
+ continue
82
+ if any(a not in alts and a != ref for a in gt):
83
+ continue # allele mismatch (likely strand issue); do not report
84
+ zyg = "homozygous" if len(gt) == 2 and gt[0] == gt[1] else ("hemizygous" if len(gt) == 1 else "heterozygous")
85
+ rev = info.get("CLNREVSTAT", "")
86
+ # Allele frequency of the ALT allele, from whichever population resource ClinVar carries.
87
+ af = None
88
+ for k_ in ("AF_EXAC", "AF_TGP", "AF_ESP"):
89
+ if k_ in info:
90
+ try:
91
+ af = max(float(x) for x in info[k_].split(","))
92
+ except ValueError:
93
+ pass
94
+ break
95
+ flags = []
96
+ ambiguous = {ref, alts[0]} in ({"A", "T"}, {"C", "G"})
97
+ if ambiguous:
98
+ flags.append("strand-ambiguous SNP (A/T or C/G): a homozygous-reference call reported on the other strand looks exactly like this")
99
+ if af is not None and af >= 0.05:
100
+ flags.append(f"ALT allele is common (AF {af:.1%}): classification is likely outdated or low-penetrance")
101
+ if zyg != "heterozygous" and (af is None or af < 0.01):
102
+ flags.append("homozygous for a rare allele: statistically far more likely a genotyping/strand artifact than a true homozygote")
103
+ if "conflicting" in rev:
104
+ flags.append("submitters disagree on classification")
105
+ confidence = "low" if (ambiguous or (zyg != "heterozygous" and (af is None or af < 0.01)) or STAR.get(rev, 0) == 0) else ("medium" if (af or 0) >= 0.05 else "high")
106
+ hits.append(dict(rsid=("rs" + info["RS"]) if info.get("RS") else p[2], chrom=chrom, pos=k[1], genotype=gt,
107
+ ref=ref, alt=",".join(alts), zygosity=zyg, gene=info.get("GENEINFO", "").split(":")[0],
108
+ significance=sig.replace("_", " "), review=rev.replace("_", " "), stars=STAR.get(rev, 0),
109
+ condition=_conditions(info.get("CLNDN", "")),
110
+ variant=info.get("CLNHGVS", ""), alt_af=af, confidence=confidence, flags="; ".join(flags)))
111
+ res["sites_in_clinvar"], res["pathogenic_sites_typed"] = n_in, n_path
112
+ out = pd.DataFrame(hits)
113
+ if not out.empty:
114
+ out["conf_rank"] = out.confidence.map({"high": 0, "medium": 1, "low": 2})
115
+ out = out.sort_values(["conf_rank", "stars"], ascending=[True, False]).drop(columns="conf_rank")
116
+ res["hits"] = out
117
+ return res
118
+
119
+
120
+ def interpret(res: dict) -> str:
121
+ if not res["available"]:
122
+ return res["note"]
123
+ h = res["hits"]
124
+ base = (f"{res['sites_in_clinvar']:,} typed sites have a ClinVar record; {res['pathogenic_sites_typed']:,} of those are "
125
+ f"classified pathogenic or likely pathogenic for at least one submitter. ")
126
+ if h.empty:
127
+ return base + "No pathogenic or likely-pathogenic alternate allele was called at any of them."
128
+ hi = h[h.confidence == "high"]
129
+ return base + (f"{len(h)} site(s) carry a pathogenic/likely-pathogenic allele; {len(hi)} pass the plausibility checks "
130
+ "(heterozygous or common, unambiguous strand, reviewed). The rest are flagged as probable artifacts. "
131
+ "Array calls at rare sites are often wrong: when a clinical lab re-tested variants reported in consumer raw data, "
132
+ "40% were false positives (Tandy-Connor et al., Genetics in Medicine 2018, doi:10.1038/gim.2018.38). Treat every row as "
133
+ "'confirm with a clinical test', not as a diagnosis. Heterozygous hits for recessive conditions are carrier states.")
gnomancer/context.py ADDED
@@ -0,0 +1,32 @@
1
+ """Short, population-level context for Y and mtDNA haplogroups (data/haplogroup_context.tsv)."""
2
+ from __future__ import annotations
3
+
4
+ import csv
5
+ from pathlib import Path
6
+
7
+ from .paths import DATA as _DATA
8
+
9
+ DATA = _DATA / "haplogroup_context.tsv"
10
+
11
+
12
+ def _rows():
13
+ with open(DATA, newline="") as f:
14
+ lines = [l for l in f if l.strip() and not l.startswith("#")]
15
+ return list(csv.DictReader(lines, delimiter="\t"))
16
+
17
+
18
+ def lookup(kind: str, *labels: str) -> list[dict]:
19
+ """Return context rows for a haplogroup, most specific first.
20
+
21
+ kind: 'Y' or 'MT'. labels: any names for the call (e.g. short 'R-M269' and long 'R1b1a2a1a2').
22
+ Matching is by prefix on each label; results are the chain of matching prefixes, longest first,
23
+ so a report can show the lineage from broad to specific.
24
+ """
25
+ hits = {}
26
+ for r in _rows():
27
+ if r["type"] != kind:
28
+ continue
29
+ for lab in labels:
30
+ if lab and (lab == r["prefix"] or lab.startswith(r["prefix"])):
31
+ hits.setdefault(r["name"], r) # one entry per lineage name, whichever prefix matched first
32
+ return sorted(hits.values(), key=lambda r: -int(r.get("depth") or len(r["prefix"])))
@@ -0,0 +1,72 @@
1
+ # Population-level context for haplogroups. Matched by prefix against the caller's labels
2
+ # (Y: ISOGG 2016 long form as emitted by yhaplo, and the short SNP form; MT: PhyloTree 17 name).
3
+ # Keep entries short, hedged, and about populations, never individuals.
4
+ type prefix depth name text
5
+ Y A 1 Haplogroup A The deepest branches of the human Y tree, found mainly in Southern and Eastern Africa (Khoisan, Ethiopian and Sudanese groups).
6
+ Y B 1 Haplogroup B An old African lineage, most common among Central African hunter-gatherer groups and in East Africa.
7
+ Y C 1 Haplogroup C Spread across Asia, Oceania and the Americas: C-M217 is common in Mongolia, Siberia and East Asia; C-M38 in New Guinea and Melanesia; C-M130 branches reach Australia.
8
+ Y D 1 Haplogroup D Found mainly in Japan (D-M55, including Ainu), Tibet and the Andaman Islands.
9
+ Y E 1 Haplogroup E The dominant African paternal lineage, with branches that also spread into the Near East and around the Mediterranean.
10
+ Y E1b1a 2 E-M2 The lineage carried by the Bantu expansion: the majority Y lineage across West, Central and Southern Africa and in the African diaspora.
11
+ Y E1b1b 2 E-M215 / E-M35 Originated in East Africa and spread to North Africa, the Near East and Southern Europe; a major lineage in Ethiopia, Somalia, the Maghreb, Greece and the Balkans.
12
+ Y E1b1b1a 3 E-M78 Common from Egypt and Sudan through the Balkans and Southern Europe; E-V13 is a signature Balkan branch.
13
+ Y E1b1b1b1 3 E-M81 The characteristic Berber lineage of the Maghreb, also present in Iberia.
14
+ Y E1b1b1b2 3 E-M123 / E-M34 A Near-Eastern and East-Mediterranean branch: frequent in Ethiopia, the Levant, Anatolia and Arabia, a well-known component (around a tenth) of Ashkenazi, Sephardic and Mizrahi Jewish paternal lineages, and present at low frequency in Italy, Iberia and the Balkans.
15
+ Y E1b1b1b2a1a1 4 E-M84 The main sub-branch of E-M34 (YFull: E-M84). Frequent in Ethiopia and Eritrea, the Levant, Arabia and Anatolia; one of the characteristic lineages of Jewish communities from all three main diasporas; sporadic in Southern Europe.
16
+ Y E-M84 4 E-M84 The main sub-branch of E-M34. Frequent in Ethiopia and Eritrea, the Levant, Arabia and Anatolia; one of the characteristic lineages of Jewish communities from all three main diasporas; sporadic in Southern Europe.
17
+ Y E-L795 5 E-L795 A branch within E-M84 (FamilyTreeDNA naming), found in the Levant, Arabia and among Jewish lineages.
18
+ Y G 1 Haplogroup G Peaks in the Caucasus and Anatolia; carried by early Neolithic farmers into Europe (the Tyrolean Iceman was G2a), today scattered at low frequency across Europe and the Near East.
19
+ Y H 1 Haplogroup H Mostly South Asian; H1a1 is the main lineage of the Roma, reflecting their Indian origin.
20
+ Y I 1 Haplogroup I A European lineage present since the Palaeolithic, with little presence outside Europe.
21
+ Y I1 2 I-M253 Concentrated in Scandinavia and Germanic-speaking regions; strongly associated with Norse and Anglo-Saxon migrations.
22
+ Y I2 2 I-M438 I2a1 peaks in the Balkans and Sardinia; I2a2 is found across Northwest Europe including Germany, Britain and Ireland.
23
+ Y J 1 Haplogroup J Originated in the Near East and spread with Neolithic farming and later movements across the Mediterranean, Caucasus and South Asia.
24
+ Y J1 2 J-M267 Most frequent in Arabia, the Levant and the Caucasus (Dagestan); the J-P58 branch is associated with Semitic-speaking populations.
25
+ Y J2 2 J-M172 Peaks in the Fertile Crescent, Anatolia, the Caucasus and Iran; common around the Mediterranean (Italy, Greece, Balkans) and in South Asia.
26
+ Y L 1 Haplogroup L Mainly South Asian, especially Pakistan and western India, with pockets in the Near East.
27
+ Y N 1 Haplogroup N Spread from Siberia westward with Uralic speakers; the main lineage in Finland, the Baltic states and northern Russia.
28
+ Y O 1 Haplogroup O The dominant lineage of East and Southeast Asia: O-M122 (Han Chinese and neighbours), O-M119 (coastal China, Taiwan, Austronesians), O-M95 (Southeast Asia).
29
+ Y Q 1 Haplogroup Q Siberian in origin; Q-M3 is the principal Native American paternal lineage, and other branches are found in Siberia and Central Asia.
30
+ Y R 1 Haplogroup R A widespread Eurasian lineage whose main branches dominate Europe, Central Asia and South Asia.
31
+ Y R1a 2 R-M420 Most common in Eastern Europe (Poland, Russia, Ukraine), Central Asia and northern India; associated with Bronze Age steppe migrations.
32
+ Y R1b 2 R-M343 The most common paternal lineage in Western Europe (peaking in Ireland, Wales, the Basque Country); the R-V88 branch is found in Central Africa.
33
+ Y R1b1a2 3 R-M269 The main Western European branch, carried by Bronze Age migrations; R-U106 is typical of Germanic regions, R-P312 and R-L21 of Atlantic Europe, R-U152 of the Alpine region and Italy.
34
+ Y R2 2 R-M479 Mainly South Asian.
35
+ Y T 1 Haplogroup T A scattered lineage with pockets in the Near East, East Africa (Somalia), Sardinia and parts of Iberia.
36
+ MT L0 2 Haplogroup L0 Among the oldest maternal lineages, concentrated in Southern Africa (Khoisan) and East Africa.
37
+ MT L1 2 Haplogroup L1 A deep African lineage found mainly in Central and West Africa.
38
+ MT L2 2 Haplogroup L2 Widespread across Sub-Saharan Africa and the most common maternal lineage among African Americans.
39
+ MT L3 2 Haplogroup L3 East African in origin and the ancestor of every non-African maternal lineage; also common across Africa.
40
+ MT M 1 Haplogroup M One of the two founding non-African lineages, now concentrated in South Asia, East Asia and among Native Americans (via branches C and D).
41
+ MT D 1 Haplogroup D An East Asian and Siberian lineage; D1 and D4h3 are Native American founder lineages.
42
+ MT C 1 Haplogroup C Found across Siberia, Central Asia and East Asia, and a Native American founder lineage (C1).
43
+ MT N 1 Haplogroup N The other founding non-African lineage; its branches cover Europe, the Near East, Asia and the Americas.
44
+ MT A 1 Haplogroup A East Asian and Siberian, and one of the main Native American founder lineages (A2).
45
+ MT B 1 Haplogroup B East and Southeast Asian; B4 spread with Austronesians to Polynesia and B2 is a Native American founder lineage.
46
+ MT I 1 Haplogroup I A minor European and Near Eastern lineage at a few percent frequency.
47
+ MT W 1 Haplogroup W A minor lineage of Europe, the Near East and South Asia.
48
+ MT X 1 Haplogroup X A rare lineage in Europe and the Near East; the X2a branch is a Native American founder lineage.
49
+ MT R 1 Haplogroup R A large branch of N whose descendants include most European lineages.
50
+ MT F 1 Haplogroup F Common in Southeast Asia and southern China.
51
+ MT U 1 Haplogroup U An old West Eurasian lineage with branches in Europe, the Near East, North Africa and South Asia.
52
+ MT U5 2 Haplogroup U5 The characteristic lineage of European hunter-gatherers; today most frequent in Finland and the Saami.
53
+ MT U6 2 Haplogroup U6 Mainly North African (Berber populations) and Iberian.
54
+ MT K 1 Haplogroup K A branch of U8 common across Europe and the Near East; three K sub-lineages account for about a third of Ashkenazi maternal lines.
55
+ MT HV 1 Haplogroup HV The parent of H and V, found across Europe, the Near East and the Caucasus.
56
+ MT H 1 Haplogroup H The most common maternal lineage in Europe (around 40%), also frequent in the Near East and North Africa; it spread widely after the last Ice Age.
57
+ MT H1 2 Haplogroup H1 The largest H branch and the most common maternal lineage in Western Europe; it peaks in Iberia and the Basque Country and is also found in North Africa and the Near East.
58
+ MT H2 2 Haplogroup H2 Found across Europe and the Caucasus; H2a is more frequent in Eastern Europe.
59
+ MT H3 2 Haplogroup H3 A Western European branch with a peak in Iberia and Western France.
60
+ MT H5 2 Haplogroup H5 Found across Europe with higher frequency in the Balkans and Central Europe.
61
+ MT H13 2 Haplogroup H13 Most frequent in the Caucasus and Near East, with a presence in Sardinia.
62
+ MT V 1 Haplogroup V A sister of H found across Europe, with high frequency among the Saami, in Iberia and the Basque Country.
63
+ MT J 1 Haplogroup J Spread from the Near East into Europe with Neolithic farmers; around a tenth of Europeans, higher in the Near East.
64
+ MT J1 2 Haplogroup J1 The main European J branch (J1c is the most common form in Europe).
65
+ MT J2 2 Haplogroup J2 Found around the Mediterranean and the Near East.
66
+ MT T 1 Haplogroup T A Near Eastern and European lineage at about a tenth of the population in parts of Europe; associated with early farming and pastoralism.
67
+ MT T2 2 Haplogroup T2 The more common T branch in Europe, especially Northern and Central Europe.
68
+ MT N1 2 Haplogroup N1 Includes N1b, common among Ashkenazi Jews, and N1a, carried by early Neolithic farmers into Europe.
69
+ MT G 1 Haplogroup G Mainly East Asian and Siberian, including Japan and Kamchatka.
70
+ MT Z 1 Haplogroup Z Found in Siberia, Central Asia and among the Saami.
71
+ MT Q 1 Haplogroup Q Found in New Guinea and Melanesia.
72
+ MT P 1 Haplogroup P Found in Australia and New Guinea.
@@ -0,0 +1,115 @@
1
+ # Curated marker panel. Alleles are on the GRCh37 forward strand (what all major vendors report).
2
+ # category: ancestry | trait | health | carrier | pgx. effect = the allele being counted.
3
+ # interp0/1/2 = text for 0/1/2 copies of the effect allele (optional).
4
+ rsid category gene label ref effect interp0 interp1 interp2 note
5
+ rs1426654 ancestry SLC24A5 Skin pigmentation A111T G A ancestral allele on both copies (typical of Sub-Saharan African and East Asian ancestry) one light-skin allele light-skin allele on both copies (near-fixed in Europe, West and South Asia)
6
+ rs16891982 ancestry SLC45A2 Skin/hair pigmentation L374F C G ancestral on both copies one European light-pigmentation allele European light-pigmentation allele on both copies
7
+ rs3827760 ancestry EDAR V370A hair thickness / shovel incisors A G ancestral; no East Asian/Native American hair-thickness allele one copy of the East Asian/Native American allele homozygous for the East Asian/Native American allele
8
+ rs2814778 ancestry ACKR1 (DARC) Duffy null (FY*O) T C Duffy-positive one Duffy-null allele (common in West/Central African ancestry) Duffy-null: resistant to P. vivax malaria; benign lower neutrophil counts
9
+ rs4988235 ancestry LCT Lactase persistence -13910 G A non-persistent at this site persistent (dominant) persistent the European persistence allele; other populations use different alleles
10
+ rs182549 ancestry LCT Lactase persistence -22018 C T non-persistent at this site persistent (linked to -13910) persistent
11
+ rs671 ancestry ALDH2 ALDH2*2 alcohol flush G A no flush allele flush reaction, reduced acetaldehyde clearance (common in East Asia) strong flush; alcohol poorly tolerated
12
+ rs1229984 ancestry ADH1B ADH1B*2 (His48) C T *1/*1 typical of Europe and Africa one fast-metabolizing *2 allele *2/*2 fast alcohol metabolism (common in East Asia)
13
+ rs12913832 trait HERC2 Eye color (main determinant) A G brown eyes likely brown or hazel likely, green possible blue or grey eyes likely
14
+ rs1800407 trait OCA2 Eye color modifier R419Q C T no modifier green/hazel modifier present homozygous modifier
15
+ rs12896399 trait SLC24A4 Eye/hair color G T darker-associated intermediate lighter-associated
16
+ rs1042602 trait TYR S192Y pigmentation / freckling C A no lighter allele one lighter/freckling allele lighter skin, freckling more likely
17
+ rs1393350 trait TYR Eye/hair color G A darker-associated intermediate lighter-associated
18
+ rs12203592 trait IRF4 Freckles, lighter hair, sun sensitivity C T no freckle allele one freckle / lighter-hair allele freckles and sun sensitivity likely
19
+ rs1805007 trait MC1R R151C red hair C T no variant red-hair carrier (R allele) red hair likely
20
+ rs1805008 trait MC1R R160W red hair C T no variant red-hair carrier (R allele) red hair likely
21
+ rs1805009 trait MC1R D294H red hair G C no variant red-hair carrier (R allele) red hair likely
22
+ rs1805006 trait MC1R D84E red hair C A no variant red-hair carrier (R allele) red hair likely
23
+ rs11547464 trait MC1R R142H red hair G A no variant red-hair carrier (R allele) red hair likely
24
+ rs1110400 trait MC1R I155T red hair T C no variant red-hair carrier (R allele) red hair likely
25
+ rs885479 trait MC1R R163Q (weak variant) G A no variant one weak r allele homozygous weak variant (common in East Asia)
26
+ rs11803731 trait TCHH Hair curl (European) T A curlier-associated intermediate straight-associated
27
+ rs17822931 trait ABCC11 Earwax type / underarm odor C T wet earwax, typical body odor wet earwax (C is dominant) dry earwax, little underarm odor
28
+ rs4481887 trait OR2M7 region Asparagus metabolite smell G A less likely to smell it intermediate likely smells asparagus metabolites
29
+ rs10427255 trait ZEB2 region Photic sneeze reflex T C less likely intermediate more likely to sneeze in bright light
30
+ rs1815739 trait ACTN3 R577X sprint/power muscle C T RR: two working copies, power-leaning RX: mixed XX: no alpha-actinin-3, endurance-leaning (~18% of Europeans)
31
+ rs762551 trait CYP1A2 Caffeine metabolism *1F A C AA: fast caffeine metabolizer intermediate slow caffeine metabolizer
32
+ rs2472297 trait CYP1A1/2 region Caffeine intake tendency C T lower-intake alleles intermediate higher-intake alleles
33
+ rs4410790 trait AHR Caffeine intake tendency T C lower-intake alleles intermediate higher-intake alleles
34
+ rs713598 trait TAS2R38 Bitter taste PTC (A49P) C G non-taster allele on both copies taster taster see combined TAS2R38 reading
35
+ rs1726866 trait TAS2R38 Bitter taste (V262A) A G non-taster allele on both copies taster taster see combined TAS2R38 reading
36
+ rs10246939 trait TAS2R38 Bitter taste (I296V) T C non-taster allele on both copies taster taster see combined TAS2R38 reading
37
+ rs72921001 trait OR6A2 Cilantro soapy taste A C no soapy-taste allele one soapy-taste allele cilantro likely tastes soapy
38
+ rs9939609 trait FTO BMI/obesity risk T A no risk allele one risk allele (~+1.2 kg on average) two risk alleles (~+3 kg on average)
39
+ rs17782313 trait MC4R BMI T C no risk allele one risk allele two risk alleles
40
+ rs16969968 trait CHRNA5 Nicotine dependence D398N G A no risk allele one risk allele: heavier smoking if a smoker two risk alleles
41
+ rs4680 trait COMT Val158Met G A Val/Val: higher COMT activity Val/Met Met/Met: lower COMT activity popular but weak-effect marker
42
+ rs6265 trait BDNF Val66Met C T Val/Val (most common) Val/Met Met/Met weak-effect marker
43
+ rs1799971 trait OPRM1 A118G A G reference one G allele two G alleles weak-effect marker
44
+ rs601338 trait FUT2 Secretor status W154X G A secretor secretor (one non-secretor allele) non-secretor: resistant to common norovirus strains; altered gut flora
45
+ rs2180439 trait PAX1 region Male pattern hair loss T C no risk allele one risk allele two risk alleles
46
+ rs53576 trait OXTR Oxytocin receptor G A no variant allele one variant allele two variant alleles weak-effect marker, popular online but poorly replicated
47
+ rs1800497 trait ANKK1/DRD2 TaqIA G A A2/A2 A1/A2 A1/A1 weak-effect marker
48
+ rs8176719 trait ABO O allele (261delG) I D no O allele one O allele blood group O see combined ABO reading
49
+ rs8176746 trait ABO B allele G T no B allele one B allele B/B see combined ABO reading
50
+ rs1050828 health G6PD G6PD A- (V68M) C T no deficiency allele carrier / affected if male deficient X-linked; hemizygous in males
51
+ rs429358 health APOE e4-defining site (C112R) T C no e4 allele one e4 allele two e4 alleles see combined APOE reading
52
+ rs7412 health APOE e2-defining site (R158C) C T no e2 allele one e2 allele two e2 alleles see combined APOE reading
53
+ rs6025 health F5 Factor V Leiden R506Q C T negative heterozygous: ~5x venous thrombosis risk; relevant for surgery, estrogen, long flights homozygous: substantially elevated clotting risk
54
+ rs1799963 health F2 Prothrombin G20210A G A negative heterozygous: ~2-3x venous thrombosis risk homozygous
55
+ rs1800562 health HFE Hemochromatosis C282Y G A negative carrier; iron overload uncommon unless compound heterozygous with H63D C282Y homozygous: high risk of iron overload, check ferritin/transferrin saturation
56
+ rs1799945 health HFE Hemochromatosis H63D C G negative carrier; little risk alone H63D homozygous: mild iron overload possible
57
+ rs1800730 health HFE S65C A T negative carrier homozygous
58
+ rs1801133 health MTHFR C677T (A222V) G A normal activity one T allele, ~65% activity, usually no consequence TT: ~30% activity; mildly higher homocysteine if folate is low widely over-interpreted online
59
+ rs1801131 health MTHFR A1298C T G normal heterozygous, benign homozygous, little effect alone
60
+ rs2187668 health HLA-DQA1 DQ2.5 tag (celiac) C T no DQ2.5 DQ2.5 present (most celiac patients carry it; most carriers never develop it) DQ2.5 homozygous
61
+ rs7454108 health HLA-DQB1 DQ8 tag (celiac) T C no DQ8 DQ8 present DQ8 homozygous
62
+ rs3135388 health HLA-DRB1 DRB1*15:01 tag (multiple sclerosis) G A non-carrier carrier (~3x MS odds, still low absolute risk) homozygous
63
+ rs2476601 health PTPN22 R620W autoimmunity G A negative one risk allele (RA, T1D, thyroid autoimmunity) two risk alleles
64
+ rs2066844 health NOD2 R702W (Crohn's) C T negative carrier homozygous
65
+ rs2066845 health NOD2 G908R (Crohn's) G C negative carrier homozygous
66
+ rs7903146 health TCF7L2 Type 2 diabetes C T no risk allele one risk allele (~1.4x odds) two risk alleles (~2x odds; ~10% of Europeans) strongest common T2D variant
67
+ rs1061170 health CFH Y402H macular degeneration T C no risk allele one risk allele (~2.5x AMD odds) two risk alleles (~6x AMD odds) smoking interacts strongly
68
+ rs10490924 health ARMS2 A69S macular degeneration G T no risk allele one risk allele two risk alleles
69
+ rs1333049 health CDKN2B-AS1 (9p21) Coronary artery disease G C no risk haplotype one copy (~1.25x) two copies (~1.5x)
70
+ rs10455872 health LPA High Lp(a) tag A G no high-Lp(a) allele elevated Lp(a) likely (~2x CAD odds); a direct Lp(a) blood test is cheap homozygous
71
+ rs3798220 health LPA I4399M high Lp(a) T C no high-Lp(a) allele elevated Lp(a) likely homozygous
72
+ rs11136000 health CLU Alzheimer's (protective T) C T no protective allele one protective allele (small effect) two protective alleles
73
+ rs75932628 health TREM2 R47H Alzheimer's C T negative carrier (~3x AD odds) homozygous
74
+ rs76763715 health GBA N370S (Gaucher / Parkinson's) T C negative carrier: ~5x Parkinson's odds; Gaucher carrier Gaucher type 1 likely
75
+ rs34637584 health LRRK2 G2019S Parkinson's G A negative carrier: elevated Parkinson's risk (penetrance ~30% by 80) homozygous
76
+ rs2802292 health FOXO3 Longevity-associated T G no longevity allele one copy two copies (modest association with reaching 90+)
77
+ rs1800629 health TNF -308 promoter G A no variant allele one variant allele two variant alleles weak-effect marker
78
+ rs113993960 carrier CFTR deltaF508 cystic fibrosis I D negative CF carrier (most common CF allele) affected arrays type 1-3 CFTR sites of 2000+
79
+ rs333 carrier CCR5 delta32 I D negative one delta32 copy homozygous: resistant to R5-tropic HIV-1
80
+ rs80357906 carrier BRCA1 185delAG (Ashkenazi founder) I D negative at this site pathogenic variant present: confirm clinically homozygous
81
+ rs80359550 carrier BRCA2 6174delT (Ashkenazi founder) I D negative at this site pathogenic variant present: confirm clinically homozygous
82
+ rs11571833 carrier BRCA2 K3326X A T negative modest cancer risk increase (not a classic BRCA2 pathogenic allele) homozygous
83
+ rs334 carrier HBB Sickle cell HbS T A negative sickle cell trait sickle cell disease
84
+ rs33930165 carrier HBB HbC C T negative HbC trait HbC disease
85
+ rs33950507 carrier HBB HbE C T negative HbE trait HbE
86
+ rs1799853 pgx CYP2C9 *2 (R144C) C T no *2 one *2: reduced warfarin/NSAID/phenytoin metabolism *2/*2
87
+ rs1057910 pgx CYP2C9 *3 (I359L) A C no *3 one *3: markedly reduced metabolism *3/*3 poor metabolizer
88
+ rs28371686 pgx CYP2C9 *5 C G no *5 one *5 (African ancestry) *5/*5
89
+ rs9332131 pgx CYP2C9 *6 I D no *6 one *6 *6/*6
90
+ rs28371685 pgx CYP2C9 *11 C T no *11 one *11 *11/*11
91
+ rs4244285 pgx CYP2C19 *2 (loss of function) G A no *2 one *2: intermediate metabolizer (clopidogrel less effective) *2/*2 poor metabolizer
92
+ rs4986893 pgx CYP2C19 *3 (loss of function) G A no *3 one *3 *3/*3 mostly East Asian
93
+ rs12248560 pgx CYP2C19 *17 (increased function) C T no *17 one *17: rapid metabolizer *17/*17 ultrarapid
94
+ rs9923231 pgx VKORC1 -1639G>A warfarin sensitivity C T normal warfarin dose requirement intermediate: lower warfarin dose high sensitivity: low warfarin dose
95
+ rs9934438 pgx VKORC1 1173C>T (proxy for -1639) G A normal intermediate sensitive in near-perfect LD with rs9923231
96
+ rs2108622 pgx CYP4F2 V433M C T normal slightly higher warfarin dose higher warfarin dose
97
+ rs3892097 pgx CYP2D6 *4 (null) C T no *4 one *4 allele *4/*4 poor metabolizer CNV not detectable on arrays
98
+ rs1065852 pgx CYP2D6 *10 (reduced) / also on *4 G A no *10 one reduced-function allele two reduced-function alleles
99
+ rs28371725 pgx CYP2D6 *41 (reduced) C T no *41 one *41 *41/*41
100
+ rs776746 pgx CYP3A5 *3 vs *1 (expressor) C T *3/*3 non-expressor (most Europeans; standard tacrolimus dosing) *1/*3 expressor: higher tacrolimus dose *1/*1 expressor
101
+ rs3745274 pgx CYP2B6 *6 (516G>T) G T *1/*1 one *6: slower efavirenz/bupropion clearance *6/*6 poor metabolizer
102
+ rs4149056 pgx SLCO1B1 *5 (V174A) statin myopathy T C normal statin transport one C: ~4x simvastatin myopathy risk two C: ~17x; avoid high-dose simvastatin
103
+ rs2231142 pgx ABCG2 Q141K G T normal reduced transport: higher rosuvastatin/allopurinol exposure homozygous reduced
104
+ rs3918290 pgx DPYD *2A (IVS14+1G>A) C T normal DPD deficiency carrier: fluoropyrimidine (5-FU, capecitabine) toxicity risk, dose reduction needed DPD deficient: avoid fluoropyrimidines
105
+ rs67376798 pgx DPYD D949V T A normal reduced DPD activity homozygous
106
+ rs887829 pgx UGT1A1 *28 proxy (Gilbert) C T no *28 one *28 *28/*28 Gilbert syndrome: irinotecan toxicity, benign high bilirubin
107
+ rs4148323 pgx UGT1A1 *6 (G71R) G A no *6 one *6 *6/*6 mostly East Asian
108
+ rs1801280 pgx NAT2 *5 (I114T) T C no *5 one slow allele slow combine with *6/*7
109
+ rs1799930 pgx NAT2 *6 (R197Q) G A no *6 one slow allele slow
110
+ rs1799931 pgx NAT2 *7 (G286E) G A no *7 one slow allele slow
111
+ rs1495741 pgx NAT2 acetylator tag A G slow acetylator (tag) intermediate rapid acetylator (tag)
112
+ rs1800460 pgx TPMT *3B C T no *3B one *3B (thiopurine dose reduction) *3B/*3B
113
+ rs1142345 pgx TPMT *3C T C no *3C one *3C (thiopurine dose reduction) *3C/*3C
114
+ rs2395029 pgx HCP5 HLA-B*57:01 tag T G negative HLA-B*57:01 carrier: abacavir hypersensitivity, avoid homozygous
115
+ rs1800566 pgx NQO1 P187S (*2) G A normal one null allele NQO1 null