diff --git a/HARNESS/run_provenance_gate.py b/HARNESS/run_provenance_gate.py new file mode 100644 index 0000000..27d38c5 --- /dev/null +++ b/HARNESS/run_provenance_gate.py @@ -0,0 +1,145 @@ +"""CI runner: block skill PRs whose effect-size citations do not hold up (ClawBio/ClawBench#3). + +For each (changed) skill that declares the `emits_effect_sizes` capability in its SKILL.md +frontmatter, this requires a provenance panel (`data/provenance.json`) and validates every +entry with the provenance gate (HARNESS/validate_provenance.py) against the committed GWAS +Catalog/PubMed snapshot. A fabricated citation, or a missing panel, fails the check; skills +that do not emit effect sizes are skipped. Emits GitHub Actions annotations and a step +summary, and exits non-zero when anything blocks. + +Convention: a skill opts in with + metadata: + capabilities: + - emits_effect_sizes +and ships skills//data/provenance.json conforming to SCHEMAS/effect_size_provenance_schema.json. + +No side effects at import. Run via .github/workflows/provenance-gate.yml in the skills repo. +""" +from __future__ import annotations + +import json +import os +import re +from pathlib import Path + +import validate_provenance as VP + +CAPABILITY = "emits_effect_sizes" +PROVENANCE_REL = Path("data") / "provenance.json" + + +def _frontmatter(text: str) -> str: + m = re.match(r"^---\s*\n(.*?)\n---", text, re.DOTALL) + return m.group(1) if m else "" + + +def declares_effect_sizes(skill_dir: Path) -> bool: + skill_md = Path(skill_dir) / "SKILL.md" + if not skill_md.exists(): + return False + return CAPABILITY in _frontmatter(skill_md.read_text()) + + +def gate_skill(skill_dir, oracle: VP.Oracle | None = None) -> dict: + skill_dir = Path(skill_dir) + name = skill_dir.name + if not declares_effect_sizes(skill_dir): + return {"skill": name, "status": "skipped", "findings": []} + + panel_path = skill_dir / PROVENANCE_REL + if not panel_path.exists(): + return {"skill": name, "status": "error", "panel": str(panel_path), + "findings": [{"error_code": "MISSING_PROVENANCE", "severity": "block", + "message": f"{name} declares {CAPABILITY} but has no " + f"{PROVENANCE_REL} provenance panel", "index": None, + "file": str(panel_path)}]} + oracle = oracle or VP.CachedOracle() + try: + entries = json.loads(panel_path.read_text()) + except Exception as exc: + return {"skill": name, "status": "error", "panel": str(panel_path), + "findings": [{"error_code": "PARSE_ERROR", "severity": "block", + "message": f"could not parse {panel_path}: {exc}", "index": None, + "file": str(panel_path)}]} + + report = VP.validate_panel(entries, oracle) + for f in report["findings"]: + f["file"] = str(panel_path) + status = "fail" if report["n_blocking"] else ("warn" if report["n_warnings"] else "pass") + return {"skill": name, "status": status, "panel": str(panel_path), + "findings": report["findings"]} + + +def _changed_skill_names(changed) -> set[str]: + names = set() + for p in changed or []: + parts = Path(p).parts + if "skills" in parts: + i = parts.index("skills") + if i + 1 < len(parts): + names.add(parts[i + 1]) + return names + + +def discover_skills(skills_root, changed=None) -> list[Path]: + skills_root = Path(skills_root) + skills = [d for d in sorted(skills_root.iterdir()) if (d / "SKILL.md").exists()] + if changed is not None: + wanted = _changed_skill_names(changed) + skills = [d for d in skills if d.name in wanted] + return skills + + +def run(skills_root, changed=None, oracle: VP.Oracle | None = None) -> dict: + oracle = oracle or VP.CachedOracle() + results = [gate_skill(d, oracle) for d in discover_skills(skills_root, changed)] + blocking = sum(1 for r in results if r["status"] in ("fail", "error")) + return {"results": results, "blocking": blocking, "exit_code": 1 if blocking else 0} + + +def _emit_annotations(report: dict) -> None: + for r in report["results"]: + for f in r.get("findings", []): + level = "error" if f.get("severity") == "block" else "warning" + print(f"::{level} file={f.get('file', '')}::[{r['skill']}] " + f"{f.get('error_code')}: {f.get('message')}") + + +def _write_summary(report: dict) -> None: + path = os.environ.get("GITHUB_STEP_SUMMARY") + if not path: + return + icon = {"pass": "✅", "warn": "🟡", "fail": "🔴", "error": "🔴", "skipped": "➖"} + lines = ["## Provenance gate", "", "| Skill | Status | Blocking | Warnings |", "|---|---|---|---|"] + for r in report["results"]: + nb = sum(1 for f in r.get("findings", []) if f.get("severity") == "block") + nw = sum(1 for f in r.get("findings", []) if f.get("severity") == "warn") + lines.append(f"| {r['skill']} | {icon.get(r['status'], '')} {r['status']} | {nb} | {nw} |") + with open(path, "a") as fh: + fh.write("\n".join(lines) + "\n") + + +def main(argv=None) -> int: + import argparse + + ap = argparse.ArgumentParser(description="Block skill PRs with unsupported effect-size citations.") + ap.add_argument("--skills-root", default="skills", help="directory containing skill folders") + ap.add_argument("--changed", default=None, + help="newline/comma-separated changed paths; if omitted, scan all skills") + args = ap.parse_args(argv) + + changed = None + if args.changed: + changed = [p.strip() for p in re.split(r"[\n,]", args.changed) if p.strip()] + + report = run(args.skills_root, changed) + _emit_annotations(report) + _write_summary(report) + for r in report["results"]: + print(f"{r['status']:>8} {r['skill']}") + print(f"\n{report['blocking']} skill(s) blocking; exit {report['exit_code']}") + return report["exit_code"] + + +if __name__ == "__main__": # pragma: no cover + raise SystemExit(main()) diff --git a/HARNESS/validate_provenance.py b/HARNESS/validate_provenance.py new file mode 100644 index 0000000..e45004f --- /dev/null +++ b/HARNESS/validate_provenance.py @@ -0,0 +1,244 @@ +"""Effect-size provenance gate: fail closed when a cited paper does not support the claim. + +For each association entry a skill emits (variant, trait, ancestry, effect, source), this +resolves the cited PMID against a citation ORACLE (GWAS Catalog + PubMed) and verifies the +paper actually reports that variant/trait in that ancestry with a comparable effect. A +citation is a falsifiable claim, not a label: "cited but wrong" is the same safety hazard as +"missing", and it is invisible to secret/lint scanners. + +Layer 1 of the scientific-correctness CI gate (ClawBio/ClawBench#3). Mirrors the pattern of +HARNESS/validate_evidence.py: a schema in SCHEMAS/, machine-readable error codes, and an +entry point that is exception-guarded so it can never raise. + +Coverage-gap handling: GWAS Catalog does not index every primary paper (e.g. it links +rs73885319/kidney to PAGE/MVP, not the original Genovese 2010 APOL1 paper). So when a cited +PMID is not a registered study for a (variant, trait), the gate does NOT hard-block; it falls +back to a PubMed topic check. If the paper's topic overlaps the trait it is flagged +TOPIC_MATCH_LOW (advisory, human sign-off); only a genuinely off-topic paper (head-and-neck +cancer cited for kidney disease) is a hard block. This keeps the gate from false-rejecting +correct-but-uncatalogued citations while still catching fabricated ones. + +The oracle is injected. Tests use a deterministic fixture or the committed snapshot +(CachedOracle); production may use LiveOracle (GWAS Catalog REST + PubMed). No import-time I/O. +""" +from __future__ import annotations + +import json +from pathlib import Path +from typing import Protocol + +ERROR_CODES = ( + "PARSE_ERROR", # entry was not a dict / unexpected shape + "SCHEMA_INVALID", # fails effect_size_provenance_schema.json + "PMID_UNRESOLVABLE", # PubMed has no such PMID (typo / fabricated) + "ASSOC_NOT_FOUND", # no catalog association AND PMID topic does not match the trait + "PMID_STUDY_MISMATCH", # association exists, cited PMID is off-topic and not a source for it + "ANCESTRY_MISMATCH", # cited study reports a different ancestry than the entry claims + "EFFECT_OUT_OF_RANGE", # effect size deviates implausibly from the catalog value + "TOPIC_MATCH_LOW", # WARN: PMID not in catalog for this assoc, but topic overlaps the trait +) + +# An effect within [LO x, HI x] of the catalog value is accepted (same direction assumed). +EFFECT_RATIO_LO = 0.5 +EFFECT_RATIO_HI = 2.0 + +_SCHEMA_PATH = Path(__file__).resolve().parents[1] / "SCHEMAS" / "effect_size_provenance_schema.json" +_DEFAULT_SNAPSHOT = Path(__file__).resolve().parents[1] / "TRUTH" / "gwas_catalog" / "snapshot.json" + + +class Oracle(Protocol): + """Citation oracle. CachedOracle reads the committed snapshot; LiveOracle hits the network.""" + + def pmid_exists(self, pmid: str) -> bool: ... + + def associations_for(self, rsid: str) -> list[dict]: + """GWAS Catalog study records: {efo_id, trait, pmid, ancestries: list[str], or_value}.""" + ... + + def pubmed_topic_terms(self, pmid: str) -> set[str]: + """Lower-cased topic terms (title/MeSH) for the coverage-gap fallback.""" + ... + + +def _finding(index, rsid, code, severity, field, message, valid): + return {"valid": valid, "error_code": code, "severity": severity, + "field": field, "message": message, "rsid": rsid, "index": index} + + +def _ok(index, rsid): + return _finding(index, rsid, None, None, None, "ok", True) + + +def _load_schema() -> dict: + with open(_SCHEMA_PATH) as fh: + return json.load(fh) + + +def _schema_error(entry) -> str | None: + try: + from jsonschema import Draft202012Validator + except Exception: # pragma: no cover - declared dependency + return None + errs = sorted(Draft202012Validator(_load_schema()).iter_errors(entry), key=lambda e: list(e.path)) + if errs: + e = errs[0] + return f"{'/'.join(str(p) for p in e.path) or ''}: {e.message}" + return None + + +def _trait_tokens(label: str) -> set[str]: + stop = {"the", "and", "for", "with", "disease", "disorder"} + toks = "".join(c.lower() if c.isalnum() else " " for c in label).split() + return {t for t in toks if len(t) > 2 and t not in stop} + + +def validate_entry(entry, oracle: Oracle, index: int = 0) -> dict: + """Validate one association entry. Fail-closed, never raises.""" + try: + if not isinstance(entry, dict): + return _finding(index, None, "PARSE_ERROR", "block", "", "entry is not an object", False) + + schema_msg = _schema_error(entry) + if schema_msg is not None: + return _finding(index, (entry.get("variant") or {}).get("rsid"), + "SCHEMA_INVALID", "block", "", schema_msg, False) + + rsid = entry["variant"]["rsid"] + efo = entry["trait"]["efo_id"] + label = entry["trait"]["label"] + ancestry = entry["ancestry"] + pmid = entry["source"]["pmid"] + value = entry["effect"]["value"] + + if not oracle.pmid_exists(pmid): + return _finding(index, rsid, "PMID_UNRESOLVABLE", "block", "source.pmid", + f"PMID {pmid} does not resolve in PubMed", False) + + matching = [r for r in oracle.associations_for(rsid) if r.get("efo_id") == efo] + pmid_studies = [r for r in matching if str(r.get("pmid")) == str(pmid)] + + if not pmid_studies: + # Cited PMID is not a registered study for this variant+trait. Coverage gap or wrong cite? + if oracle.pubmed_topic_terms(pmid) & _trait_tokens(label): + return _finding(index, rsid, "TOPIC_MATCH_LOW", "warn", "source.pmid", + f"PMID {pmid} is not a GWAS Catalog source for {rsid}-{efo}, but its " + "topic overlaps the trait. Needs human sign-off.", True) + if matching: + return _finding(index, rsid, "PMID_STUDY_MISMATCH", "block", "source.pmid", + f"PMID {pmid} is not a source for the {rsid}-{efo} association and its " + "topic does not match the trait (cited paper is about something else)", False) + return _finding(index, rsid, "ASSOC_NOT_FOUND", "block", "variant.rsid", + f"No GWAS Catalog association for {rsid} with trait {efo}, and PMID {pmid} " + "topic does not match the trait", False) + + # Cited PMID IS a catalog study for this variant+trait: check ancestry then effect. + known_anc = {a for r in pmid_studies for a in (r.get("ancestries") or [])} + if known_anc and ancestry not in known_anc: + return _finding(index, rsid, "ANCESTRY_MISMATCH", "block", "ancestry", + f"Entry claims {ancestry}; cited study reports {sorted(known_anc)}", False) + + for r in pmid_studies: + cat = r.get("or_value") + if cat and value and cat > 0 and value > 0: + ratio = value / cat + if not (EFFECT_RATIO_LO <= ratio <= EFFECT_RATIO_HI): + return _finding(index, rsid, "EFFECT_OUT_OF_RANGE", "block", "effect.value", + f"Effect {value} deviates from catalog {cat} (ratio {ratio:.2f}, " + f"allowed {EFFECT_RATIO_LO}-{EFFECT_RATIO_HI}x)", False) + return _ok(index, rsid) + except Exception as exc: + return _finding(index, None, "PARSE_ERROR", "block", "", f"unexpected error: {exc}", False) + + +def validate_panel(entries, oracle: Oracle) -> dict: + """Validate a list of entries. Returns a summary + per-entry findings. Never raises.""" + try: + items = list(entries) + except Exception: + return {"passed": False, "n_entries": 0, "n_blocking": 0, "n_warnings": 0, + "findings": [_finding(0, None, "PARSE_ERROR", "block", "", + "panel is not iterable", False)]} + findings = [validate_entry(e, oracle, i) for i, e in enumerate(items)] + blocking = [f for f in findings if f["severity"] == "block"] + warnings = [f for f in findings if f["severity"] == "warn"] + return {"passed": len(blocking) == 0, "n_entries": len(items), + "n_blocking": len(blocking), "n_warnings": len(warnings), + "findings": [f for f in findings if not f["valid"] or f["severity"] == "warn"]} + + +class CachedOracle: + """Offline oracle backed by the committed TRUTH/gwas_catalog/snapshot.json freeze.""" + + def __init__(self, snapshot_path: Path | str | None = None): + with open(snapshot_path or _DEFAULT_SNAPSHOT) as fh: + snap = json.load(fh) + self._assoc = snap.get("associations", {}) + self._titles = snap.get("pmid_titles", {}) + self._topic = snap.get("pmid_topic", {}) + + def pmid_exists(self, pmid: str) -> bool: + pmid = str(pmid) + if pmid in self._titles: + return True + return any(str(r.get("pmid")) == pmid for recs in self._assoc.values() for r in recs) + + def associations_for(self, rsid: str) -> list[dict]: + return self._assoc.get(rsid, []) + + def pubmed_topic_terms(self, pmid: str) -> set[str]: + return set(self._topic.get(str(pmid), [])) + + +class LiveOracle: # pragma: no cover - network, exercised by scripts/build_provenance_snapshot.py + """Production oracle: GWAS Catalog REST + PubMed E-utilities. Prefer CachedOracle in CI.""" + + GWAS = "https://www.ebi.ac.uk/gwas/rest/api" + EUTILS = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils" + + def _get(self, url: str) -> dict: + import urllib.request + req = urllib.request.Request(url, headers={"Accept": "application/json"}) + with urllib.request.urlopen(req, timeout=30) as fh: + return json.load(fh) + + def pmid_exists(self, pmid: str) -> bool: + r = self._get(f"{self.EUTILS}/esummary.fcgi?db=pubmed&retmode=json&id={pmid}") + return str(pmid) in r.get("result", {}).get("uids", []) + + def associations_for(self, rsid: str) -> list[dict]: + url = f"{self.GWAS}/singleNucleotidePolymorphisms/{rsid}/associations?projection=associationBySnp" + out = [] + for a in self._get(url).get("_embedded", {}).get("associations", []): + try: + study = self._get(a["_links"]["study"]["href"]) + except Exception: + continue + pmid = str(study.get("publicationInfo", {}).get("pubmedId") or "") + for t in a.get("efoTraits", []) or []: + out.append({"efo_id": t.get("shortForm"), "trait": t.get("trait"), + "pmid": pmid, "ancestries": [], "or_value": a.get("orPerCopyNum")}) + return out + + def pubmed_topic_terms(self, pmid: str) -> set[str]: + r = self._get(f"{self.EUTILS}/esummary.fcgi?db=pubmed&retmode=json&id={pmid}") + title = r.get("result", {}).get(str(pmid), {}).get("title", "") + return {t for t in "".join(c.lower() if c.isalnum() else " " for c in title).split() if len(t) > 2} + + +def main(argv=None) -> int: # pragma: no cover - CLI wiring + import argparse + + ap = argparse.ArgumentParser(description="Validate a skill's effect-size provenance panel.") + ap.add_argument("panel", help="JSON file: a list of association entries") + ap.add_argument("--live", action="store_true", help="use the live GWAS Catalog/PubMed oracle") + args = ap.parse_args(argv) + with open(args.panel) as fh: + entries = json.load(fh) + oracle = LiveOracle() if args.live else CachedOracle() + report = validate_panel(entries, oracle) + print(json.dumps(report, indent=2, default=list)) + return 0 if report["passed"] else 1 + + +if __name__ == "__main__": # pragma: no cover + raise SystemExit(main()) diff --git a/SCHEMAS/effect_size_provenance_schema.json b/SCHEMAS/effect_size_provenance_schema.json new file mode 100644 index 0000000..9d078b1 --- /dev/null +++ b/SCHEMAS/effect_size_provenance_schema.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/ClawBio/ClawBench/SCHEMAS/effect_size_provenance_schema.json", + "title": "ClawBench effect-size provenance contract", + "description": "Structural contract for one association entry emitted by a ClawBio skill that reports effect sizes or ancestry-stratified disease risk (e.g. ancestry-risk-profiler). Every entry must name the variant, the trait (with an EFO id), the ancestry, the effect, and a resolvable source. Structural rules live here; semantic cross-checks (does the cited PMID actually report this variant/trait/ancestry in GWAS Catalog?) are enforced by HARNESS/validate_provenance.py and reported with machine-readable error codes. See ClawBio/ClawBench#3.", + "x-provenance": { + "principle": "A citation is a falsifiable claim, not a label. 'Cited but wrong' is the same hazard as 'missing'.", + "oracles": "GWAS Catalog REST API (associations by rsid; study PMID + ancestry) and PubMed E-utilities (PMID resolves + topic terms).", + "trigger": "ancestry-risk-profiler PR #297 reached a third round with 5 of 6 high-reuse PMIDs resolving to the wrong paper (flagship APOL1 20566908 = a head-and-neck-cancer survey; real Genovese = 20647424)." + }, + "type": "object", + "additionalProperties": false, + "required": ["variant", "trait", "ancestry", "effect", "source"], + "properties": { + "variant": { + "type": "object", + "additionalProperties": true, + "required": ["rsid"], + "properties": { + "rsid": { "type": "string", "pattern": "^rs[0-9]+$" } + } + }, + "trait": { + "type": "object", + "additionalProperties": true, + "required": ["label", "efo_id"], + "properties": { + "label": { "type": "string", "minLength": 1 }, + "efo_id": { "type": "string", "pattern": "^(EFO|MONDO|HP|Orphanet)_[0-9]+$" } + } + }, + "ancestry": { "enum": ["AFR", "AMR", "EAS", "EUR", "SAS"] }, + "effect": { + "type": "object", + "additionalProperties": false, + "required": ["measure", "value"], + "properties": { + "measure": { "enum": ["OR", "beta", "HR", "RR"] }, + "value": { "type": "number" }, + "ci_low": { "type": "number" }, + "ci_high": { "type": "number" } + } + }, + "source": { + "type": "object", + "additionalProperties": true, + "required": ["pmid"], + "properties": { + "pmid": { "type": "string", "pattern": "^[0-9]{6,9}$" }, + "gwas_catalog_accession": { "type": "string", "pattern": "^GCST[0-9]+$" } + } + } + } +} diff --git a/TRUTH/gwas_catalog/README.md b/TRUTH/gwas_catalog/README.md new file mode 100644 index 0000000..e5f1e1b --- /dev/null +++ b/TRUTH/gwas_catalog/README.md @@ -0,0 +1,25 @@ +# GWAS Catalog + PubMed snapshot (provenance gate) + +`snapshot.json` is a frozen extract of the GWAS Catalog REST API and PubMed E-utilities, +used by `HARNESS/validate_provenance.py` (`CachedOracle`) so the provenance gate runs +deterministically and offline in CI. See ClawBio/ClawBench#3 (L1). + +## Contents +- `associations`: `rsid -> [ {efo_id, trait, pmid, ancestries[], or_value} ]` — for each + variant, the studies GWAS Catalog links it to (filtered to the traits of interest), with + the study PMID, mapped super-population ancestry, and per-copy OR. +- `pmid_titles` / `pmid_topic`: PubMed title + tokenised topic terms, for the coverage-gap + fallback (a cited PMID absent from the catalog but on-topic is flagged for sign-off, not + hard-blocked). + +## Regenerate +``` +python scripts/build_provenance_snapshot.py +``` +Hits the live APIs and overwrites this file. The committed snapshot is the truth-of-record; +refresh it when the panel of variants under test changes. + +## Scope +This is a demonstration freeze for the audited `ancestry-risk-profiler` variants, not the +full catalog. Scaling the panel and wiring the gate to block on `skills/*` PRs that declare +`emits_effect_sizes` is tracked in the issue. diff --git a/TRUTH/gwas_catalog/snapshot.json b/TRUTH/gwas_catalog/snapshot.json new file mode 100644 index 0000000..38e4003 --- /dev/null +++ b/TRUTH/gwas_catalog/snapshot.json @@ -0,0 +1,4176 @@ +{ + "associations": { + "rs11591147": [ + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "18193044", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0007804", + "or_value": null, + "pmid": "22331829", + "trait": "LDL cholesterol change measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "GO_0036273", + "or_value": null, + "pmid": "22331829", + "trait": "response to statin" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0010351", + "or_value": null, + "pmid": "35213538", + "trait": "cholesteryl ester measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "OBA_VT0005144", + "or_value": null, + "pmid": "35213538", + "trait": "blood VLDL cholesterol amount" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022154", + "or_value": null, + "pmid": "36764567", + "trait": "phospholipids in medium VLDL measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004612", + "or_value": null, + "pmid": "38448586", + "trait": "high density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "38448586", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0008591", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0020944", + "or_value": null, + "pmid": "38448586", + "trait": "Cholesteryl esters:total lipids ratio" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "OBA_VT0005144", + "or_value": null, + "pmid": "38448586", + "trait": "blood VLDL cholesterol amount" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "36357675", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0020947", + "or_value": null, + "pmid": "35213538", + "trait": "Triglycerides:total lipids ratio" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "35213538", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0008589", + "or_value": null, + "pmid": "35213538", + "trait": "esterified cholesterol measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "HP_0003124", + "or_value": null, + "pmid": "39024449", + "trait": "hypercholesterolemia" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "EFO_0000493", + "or_value": null, + "pmid": "39024449", + "trait": "family history" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022164", + "or_value": null, + "pmid": "38448586", + "trait": "phospholipids in IDL measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022163", + "or_value": null, + "pmid": "38448586", + "trait": "total lipids in large LDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022253", + "or_value": null, + "pmid": "36764567", + "trait": "cholesteryl esters to total lipids in medium VLDL percentage " + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "30926973", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022256", + "or_value": null, + "pmid": "39091897", + "trait": "cholesteryl esters to total lipids in small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022301", + "or_value": null, + "pmid": "39091897", + "trait": "phospholipids in VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022236", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol to total lipids in large VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022176", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol in large LDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "35213538", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0020947", + "or_value": null, + "pmid": "38448586", + "trait": "Triglycerides:total lipids ratio" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022268", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol in medium LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0020944", + "or_value": null, + "pmid": "35213538", + "trait": "Cholesteryl esters:total lipids ratio" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "30275531", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022341", + "or_value": null, + "pmid": "39091897", + "trait": "triglycerides to total lipids in very small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "39024449", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022246", + "or_value": null, + "pmid": "36764567", + "trait": "cholesteryl esters to total lipids in chylomicrons and extremely large VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022285", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol to total lipids in small HDL percentage " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0008589", + "or_value": null, + "pmid": "38448586", + "trait": "esterified cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022335", + "or_value": null, + "pmid": "36764567", + "trait": "triglycerides to total lipids in medium VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022174", + "or_value": null, + "pmid": "36764567", + "trait": "phospholipids in large LDL measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0020943", + "or_value": null, + "pmid": "38448586", + "trait": "Cholesterol:total lipids ratio" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004612", + "or_value": null, + "pmid": "35213538", + "trait": "high density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "OBA_1001011", + "or_value": null, + "pmid": "35213538", + "trait": "phospholipid amount" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "38448586", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0020946", + "or_value": null, + "pmid": "38448586", + "trait": "Phospholipids:total lipids ratio" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0020946", + "or_value": null, + "pmid": "39091897", + "trait": "Phospholipids:total lipids ratio" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022294", + "or_value": null, + "pmid": "39091897", + "trait": "phospholipids in LDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004529", + "or_value": null, + "pmid": "38448586", + "trait": "lipid measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "OBA_1000840", + "or_value": null, + "pmid": "30698716", + "trait": "alcohol consumption quality" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "30698716", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022273", + "or_value": null, + "pmid": "38448586", + "trait": "free cholesterol in very large HDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0008595", + "or_value": null, + "pmid": "38448586", + "trait": "intermediate density lipoprotein measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "34887591", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "27005778", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0008591", + "or_value": null, + "pmid": "27005778", + "trait": "free cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022275", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol in very small VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022279", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol to total lipids in large HDL percentage " + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "EFO_0007010", + "or_value": null, + "pmid": "39024449", + "trait": "drug use measurement" + }, + { + "ancestries": [ + "AFR", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "33462484", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "39024449", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0020945", + "or_value": null, + "pmid": "35213538", + "trait": "Free cholesterol:total lipids ratio" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "OBA_2050296", + "or_value": null, + "pmid": "38448586", + "trait": "chylomicron amount" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004530", + "or_value": null, + "pmid": "38448586", + "trait": "triglyceride measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "32154731", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "39091897", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0020946", + "or_value": null, + "pmid": "36764567", + "trait": "Phospholipids:total lipids ratio" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0021899", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol in IDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "36764567", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "MONDO_0021187", + "or_value": null, + "pmid": "39024449", + "trait": "hyperlipidemia" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022250", + "or_value": null, + "pmid": "39091897", + "trait": "cholesteryl esters to total lipids in large VLDL percentage " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022301", + "or_value": null, + "pmid": "38448586", + "trait": "phospholipids in VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022154", + "or_value": null, + "pmid": "39091897", + "trait": "phospholipids in medium VLDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022329", + "or_value": null, + "pmid": "36764567", + "trait": "triglycerides to total lipids in IDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "39091897", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022227", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol in small LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022156", + "or_value": null, + "pmid": "36764567", + "trait": "total lipids in very small VLDL measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "36220816", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022279", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol to total lipids in large HDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0021661", + "or_value": null, + "pmid": "39024449", + "trait": "coronary atherosclerosis" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "OBA_VT0005144", + "or_value": null, + "pmid": "36764567", + "trait": "blood VLDL cholesterol amount" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0009696", + "or_value": null, + "pmid": "40537477", + "trait": "social deprivation" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "40537477", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0010815", + "or_value": null, + "pmid": "35213538", + "trait": "remnant cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022239", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol to total lipids in medium VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022239", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol to total lipids in medium VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022340", + "or_value": null, + "pmid": "36764567", + "trait": "triglycerides to total lipids in very large VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022269", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol in medium VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "36376304", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022245", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol to total lipids in very small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022224", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol in medium LDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022319", + "or_value": null, + "pmid": "38448586", + "trait": "triglycerides in large LDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0020945", + "or_value": null, + "pmid": "38448586", + "trait": "Free cholesterol:total lipids ratio" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022164", + "or_value": null, + "pmid": "39091897", + "trait": "phospholipids in IDL measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022176", + "or_value": null, + "pmid": "38448586", + "trait": "free cholesterol in large LDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022231", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol in very small VLDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "34594039", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0803367", + "or_value": null, + "pmid": "36653479", + "trait": "antihyperlipidemic drug use measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022168", + "or_value": null, + "pmid": "36764567", + "trait": "total lipids in small LDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0008591", + "or_value": null, + "pmid": "35213538", + "trait": "free cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004529", + "or_value": null, + "pmid": "35213538", + "trait": "lipid measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022235", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol to total lipids in large LDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0001336", + "or_value": null, + "pmid": "34906840", + "trait": "familial hyperlipidemia" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0920021", + "or_value": null, + "pmid": "39091897", + "trait": "cholesteryl esters in VLDL measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022312", + "or_value": null, + "pmid": "38448586", + "trait": "total lipids in very large HDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022182", + "or_value": null, + "pmid": "38448586", + "trait": "cholesterol esters in medium VLDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0920028", + "or_value": null, + "pmid": "39091897", + "trait": "cholesteryl esters in small LDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022334", + "or_value": null, + "pmid": "39091897", + "trait": "triglycerides to total lipids in medium LDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022163", + "or_value": null, + "pmid": "36764567", + "trait": "total lipids in large LDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022180", + "or_value": null, + "pmid": "36764567", + "trait": "total lipids in medium LDL" + }, + { + "ancestries": [ + "EAS", + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "40436827", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0008595", + "or_value": null, + "pmid": "32150548", + "trait": "intermediate density lipoprotein measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0008591", + "or_value": null, + "pmid": "32150548", + "trait": "free cholesterol measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "31217584", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022253", + "or_value": null, + "pmid": "39091897", + "trait": "cholesteryl esters to total lipids in medium VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022235", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol to total lipids in large LDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022312", + "or_value": null, + "pmid": "36764567", + "trait": "total lipids in very large HDL measurement " + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "MONDO_0005010", + "or_value": null, + "pmid": "39024449", + "trait": "coronary artery disorder" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022284", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol to total lipids in medium VLDL percentage " + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "HP_0003077", + "or_value": null, + "pmid": "39024449", + "trait": "Hyperlipidemia" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004530", + "or_value": null, + "pmid": "35213538", + "trait": "triglyceride measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "28270201", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022168", + "or_value": null, + "pmid": "39091897", + "trait": "total lipids in small LDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004732", + "or_value": null, + "pmid": "35213538", + "trait": "lipoprotein measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022156", + "or_value": null, + "pmid": "38448586", + "trait": "total lipids in very small VLDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0020943", + "or_value": null, + "pmid": "35213538", + "trait": "Cholesterol:total lipids ratio" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022273", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol in very large HDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022151", + "or_value": null, + "pmid": "39091897", + "trait": "concentration of very small VLDL particles" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022272", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol in small VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022186", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol esters in medium LDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022249", + "or_value": null, + "pmid": "39091897", + "trait": "cholesteryl esters to total lipids in large LDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "OBA_2050296", + "or_value": null, + "pmid": "35213538", + "trait": "chylomicron amount" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "32203549", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022297", + "or_value": null, + "pmid": "39091897", + "trait": "phospholipids in small LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022241", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol to total lipids in small LDL percentage " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022165", + "or_value": null, + "pmid": "38448586", + "trait": "cholesterol esters in large LDL measurement" + }, + { + "ancestries": [ + "AMR" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "34887591", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0021901", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol in large LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022181", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol in IDL measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004329", + "or_value": null, + "pmid": "30698716", + "trait": "alcohol drinking" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0920025", + "or_value": null, + "pmid": "39091897", + "trait": "cholesteryl esters in small VLDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022180", + "or_value": null, + "pmid": "39091897", + "trait": "total lipids in medium LDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022259", + "or_value": null, + "pmid": "36764567", + "trait": "cholesteryl esters to total lipids in very small VLDL percentage " + }, + { + "ancestries": [ + "EAS", + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "33339817", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022179", + "or_value": null, + "pmid": "38448586", + "trait": "total cholesterol in small LDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022227", + "or_value": null, + "pmid": "40307439", + "trait": "cholesterol in small LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022294", + "or_value": null, + "pmid": "36764567", + "trait": "phospholipids in LDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022171", + "or_value": null, + "pmid": "38448586", + "trait": "total cholesterol in small VLDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022160", + "or_value": null, + "pmid": "36764567", + "trait": "concentration of large LDL particles measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022244", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol to total lipids in very large VLDL percentage " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022274", + "or_value": null, + "pmid": "38448586", + "trait": "free cholesterol in very large VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022229", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol in very large HDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022280", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol to total lipids in large LDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0008595", + "or_value": null, + "pmid": "35213538", + "trait": "intermediate density lipoprotein measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "HP_0003124", + "or_value": null, + "pmid": "39789286", + "trait": "hypercholesterolemia" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022227", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol in small LDL measurement " + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "32226016", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022228", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol in small VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0008589", + "or_value": null, + "pmid": "39091897", + "trait": "esterified cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022183", + "or_value": null, + "pmid": "39091897", + "trait": "phospholipids in medium LDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022233", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol to total lipids in IDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022341", + "or_value": null, + "pmid": "36764567", + "trait": "triglycerides to total lipids in very small VLDL percentage " + }, + { + "ancestries": [ + "AMR" + ], + "efo_id": "EFO_0005689", + "or_value": null, + "pmid": "34887591", + "trait": "non-high density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0008589", + "or_value": null, + "pmid": "36764567", + "trait": "esterified cholesterol measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022272", + "or_value": null, + "pmid": "38448586", + "trait": "free cholesterol in small VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022297", + "or_value": null, + "pmid": "36764567", + "trait": "phospholipids in small LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022242", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol to total lipids in small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0020946", + "or_value": null, + "pmid": "35213538", + "trait": "Phospholipids:total lipids ratio" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022225", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol in medium VLDL measurement " + }, + { + "ancestries": [], + "efo_id": "MONDO_0005010", + "or_value": null, + "pmid": "29212778", + "trait": "coronary artery disorder" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022275", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol in very small VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022247", + "or_value": null, + "pmid": "39091897", + "trait": "cholesteryl esters to total lipids in IDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005010", + "or_value": 0.778, + "pmid": "36474045", + "trait": "coronary artery disorder" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022270", + "or_value": null, + "pmid": "38448586", + "trait": "free cholesterol in small HDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022231", + "or_value": null, + "pmid": "38448586", + "trait": "cholesterol in very small VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022338", + "or_value": null, + "pmid": "39091897", + "trait": "triglycerides to total lipids in small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022287", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol to total lipids in small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0021899", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol in IDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022287", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol to total lipids in small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "36764567", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022182", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol esters in medium VLDL measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "GO_0036273", + "or_value": null, + "pmid": "31969989", + "trait": "response to statin" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "EFO_0007804", + "or_value": null, + "pmid": "31969989", + "trait": "LDL cholesterol change measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022258", + "or_value": null, + "pmid": "36764567", + "trait": "cholesteryl esters to total lipids in very large VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0920023", + "or_value": null, + "pmid": "39091897", + "trait": "cholesteryl esters in LDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022329", + "or_value": null, + "pmid": "39091897", + "trait": "triglycerides to total lipids in IDL percentage " + }, + { + "ancestries": [ + "AFR", + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "24507774", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005010", + "or_value": null, + "pmid": "35285134", + "trait": "coronary artery disorder" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004694", + "or_value": null, + "pmid": "35285134", + "trait": "factor XI measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022163", + "or_value": null, + "pmid": "39091897", + "trait": "total lipids in large LDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022250", + "or_value": null, + "pmid": "36764567", + "trait": "cholesteryl esters to total lipids in large VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022176", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol in large LDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022340", + "or_value": null, + "pmid": "39091897", + "trait": "triglycerides to total lipids in very large VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022335", + "or_value": null, + "pmid": "39091897", + "trait": "triglycerides to total lipids in medium VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022269", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol in medium VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022338", + "or_value": null, + "pmid": "36764567", + "trait": "triglycerides to total lipids in small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022152", + "or_value": null, + "pmid": "39091897", + "trait": "concentration of medium VLDL particles" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022242", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol to total lipids in small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022161", + "or_value": null, + "pmid": "39091897", + "trait": "total lipids in IDL" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022144", + "or_value": null, + "pmid": "38448586", + "trait": "triglycerides in very small VLDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022332", + "or_value": null, + "pmid": "36764567", + "trait": "triglycerides to total lipids in large VLDL percentage " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022174", + "or_value": null, + "pmid": "38448586", + "trait": "phospholipids in large LDL measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022181", + "or_value": null, + "pmid": "38448586", + "trait": "free cholesterol in IDL measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022271", + "or_value": null, + "pmid": "38448586", + "trait": "free cholesterol in small LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022166", + "or_value": null, + "pmid": "39091897", + "trait": "concentration of small LDL particles measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0010815", + "or_value": null, + "pmid": "38448586", + "trait": "remnant cholesterol measurement" + }, + { + "ancestries": [ + "AFR", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "33462484", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004612", + "or_value": null, + "pmid": "36376304", + "trait": "high density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022308", + "or_value": null, + "pmid": "36764567", + "trait": "total lipids in LDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "OBA_2050114", + "or_value": null, + "pmid": "38448586", + "trait": "VLDL particle size" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022183", + "or_value": null, + "pmid": "38448586", + "trait": "phospholipids in medium LDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022160", + "or_value": null, + "pmid": "39091897", + "trait": "concentration of large LDL particles measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "OBA_1000199", + "or_value": null, + "pmid": "35285134", + "trait": "von Willebrand factor quality" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "32226016", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022224", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol in medium LDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022186", + "or_value": null, + "pmid": "38448586", + "trait": "cholesterol esters in medium LDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0021901", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol in large LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022266", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol in LDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022162", + "or_value": null, + "pmid": "38448586", + "trait": "total cholesterol in medium VLDL" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "30670697", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0003940", + "or_value": null, + "pmid": "30670697", + "trait": "physical activity" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "OBA_2050113", + "or_value": null, + "pmid": "36764567", + "trait": "LDL particle size" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022272", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol in small VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022334", + "or_value": null, + "pmid": "36764567", + "trait": "triglycerides to total lipids in medium LDL percentage " + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004630", + "or_value": null, + "pmid": "35285134", + "trait": "factor VIII measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0008591", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022238", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol to total lipids in medium LDL percentage " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022322", + "or_value": null, + "pmid": "38448586", + "trait": "triglycerides in medium LDL measurement " + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "31217584", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022183", + "or_value": null, + "pmid": "36764567", + "trait": "phospholipids in medium LDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022273", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol in very large HDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "40436827", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0010815", + "or_value": null, + "pmid": "39091897", + "trait": "remnant cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022289", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol to total lipids in very large VLDL percentage " + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "29507422", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022161", + "or_value": null, + "pmid": "38448586", + "trait": "total lipids in IDL" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022148", + "or_value": null, + "pmid": "38448586", + "trait": "total lipids in small VLDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022285", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol to total lipids in small HDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "OBA_2050113", + "or_value": null, + "pmid": "39091897", + "trait": "LDL particle size" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005010", + "or_value": 1.25, + "pmid": "28714975", + "trait": "coronary artery disorder" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022268", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol in medium LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022266", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol in LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022300", + "or_value": null, + "pmid": "36764567", + "trait": "phospholipids in very small VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022256", + "or_value": null, + "pmid": "36764567", + "trait": "cholesteryl esters to total lipids in small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005010", + "or_value": null, + "pmid": "38965376", + "trait": "coronary artery disorder" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022231", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol in very small VLDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022184", + "or_value": null, + "pmid": "38448586", + "trait": "total cholesterol in IDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "25961943", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022297", + "or_value": null, + "pmid": "38448586", + "trait": "phospholipids in small LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022281", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol to total lipids in large VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022271", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol in small LDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022185", + "or_value": null, + "pmid": "38448586", + "trait": "total cholesterol in medium LDL" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "30275531", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022181", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol in IDL measurement" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0010351", + "or_value": null, + "pmid": "38448586", + "trait": "cholesteryl ester measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022229", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol in very large HDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022156", + "or_value": null, + "pmid": "39091897", + "trait": "total lipids in very small VLDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005010", + "or_value": null, + "pmid": "37947095", + "trait": "coronary artery disorder" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022168", + "or_value": null, + "pmid": "38448586", + "trait": "total lipids in small LDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022308", + "or_value": null, + "pmid": "39091897", + "trait": "total lipids in LDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022323", + "or_value": null, + "pmid": "38448586", + "trait": "triglycerides in small LDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022236", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol to total lipids in large VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005010", + "or_value": null, + "pmid": "30104761", + "trait": "coronary artery disorder" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022331", + "or_value": null, + "pmid": "39091897", + "trait": "triglycerides to total lipids in large LDL percentage " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022296", + "or_value": null, + "pmid": "38448586", + "trait": "phospholipids in small HDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022233", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol to total lipids in IDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022241", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol to total lipids in small LDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022247", + "or_value": null, + "pmid": "36764567", + "trait": "cholesteryl esters to total lipids in IDL percentage " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022275", + "or_value": null, + "pmid": "38448586", + "trait": "free cholesterol in very small VLDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0022167", + "or_value": null, + "pmid": "38448586", + "trait": "total cholesterol in large LDL" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022284", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol to total lipids in medium VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022312", + "or_value": null, + "pmid": "39091897", + "trait": "total lipids in very large HDL measurement " + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004611", + "or_value": null, + "pmid": "36220816", + "trait": "low density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022332", + "or_value": null, + "pmid": "39091897", + "trait": "triglycerides to total lipids in large VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004612", + "or_value": null, + "pmid": "36764567", + "trait": "high density lipoprotein cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022271", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol in small LDL measurement " + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "OBA_0000061", + "or_value": null, + "pmid": "35285134", + "trait": "circulating fibrinogen levels" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022238", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol to total lipids in medium LDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022146", + "or_value": null, + "pmid": "36764567", + "trait": "phospholipids in small VLDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "OBA_2050113", + "or_value": null, + "pmid": "35213538", + "trait": "LDL particle size" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022174", + "or_value": null, + "pmid": "39091897", + "trait": "phospholipids in large LDL measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022290", + "or_value": null, + "pmid": "39091897", + "trait": "free cholesterol to total lipids in very small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022331", + "or_value": null, + "pmid": "36764567", + "trait": "triglycerides to total lipids in large LDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022165", + "or_value": null, + "pmid": "39091897", + "trait": "cholesterol esters in large LDL measurement" + }, + { + "ancestries": [ + "EAS", + "EUR" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "34594039", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022259", + "or_value": null, + "pmid": "39091897", + "trait": "cholesteryl esters to total lipids in very small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0010815", + "or_value": null, + "pmid": "36764567", + "trait": "remnant cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022172", + "or_value": null, + "pmid": "39091897", + "trait": "concentration of medium LDL particles measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022225", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol in medium VLDL measurement " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022290", + "or_value": null, + "pmid": "36764567", + "trait": "free cholesterol to total lipids in very small VLDL percentage " + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022161", + "or_value": null, + "pmid": "36764567", + "trait": "total lipids in IDL" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "OBA_1001011", + "or_value": null, + "pmid": "38448586", + "trait": "phospholipid amount" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022164", + "or_value": null, + "pmid": "36764567", + "trait": "phospholipids in IDL measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "29507422", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EAS", + "EUR" + ], + "efo_id": "EFO_0004574", + "or_value": null, + "pmid": "33339817", + "trait": "total cholesterol measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0022228", + "or_value": null, + "pmid": "36764567", + "trait": "cholesterol in small VLDL measurement " + } + ], + "rs2981582": [ + { + "ancestries": [ + "EAS", + "EUR" + ], + "efo_id": "MONDO_0004989", + "or_value": 1.26, + "pmid": "17529967", + "trait": "breast carcinoma" + }, + { + "ancestries": [ + "EAS" + ], + "efo_id": "MONDO_0004989", + "or_value": 0.86, + "pmid": "37340002", + "trait": "breast carcinoma" + }, + { + "ancestries": [ + "EAS" + ], + "efo_id": "MONDO_0004989", + "or_value": 1.18, + "pmid": "27354352", + "trait": "breast carcinoma" + } + ], + "rs429358": [ + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004670", + "or_value": null, + "pmid": "21123754", + "trait": "beta-amyloid 1-42 measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004763", + "or_value": null, + "pmid": "21123754", + "trait": "p-tau measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004760", + "or_value": null, + "pmid": "21123754", + "trait": "t-tau measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": null, + "pmid": "21123754", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": null, + "pmid": "23419831", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0005194", + "or_value": null, + "pmid": "23419831", + "trait": "amyloid-beta measurement" + }, + { + "ancestries": [ + "AMR" + ], + "efo_id": "MONDO_0004975", + "or_value": 2.19, + "pmid": "39295643", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 2.92, + "pmid": "31473137", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 5.24, + "pmid": "31055733", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "EFO_0009268", + "or_value": null, + "pmid": "39024449", + "trait": "family history of Alzheimer\u2019s disease" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "MONDO_0004975", + "or_value": 0.41932878, + "pmid": "40708016", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "EFO_0009268", + "or_value": 0.41932878, + "pmid": "40708016", + "trait": "family history of Alzheimer\u2019s disease" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "MONDO_0001627", + "or_value": 0.41932878, + "pmid": "40708016", + "trait": "dementia" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "MONDO_0004975", + "or_value": 2.9, + "pmid": "41268768", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 3.07, + "pmid": "42237039", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0001627", + "or_value": 3.07, + "pmid": "42237039", + "trait": "dementia" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": null, + "pmid": "39998322", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "EFO_0009268", + "or_value": null, + "pmid": "39998322", + "trait": "family history of Alzheimer\u2019s disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_1001870", + "or_value": null, + "pmid": "34493870", + "trait": "late-onset Alzheimer's disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 1.798, + "pmid": "39129223", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0001627", + "or_value": 1.798, + "pmid": "39129223", + "trait": "dementia" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0009268", + "or_value": 1.798, + "pmid": "39129223", + "trait": "family history of Alzheimer\u2019s disease" + }, + { + "ancestries": [ + "AFR", + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 1.788, + "pmid": "38184787", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 3.245, + "pmid": "34122051", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0006514", + "or_value": null, + "pmid": "29860282", + "trait": "Alzheimer's disease biomarker measurement" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 4.22, + "pmid": "37700353", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 2.39, + "pmid": "30979435", + "trait": "Alzheimer disease" + }, + { + "ancestries": [], + "efo_id": "MONDO_0004975", + "or_value": 4.348, + "pmid": "34336000", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EAS", + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": null, + "pmid": "39023044", + "trait": "Alzheimer disease" + }, + { + "ancestries": [], + "efo_id": "MONDO_0004648", + "or_value": 1.69, + "pmid": "31473137", + "trait": "vascular dementia" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 3.8628085, + "pmid": "30636644", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "MONDO_0004975", + "or_value": null, + "pmid": "36543923", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "EFO_0009268", + "or_value": null, + "pmid": "36543923", + "trait": "family history of Alzheimer\u2019s disease" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "MONDO_0001627", + "or_value": null, + "pmid": "36543923", + "trait": "dementia" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 3.1, + "pmid": "40676597", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 2.722, + "pmid": "35694926", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0007488", + "or_value": 4.24, + "pmid": "37700353", + "trait": "Lewy body dementia" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": null, + "pmid": "39024449", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "AMR" + ], + "efo_id": "MONDO_0004975", + "or_value": 2.03, + "pmid": "41755704", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0009268", + "or_value": 1.6, + "pmid": "41755704", + "trait": "family history of Alzheimer\u2019s disease" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 2.55, + "pmid": "41298473", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": null, + "pmid": "33589840", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0009268", + "or_value": null, + "pmid": "33589840", + "trait": "family history of Alzheimer\u2019s disease" + }, + { + "ancestries": [ + "EAS" + ], + "efo_id": "EFO_0006514", + "or_value": null, + "pmid": "41804841", + "trait": "Alzheimer's disease biomarker measurement" + }, + { + "ancestries": [ + "EAS" + ], + "efo_id": "EFO_1001870", + "or_value": 3.97, + "pmid": "41951582", + "trait": "late-onset Alzheimer's disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 2.256, + "pmid": "33223526", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0004975", + "or_value": 3.32, + "pmid": "33637690", + "trait": "Alzheimer disease" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0004847", + "or_value": 3.32, + "pmid": "33637690", + "trait": "age at onset" + } + ], + "rs73885319": [ + { + "ancestries": [ + "AFR" + ], + "efo_id": "MONDO_0005300", + "or_value": null, + "pmid": "39024449", + "trait": "chronic kidney disease" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "OBA_2050096", + "or_value": null, + "pmid": "39024449", + "trait": "serum creatinine amount" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "EFO_0010134", + "or_value": null, + "pmid": "39024449", + "trait": "kidney transplant" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "MONDO_0100313", + "or_value": null, + "pmid": "36623684", + "trait": "focal segmental glomerulosclerosis" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS" + ], + "efo_id": "MONDO_0005300", + "or_value": 1.51, + "pmid": "31178898", + "trait": "chronic kidney disease" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "MONDO_0002462", + "or_value": null, + "pmid": "39024449", + "trait": "glomerulonephritis" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "EFO_0010690", + "or_value": null, + "pmid": "39024449", + "trait": "renal dialysis" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "MONDO_0005240", + "or_value": null, + "pmid": "39024449", + "trait": "kidney disorder" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "MONDO_0001530", + "or_value": null, + "pmid": "39024449", + "trait": "secondary hyperparathyroidism of renal origin" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "MONDO_0001106", + "or_value": null, + "pmid": "39024449", + "trait": "kidney failure" + } + ], + "rs7903146": [ + { + "ancestries": [ + "EAS" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.54, + "pmid": "19401414", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.4, + "pmid": "20581827", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.37, + "pmid": "18372903", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AFR", + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.46, + "pmid": "22101970", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.51, + "pmid": "23209189", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.71, + "pmid": "17668382", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.33, + "pmid": "20694148", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "SAS" + ], + "efo_id": "EFO_0000195", + "or_value": 1.33, + "pmid": "20694148", + "trait": "metabolic syndrome" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.37, + "pmid": "17463246", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.34, + "pmid": "17463248", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.48, + "pmid": "19734900", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.19, + "pmid": "23300278", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [], + "efo_id": "MONDO_0005148", + "or_value": 1.26, + "pmid": "22693455", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.49, + "pmid": "19056611", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.38, + "pmid": "17460697", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EAS" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.48, + "pmid": "23945395", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AFR", + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.36, + "pmid": "25102180", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.4, + "pmid": "24509480", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.65, + "pmid": "17293876", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.37, + "pmid": "24390345", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.39, + "pmid": "22885922", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.559, + "pmid": "34737425", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.66, + "pmid": "30470734", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "EFO_0004997", + "or_value": null, + "pmid": "39024449", + "trait": "type 2 diabetes nephropathy" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0006842", + "or_value": null, + "pmid": "38627641", + "trait": "diabetes mellitus biomarker" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.33, + "pmid": "35551307", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": null, + "pmid": "32541925", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EAS", + "EUR" + ], + "efo_id": "EFO_0009924", + "or_value": null, + "pmid": "34594039", + "trait": "Drugs used in diabetes use measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "MONDO_0005015", + "or_value": null, + "pmid": "39024449", + "trait": "diabetes mellitus" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "EFO_0000493", + "or_value": null, + "pmid": "39024449", + "trait": "family history" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "MONDO_0005147", + "or_value": null, + "pmid": "39024449", + "trait": "type 1 diabetes mellitus" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR" + ], + "efo_id": "EFO_0009924", + "or_value": null, + "pmid": "39024449", + "trait": "Drugs used in diabetes use measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005406", + "or_value": 1.22, + "pmid": "35220425", + "trait": "gestational diabetes" + }, + { + "ancestries": [ + "EAS" + ], + "efo_id": "MONDO_0005406", + "or_value": null, + "pmid": "37779084", + "trait": "gestational diabetes" + }, + { + "ancestries": [ + "AFR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0007800", + "or_value": null, + "pmid": "33619380", + "trait": "body fat percentage" + }, + { + "ancestries": [ + "AFR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": null, + "pmid": "33619380", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": null, + "pmid": "35393509", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.22, + "pmid": "41298473", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": null, + "pmid": "30595370", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.58, + "pmid": "33479058", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": null, + "pmid": "39379762", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AMR" + ], + "efo_id": "MONDO_0005148", + "or_value": null, + "pmid": "39024449", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005090", + "or_value": 1.53, + "pmid": "30470734", + "trait": "schizophrenia" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": null, + "pmid": "38374256", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.34, + "pmid": "31118516", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.37, + "pmid": "30297969", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.32, + "pmid": "31049640", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.366, + "pmid": "35587468", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": null, + "pmid": "30054458", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EAS", + "EUR" + ], + "efo_id": "MONDO_0005147", + "or_value": null, + "pmid": "34594039", + "trait": "type 1 diabetes mellitus" + }, + { + "ancestries": [ + "SAS" + ], + "efo_id": "OBA_2001013", + "or_value": null, + "pmid": "37308106", + "trait": "age of onset of type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.4, + "pmid": "26961502", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AFR" + ], + "efo_id": "MONDO_0005148", + "or_value": null, + "pmid": "29691896", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AMR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.36, + "pmid": "28254843", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EAS", + "EUR", + "SAS" + ], + "efo_id": "MONDO_0005148", + "or_value": null, + "pmid": "28869590", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "GO_0036273", + "or_value": 1.3, + "pmid": "40754711", + "trait": "response to statin" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "MONDO_0005148", + "or_value": 1.3, + "pmid": "40754711", + "trait": "type 2 diabetes mellitus" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EUR" + ], + "efo_id": "MONDO_0005147", + "or_value": 1.31, + "pmid": "41298473", + "trait": "type 1 diabetes mellitus" + }, + { + "ancestries": [ + "EUR" + ], + "efo_id": "EFO_0009924", + "or_value": null, + "pmid": "31015401", + "trait": "Drugs used in diabetes use measurement" + }, + { + "ancestries": [ + "AFR", + "AMR", + "EAS", + "EUR", + "SAS" + ], + "efo_id": "EFO_0004340", + "or_value": null, + "pmid": "33619380", + "trait": "body mass index" + } + ] + }, + "pmid_titles": { + "16415884": "Variant of transcription factor 7-like 2 (TCF7L2) gene confers risk of type 2 diabetes.", + "17478679": "A common variant on chromosome 9p21 affects the risk of myocardial infarction.", + "17529973": "A genome-wide association study identifies alleles in FGFR2 associated with risk of sporadic postmenopausal breast cancer.", + "18193044": "Six new loci associated with blood low-density lipoprotein cholesterol, high-density lipoprotein cholesterol or triglycerides in humans.", + "20566908": "Health professional perspective on disability in head and neck cancer.", + "20647424": "Association of trypanolytic ApoL1 variants with kidney disease in African Americans.", + "22158537": "Meta-analysis of genome-wide association studies identifies eight new loci for type 2 diabetes in east Asians.", + "23945395": "Genome-wide association study identifies three novel loci for type 2 diabetes.", + "24162737": "Meta-analysis of 74,046 individuals identifies 11 new susceptibility loci for Alzheimer's disease.", + "27005778": "Genome-wide study for circulating metabolites identifies 62 loci and reveals novel systemic effects of LPA." + }, + "pmid_topic": { + "16415884": [ + "confers", + "diabetes", + "factor", + "gene", + "like", + "risk", + "tcf7l2", + "transcription", + "type", + "variant" + ], + "17478679": [ + "9p21", + "affects", + "chromosome", + "common", + "infarction", + "myocardial", + "risk", + "the", + "variant" + ], + "17529973": [ + "alleles", + "associated", + "association", + "breast", + "cancer", + "fgfr2", + "genome", + "identifies", + "postmenopausal", + "risk", + "sporadic", + "study", + "wide", + "with" + ], + "18193044": [ + "associated", + "blood", + "cholesterol", + "density", + "high", + "humans", + "lipoprotein", + "loci", + "low", + "new", + "six", + "triglycerides", + "with" + ], + "20566908": [ + "and", + "cancer", + "disability", + "head", + "health", + "neck", + "perspective", + "professional" + ], + "20647424": [ + "african", + "americans", + "apol1", + "association", + "disease", + "kidney", + "trypanolytic", + "variants", + "with" + ], + "22158537": [ + "analysis", + "asians", + "association", + "diabetes", + "east", + "eight", + "for", + "genome", + "identifies", + "loci", + "meta", + "new", + "studies", + "type", + "wide" + ], + "23945395": [ + "association", + "diabetes", + "for", + "genome", + "identifies", + "loci", + "novel", + "study", + "three", + "type", + "wide" + ], + "24162737": [ + "046", + "alzheimer", + "analysis", + "disease", + "for", + "identifies", + "individuals", + "loci", + "meta", + "new", + "susceptibility" + ], + "27005778": [ + "and", + "circulating", + "effects", + "for", + "genome", + "identifies", + "loci", + "lpa", + "metabolites", + "novel", + "reveals", + "study", + "systemic", + "wide" + ] + } +} diff --git a/scripts/build_provenance_snapshot.py b/scripts/build_provenance_snapshot.py new file mode 100644 index 0000000..25c515a --- /dev/null +++ b/scripts/build_provenance_snapshot.py @@ -0,0 +1,112 @@ +"""Build the offline GWAS Catalog + PubMed snapshot for the provenance gate (ClawBio/ClawBench#3). + +Queries the live GWAS Catalog REST API and PubMed E-utilities for a fixed panel of variants and +PMIDs, then writes TRUTH/gwas_catalog/snapshot.json so CI can run the gate deterministically and +offline. Re-run this to refresh the freeze; the committed snapshot is the truth-of-record. + +Usage: + python scripts/build_provenance_snapshot.py + +No side effects at import. Network only inside main(). +""" +from __future__ import annotations + +import json +import sys +import time +import urllib.request +from pathlib import Path + +GWAS = "https://www.ebi.ac.uk/gwas/rest/api" +EUTILS = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils" +SNAPSHOT = Path(__file__).resolve().parents[1] / "TRUTH" / "gwas_catalog" / "snapshot.json" + +# Panel: rsid -> trait keywords whose associations we resolve to studies (bounds study fetches). +PANEL = { + "rs7903146": ["diabetes"], + "rs73885319": ["kidney", "renal", "glomerulo", "creatinine"], + "rs429358": ["alzheimer"], + "rs2981582": ["breast"], + "rs11591147": ["cholesterol", "ldl", "lipid", "coronary"], +} +# PMIDs we need PubMed topic terms for (cited + correct + discovery references). +TOPIC_PMIDS = ["20566908", "20647424", "22158537", "23945395", "27005778", + "17478679", "16415884", "24162737", "17529973", "18193044"] + +ANCESTRY_MAP = [ + ("european", "EUR"), ("east asian", "EAS"), ("south asian", "SAS"), + ("central asian", "SAS"), ("african american", "AFR"), ("afro-caribbean", "AFR"), + ("sub-saharan", "AFR"), ("african unspecified", "AFR"), ("african", "AFR"), + ("hispanic", "AMR"), ("latin american", "AMR"), +] + + +def _get(url: str) -> dict: + req = urllib.request.Request(url, headers={"Accept": "application/json"}) + with urllib.request.urlopen(req, timeout=30) as fh: + return json.load(fh) + + +def _map_ancestry(group: str) -> str | None: + g = (group or "").lower() + for needle, code in ANCESTRY_MAP: + if needle in g: + return code + return None + + +def _topic_terms(title: str) -> list[str]: + return sorted({t for t in "".join(c.lower() if c.isalnum() else " " for c in title).split() if len(t) > 2}) + + +def main() -> int: + associations: dict[str, list[dict]] = {} + for rsid, keywords in PANEL.items(): + url = f"{GWAS}/singleNucleotidePolymorphisms/{rsid}/associations?projection=associationBySnp" + assoc = _get(url).get("_embedded", {}).get("associations", []) + records, seen = [], set() + for a in assoc: + traits = a.get("efoTraits", []) or [] + if not any(any(k in t.get("trait", "").lower() for k in keywords) for t in traits): + continue + try: + study = _get(a["_links"]["study"]["href"]) + except Exception: + continue + pmid = str(study.get("publicationInfo", {}).get("pubmedId") or "") + ancestries = sorted({c for anc in study.get("ancestries", []) + for grp in anc.get("ancestralGroups", []) + if (c := _map_ancestry(grp.get("ancestralGroup")))}) + for t in traits: + key = (t.get("shortForm"), pmid) + if not pmid or key in seen: + continue + seen.add(key) + records.append({ + "efo_id": t.get("shortForm"), "trait": t.get("trait"), + "pmid": pmid, "ancestries": ancestries, + "or_value": a.get("orPerCopyNum"), + }) + time.sleep(0.1) + associations[rsid] = records + print(f" {rsid}: {len(records)} study-trait records", file=sys.stderr) + + pmid_titles, pmid_topic = {}, {} + ids = ",".join(TOPIC_PMIDS) + res = _get(f"{EUTILS}/esummary.fcgi?db=pubmed&retmode=json&id={ids}")["result"] + for u in res.get("uids", []): + title = res[u].get("title", "") + pmid_titles[u] = title + pmid_topic[u] = _topic_terms(title) + + SNAPSHOT.parent.mkdir(parents=True, exist_ok=True) + SNAPSHOT.write_text(json.dumps( + {"associations": associations, "pmid_titles": pmid_titles, "pmid_topic": pmid_topic}, + indent=2, sort_keys=True) + "\n") + print(f"wrote {SNAPSHOT} :: {sum(len(v) for v in associations.values())} records, " + f"{len(pmid_titles)} PMIDs", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_provenance_snapshot.py b/tests/test_provenance_snapshot.py new file mode 100644 index 0000000..8ac2d3d --- /dev/null +++ b/tests/test_provenance_snapshot.py @@ -0,0 +1,90 @@ +"""End-to-end test of the provenance gate against the REAL committed GWAS Catalog + PubMed +snapshot (TRUTH/gwas_catalog/snapshot.json), via CachedOracle. Offline and deterministic. + +Refresh the snapshot with: python scripts/build_provenance_snapshot.py + +This proves the gate works on real catalog data, not just the hand-built fixture: +- the flagship APOL1 citation (20566908 = a head-and-neck-cancer survey) is hard-blocked; +- the correct primary paper that GWAS Catalog has not indexed (Genovese 2010, 20647424) is + flagged for sign-off rather than false-rejected; +- a genuinely catalogued (variant, trait, ancestry, PMID, effect) tuple passes. +""" +from __future__ import annotations + +import json +from pathlib import Path + +import validate_provenance as VP + +_SNAPSHOT = Path(VP.__file__).resolve().parents[1] / "TRUTH" / "gwas_catalog" / "snapshot.json" + + +def _snap(): + with open(_SNAPSHOT) as fh: + return json.load(fh) + + +def _entry(rsid, efo, label, ancestry, pmid, value): + return {"variant": {"rsid": rsid}, "trait": {"label": label, "efo_id": efo}, + "ancestry": ancestry, "effect": {"measure": "OR", "value": value}, + "source": {"pmid": pmid}} + + +def test_snapshot_present_and_nonempty(): + snap = _snap() + assert snap["associations"], "snapshot has no associations - rebuild it" + assert "20566908" in snap["pmid_titles"] and "20647424" in snap["pmid_titles"] + + +def test_real_catalogued_tuple_passes(): + snap = _snap() + oracle = VP.CachedOracle(_SNAPSHOT) + # Pick any real record with an OR and a concrete ancestry, and cite it exactly. + rec = next(r for recs in snap["associations"].values() for r in recs + if r.get("or_value") and r.get("ancestries")) + rsid = next(rs for rs, recs in snap["associations"].items() if rec in recs) + f = VP.validate_entry( + _entry(rsid, rec["efo_id"], rec["trait"], rec["ancestries"][0], + rec["pmid"], rec["or_value"]), oracle) + assert f["valid"] is True, f + assert f["error_code"] is None + + +def test_flagship_apol1_headneck_pmid_is_blocked_on_real_data(): + oracle = VP.CachedOracle(_SNAPSHOT) + # 20566908 (head-and-neck-cancer survey) cited for APOL1/chronic kidney disease. + f = VP.validate_entry( + _entry("rs73885319", "MONDO_0005300", "chronic kidney disease", "AFR", "20566908", 1.89), + oracle) + assert f["valid"] is False + assert f["error_code"] in ("PMID_STUDY_MISMATCH", "ASSOC_NOT_FOUND") + + +def test_correct_genovese_paper_is_signoff_not_block_on_real_data(): + oracle = VP.CachedOracle(_SNAPSHOT) + # Genovese 2010 (20647424) really reports APOL1/kidney, but GWAS Catalog links the + # variant to PAGE/MVP instead - a coverage gap, not a fabrication. + f = VP.validate_entry( + _entry("rs73885319", "MONDO_0005300", "chronic kidney disease", "AFR", "20647424", 1.89), + oracle) + assert f["error_code"] == "TOPIC_MATCH_LOW" + assert f["severity"] == "warn" and f["valid"] is True + + +def test_cached_oracle_pmid_exists(): + oracle = VP.CachedOracle(_SNAPSHOT) + assert oracle.pmid_exists("20566908") is True + assert oracle.pmid_exists("99999999") is False + + +def test_panel_on_real_snapshot_blocks_flagship(): + oracle = VP.CachedOracle(_SNAPSHOT) + panel = [ + _entry("rs73885319", "MONDO_0005300", "chronic kidney disease", "AFR", "20566908", 1.89), + _entry("rs73885319", "MONDO_0005300", "chronic kidney disease", "AFR", "20647424", 1.89), + ] + report = VP.validate_panel(panel, oracle) + assert report["n_entries"] == 2 + assert report["n_blocking"] == 1 # the head-and-neck-cancer cite + assert report["n_warnings"] == 1 # the uncatalogued-but-correct Genovese cite + assert report["passed"] is False diff --git a/tests/test_run_provenance_gate.py b/tests/test_run_provenance_gate.py new file mode 100644 index 0000000..2c86089 --- /dev/null +++ b/tests/test_run_provenance_gate.py @@ -0,0 +1,113 @@ +"""TDD for the CI runner that blocks bad-citation skill PRs (ClawBio/ClawBench#3, L1 enforcement). + +The runner walks changed skills, and for any skill that declares the `emits_effect_sizes` +capability it requires + validates a provenance panel against the gate. A skill that declares +the capability but ships a fabricated citation (or no panel at all) fails the check; a skill +that does not emit effect sizes is skipped. Uses the committed GWAS Catalog/PubMed snapshot, +so it is offline and deterministic. +""" +from __future__ import annotations + +import json + +import run_provenance_gate as G + +# A real catalogued tuple from the committed snapshot (rs7903146 / T2D / EUR / Repin 2010). +GOOD_ENTRY = {"variant": {"rsid": "rs7903146"}, + "trait": {"label": "type 2 diabetes", "efo_id": "MONDO_0005148"}, + "ancestry": "EUR", "effect": {"measure": "OR", "value": 1.4}, + "source": {"pmid": "20581827"}} +# The flagship fabricated citation: head-and-neck-cancer survey cited as APOL1. +BAD_ENTRY = {"variant": {"rsid": "rs73885319"}, + "trait": {"label": "chronic kidney disease", "efo_id": "MONDO_0005300"}, + "ancestry": "AFR", "effect": {"measure": "OR", "value": 1.89}, + "source": {"pmid": "20566908"}} + +SKILL_MD_EFFECTS = """--- +name: {name} +metadata: + capabilities: + - emits_effect_sizes +--- +# {name} +""" +SKILL_MD_PLAIN = """--- +name: {name} +metadata: + capabilities: [] +--- +# {name} +""" + + +def _make_skill(root, name, skill_md, panel=None): + d = root / "skills" / name + (d / "data").mkdir(parents=True, exist_ok=True) + (d / "SKILL.md").write_text(skill_md.format(name=name)) + if panel is not None: + (d / "data" / "provenance.json").write_text(json.dumps(panel)) + return d + + +def test_declares_effect_sizes_detects_capability(tmp_path): + good = _make_skill(tmp_path, "a", SKILL_MD_EFFECTS, [GOOD_ENTRY]) + plain = _make_skill(tmp_path, "b", SKILL_MD_PLAIN, None) + assert G.declares_effect_sizes(good) is True + assert G.declares_effect_sizes(plain) is False + + +def test_good_panel_passes(tmp_path): + d = _make_skill(tmp_path, "good", SKILL_MD_EFFECTS, [GOOD_ENTRY]) + res = G.gate_skill(d) + assert res["status"] == "pass", res + + +def test_fabricated_citation_fails(tmp_path): + d = _make_skill(tmp_path, "bad", SKILL_MD_EFFECTS, [BAD_ENTRY]) + res = G.gate_skill(d) + assert res["status"] == "fail" + codes = [f["error_code"] for f in res["findings"]] + assert "PMID_STUDY_MISMATCH" in codes + + +def test_capability_without_panel_is_error(tmp_path): + d = _make_skill(tmp_path, "nopanel", SKILL_MD_EFFECTS, panel=None) + res = G.gate_skill(d) + assert res["status"] == "error" + assert res["findings"][0]["error_code"] == "MISSING_PROVENANCE" + + +def test_non_emitting_skill_is_skipped(tmp_path): + d = _make_skill(tmp_path, "plain", SKILL_MD_PLAIN, None) + res = G.gate_skill(d) + assert res["status"] == "skipped" + + +def test_run_blocks_when_any_skill_fails(tmp_path): + _make_skill(tmp_path, "good", SKILL_MD_EFFECTS, [GOOD_ENTRY]) + _make_skill(tmp_path, "bad", SKILL_MD_EFFECTS, [BAD_ENTRY]) + _make_skill(tmp_path, "plain", SKILL_MD_PLAIN, None) + report = G.run(tmp_path / "skills") + assert report["blocking"] >= 1 + assert report["exit_code"] == 1 + statuses = {r["skill"]: r["status"] for r in report["results"]} + assert statuses["good"] == "pass" + assert statuses["bad"] == "fail" + assert statuses["plain"] == "skipped" + + +def test_run_passes_when_all_clean(tmp_path): + _make_skill(tmp_path, "good", SKILL_MD_EFFECTS, [GOOD_ENTRY]) + _make_skill(tmp_path, "plain", SKILL_MD_PLAIN, None) + report = G.run(tmp_path / "skills") + assert report["blocking"] == 0 + assert report["exit_code"] == 0 + + +def test_changed_filter_restricts_scope(tmp_path): + _make_skill(tmp_path, "good", SKILL_MD_EFFECTS, [GOOD_ENTRY]) + _make_skill(tmp_path, "bad", SKILL_MD_EFFECTS, [BAD_ENTRY]) + # Only the good skill changed in this PR: the bad skill must not be evaluated. + report = G.run(tmp_path / "skills", changed=["skills/good/data/provenance.json"]) + assert report["exit_code"] == 0 + assert {r["skill"] for r in report["results"]} == {"good"} diff --git a/tests/test_validate_provenance.py b/tests/test_validate_provenance.py new file mode 100644 index 0000000..4382361 --- /dev/null +++ b/tests/test_validate_provenance.py @@ -0,0 +1,196 @@ +"""TDD for the effect-size provenance gate (ClawBio/ClawBench#3, L1) - deterministic fixture. + +The gate resolves every association entry a skill emits against a citation oracle +(GWAS Catalog + PubMed) and FAILS CLOSED when the cited paper does not actually support +the (variant, trait, ancestry, effect) claim. "Cited but wrong" is the same hazard as +"missing". A correct-but-uncatalogued citation (GWAS Catalog does not index every primary +paper) is flagged for human sign-off rather than hard-blocked. + +These cases come from ancestry-risk-profiler (PR #297), resolved against PubMed during the +2026-06 audit. The fixture oracle encodes ground truth so the test is deterministic and +needs no network; test_provenance_snapshot.py exercises the same logic on the real +committed GWAS Catalog/PubMed snapshot. +""" +from __future__ import annotations + +import validate_provenance as VP + + +# rsid -> real study records. Comments give the real paper each PMID resolves to. +CATALOG = { + # TCF7L2 / type 2 diabetes. Grant 2006 (16415884, EUR); Cho 2011 (22158537, EAS). + "rs7903146": [ + {"efo_id": "EFO_0001360", "trait": "type 2 diabetes", "pmid": "16415884", + "ancestries": ["EUR"], "or_value": 1.45}, + {"efo_id": "EFO_0001360", "trait": "type 2 diabetes", "pmid": "22158537", + "ancestries": ["EAS"], "or_value": 1.40}, + ], + # APOE / Alzheimer's. Lambert 2013 IGAP (24162737, EUR) - NOT a T2D paper. + "rs429358": [ + {"efo_id": "EFO_0000249", "trait": "Alzheimer disease", "pmid": "24162737", + "ancestries": ["EUR"], "or_value": 3.2}, + ], + # FGFR2 / breast cancer. Easton 2007 (17529973, EUR). + "rs2981582": [ + {"efo_id": "EFO_0000305", "trait": "breast carcinoma", "pmid": "17529973", + "ancestries": ["EUR"], "or_value": 1.26}, + ], + # PCSK9 / LDL cholesterol. Kathiresan 2008 (18193044, EUR). The 9p21 MI paper (17478679) + # does not report this variant. + "rs11591147": [ + {"efo_id": "EFO_0004611", "trait": "LDL cholesterol", "pmid": "18193044", + "ancestries": ["EUR"], "or_value": 0.6}, + ], + # APOL1 / chronic kidney disease. GWAS Catalog links this to PAGE (31178898), NOT to the + # original Genovese 2010 paper (20647424) - a real coverage gap. + "rs73885319": [ + {"efo_id": "EFO_0003086", "trait": "chronic kidney disease", "pmid": "31178898", + "ancestries": ["AFR"], "or_value": None}, + ], +} + +# PMIDs that resolve in PubMed (none is a typo). +RESOLVABLE = { + "16415884", "22158537", "23945395", "17478679", "27005778", "20566908", "20647424", + "24162737", "17529973", "18193044", "31178898", +} + +# Real PubMed title topic terms. The wrong citations are deliberately off-topic for their +# claimed trait; 20647424 (correct Genovese) overlaps "kidney". +TOPIC_TERMS = { + "20566908": {"health", "professional", "perspective", "disability", "head", "neck", "cancer"}, + "20647424": {"association", "trypanolytic", "apol1", "variants", "kidney", "african", "americans"}, + "22158537": {"meta", "analysis", "loci", "type", "diabetes", "east", "asians"}, + "23945395": {"genome", "association", "three", "novel", "loci", "type", "diabetes"}, + "27005778": {"genome", "circulating", "metabolites", "loci", "systemic", "effects", "lpa"}, + "17478679": {"common", "variant", "chromosome", "9p21", "risk", "myocardial", "infarction"}, + "16415884": {"variant", "transcription", "factor", "tcf7l2", "gene", "diabetes"}, +} + + +class FixtureOracle: + def pmid_exists(self, pmid: str) -> bool: + return pmid in RESOLVABLE + + def associations_for(self, rsid: str) -> list[dict]: + return CATALOG.get(rsid, []) + + def pubmed_topic_terms(self, pmid: str) -> set[str]: + return TOPIC_TERMS.get(pmid, set()) + + +ORACLE = FixtureOracle() + + +def _entry(rsid, efo, trait, ancestry, pmid, or_value=1.4): + return { + "variant": {"rsid": rsid}, + "trait": {"label": trait, "efo_id": efo}, + "ancestry": ancestry, + "effect": {"measure": "OR", "value": or_value}, + "source": {"pmid": pmid}, + } + + +def _code(entry): + return VP.validate_entry(entry, ORACLE)["error_code"] + + +# --- The six audit PMIDs ------------------------------------------------------ + +def test_correct_citation_passes(): + f = VP.validate_entry( + _entry("rs7903146", "EFO_0001360", "type 2 diabetes", "EUR", "16415884", 1.45), ORACLE) + assert f["valid"] is True and f["error_code"] is None + + +def test_apol1_headneck_cancer_pmid_rejected(): + # Flagship: 20566908 is a head-and-neck-cancer survey, cited as Genovese APOL1. + assert _code(_entry("rs73885319", "EFO_0003086", "chronic kidney disease", + "AFR", "20566908", 1.89)) == "PMID_STUDY_MISMATCH" + + +def test_east_asian_t2d_paper_cited_for_alzheimers_rejected(): + assert _code(_entry("rs429358", "EFO_0000249", "Alzheimer disease", + "SAS", "22158537", 2.7)) == "PMID_STUDY_MISMATCH" + + +def test_metabolomics_paper_cited_for_breast_cancer_rejected(): + assert _code(_entry("rs2981582", "EFO_0000305", "breast carcinoma", + "AFR", "27005778", 1.11)) == "PMID_STUDY_MISMATCH" + + +def test_japanese_t2d_paper_cited_for_alzheimers_rejected(): + assert _code(_entry("rs429358", "EFO_0000249", "Alzheimer disease", + "EAS", "23945395", 2.8)) == "PMID_STUDY_MISMATCH" + + +def test_9p21_mi_paper_cited_for_pcsk9_rejected(): + assert _code(_entry("rs11591147", "EFO_0004611", "LDL cholesterol", + "EUR", "17478679", 0.61)) == "PMID_STUDY_MISMATCH" + + +# --- Coverage gap: correct primary paper GWAS Catalog has not indexed -------- + +def test_correct_but_uncatalogued_primary_is_topic_warning(): + # Genovese 2010 (20647424) genuinely reports APOL1/kidney, but GWAS Catalog links the + # variant to PAGE instead. The gate must WARN for sign-off, not hard-block. + f = VP.validate_entry( + _entry("rs73885319", "EFO_0003086", "chronic kidney disease", "AFR", "20647424", 1.89), + ORACLE) + assert f["error_code"] == "TOPIC_MATCH_LOW" + assert f["severity"] == "warn" and f["valid"] is True + + +# --- Other error codes (full validator surface) ------------------------------- + +def test_unresolvable_pmid(): + assert _code(_entry("rs7903146", "EFO_0001360", "type 2 diabetes", + "EUR", "99999999", 1.45)) == "PMID_UNRESOLVABLE" + + +def test_ancestry_mismatch(): + assert _code(_entry("rs7903146", "EFO_0001360", "type 2 diabetes", + "SAS", "16415884", 1.45)) == "ANCESTRY_MISMATCH" + + +def test_effect_out_of_range(): + assert _code(_entry("rs7903146", "EFO_0001360", "type 2 diabetes", + "EUR", "16415884", 9.8)) == "EFFECT_OUT_OF_RANGE" + + +def test_assoc_not_found_when_variant_absent_and_topic_off(): + # Variant not in catalog; PMID topic (head-and-neck cancer) does not overlap the trait. + assert _code(_entry("rs99999999", "EFO_0001360", "type 2 diabetes", + "EUR", "20566908", 1.4)) == "ASSOC_NOT_FOUND" + + +def test_topic_match_low_is_warning_not_block(): + # Variant absent from catalog, but the PMID's topic overlaps the trait label: advisory. + f = VP.validate_entry( + _entry("rs99999999", "EFO_0001360", "diabetes", "EUR", "16415884", 1.4), ORACLE) + assert f["error_code"] == "TOPIC_MATCH_LOW" + assert f["severity"] == "warn" and f["valid"] is True + + +def test_schema_invalid_entry_is_caught(): + f = VP.validate_entry({"variant": {"rsid": "rs7903146"}}, ORACLE) + assert f["valid"] is False and f["error_code"] == "SCHEMA_INVALID" + + +def test_validate_panel_summarises_and_never_raises(): + panel = [ + _entry("rs7903146", "EFO_0001360", "type 2 diabetes", "EUR", "16415884", 1.45), + _entry("rs73885319", "EFO_0003086", "chronic kidney disease", "AFR", "20566908", 1.89), + ] + report = VP.validate_panel(panel, ORACLE) + assert report["n_entries"] == 2 + assert report["n_blocking"] == 1 + assert report["passed"] is False + + +def test_error_codes_are_declared(): + for code in ("PMID_UNRESOLVABLE", "ASSOC_NOT_FOUND", "PMID_STUDY_MISMATCH", + "ANCESTRY_MISMATCH", "EFFECT_OUT_OF_RANGE", "TOPIC_MATCH_LOW", + "SCHEMA_INVALID"): + assert code in VP.ERROR_CODES