Skip to main content

Turn raw consumer DNA data (FTDNA, AncestryDNA, 23andMe, MyHeritage) into a research/educational genomic PDF report. Not medical advice.

Project description

SNP Genomic Report Skill (v4.21)

Author: Benjamin Guscott · benguscott@gmail.com

Disclaimer

This tool is for research and informational purposes only. It does not provide medical advice, genetic counselling, or clinical diagnosis, and its output is not a substitute for clinical-grade genetic testing. Consumer genotyping arrays have appreciable error rates and limited coverage; any finding of interest must be confirmed with an accredited clinical laboratory before it informs a medical decision. Consult a qualified healthcare professional or certified genetic counsellor before acting on anything in a generated report.

Interpretation provenance and key references: see SOURCES.md.

Privacy

Raw DNA files are among the most sensitive personal data you can hold. Everything here runs locally — no genotype data is transmitted anywhere (the optional generate_summaries.py step is the sole exception: it sends section text, not raw genotypes, to the Anthropic API only if you opt in). The .gitignore is configured to keep raw data and generated reports out of version control; do not commit real DNA files or PDFs. Treat any report as identifying health information.

Requirements

  • Python 3.8+
  • reportlab and matplotlib (see requirements.txt)
  • Optional: an ANTHROPIC_API_KEY for generate_summaries.py

New here? Run the pipeline on the bundled synthetic example before using real data — see examples/.

Changelog

Version Date Changes
4.21 2026-06 Public-release audit fixes. ROH: emit terminal runs (end-of-chromosome runs were dropped) and restrict scan to autosomes 1–22 so AncestryDNA chr23–26 no longer inflate F_ROH (cross-platform F_ROH now consistent). data_platform() matches ancestrydna not the dead ancestry branch. CYP2C9 no-call no longer reported as Normal metaboliser (*1/*1); both *2/*3 het orders accepted. Cover/provenance coverage labels disambiguated (genotyped/called vs present-on-array vs in-DB). SKILL.md restored as canonical skill definition; validator guards missing SKILL.md. Added transparent .gz input handling (stdlib gzip, no new deps). Honest scoping: VCF marked not implemented, LivingDNA marked experimental/untested; documented the validated platform vintages (FTDNA 2023 GSA, AncestryDNA V2.0, 23andMe v5, MyHeritage OmniExpress — all GRCh37). Added MIT LICENSE, synthetic example, disclaimer/privacy/requirements.
4.20 2026-06 Root fix: infer missing ref/alt for 94 DB entries from observed alleles; derive all missing diplotype keys. Eliminates reactive per-file gap-filling. rs1800629 TNF-α GG/AA sentiment corrected. rs13266634 SLC30A8 R325W inverted-risk corrected.
4.16 2026-06 21 genotype keys added from additional AncestryDNA validation data. rs13266634 SLC30A8 R325W inverted-risk fix.
4.15 2026-06 96 heterozygous keys added from observed validation genotypes. rs4588 hom keys. 8 fallback entries enriched. rs1800629 TNF-α GG/AA sentiment corrected.
4.14 2026-06 AncestryDNA strand audit. supplemented_rsids scoped to DB targets. 355 hom keys from het keys. 194 misleading fallback stubs replaced. rs17817449 FTO ref/alt corrected. rs1800896 IL-10 + rs8050136 FTO keys added. get_sentiment/get_interp: complement lookup chain (exact→rev→comp→comp-rev). get_interp fallback rewritten to "Genotype class (XY) — locus context".
4.13 2026-06 validate_skill.py pre-packaging integrity checker added (37 checks, auto-fix mode). Systematic bug class analysis: (1) Class 2 — replaced 'ε4' in apoe_call string search with e4_allele_count >= 1 in APOE key findings block (same root cause as the reassuring bug fixed in 4.11). (2) Class 3 — fixed 9 allele-order mismatches in annotation DB (TC/CT, TG/GT, GA/AG etc.) so platforms reporting alphabetical genotype order get specific interpretation text. (3) Class 4 — added half-call guards ('-' not in geno) to all substring membership checks to prevent -T or C- half-calls triggering risk findings. (4) Class 5 — added TPMT poor/intermediate, CYP2C19*2 poor/intermediate, Prothrombin G20210A carrier, HFE compound het to §35 key findings.
4.12 2026-06 Add LRRK2 G2019S GA (carrier) and AA (homozygous) to §35 key findings — previously the 4.11 fix stopped AA appearing in Reassuring but neither variant was flagged in Key Findings. Confirmed via 18-case synthetic genotype test suite.
4.11 2026-06 Bug fix: LRRK2 §35 reassuring — AA homozygous G2019S was marked reassuring (wrong). Bug fix: APOE ε3/ε3 excluded from reassuring by substring match on "No ε4". Bug fix: data_platform() misidentified AncestryDNA V2/23andMe v5 as FTDNA by SNP count. New: Integrated Warfarin Profile table (CYP2C9 + VKORC1 + CYP4F2 synthesis). Fix: §9 MT zero-call explained per platform. Fix: §4 het rate expected range platform-aware. Fix: cover/footer counts use DB size (413) not matched count. Fix: §31 APOE note conditional. Fix: §17 HLA-C/CTLA4 dedup. Fix: §28 archaic/§29 ancestry dedup. Fix: §33 blood type O-allele no-call flagged. Fix: forensic table widths to mm.
4.10 2026-06 Fix 47 sentiment misclassifications (GOOD/NEUTRAL/BAD) across annotation DB. Fix get_sentiment() unsafe fallback — homozygous reference ≠ GOOD, fallback now NEUTRAL. Remove rs4148323 duplicate from §18. Fix SKILL.md stale version and SNP count references. Document sentiment_by_geno requirement for new DB entries.
4.9 2026-06 Per-row colour coding (green/amber/red/neutral) in all SNP tables. sentiment_by_geno field (626 mappings) added to all 413 DB entries. coloured_T(), SNP_TABLE(), colour legend in §10.
4.8 2026-06 Fix generator version stamp; dynamic polymorphism counts everywhere; no-call vs absent-from-array in §11; dynamic APOE haplotype calling in §12; breast cancer male caveat in §18; §34 conditional APOE limitation; §35 expanded key findings (CFH, SLCO1B1, DPYD *13 conditional wording, APOE ε4).
4.7 2026-05 API-powered section summaries via generate_summaries.py; forensic phenotype table; 12 key SNP interpretations expanded to 30+ words; rs429358 (APOE ε4) added to annotation DB
4.6 2026-05 Remove filename-based sex hints — sex determined wholly from X het rate and FTDNA XY-only pattern
4.5 2026-05 Fix FTDNA male sex determination (XY-only → infer Male); expanded filename sex-hint keywords; FTDNA/GSA het rate note in QC section
4.4 2026-05 Fix het rate formula (het/het+hom, excludes hemizygous X/Y/MT calls); sex mismatch warning from filename; 23andMe GSA het rate platform note in QC section
4.3 2026-05 Fix ROH artefact in dual-dataset mode: QC now computed on primary platform only; chromosome name normalisation (AncestryDNA chr23→X etc.) prevents cross-platform ROH inflation
4.2 2026-05 Dual-dataset merging (optional secondary file fills coverage gaps); coverage warning and [SUPP] tags; §35 low-coverage note; MyHeritage workflow guidance
4.1 2026-05 Fix MyHeritage/AncestryDNA comment-header parse bugs; add DPYD*13, FGFR2, breast cancer panel, SLCO1B1*5, CYP2B6*6, KCNQ1 S225L, SCN5A, CTLA4; remove unreliable MTHFR proxy rs2274976; annotation DB 411 entries
4.0 2026-05 Initial standalone skill: replace static template with executable generate_report.py; add snp_annotations_enhanced.json with per-genotype interpretations; streamlined 3-command workflow; dynamic §35 summary
3.0 2026-03 Previous report template version (generate_report_template.py) — retired

Overview

Analyses raw SNP genotyping data from consumer DNA tests and produces a comprehensive ~50-page clinical genomic PDF report covering ~413 individually annotated polymorphisms across 33 biological and clinical domains.

DO NOT ASK QUESTIONS — begin analysis immediately when a genotype file is uploaded.

Supported Input Formats

Files may be plain text or gzip-compressed — a .gz file is decompressed transparently, so there's no need to gunzip a download first.

Platform Format Header Pattern Status
FTDNA CSV RSID,CHROMOSOME,POSITION,RESULT Validated
23andMe TSV # rsid chromosome position genotype Validated
AncestryDNA TSV rsid chromosome position allele1 allele2 (# comment header) Validated
MyHeritage CSV RSID,CHROMOSOME,POSITION,RESULT (FTDNA-like, # header) Validated
LivingDNA CSV/TSV FTDNA-like ⚠️ Experimental — untested. No LivingDNA file has been run through this pipeline. It is not explicitly detected and only works if its export closely matches FTDNA's layout.
VCF Not implemented. No VCF parser exists; a VCF input will be misdetected and will not produce a correct report. Convert to a supported raw-text format first.

What has actually been validated

The four Validated formats were each run end-to-end on at least one real consumer export, all on GRCh37 / build 37:

Platform Export tested
FTDNA 2023 autosomal CSV, Illumina GSA chip
AncestryDNA array version V2.0, forward strand
23andMe v5 "Full" export (build 37, Annotation Release 104)
MyHeritage OmniExpress-based export (2019-vintage format)

The dual-dataset merge was validated with an AncestryDNA V2.0 + MyHeritage pairing.

Not validated — use with caution: other platform vintages (e.g. 23andMe v3/v4, older FTDNA builds); any GRCh38 / build 38 file (rsID-based SNP lookup is build-independent, but the ROH metric uses base-pair positions and assumes build 37, so ROH figures on a build-38 file are unreliable); LivingDNA; and VCF. Strand and allele-order handling are calibrated to the exports above and may differ on an untested platform.

Workflow

Run these commands in sequence. Do not write custom code — the bundled scripts handle everything.

MyHeritage users: The MyHeritage OmniExpress array covers only ~150 of the 413 target clinical SNPs. If you also have an AncestryDNA or FTDNA file, upload both — the dual-dataset workflow below fills most coverage gaps and produces a substantially more complete report.

Step 1 — Install dependencies

pip install reportlab matplotlib

Step 2 — Run the analysis pipeline

python3 scripts/analyze_snps.py \
  /path/to/your_dna_file.csv \
  analysis.json \
  references/snp_annotations_enhanced.json

Expected output confirms platform, SNP count, call rate, and how many curated SNPs were found.

If platform shows unknown, check the file header with head -3 <file>.

Step 2b — Optional: generate API-enriched section summaries

Requires ANTHROPIC_API_KEY set in the environment. Produces subject-specific synthesis paragraphs for each report section.

export ANTHROPIC_API_KEY=your_key_here

python3 scripts/generate_summaries.py \
  analysis.json \
  references/snp_annotations_enhanced.json \
  --output analysis_rich.json

Then use analysis_rich.json as the input to generate_report.py.

Without this step, the report uses static section summaries. With it, each section gets a Claude-authored synthesis of the actual genotype pattern, covering biological mechanisms and drug interactions.

Step 3b — Optional: supplement with a second platform file

If the subject has data from two platforms (e.g., MyHeritage + AncestryDNA, or any combination), pass the secondary file as a fourth argument:

python3 scripts/analyze_snps.py \
  /path/to/primary.csv \
  analysis.json \
  references/snp_annotations_enhanced.json \
  /path/to/secondary.csv

The secondary file supplements the primary — its calls are only used where the primary has no data or a no-call. SNPs sourced from the secondary are tagged [SUPP] in the report tables.

Recommended pairings:

Primary Secondary Coverage
AncestryDNA V2.0 MyHeritage ~410 SNPs
FTDNA GSA MyHeritage ~410 SNPs — near-complete
MyHeritage AncestryDNA V2.0 ~360 SNPs
MyHeritage FTDNA GSA ~410 SNPs

Step 4 — Generate the PDF report

python3 scripts/generate_report.py \
  analysis.json \
  report.pdf \
  references/snp_annotations_enhanced.json

Expected output: Report saved: report.pdf


Report Contents (36 Sections)

  1. Cover page (platform, SNP count, date, predicted sex)
  2. Abbreviation key
  3. Data provenance & platform identification
  4. Global quality metrics
  5. Per-chromosome distribution & heterozygosity (table + 2 charts)
  6. Allele frequencies, GC content & Ti/Tv (3 charts)
  7. ROH & F_ROH
  8. Sex determination
  9. Mitochondrial DNA
  10. Pharmacogenomics: CYP2C9, VKORC1, CYP4F2, integrated warfarin summary, CYP2D6, NAT2, CYP1A2, CYP3A5, DPYD, TPMT, CYP2C19, UGT1A1, metformin, methotrexate
  11. Clinical no-call gaps
  12. Alzheimer's GWAS (APOE note + IGAP loci)
  13. Cardiovascular (lipids, CAD/9p21, blood pressure, haemostasis/thrombosis, QT interval, AF)
  14. T2D & glucose metabolism
  15. Obesity & leptin/adiponectin axis
  16. Inflammatory cytokine profile (Th1/Th2)
  17. Autoimmune (HLA screen, coeliac, IBD, MS + Vitamin D convergence, asthma, psoriasis)
  18. Cancer, liver/NAFLD, AMD, restless legs syndrome
  19. Iron/HFE (haemochromatosis)
  20. Methylation cycle (MTHFR, MTR, MTRR, CBS)
  21. Vitamin D axis (synthesis → activation → transport → receptor)
  22. Micronutrient metabolism (vitamin A, choline, omega-3, B12, selenium, histamine/DAO)
  23. Neuropsychiatric (serotonin, dopamine/COMT, BDNF, FKBP5/HPA, oxytocin, GABA, opioid, PD, GWAS)
  24. Circadian rhythm & sleep
  25. Pain & endocannabinoid system
  26. Sensory genetics (bitter taste TAS2R38, sweet/umami, olfaction, photic sneeze)
  27. Blood type & antigen systems
  28. Archaic introgression
  29. Ancestry informative markers
  30. Prostate & PSA
  31. Longevity (FOXO3, IGF-1/mTOR, telomere panel, CETP centenarian variant, antioxidants)
  32. ECM, connective tissue & musculoskeletal (collagen, elastin, MMP, muscle, OA, bone, vascular, migraine)
  33. Forensic phenotype reconstruction (skin, eye, hair colour; earwax)
  34. Limitations & caveats
  35. Summary & conclusions (dynamically generated key findings based on actual genotypes)
  36. References

How Genotype Interpretation Works

snp_annotations_enhanced.json contains per-genotype interpretation text for all 413 annotated SNPs. Every entry has three required fields for colour coding to work correctly:

  • interp_by_geno — dict mapping each genotype string to interpretation text
  • sentiment_by_geno — dict mapping each genotype string to GOOD, MIXED, BAD, or NEUTRAL

sentiment_by_geno — rules for new entries

Every key in interp_by_geno must have a corresponding key in sentiment_by_geno (excluding ref and alt descriptor keys, which should be NEUTRAL). If a key is missing, get_sentiment() returns NEUTRAL rather than guessing — this is safe but means the row will be uncoloured.

Sentiment values:

Value Meaning Colour
GOOD Reassuring — wild-type, no risk variant, protective allele, longevity-associated Green
MIXED Heterozygous risk carrier, intermediate phenotype, or genuine trade-off Amber
BAD Risk variant present — elevated disease risk, reduced enzyme activity requiring dose adjustment, pathogenic allele Red
NEUTRAL No directional clinical interpretation — dose modifier only, performance trait, pharmacokinetic variant without health direction, incomplete information, or bidirectional effect White/grey

Critical rules — learned from audit of 530 assignments:

  1. Homozygous reference ≠ GOOD by default. Some loci have the risk allele on the reference strand (e.g. PNPLA3 I148M: reference G = risk allele; homozygous GG = BAD not GOOD). Always check which allele carries the risk before assigning GOOD to a homozygous-reference genotype.

  2. Absence of a beneficial allele ≠ GOOD. FOXO3 CC (longevity allele absent) = NEUTRAL, not GOOD. CETP AA (I405V absent, no centenarian HDL phenotype) = NEUTRAL. Reserve GOOD for genotypes that are actively reassuring.

  3. Dose/pharmacokinetic modifiers = NEUTRAL throughout. VKORC1, CYP4F2, CYP2C19*17, CYP4F2 — these affect drug dosing but neither direction is a health risk. All genotypes = NEUTRAL.

  4. Bidirectional pleiotropic variants = NEUTRAL. PHACTR1 G (CAD protective, migraine risk), COMT Val/Met (neither warrior nor worrier is clinically bad). Use NEUTRAL when the effect direction is genuinely ambiguous.

  5. PPARG-type inversions. The Ala12 allele (G at rs1801282) is protective; Pro12 (C) is the less-protective reference. Protective homozygote (GG) = GOOD; reference homozygote (CC) = NEUTRAL. Do not assume ref = GOOD.

  6. Dominant-negative and gain-of-function variants on the reference strand. TERT rs10069690 T allele creates a dominant-negative isoform — CT = BAD despite being heterozygous. Check the mechanistic direction, not just allele frequency.

  7. Inverted-risk loci. Some SNPs have the common allele as the risk allele — do not assume common = non-risk. Known examples: rs1800629 TNF-α -308G>A (G is ancestral/common; A is the rare risk allele — GG is GOOD, AA is BAD, but the DB was originally calibrated with ref=A causing inversion); rs13266634 SLC30A8 R325W (C/Arg325 is the common risk allele; T/Trp325 is the rare protective allele — CC is MIXED, TT is GOOD). Always verify which allele carries clinical risk before assigning sentiment.

  8. -- (no-call) is always NEUTRAL — unknown is not bad.

For all other SNPs, interpretation is automatically derived by comparing the observed genotype to reference/alternate alleles — producing "Homozygous reference / Heterozygous / Homozygous variant" classifications with gene context. The report works for any subject's genotype profile.

Platform strand conventions

The annotation DB was originally calibrated on FTDNA GSA data. AncestryDNA, 23andMe, and MyHeritage report on the forward (+) strand per their file headers, but at some loci the allele encoding differs from the FTDNA convention. get_sentiment() and get_interp() both try four lookups before falling back: exact genotype → reversed (GA→AG) → complement (CC→GG) → complement-reversed. This handles cross-platform strand differences automatically at runtime.

When adding new DB entries, include keys for all three diplotype states (hom-ref, het, hom-alt) for both allele orderings (AG and GA). The ref and alt fields must be the forward-strand alleles; the complement lookup handles platforms that report the opposite strand.

The §35 Summary is dynamically generated: key findings (HFE carrier/homozygous, HFE compound het C282Y+H63D, MTHFR 677TT, Factor V Leiden het/hom, Prothrombin G20210A carrier, TPMT poor/intermediate metaboliser, CYP2C19*2 poor/intermediate, DPYD *2A, DPYD putative *13, CFH Y402H, SLCO1B1 proxy, APOE ε3/ε4, ε4/ε4 unresolved, LRRK2 G2019S carrier/homozygous, FOXO3 longevity allele, CYP2C9 warfarin variants) are only flagged when the relevant genotype is actually detected. The Reassuring block covers: MTHFR wild-type, thrombophilia absent, DPYD *2A absent, LRRK2 G2019S absent, APOE ε3/ε3.


Release Process

Before packaging or publishing any update, run the integrity validator:

python3 validate_skill.py

The validator runs a series of checks across these categories:

  • Version consistency across all files (single source of truth: GENERATOR_VERSION in generate_report.py)
  • analyze_snps.py derives version dynamically — no hardcoded literal to drift
  • generate_summaries.py API key handling
  • SKILL.md structural content (no broken Step 2b, no stale SNP counts, changelog current)
  • No personal data or Cowork-specific output paths in scripts
  • Annotation DB integrity (sentiment_by_geno coverage, valid values, allele-order gaps)
  • Critical §35 logic (LRRK2 exact match, APOE count-based check, DPYD half-call guard)
  • Live pipeline smoke test

Fix any failures before packaging. Use --fix to auto-resolve version drift in SKILL.md:

python3 validate_skill.py --fix

Per-release checklist:

  1. python3 validate_skill.py — must return ALL CHECKS PASS
  2. Bump GENERATOR_VERSION in generate_report.py (one edit; all other version references derive from it or are updated by --fix)
  3. Add changelog entry in generate_report.py docstring
  4. Run python3 validate_skill.py --fix to propagate new version to SKILL.md title
  5. Add changelog row to SKILL.md manually (validator checks this is present)
  6. Run pipeline on a test file to confirm PDF output

Troubleshooting

Symptom Fix
Platform: unknown Check file header: head -3 <file> — FTDNA starts RSID,CHROMOSOME,POSITION,RESULT
Found 0 of target SNPs Verify path to snp_annotations_enhanced.json
reportlab not found Re-run Step 1
PDF is very small or blank Check the output path is writable
Many no-calls for PGx SNPs Normal for some platforms; note in report and recommend clinical PGx testing
MyHeritage: only ~150 SNPs found Expected — OmniExpress array has limited clinical coverage. Add AncestryDNA/FTDNA as supplement (Step 3b)

Files in This Skill

snp-genomic-report/
├── SKILL.md                       ← canonical skill definition
├── README.md                     ← this file
├── SOURCES.md                    ← interpretation provenance + key references
├── RELEASE_NOTES_v4.21.md
├── LICENSE
├── requirements.txt
├── .gitignore
├── validate_skill.py             ← Pre-packaging integrity checker — run before every release
├── examples/
│   ├── example_ftdna_SYNTHETIC.csv  ← synthetic demo input (not a real person)
│   ├── example_report_SYNTHETIC.pdf ← sample output from the synthetic input
│   └── README.md
├── scripts/
│   ├── analyze_snps.py           ← Platform detection, parsing, QC, ROH, SNP lookup
│   ├── generate_report.py        ← Full report generator: charts + 36-section PDF (all in one)
│   └── generate_summaries.py     ← Optional: Anthropic API section summaries (requires ANTHROPIC_API_KEY)
└── references/
    ├── snp_annotations_enhanced.json  ← 413 SNPs with per-genotype interpretations + sentiment_by_geno (PRIMARY)
    └── target_rsids.txt               ← Flat list of target rsIDs

Trademarks & affiliation

This project is independent and is not affiliated with, endorsed by, or sponsored by any genetic testing company. Family Tree DNA / FTDNA, AncestryDNA, 23andMe, MyHeritage, and Living DNA are trademarks of their respective owners; they are referenced here only to describe the raw-data file formats the tool can read.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

snp_genomic_report-4.21.tar.gz (324.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

snp_genomic_report-4.21-py3-none-any.whl (107.8 kB view details)

Uploaded Python 3

File details

Details for the file snp_genomic_report-4.21.tar.gz.

File metadata

  • Download URL: snp_genomic_report-4.21.tar.gz
  • Upload date:
  • Size: 324.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for snp_genomic_report-4.21.tar.gz
Algorithm Hash digest
SHA256 83450efba657b2375fee9f3345486dcb544c082bfa37d9911cb2489ceece83d1
MD5 56b6f9f955704e9e675395b8c31a0817
BLAKE2b-256 f8828effe35039d167bda2c8d31caadaccba0314f9ca1274730bb6f10ae77bbb

See more details on using hashes here.

Provenance

The following attestation bundles were made for snp_genomic_report-4.21.tar.gz:

Publisher: publish.yml on benguscott/snp-genomic-report

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file snp_genomic_report-4.21-py3-none-any.whl.

File metadata

File hashes

Hashes for snp_genomic_report-4.21-py3-none-any.whl
Algorithm Hash digest
SHA256 f667169e00e72bc065b88ca2feeab0e5eb7e3fe0a2a47819abae8cb65d211972
MD5 f10aceb9eee1edce1cc75b5b48a5ab62
BLAKE2b-256 928cc4bfd8bf39a65d0c25ba1e89de1a8b9457ec789eb33425f249d544965f82

See more details on using hashes here.

Provenance

The following attestation bundles were made for snp_genomic_report-4.21-py3-none-any.whl:

Publisher: publish.yml on benguscott/snp-genomic-report

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page