Locus-to-Gene Mapper
Generate a reproducible locus-to-gene mapping for one trait (or a seed set of lead variants), with explicit evidence attribution and conservative confidence labels.
This skill is optimized for bioinformaticians who need executable, traceable mapping from variant signals to plausible causal genes.
Required Inputs
Provide at least one anchor source:
trait_query (string), for example chronic obstructive pulmonary disease
efo_id (string), for example EFO_0000341
seed_rsids (list[string]), for example ["rs1873625", "rs7903146"]
Optional Inputs
target_gene (string), optional gene of interest for highlighting in output
show_child_traits (bool), default true
phenotype_terms (list[string]), optional additional terms to include when finding anchors
max_anchor_associations (int), default 1200
max_loci (int), default 25
max_genes_per_locus (int), default 10
max_coloc_rows_per_locus (int), default 100
max_eqtl_rows_per_variant (int), default 200
genebass_burden_sets (list[string]), default ["pLoF", "missense|LC"]
include_clinvar (bool), default true
include_gnomad_context (bool), default true
include_hpa_tissue_context (bool), default true
include_figures (bool), default false
disable_default_seeds (bool), default false; if false, common traits automatically get built-in seed rsIDs
figure_output_dir (string), default ./output/figures
mapping_output_path (string), default ./output/locus_to_gene_mapping.json
summary_output_path (string), default ./output/locus_to_gene_summary.md
Runtime Requirements
- Python
3.11+
requests
- Optional for figure generation:
matplotlib, seaborn, pandas
Bundled Script (Deterministic Runner)
- Primary entrypoint:
scripts/map_locus_to_gene.py
- This script:
- resolves trait/EFO and anchor variants,
- gathers locus-to-gene evidence through the chained skills,
- writes mapping JSON and summary markdown,
- optionally renders figures when plotting deps are available.
Run:
python locus-to-gene-mapper-skill/scripts/map_locus_to_gene.py \
--input-json /path/to/input.json \
--print-result
Quick start (no input JSON file):
python locus-to-gene-mapper-skill/scripts/map_locus_to_gene.py \
--trait-query "type 2 diabetes" \
--print-result
Trait-only runs default to include_figures=true unless explicitly disabled with --no-include-figures.
Minimal input JSON:
{
"trait_query": "type 2 diabetes"
}
Built-in default seeds (when disable_default_seeds=false):
type 2 diabetes / t2d -> rs7903146, rs13266634, rs7756992, rs5219, rs1801282, rs4402960
coronary artery disease / cad -> rs1333049, rs4977574, rs9349379, rs6725887, rs1746048, rs3184504
body mass index / bmi -> rs9939609, rs17782313, rs6548238, rs10938397, rs7498665, rs7138803
asthma -> rs7216389, rs2305480, rs9273349
rheumatoid arthritis -> rs2476601, rs3761847, rs660895
alzheimer disease -> rs429358, rs7412, rs6733839, rs11136000, rs3851179
ldl cholesterol / total cholesterol -> rs7412, rs429358, rs6511720, rs629301, rs12740374, rs11591147
Autonomous Execution Contract (Embedded Behavior)
When a user asks for locus-to-gene mapping and gives only a trait (for example, type 2 diabetes), do the following automatically:
- Run the bundled script with
--trait-query "<user_trait>" --print-result (no manual JSON required).
- If it returns
No anchors remained, rerun once with a built-in default seed rsID for that trait (unless disable_default_seeds=true).
- Read the generated
mapping_output_path and summary_output_path.
- Return this concise response structure:
Top 5 cross-locus prioritized genes
Per-locus top gene (score, confidence)
Visualization artifact (figure path(s) or Mermaid fallback block)
Warnings and limitations
- For inline image rendering in chat:
- read
inline_image_markdown from script result
- emit those lines exactly as plain markdown (no code fences)
- if inline rendering still fails, instruct user to upload PNG files into the chat
Do not ask the user to run python manually unless execution is actually blocked.
Skill Chaining Order (Mandatory)
Use these skills in order. Skip only when an earlier step is not needed by provided inputs.
efo-ontology-skill
- Resolve
trait_query to canonical EFO term and synonyms.
- Expand descendants when
show_child_traits=true.
gwas-catalog-skill
- Discover anchor variants for the trait/EFO scope.
- Pull association/study metadata for locus context.
variant-coordinate-finder-skill
- Normalize each anchor to rsID plus GRCh37/GRCh38 coordinates.
opentargets-skill
- Retrieve credible set context, L2G predictions, and colocalisation evidence per locus.
gtex-eqtl-skill
- Retrieve single-tissue eQTL support for anchor variants.
genebass-gene-burden-skill
- Retrieve rare-variant burden support for candidate genes.
clinvar-variation-skill (when include_clinvar=true)
- Add variant clinical/coding annotations.
gnomad-graphql-skill (when include_gnomad_context=true)
- Add frequency and gene-level constraint context.
human-protein-atlas-skill (when include_hpa_tissue_context=true)
- Add tissue plausibility context for top genes.
Never perform additional retrieval after final candidate-gene scoring starts.
Output Contract (Required)
Always return:
locus_to_gene_mapping.json
locus_to_gene_summary.md
JSON contract
{
"meta": {
"trait_query": "...",
"efo_id": "EFO_...",
"generated_at": "ISO-8601",
"sources_queried": []
},
"anchors": [
{
"rsid": "rs...",
"grch38": {"chr": "3", "pos": 49629531, "ref": "A", "alt": "C"},
"lead_trait": "...",
"p_value": 2e-11,
"cohort": "..."
}
],
"loci": [
{
"locus_id": "chr3:49000000-50200000",
"lead_rsid": "rs...",
"candidate_genes": [
{
"symbol": "MST1",
"ensembl_id": "ENSG...",
"overall_score": 0.71,
"confidence": "High|Medium|Low|VeryLow",
"evidence": {
"l2g_max": 0.83,
"coloc_max_h4": 0.84,
"eqtl_tissues": ["Lung"],
"rare_variant_support": "none|nominal|strong",
"coding_support": "none|noncoding|coding",
"clinvar_support": "none|present",
"gnomad_context": "...",
"hpa_tissue_support": ["lung"]
},
"rationale": [
"..."
],
"limitations": [
"..."
]
}
]
}
],
"cross_locus_ranked_genes": [
{
"symbol": "...",
"supporting_loci": 3,
"mean_score": 0.62,
"max_score": 0.81
}
],
"warnings": [],
"limitations": []
}
Markdown summary contract
The summary must include sections in this exact order:
Objective
Inputs and scope
Anchor variant summary
Per-locus top genes
Cross-locus prioritized genes
Key caveats
Recommended next analyses
Optional Figure Contract
Only produce figures when include_figures=true.
If figures are generated, append this block to JSON:
{
"figures": [
{
"id": "locus_gene_heatmap",
"path": "./output/figures/locus_gene_heatmap.png",
"caption": "Top candidate genes by evidence component across loci"
}
]
}
Recommended figure set:
locus_gene_heatmap.png
- Rows: top genes, columns: evidence components (
L2G, coloc, eQTL, burden, coding).
locus_score_decomposition.png
- Stacked bars per locus for top 3 genes.
tissue_support_dotplot.png
- Gene-by-tissue evidence dots from GTEx/HPA context.
If plotting dependencies are unavailable, skip PNG generation and output Mermaid diagrams in markdown as fallback.
The script also returns inline_image_markdown and render_instructions fields to support inline chat rendering.
Scoring Rules (Deterministic)
For each candidate gene per locus, compute:
l2g_component: max L2G score for the gene in locus (0..1)
coloc_component: max h4 (or clpp when only CLPP is available), clipped to 0..1
eqtl_component: min(1, relevant_tissue_hits / 3)
burden_component:
1.0 if burden p < 2.5e-6
0.6 if 2.5e-6 <= p < 0.05
0.0 otherwise
coding_component:
1.0 for coding consequence in target gene with supportive ClinVar annotation
0.6 for coding consequence in target gene without supportive ClinVar annotation
0.3 for noncoding-in-gene support only
0.0 otherwise
Overall score:
overall_score = 0.40*l2g + 0.25*coloc + 0.15*eqtl + 0.10*burden + 0.10*coding
Confidence label:
High if score >= 0.75
Medium if 0.55 <= score < 0.75
Low if 0.35 <= score < 0.55
VeryLow if score < 0.35
Pipeline Contract
Phase 0: Validate and normalize input
- Enforce that at least one of
trait_query, efo_id, seed_rsids is present.
- Normalize rsID formatting and deduplicate seed variants.
- Resolve free-text trait to one canonical EFO term when needed.
Phase 1: Build anchor set
- If trait/EFO input is provided, pull associations and rank anchors by p-value and effect availability.
- Merge trait-derived anchors with user-supplied
seed_rsids.
- Cap anchors using
max_loci and log dropped anchors in warnings.
Phase 2: Gather locus-to-gene evidence
- Normalize anchor coordinates (both builds when possible).
- Pull Open Targets locus evidence (credible set/L2G/coloc).
- Pull GTEx variant-level eQTL rows.
- Pull gene-level burden results for mapped candidate genes.
- Pull ClinVar and gnomAD context when enabled.
Phase 3: Harmonize and score
- Build a per-locus candidate-gene table.
- Compute deterministic component scores and overall score.
- Create cross-locus aggregate rankings.
Phase 4: Synthesize outputs
- Write JSON mapping file.
- Write markdown summary in exact section order.
- Optionally generate figures and append
figures metadata.
Phase 5: QC gates
Fail the run when any of the following occurs:
- No anchors after normalization.
- Any locus has candidate genes without score fields.
overall_score outside 0..1.
- Summary section order mismatch.
- Claim of causality without explicit evidence support in rationale text.
Public Interface
def map_locus_to_gene(input_json: dict) -> dict:
...
Return:
{
"status": "ok",
"mapping_output_path": "./output/locus_to_gene_mapping.json",
"summary_output_path": "./output/locus_to_gene_summary.md",
"figure_paths": [],
"warnings": []
}
Non-Invention Rules
- Never invent rsIDs, p-values, scores, cohort labels, tissues, or gene links.
- Never silently impute missing evidence as positive support.
- When evidence is missing, record it as a limitation and reduce confidence.
- Keep evidence provenance explicit (
source skill + endpoint family) in rationale lines.
Non-Goals
- Do not claim definitive causal genes from association evidence alone.
- Do not run fine-mapping methods not directly provided by upstream sources.
- Do not collapse multiple independent signals into one without stating assumptions.
1---2name: locus-to-gene-mapper-skill3description: Map GWAS loci to ranked candidate genes using a deterministic multi-skill chain (EFO -> GWAS -> coordinates -> Open Targets L2G/coloc -> eQTL -> burden/coding context), with reproducible tables and optional figures. Use when a user provides a trait/EFO term and/or lead variants and needs locus-to-gene prioritization for downstream biology decisions.4---56## Locus-to-Gene Mapper78Generate a reproducible locus-to-gene mapping for one trait (or a seed set of lead variants), with explicit evidence attribution and conservative confidence labels.910This skill is optimized for bioinformaticians who need executable, traceable mapping from variant signals to plausible causal genes.1112## Required Inputs1314Provide at least one anchor source:1516- `trait_query` (string), for example `chronic obstructive pulmonary disease`17- `efo_id` (string), for example `EFO_0000341`18- `seed_rsids` (list[string]), for example `["rs1873625", "rs7903146"]`1920## Optional Inputs2122- `target_gene` (string), optional gene of interest for highlighting in output23- `show_child_traits` (bool), default `true`24- `phenotype_terms` (list[string]), optional additional terms to include when finding anchors25- `max_anchor_associations` (int), default `1200`26- `max_loci` (int), default `25`27- `max_genes_per_locus` (int), default `10`28- `max_coloc_rows_per_locus` (int), default `100`29- `max_eqtl_rows_per_variant` (int), default `200`30- `genebass_burden_sets` (list[string]), default `["pLoF", "missense|LC"]`31- `include_clinvar` (bool), default `true`32- `include_gnomad_context` (bool), default `true`33- `include_hpa_tissue_context` (bool), default `true`34- `include_figures` (bool), default `false`35- `disable_default_seeds` (bool), default `false`; if `false`, common traits automatically get built-in seed rsIDs36- `figure_output_dir` (string), default `./output/figures`37- `mapping_output_path` (string), default `./output/locus_to_gene_mapping.json`38- `summary_output_path` (string), default `./output/locus_to_gene_summary.md`3940## Runtime Requirements4142- Python `3.11+`43- `requests`44- Optional for figure generation: `matplotlib`, `seaborn`, `pandas`4546## Bundled Script (Deterministic Runner)4748- Primary entrypoint: `scripts/map_locus_to_gene.py`49- This script:50 - resolves trait/EFO and anchor variants,51 - gathers locus-to-gene evidence through the chained skills,52 - writes mapping JSON and summary markdown,53 - optionally renders figures when plotting deps are available.5455Run:5657```bash58python locus-to-gene-mapper-skill/scripts/map_locus_to_gene.py \59 --input-json /path/to/input.json \60 --print-result61```6263Quick start (no input JSON file):6465```bash66python locus-to-gene-mapper-skill/scripts/map_locus_to_gene.py \67 --trait-query "type 2 diabetes" \68 --print-result69```7071Trait-only runs default to `include_figures=true` unless explicitly disabled with `--no-include-figures`.7273Minimal input JSON:7475```json76{77 "trait_query": "type 2 diabetes"78}79```8081Built-in default seeds (when `disable_default_seeds=false`):8283- `type 2 diabetes` / `t2d` -> `rs7903146`, `rs13266634`, `rs7756992`, `rs5219`, `rs1801282`, `rs4402960`84- `coronary artery disease` / `cad` -> `rs1333049`, `rs4977574`, `rs9349379`, `rs6725887`, `rs1746048`, `rs3184504`85- `body mass index` / `bmi` -> `rs9939609`, `rs17782313`, `rs6548238`, `rs10938397`, `rs7498665`, `rs7138803`86- `asthma` -> `rs7216389`, `rs2305480`, `rs9273349`87- `rheumatoid arthritis` -> `rs2476601`, `rs3761847`, `rs660895`88- `alzheimer disease` -> `rs429358`, `rs7412`, `rs6733839`, `rs11136000`, `rs3851179`89- `ldl cholesterol` / `total cholesterol` -> `rs7412`, `rs429358`, `rs6511720`, `rs629301`, `rs12740374`, `rs11591147`9091## Autonomous Execution Contract (Embedded Behavior)9293When a user asks for locus-to-gene mapping and gives only a trait (for example, `type 2 diabetes`), do the following automatically:94951. Run the bundled script with `--trait-query "<user_trait>" --print-result` (no manual JSON required).962. If it returns `No anchors remained`, rerun once with a built-in default seed rsID for that trait (unless `disable_default_seeds=true`).973. Read the generated `mapping_output_path` and `summary_output_path`.984. Return this concise response structure:99 - `Top 5 cross-locus prioritized genes`100 - `Per-locus top gene (score, confidence)`101 - `Visualization artifact` (figure path(s) or Mermaid fallback block)102 - `Warnings and limitations`1035. For inline image rendering in chat:104 - read `inline_image_markdown` from script result105 - emit those lines exactly as plain markdown (no code fences)106 - if inline rendering still fails, instruct user to upload PNG files into the chat107108Do not ask the user to run python manually unless execution is actually blocked.109110## Skill Chaining Order (Mandatory)111112Use these skills in order. Skip only when an earlier step is not needed by provided inputs.1131141. `efo-ontology-skill`115 - Resolve `trait_query` to canonical EFO term and synonyms.116 - Expand descendants when `show_child_traits=true`.1172. `gwas-catalog-skill`118 - Discover anchor variants for the trait/EFO scope.119 - Pull association/study metadata for locus context.1203. `variant-coordinate-finder-skill`121 - Normalize each anchor to rsID plus GRCh37/GRCh38 coordinates.1224. `opentargets-skill`123 - Retrieve credible set context, L2G predictions, and colocalisation evidence per locus.1245. `gtex-eqtl-skill`125 - Retrieve single-tissue eQTL support for anchor variants.1266. `genebass-gene-burden-skill`127 - Retrieve rare-variant burden support for candidate genes.1287. `clinvar-variation-skill` (when `include_clinvar=true`)129 - Add variant clinical/coding annotations.1308. `gnomad-graphql-skill` (when `include_gnomad_context=true`)131 - Add frequency and gene-level constraint context.1329. `human-protein-atlas-skill` (when `include_hpa_tissue_context=true`)133 - Add tissue plausibility context for top genes.134135Never perform additional retrieval after final candidate-gene scoring starts.136137## Output Contract (Required)138139Always return:1401411. `locus_to_gene_mapping.json`1422. `locus_to_gene_summary.md`143144### JSON contract145146```json147{148 "meta": {149 "trait_query": "...",150 "efo_id": "EFO_...",151 "generated_at": "ISO-8601",152 "sources_queried": []153 },154 "anchors": [155 {156 "rsid": "rs...",157 "grch38": {"chr": "3", "pos": 49629531, "ref": "A", "alt": "C"},158 "lead_trait": "...",159 "p_value": 2e-11,160 "cohort": "..."161 }162 ],163 "loci": [164 {165 "locus_id": "chr3:49000000-50200000",166 "lead_rsid": "rs...",167 "candidate_genes": [168 {169 "symbol": "MST1",170 "ensembl_id": "ENSG...",171 "overall_score": 0.71,172 "confidence": "High|Medium|Low|VeryLow",173 "evidence": {174 "l2g_max": 0.83,175 "coloc_max_h4": 0.84,176 "eqtl_tissues": ["Lung"],177 "rare_variant_support": "none|nominal|strong",178 "coding_support": "none|noncoding|coding",179 "clinvar_support": "none|present",180 "gnomad_context": "...",181 "hpa_tissue_support": ["lung"]182 },183 "rationale": [184 "..."185 ],186 "limitations": [187 "..."188 ]189 }190 ]191 }192 ],193 "cross_locus_ranked_genes": [194 {195 "symbol": "...",196 "supporting_loci": 3,197 "mean_score": 0.62,198 "max_score": 0.81199 }200 ],201 "warnings": [],202 "limitations": []203}204```205206### Markdown summary contract207208The summary must include sections in this exact order:2092101. `Objective`2112. `Inputs and scope`2123. `Anchor variant summary`2134. `Per-locus top genes`2145. `Cross-locus prioritized genes`2156. `Key caveats`2167. `Recommended next analyses`217218## Optional Figure Contract219220Only produce figures when `include_figures=true`.221222If figures are generated, append this block to JSON:223224```json225{226 "figures": [227 {228 "id": "locus_gene_heatmap",229 "path": "./output/figures/locus_gene_heatmap.png",230 "caption": "Top candidate genes by evidence component across loci"231 }232 ]233}234```235236Recommended figure set:2372381. `locus_gene_heatmap.png`239 - Rows: top genes, columns: evidence components (`L2G`, `coloc`, `eQTL`, `burden`, `coding`).2402. `locus_score_decomposition.png`241 - Stacked bars per locus for top 3 genes.2423. `tissue_support_dotplot.png`243 - Gene-by-tissue evidence dots from GTEx/HPA context.244245If plotting dependencies are unavailable, skip PNG generation and output Mermaid diagrams in markdown as fallback.246The script also returns `inline_image_markdown` and `render_instructions` fields to support inline chat rendering.247248## Scoring Rules (Deterministic)249250For each candidate gene per locus, compute:251252- `l2g_component`: max L2G score for the gene in locus (`0..1`)253- `coloc_component`: max `h4` (or `clpp` when only CLPP is available), clipped to `0..1`254- `eqtl_component`: `min(1, relevant_tissue_hits / 3)`255- `burden_component`:256 - `1.0` if burden `p < 2.5e-6`257 - `0.6` if `2.5e-6 <= p < 0.05`258 - `0.0` otherwise259- `coding_component`:260 - `1.0` for coding consequence in target gene with supportive ClinVar annotation261 - `0.6` for coding consequence in target gene without supportive ClinVar annotation262 - `0.3` for noncoding-in-gene support only263 - `0.0` otherwise264265Overall score:266267`overall_score = 0.40*l2g + 0.25*coloc + 0.15*eqtl + 0.10*burden + 0.10*coding`268269Confidence label:270271- `High` if score `>= 0.75`272- `Medium` if `0.55 <= score < 0.75`273- `Low` if `0.35 <= score < 0.55`274- `VeryLow` if score `< 0.35`275276## Pipeline Contract277278### Phase 0: Validate and normalize input279280- Enforce that at least one of `trait_query`, `efo_id`, `seed_rsids` is present.281- Normalize rsID formatting and deduplicate seed variants.282- Resolve free-text trait to one canonical EFO term when needed.283284### Phase 1: Build anchor set285286- If trait/EFO input is provided, pull associations and rank anchors by p-value and effect availability.287- Merge trait-derived anchors with user-supplied `seed_rsids`.288- Cap anchors using `max_loci` and log dropped anchors in `warnings`.289290### Phase 2: Gather locus-to-gene evidence291292- Normalize anchor coordinates (both builds when possible).293- Pull Open Targets locus evidence (credible set/L2G/coloc).294- Pull GTEx variant-level eQTL rows.295- Pull gene-level burden results for mapped candidate genes.296- Pull ClinVar and gnomAD context when enabled.297298### Phase 3: Harmonize and score299300- Build a per-locus candidate-gene table.301- Compute deterministic component scores and overall score.302- Create cross-locus aggregate rankings.303304### Phase 4: Synthesize outputs305306- Write JSON mapping file.307- Write markdown summary in exact section order.308- Optionally generate figures and append `figures` metadata.309310### Phase 5: QC gates311312Fail the run when any of the following occurs:313314- No anchors after normalization.315- Any locus has candidate genes without score fields.316- `overall_score` outside `0..1`.317- Summary section order mismatch.318- Claim of causality without explicit evidence support in rationale text.319320## Public Interface321322```python323def map_locus_to_gene(input_json: dict) -> dict:324 ...325```326327Return:328329```json330{331 "status": "ok",332 "mapping_output_path": "./output/locus_to_gene_mapping.json",333 "summary_output_path": "./output/locus_to_gene_summary.md",334 "figure_paths": [],335 "warnings": []336}337```338339## Non-Invention Rules340341- Never invent rsIDs, p-values, scores, cohort labels, tissues, or gene links.342- Never silently impute missing evidence as positive support.343- When evidence is missing, record it as a limitation and reduce confidence.344- Keep evidence provenance explicit (`source skill` + endpoint family) in rationale lines.345346## Non-Goals347348- Do not claim definitive causal genes from association evidence alone.349- Do not run fine-mapping methods not directly provided by upstream sources.350- Do not collapse multiple independent signals into one without stating assumptions.