just-prs scores a genome against 5,000+ published
PGS Catalog models (or your own scoring file),
places the result on a reference-population bell curve, and estimates absolute
risk when the evidence supports it. It is research software, not medical
advice.
Most PRS tools either hide the catalog behind one curated score or dump raw
sheets with no guidance. just-prs shows every available model for a trait,
plus the quality, match-rate, ancestry, and consensus metrics you need to decide
which ones to trust.
Three ways in:
- Browser —
uv run ui, upload a VCF, browse traits, inspect bell curves. - Agents — just-prs-mcp for Claude, Cursor, Codex, and other MCP clients.
- CLI / Python — scripts, notebooks, and pipelines.
pip install just-prs # HTML/JSON charts included
pip install "just-prs[viz]" # optional PNG/SVG export
prs compute --vcf anton --pgs-id PGS000001
prs plot trait BMI --vcf anton -o bmi.html --show-tableBuilt-in aliases anton and livia auto-download public test genomes from
Zenodo on first use. From this workspace: uv sync --all-packages then
uv run ui or uv run prs ….
Never used this repo? Start with the beginner's guide (install, web UI, MCP, troubleshooting).
- Beginner's guide
- Web UI
- Agents and slash skill
- Visualization
- CLI and Python
- Research use only
- Installation
- Documentation
Features — what the toolbox covers
- PRS from VCF or consumer arrays — normalize, score one or many PGS IDs
(catalog or a local
.txt.gz/.parquet), inspect match rates, quality labels, percentiles, and absolute-risk context. - Trait-first analysis — pick a trait such as type 2 diabetes; compute all associated models and summarize agreement, outliers, and quality.
- PGS Catalog metadata — search cleaned score, trait, performance, publication, prevalence, and scoring-file sheets.
- Fast data engine — Polars and DuckDB scoring, zstd Parquet caches, HuggingFace sync for cleaned metadata and reference distributions.
- Reference and pgen workflows — optional Linux/WSL
.pgen/ 1000G / HGDP+1kGP scoring and PLINK2 cross-validation. - Reusable UI components — embed the workbench in another Reflex app via
PRSComputeStateMixinandload_genotypes(path).
uv sync --all-packages
uv run ui # http://localhost:3000Upload a VCF once (or drop several for a family comparison). The app detects build, normalizes to Parquet, and feeds both Select by PRS and Select by Trait. Ancestry is inferred after upload and used as the default reference population.
Workbench tabs and compute flow
Tabs: Compute PRS (default), Metadata Sheets, Scoring File.
- Upload a VCF — build detection, normalization, cached Parquet, shared across both selection modes.
- Select by PRS or by Trait — individual PGS IDs, or a whole trait aggregated into a consensus summary.
- Download CSV from the results table.
The metadata and scoring-file tabs browse PGS Catalog sheets and stream harmonized scoring files by PGS ID.
The MCP server is
just-prs-mcp. Ask in plain language:
"Download Anton's sample genome, normalize it, and compute PRS for type 2
diabetes."
Step-by-step Cursor / Claude / Codex setup and MCP troubleshooting:
beginner's guide — MCP.
Claude Code, Cursor, Codex, Antigravity
Claude Code:
claude mcp add just-prs -- uvx just-prs-mcp@latest stdioCursor (.cursor/mcp.json or user MCP config):
{
"mcpServers": {
"just-prs": {
"command": "uvx",
"args": ["just-prs-mcp@latest", "stdio"],
"env": {
"PRS_MCP_MODE": "essentials"
}
}
}
}Codex:
[mcp_servers.just-prs]
command = "uvx"
args = ["just-prs-mcp@latest", "stdio"]Antigravity (and other MCP clients): uvx just-prs-mcp@latest stdio.
Claude Code / Antigravity /prs skill (no MCP required)
The skill at docs/skills/prs/SKILL.md teaches the
assistant to search the catalog, compute scores, generate Altair charts, and
interpret results through the CLI.
Workspace (this repo):
mkdir -p .claude/skills/prs
ln -sf ../../../docs/skills/prs/SKILL.md .claude/skills/prs/SKILL.mdPersonal (every project):
mkdir -p ~/.claude/skills/prs
curl -o ~/.claude/skills/prs/SKILL.md \
https://raw.githubusercontent.com/dna-seq/just-prs/main/docs/skills/prs/SKILL.mdThen /prs BMI or /prs type 2 diabetes. The skill calls uvx just-prs.
Use the skill for interactive sessions; use MCP when you need typed tool schemas.
Altair charts are built in. HTML (interactive) and JSON (Vega-Lite) work out of
the box; just-prs[viz] adds PNG/SVG. plot trait auto-detects each sample's
ancestry unless you pass --ancestry. --reference-restoration is the same
off / wgs / chip-id flag as prs compute (default off — see
reference restoration). Results are
cached per VCF × PGS ID × build × ancestry × restoration.
prs plot commands
prs plot trait "type 1 diabetes" --vcf livia -o t1d.html --show-table
prs plot trait BMI --vcf anton -o bmi.html --show-table
prs plot trait thrombosis --vcf anton -o dvt.html --fuzzy --show-table
# Overlay population curves
prs plot trait BMI --vcf anton -o bmi.html --show-table --all-ancestries
prs plot trait BMI --vcf anton -o bmi.html --ancestries EUR,AFR,EAS
# Specific PGS IDs (full trait-style report)
prs plot trait PGS000001 --vcf anton -o pgs1.html
prs plot trait PGS000001,PGS000002 --vcf anton -o two_scores.html
# Minimal single-score charts
prs plot bell-curve PGS000001 --vcf anton -o bell.html
prs plot multi-ancestry PGS000001 --vcf livia -o multi.html
# From pre-computed JSON
prs plot trait "type 2 diabetes" -o t2d.html --results my_results.json
prs plot strip results.json -o strip.html --title "My PRS Report"
prs plot trait BMI --vcf anton -o bmi.html --no-cache # force recompute
prs plot trait intelligence --vcf anton --reference-restoration wgs -o compare.htmlFormat follows the file extension (.html, .json, .png, .svg). Trait
matching uses the UI EFO label (e.g. intelligence) first, then the catalog
reported name; --fuzzy is only needed when a substring hits several traits.
The plot trait positional also accepts comma-separated PGS IDs.
AI prompts for agents (prs prompt)
The Compute UI's Ask Claude / ChatGPT / … buttons are also a CLI. Progress goes to stderr; the prompt itself is stdout, so you can pipe it into an agent.
# Multi-sample comparison prompt (same text the HTML Ask-AI buttons prefill)
prs prompt intelligence --vcf Anton=anton --vcf Livia=livia
# Pipe into an agent
prs prompt BMI --vcf anton | claude
# Already-computed JSON ({sample: [rows]} or a list with a sample field)
prs prompt intelligence --results family.json -o prompt.txt
# Prefill URL instead of prompt text
prs prompt PGS000001 --vcf anton --assistant claude --url--assistant other (the default) uses the full 6000-character budget with no
URL encoding. --assistant claude|chatgpt|perplexity|grok matches that
assistant's UI character limit. Repeat --vcf with Label=path the same way
as plot trait.
Multi-sample / family comparison
Repeat --vcf with optional Label=path (aliases work: Anton=anton). Each
sample gets its own color, dots, and median line. Ancestry is detected per
genome, so mixed-ancestry families compare correctly. The intelligence
screenshot under Research use only is the example family
(Mom, Dad, Son1, Son2, Daughter).
prs plot trait intelligence --vcf Anton=anton --vcf Livia=livia -o compare.html
prs plot trait intelligence \
--vcf Mom=mom.vcf.gz --vcf Dad=dad.vcf.gz \
--vcf Son1=son1.vcf --vcf Son2=son2.vcf --vcf Daughter=daughter.vcf \
-o intel_o_family.html
prs plot trait PGS000001 --vcf Anton=anton --vcf Livia=livia -o pgs1_compare.html
prs plot bell-curve PGS000001 --vcf Anton=anton --vcf Livia=livia -o bell_compare.htmlThe web UI does the same: drop several VCFs, get colored sample chips, and overlay percentiles on every bell curve.
Python API (just_prs.viz)
| Function | What it shows |
|---|---|
plot_prs_bell_curve |
One model + ancestry, user score marker |
plot_prs_multi_ancestry |
Five population curves + user score |
plot_trait_scores |
Trait-grouped N(0,1) curve + quality-colored model dots |
plot_prs_percentile_strip |
Horizontal risk-band strip |
from pathlib import Path
import polars as pl
from just_prs.viz import plot_trait_scores, save_chart
dists = pl.read_parquet("~/.cache/just-prs/percentiles/1000g_distributions.parquet")
quality = pl.read_parquet("~/.cache/just-prs/percentiles/1000g_quality.parquet")
chart = plot_trait_scores(
"BMI", dists, quality_df=quality,
user_results=my_results,
height=150, show_table=True,
)
save_chart(chart, Path("bmi_report.html"))
save_chart(chart, Path("bmi_report.png"))prs compute --vcf sample.vcf.gz --pgs-id PGS000001
prs compute --vcf sample.vcf.gz --pgs-id PGS000001,PGS000002,PGS000003
prs compute --vcf anton --pgs-id PGS000001,PGS000002 --ancestry --ancestry-aadr
prs compute --vcf sample.vcf.gz --scoring-file my_custom_score.txt.gz
prs normalize --vcf sample.vcf.gz --pass-filters "PASS,." --min-depth 10
prs catalog scores search --term "breast cancer"Python scoring snippet
import polars as pl
from pathlib import Path
from just_prs import PRSCatalog, VcfFilterConfig, normalize_vcf
from just_prs.prs import compute_prs
catalog = PRSCatalog()
config = VcfFilterConfig(pass_filters=["PASS", "."], min_depth=10)
parquet_path = normalize_vcf(Path("sample.vcf.gz"), Path("sample.parquet"), config=config)
genotypes_lf = pl.scan_parquet(parquet_path)
result = compute_prs(
vcf_path="sample.vcf.gz",
scoring_file="PGS000001",
genome_build="GRCh38",
genotypes_lf=genotypes_lf,
)
print(f"Score: {result.score:.6f}, Match rate: {result.match_rate:.1%}")Full command reference: docs/cli.md. Python API: docs/python-api.md.
VCF aliases and public test genomes
--vcf accepts a path or an alias (compute, plot bell-curve,
plot multi-ancestry, plot trait). Built-ins auto-download from Zenodo:
| Alias | File | License | Zenodo |
|---|---|---|---|
anton |
antonkulaga.vcf (~482 MB) |
CC0 | 18370498 |
livia |
SIMHIFQTILQ.hard-filtered.vcf.gz (~349 MB) |
CC-BY-4.0 | 19487816 |
prs alias list
prs alias set mygenome /path/to/my/sample.vcf.gz
prs alias remove mygenome
prs compute --vcf anton --pgs-id PGS000001User aliases live in ~/.cache/just-prs/vcf_aliases.json. MCP agents can fetch
either genome with download_sample_genome (sample="anton" or "livia").
Genetic ancestry inference
Runtime is pure Python (no plink2). Models are built offline and pulled from HuggingFace on first use.
prs ancestry infer --vcf anton
prs ancestry infer --vcf anton --mode label --panel 1000g
prs ancestry infer --vcf anton --mode mixture --panel hgdp_1kg
prs ancestry infer --vcf anton --panel hgdp_1kg --resolution population
prs ancestry infer --vcf newton --mode all --prive --aadr --resolution population
prs ancestry check PGS000001 --vcf antonModes: label, mixture, prive, consensus, all (default). Panels
1000g and hgdp_1kg are on HuggingFace; prive and aadr_ho are built
locally because of data-license terms.
Within-continent calls are best read as soft proportions, not a single hard label — East-Slavic groups form roughly one autosomal cluster. Methodology: docs/sample-ancestry-methodology.md.
PRS is a statistical predisposition signal in a studied population — not a diagnosis, not a probability you “will get” a disease, and not a substitute for clinical testing.
This is the example family from prs plot trait intelligence with five VCFs
labeled Mom, Dad, Son1, Son2, and Daughter. Each person has a median card and
colored markers on the same bell curve. Relatives can sit far apart, and the
models for this trait still disagree — read match rate and quality, not one
percentile. A family PRS plot is not a diagnosis and not a relatedness test.
FAQ — quality, ancestry, coverage, absolute risk
What does "research use only" mean? Many people are used to tests that look at a narrow, high-confidence question (a known pathogenic variant). PRS are statistical models from many small associations, often with modest predictive power. Being in the PGS Catalog does not mean a score is clinically ready, ancestry-portable, or useful for an individual decision.
Why do several PRS for the same trait disagree? Different cohorts, ancestries, phenotype definitions, builds, variant sets, and methods. Prefer better published metrics, higher match rates, relevant ancestry, and agreement among high-quality models. The trait summary is built to show consensus and outliers.
Does a high PRS mean I will get a disease? No. Heritability for most common diseases is moderate; current GWAS-based PRS typically capture only a fraction of it. Tag SNPs in LD with causal loci are not a mechanistic readout. Environment, age, sex, lifestyle, and chance often matter as much or more.
Why does ancestry matter? LD patterns and allele frequencies differ across populations, so a score trained mostly in Europeans often transfers poorly. Reference percentiles (“where does this sit vs this panel?”) do not prove the original model works equally well in that population.
Why is my match rate so low? Consumer arrays type ~600–700k SNPs, not a
whole genome. Exomes miss non-coding GWAS tags. Build mismatch matches ~0
variants. just-prs can recover some untyped sites via LD-proxy substitution
on GSA v3 / GRCh38 (1000g); that lifts coverage but does not replace
imputation. A 12% match is a fragment of the model — check matched vs total
before trusting a number.
How is quality determined? A synthetic 0–100 score from published discrimination (AUROC / C-index / beta / OR), cohort size, match rate, and a harmonized-liftover penalty, then a combined score after computation on real genomes. Details: docs/prs-quality-score.md.
What does absolute risk mean? A conversion from percentile + prevalence + published performance into a probability. It is only as good as those inputs; weak evidence should show as N/A, not a precise-looking number. See docs/absolute-risk-methodology.md.
Requires Python >= 3.13. Uses uv. First time on a new machine: beginner's guide.
pip install just-prs
git clone https://github.com/antonkulaga/just-prs
cd just-prs
uv sync --all-packagesCLI names: just-prs and prs. Core library only:
cd just-prs/just-prs && uv sync.
Windows
The web UI and VCF-based PRS work on Windows with no C compiler. pgenlib
is excluded via sys_platform != 'win32' (no Windows wheels; bundled C fails
on MSVC).
cd just-prs
uv sync --all-packages
uv run uiReference-panel / .pgen scoring (prs reference, prs pgen, the Dagster
pipeline) needs WSL or Linux.
Project structure
uv workspace with three packages:
| Package | Directory | Description |
|---|---|---|
| just-prs | just-prs/ |
Core library. Published to PyPI. |
| prs-ui | prs-ui/ |
Reflex web UI. Published to PyPI. |
| prs-pipeline | prs-pipeline/ |
Dagster pipeline for reference distributions. |
The workspace root is a non-published wrapper (uv run ui, uv run pipeline).
Why not PLINK2?
PLINK2 --score is the gold standard.
just-prs matches it (Pearson r = 1.0 across 3,202 samples, relative per-sample
differences < 5e-7 — docs/validation.md) and is easier
to compose in Python.
| PLINK2 | just-prs | |
|---|---|---|
| Install | Platform binary | pip install just-prs |
| Integration | Subprocess + text I/O | Polars DataFrames |
| Batch | One process per PGS ID | Reuses parsed .pvar / genotype caches |
from pathlib import Path
from just_prs import compute_reference_prs_polars
scores_df = compute_reference_prs_polars(
pgs_id="PGS000001",
scoring_file=Path("PGS000001_hmPOS_GRCh38.txt.gz"),
ref_dir=Path("path/to/pgen_dir"),
out_dir=Path("data/output/results/pgs000001"),
genome_build="GRCh38",
)prs pgen score PGS000001 path/to/pgen_dir/
prs reference score PGS000001
prs reference score-batch
prs reference compare PGS000001Embed the PRS UI in another Reflex app
Install prs-ui, mix in PRSComputeStateMixin, and push genotypes through
load_genotypes(path) — your app supplies the source.
import reflex as rx
from reflex_mui_datagrid import LazyFrameGridMixin
from prs_ui import PRSComputeStateMixin, prs_section
class MyAppState(rx.State):
genome_build: str = "GRCh38"
cache_dir: str = ""
status_message: str = ""
class PRSState(PRSComputeStateMixin, LazyFrameGridMixin, MyAppState):
"""Consumer state — load_genotypes is built in."""
def prs_page() -> rx.Component:
return prs_section(PRSState)For the full By PRS / By Trait workbench with your own source, render
prs_workbench(...). See AGENTS.md for the embedding contract.
Testing
uv run pytest just-prs/tests/ -vIntegration tests use real genomic data (no mocks). PLINK2 cross-validation, live catalog metadata, and Zenodo test VCFs are documented in docs/validation.md.
- Beginner's guide — install, web UI, MCP, troubleshooting
- CLI Reference
- Python API
- Absolute Risk Methodology
- Dagster Pipelines
- Validation
- Cleanup Pipeline
- PRS quality score
- Sample ancestry methodology
- Reference restoration (why default is off; gVCF is never filled)
Data sources: PGS Catalog REST · EBI FTP · HuggingFace pgs-catalog

