diff --git a/plugins/life-science-research/skills/encode-skill/SKILL.md b/plugins/life-science-research/skills/encode-skill/SKILL.md index 418ad005..b085fcd8 100644 --- a/plugins/life-science-research/skills/encode-skill/SKILL.md +++ b/plugins/life-science-research/skills/encode-skill/SKILL.md @@ -23,7 +23,7 @@ description: Submit compact ENCODE REST API requests for object lookups, portal- - Optional fields: `method`, `params`, `headers`, `json_body`, `form_body`, `record_path`, `response_format`, `max_items`, `max_depth`, `timeout_sec`, `save_raw`, `raw_output_path` - Common ENCODE patterns: - `{"base_url":"https://www.encodeproject.org","path":"biosamples/ENCBS000AAA/","params":{"frame":"object","format":"json"},"headers":{"Accept":"application/json"}}` - - `{"base_url":"https://www.encodeproject.org","path":"search/","params":{"type":"Experiment","assay_title":"RNA-seq","limit":10,"format":"json"},"record_path":"@graph","headers":{"Accept":"application/json"},"max_items":10}` + - `{"base_url":"https://www.encodeproject.org","path":"search/","params":{"type":"Experiment","assay_term_name":"RNA-seq","limit":10,"format":"json"},"record_path":"@graph","headers":{"Accept":"application/json"},"max_items":10}` ## Output - Success returns `ok`, `source`, `path`, `method`, `status_code`, `warnings`, and either compact `records` or a compact `summary`. @@ -32,7 +32,7 @@ description: Submit compact ENCODE REST API requests for object lookups, portal- ## Execution ```bash -echo '{"base_url":"https://www.encodeproject.org","path":"search/","params":{"type":"Experiment","assay_title":"RNA-seq","limit":10,"format":"json"},"record_path":"@graph","headers":{"Accept":"application/json"},"max_items":10}' | python scripts/rest_request.py +echo '{"base_url":"https://www.encodeproject.org","path":"search/","params":{"type":"Experiment","assay_term_name":"RNA-seq","limit":10,"format":"json"},"record_path":"@graph","headers":{"Accept":"application/json"},"max_items":10}' | python scripts/rest_request.py ``` ## References diff --git a/plugins/life-science-research/skills/eqtl-catalogue-skill/SKILL.md b/plugins/life-science-research/skills/eqtl-catalogue-skill/SKILL.md index 9d25d70b..f0a151c6 100644 --- a/plugins/life-science-research/skills/eqtl-catalogue-skill/SKILL.md +++ b/plugins/life-science-research/skills/eqtl-catalogue-skill/SKILL.md @@ -15,16 +15,16 @@ description: Submit compact eQTL Catalogue API requests for association retrieva ## Execution behavior - Return concise markdown summaries from the script JSON by default. - Return raw JSON only if the user explicitly asks for machine-readable output. -- Prefer documented association paths such as `genes//associations`, `studies//associations`, or tissue/study-scoped association routes with explicit filters, and surface upstream `400`/`500` errors verbatim when they occur. +- Prefer documented versioned paths such as `v3/studies`, `v3/associations`, `v3/studies//associations`, or legacy `v1/.../associations` routes with explicit filters, and surface upstream `400`/`500` errors verbatim when they occur. ## Input - Read one JSON object from stdin. - Required fields: `base_url`, `path` - Optional fields: `method`, `params`, `headers`, `json_body`, `form_body`, `record_path`, `response_format`, `max_items`, `max_depth`, `timeout_sec`, `save_raw`, `raw_output_path` - Common eQTL Catalogue patterns: - - `{"base_url":"https://www.ebi.ac.uk/eqtl/api","path":"genes/ENSG00000141510/associations","params":{"study":"","tissue":"","variant_id":"rs7903146","size":10},"max_items":10}` - - `{"base_url":"https://www.ebi.ac.uk/eqtl/api","path":"studies//associations","params":{"tissue":"","variant_id":"rs7903146","size":10},"max_items":10}` - - `{"base_url":"https://www.ebi.ac.uk/eqtl/api","path":"associations/rs7903146","params":{"size":10},"max_items":10}` + - `{"base_url":"https://www.ebi.ac.uk/eqtl/api","path":"v3/studies","max_items":10}` + - `{"base_url":"https://www.ebi.ac.uk/eqtl/api","path":"v3/associations","params":{"gene_id":"ENSG00000141510","rsid":"rs7903146","size":10},"max_items":10}` + - `{"base_url":"https://www.ebi.ac.uk/eqtl/api","path":"v1/genes/ENSG00000141510/associations","params":{"variant_id":"rs7903146","size":10},"max_items":10}` ## Output - Success returns `ok`, `source`, `path`, `method`, `status_code`, `warnings`, and either compact `records` or a compact `summary`. @@ -33,7 +33,7 @@ description: Submit compact eQTL Catalogue API requests for association retrieva ## Execution ```bash -echo '{"base_url":"https://www.ebi.ac.uk/eqtl/api","path":"genes/ENSG00000141510/associations","params":{"study":"","tissue":"","variant_id":"rs7903146","size":10},"max_items":10}' | python scripts/rest_request.py +echo '{"base_url":"https://www.ebi.ac.uk/eqtl/api","path":"v3/studies","max_items":10}' | python scripts/rest_request.py ``` ## References diff --git a/plugins/life-science-research/skills/locus-to-gene-mapper-skill/SKILL.md b/plugins/life-science-research/skills/locus-to-gene-mapper-skill/SKILL.md index 18f4fcca..70d94fbd 100644 --- a/plugins/life-science-research/skills/locus-to-gene-mapper-skill/SKILL.md +++ b/plugins/life-science-research/skills/locus-to-gene-mapper-skill/SKILL.md @@ -48,6 +48,7 @@ Provide at least one anchor source: - Primary entrypoint: `scripts/map_locus_to_gene.py` - This script: - resolves trait/EFO and anchor variants, + - resolves seed and anchor rsID coordinates directly through NCBI RefSNP/dbSNP placements, - gathers locus-to-gene evidence through the chained skills, - writes mapping JSON and summary markdown, - optionally renders figures when plotting deps are available. @@ -117,8 +118,8 @@ Use these skills in order. Skip only when an earlier step is not needed by provi 2. `gwas-catalog-skill` - Discover anchor variants for the trait/EFO scope. - Pull association/study metadata for locus context. -3. `variant-coordinate-finder-skill` - - Normalize each anchor to rsID plus GRCh37/GRCh38 coordinates. +3. Built-in NCBI RefSNP coordinate resolution + - Normalize each anchor rsID to GRCh37/GRCh38 top-level chromosome placements. 4. `opentargets-skill` - Retrieve credible set context, L2G predictions, and colocalisation evidence per locus. 5. `gtex-eqtl-skill` @@ -312,6 +313,7 @@ Confidence label: Fail the run when any of the following occurs: - No anchors after normalization. +- Unresolved GRCh38 coordinates should be surfaced as `status=degraded`, not treated as an analytically clean pass. - Any locus has candidate genes without score fields. - `overall_score` outside `0..1`. - Summary section order mismatch. @@ -332,7 +334,8 @@ Return: "mapping_output_path": "./output/locus_to_gene_mapping.json", "summary_output_path": "./output/locus_to_gene_summary.md", "figure_paths": [], - "warnings": [] + "warnings": [], + "limitations": [] } ``` diff --git a/plugins/life-science-research/skills/locus-to-gene-mapper-skill/scripts/map_locus_to_gene.py b/plugins/life-science-research/skills/locus-to-gene-mapper-skill/scripts/map_locus_to_gene.py index b39aef89..49c2c276 100644 --- a/plugins/life-science-research/skills/locus-to-gene-mapper-skill/scripts/map_locus_to_gene.py +++ b/plugins/life-science-research/skills/locus-to-gene-mapper-skill/scripts/map_locus_to_gene.py @@ -18,14 +18,13 @@ EFO_BASE = "https://www.ebi.ac.uk/ols4/api" OT_BASE = "https://api.platform.opentargets.org/api/v4/graphql" GNOMAD_BASE = "https://gnomad.broadinstitute.org/api" -REFSNP_BASE = "https://api.ncbi.nlm.nih.gov/variation/v0/beta/refsnp" +REFSNP_BASE = "https://api.ncbi.nlm.nih.gov/variation/v0/refsnp" DEFAULT_LOCUS_PADDING_BP = 1_000_000 +REFSEQ_CHROMOSOMES = {f"NC_{i:06d}": str(i) for i in range(1, 23)} +REFSEQ_CHROMOSOMES.update({"NC_000023": "X", "NC_000024": "Y", "NC_012920": "MT"}) REPO_ROOT = Path(__file__).resolve().parents[2] -VARIANT_COORDINATE_FINDER_SCRIPT = ( - REPO_ROOT / "variant-coordinate-finder-skill" / "scripts" / "variant_coordinate_finder.py" -) GTEX_EQTL_SCRIPT = REPO_ROOT / "gtex-eqtl-skill" / "scripts" / "gtex_eqtl.py" GENEBASS_GENE_BURDEN_SCRIPT = ( REPO_ROOT / "genebass-gene-burden-skill" / "scripts" / "genebass_gene_burden.py" @@ -552,6 +551,125 @@ def fetch_gwas_study_metadata( return out +def chromosome_from_refseq(seq_id: str) -> str | None: + accession = seq_id.split(".", 1)[0] + return REFSEQ_CHROMOSOMES.get(accession) + + +def assembly_key_from_traits(traits: list[dict[str, Any]]) -> str | None: + for trait in traits: + assembly_name = str(trait.get("assembly_name") or "") + if assembly_name.startswith("GRCh38"): + return "grch38" + if assembly_name.startswith("GRCh37"): + return "grch37" + return None + + +def coordinate_from_placement(placement: dict[str, Any]) -> dict[str, Any] | None: + seq_id = str(placement.get("seq_id") or "") + chrom = chromosome_from_refseq(seq_id) + if not chrom: + return None + + placement_annot = coerce_dict(placement.get("placement_annot")) + traits = coerce_list_of_dicts(placement_annot.get("seq_id_traits_by_assembly")) + if not traits: + return None + + # Prefer primary top-level chromosome placements over alt loci or patches. + if not any( + trait.get("is_top_level") + and trait.get("is_chromosome") + and not trait.get("is_alt") + and not trait.get("is_patch") + for trait in traits + ): + return None + + spdis: list[dict[str, Any]] = [] + for allele in coerce_list_of_dicts(placement.get("alleles")): + spdi = coerce_dict(coerce_dict(allele.get("allele")).get("spdi")) + if spdi: + spdis.append(spdi) + if not spdis: + return None + + positions = {spdi.get("position") for spdi in spdis if spdi.get("position") is not None} + if not positions: + return None + try: + pos = int(sorted(positions)[0]) + 1 + except Exception: + return None + + deleted_sequences = [ + str(spdi.get("deleted_sequence") or "") + for spdi in spdis + if str(spdi.get("deleted_sequence") or "") + ] + if not deleted_sequences: + return None + ref = deleted_sequences[0] + + alternate_alleles = sorted( + { + str(spdi.get("inserted_sequence") or "") + for spdi in spdis + if str(spdi.get("inserted_sequence") or "") + and str(spdi.get("inserted_sequence") or "") != str(spdi.get("deleted_sequence") or "") + } + ) + alt = alternate_alleles[-1] if alternate_alleles else ref + + assembly_name = str(traits[0].get("assembly_name") or "") + return { + "chr": chrom, + "pos": pos, + "ref": ref, + "alt": alt, + "alternate_alleles": alternate_alleles, + "seq_id": seq_id, + "assembly": assembly_name, + } + + +def fetch_refsnp_payload(rsid: str, limitations: list[str]) -> dict[str, Any] | None: + digits = "".join(ch for ch in rsid if ch.isdigit()) + if not digits: + return None + try: + return safe_get_json(f"{REFSNP_BASE}/{digits}", timeout=35) + except Exception as exc: + limitations.append(f"RefSNP lookup failed for {rsid}: {exc}") + return None + + +def resolve_refsnp_coordinates( + rsid: str, warnings: list[str], limitations: list[str] +) -> dict[str, dict[str, Any]]: + payload = fetch_refsnp_payload(rsid, limitations) + if not payload: + return {} + + coords: dict[str, dict[str, Any]] = {} + snapshot = coerce_dict(payload.get("primary_snapshot_data")) + for placement in coerce_list_of_dicts(snapshot.get("placements_with_allele")): + traits = coerce_list_of_dicts( + coerce_dict(placement.get("placement_annot")).get("seq_id_traits_by_assembly") + ) + assembly_key = assembly_key_from_traits(traits) + if not assembly_key or assembly_key in coords: + continue + coord = coordinate_from_placement(placement) + if coord: + coords[assembly_key] = coord + + if "grch38" not in coords: + warnings.append(f"Coordinate lookup did not find a GRCh38 top-level placement for {rsid}.") + return coords + + def resolve_anchor_coordinates( anchors: list[dict[str, Any]], warnings: list[str], limitations: list[str] ) -> None: @@ -559,26 +677,7 @@ def resolve_anchor_coordinates( rsid = str(anchor.get("rsid") or "") if not rsid: continue - coord_result = run_json_skill_script( - VARIANT_COORDINATE_FINDER_SCRIPT, - {"rsid": rsid}, - limitations, - timeout_s=25, - ) - if not coord_result: - anchor["grch38"] = None - anchor["grch37"] = None - anchor["locus_id"] = f"rsid:{rsid}" - continue - - if not coord_result.get("ok"): - error = coerce_dict(coord_result.get("error")).get("message") - warnings.append(f"Coordinate lookup failed for {rsid}: {error}") - anchor["grch38"] = None - anchor["grch37"] = None - anchor["locus_id"] = f"rsid:{rsid}" - continue - + coord_result = resolve_refsnp_coordinates(rsid, warnings, limitations) g38 = coerce_dict(coord_result.get("grch38")) g37 = coerce_dict(coord_result.get("grch37")) anchor["grch38"] = g38 if g38 else None @@ -957,21 +1056,15 @@ def fetch_refsnp_annotations(rsids: list[str], limitations: list[str]) -> dict[s out: dict[str, dict[str, Any]] = {} for rsid in rsids: - digits = "".join(ch for ch in rsid if ch.isdigit()) - if not digits: - continue - url = f"{REFSNP_BASE}/{digits}" - try: - payload = safe_get_json(url, timeout=35) - except Exception as exc: - limitations.append(f"RefSNP lookup failed for {rsid}: {exc}") + payload = fetch_refsnp_payload(rsid, limitations) + if not payload: continue snapshot = coerce_dict(payload.get("primary_snapshot_data")) genes = { - str(item.get("name")).strip() + str(item.get("locus") or item.get("name")).strip() for item in coerce_list_of_dicts(snapshot.get("genes")) - if item.get("name") + if item.get("locus") or item.get("name") } coding_genes: set[str] = set() consequence_terms: set[str] = set() @@ -979,9 +1072,19 @@ def fetch_refsnp_annotations(rsids: list[str], limitations: list[str]) -> dict[s for allele_ann in coerce_list_of_dicts(snapshot.get("allele_annotations")): for asm_ann in coerce_list_of_dicts(allele_ann.get("assembly_annotation")): for gene in coerce_list_of_dicts(asm_ann.get("genes")): - gene_symbol = str(gene.get("name") or "").strip() + gene_symbol = str(gene.get("locus") or gene.get("name") or "").strip() + if gene_symbol: + genes.add(gene_symbol) is_coding = False + for so in coerce_list_of_dicts(gene.get("sequence_ontology")): + term = str(so.get("name") or "").strip() + if term: + consequence_terms.add(term) for rna in coerce_list_of_dicts(gene.get("rnas")): + for so in coerce_list_of_dicts(rna.get("sequence_ontology")): + term = str(so.get("name") or "").strip() + if term: + consequence_terms.add(term) protein = rna.get("protein") protein_items = [protein] if isinstance(protein, dict) else protein if not isinstance(protein_items, list): @@ -1635,6 +1738,17 @@ def map_locus_to_gene(input_json: dict[str, Any]) -> dict[str, Any]: if not anchors: raise ValueError("No anchors remained after normalization.") + unresolved_coord_rsids = [ + str(anchor.get("rsid")) + for anchor in anchors + if anchor.get("rsid") and not coerce_dict(anchor.get("grch38")) + ] + if unresolved_coord_rsids: + limitations.append( + "Unresolved GRCh38 coordinates for anchors: " + + ", ".join(dedupe_keep_order(unresolved_coord_rsids)) + ) + anchor_rsids = dedupe_keep_order([str(a.get("rsid")) for a in anchors if a.get("rsid")]) trait_terms = dedupe_keep_order( [ @@ -1672,6 +1786,9 @@ def map_locus_to_gene(input_json: dict[str, Any]) -> dict[str, Any]: for anchor in coerce_list_of_dicts(locus.get("anchors")): locus_symbols.extend(as_string_list(anchor.get("mapped_genes"))) rsid = str(anchor.get("rsid") or "") + annot = coerce_dict(refsnp_annotations.get(rsid)) + locus_symbols.extend(as_string_list(annot.get("coding_genes"))) + locus_symbols.extend(as_string_list(annot.get("genes"))) l2g_rows = coerce_list_of_dicts( coerce_dict(ot_support.get("per_anchor", {})).get(rsid, {}).get("l2g") ) @@ -1935,7 +2052,7 @@ def map_locus_to_gene(input_json: dict[str, Any]) -> dict[str, Any]: "sources_queried": [ "efo-ontology-skill", "gwas-catalog-skill", - "variant-coordinate-finder-skill", + "ncbi-refsnp-coordinate-resolution", "opentargets-skill", "gtex-eqtl-skill", "genebass-gene-burden-skill", @@ -1987,8 +2104,12 @@ def map_locus_to_gene(input_json: dict[str, Any]) -> dict[str, Any]: mapping_output_path.write_text(json.dumps(mapping_payload, indent=2), encoding="utf-8") summary_output_path.write_text(summary, encoding="utf-8") + critical_limitations = [ + item for item in limitations if item.startswith("Unresolved GRCh38 coordinates") + ] + return { - "status": "ok", + "status": "degraded" if critical_limitations else "ok", "mapping_output_path": str(mapping_output_path), "summary_output_path": str(summary_output_path), "figure_paths": [str(fig.get("path")) for fig in figure_entries], @@ -1998,6 +2119,7 @@ def map_locus_to_gene(input_json: dict[str, Any]) -> dict[str, Any]: "Do not wrap them in code fences." ), "warnings": dedupe_keep_order(warnings), + "limitations": dedupe_keep_order(limitations), } diff --git a/plugins/life-science-research/skills/locus-to-gene-mapper-skill/scripts/test_map_locus_to_gene.py b/plugins/life-science-research/skills/locus-to-gene-mapper-skill/scripts/test_map_locus_to_gene.py new file mode 100644 index 00000000..5fa48814 --- /dev/null +++ b/plugins/life-science-research/skills/locus-to-gene-mapper-skill/scripts/test_map_locus_to_gene.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import importlib.util +import unittest +from pathlib import Path +from unittest import mock + +SCRIPT_PATH = Path(__file__).with_name("map_locus_to_gene.py") +SPEC = importlib.util.spec_from_file_location("map_locus_to_gene", SCRIPT_PATH) +assert SPEC and SPEC.loader +map_locus_to_gene = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(map_locus_to_gene) + + +def refsnp_payload() -> dict: + return { + "primary_snapshot_data": { + "placements_with_allele": [ + { + "seq_id": "NC_000010.11", + "placement_annot": { + "seq_id_traits_by_assembly": [ + { + "assembly_name": "GRCh38.p14", + "is_top_level": True, + "is_chromosome": True, + "is_alt": False, + "is_patch": False, + } + ] + }, + "alleles": [ + { + "allele": { + "spdi": { + "position": 112998589, + "deleted_sequence": "C", + "inserted_sequence": "C", + } + } + }, + { + "allele": { + "spdi": { + "position": 112998589, + "deleted_sequence": "C", + "inserted_sequence": "G", + } + } + }, + { + "allele": { + "spdi": { + "position": 112998589, + "deleted_sequence": "C", + "inserted_sequence": "T", + } + } + }, + ], + }, + { + "seq_id": "NC_000010.10", + "placement_annot": { + "seq_id_traits_by_assembly": [ + { + "assembly_name": "GRCh37.p13", + "is_top_level": True, + "is_chromosome": True, + "is_alt": False, + "is_patch": False, + } + ] + }, + "alleles": [ + { + "allele": { + "spdi": { + "position": 114758348, + "deleted_sequence": "C", + "inserted_sequence": "C", + } + } + }, + { + "allele": { + "spdi": { + "position": 114758348, + "deleted_sequence": "C", + "inserted_sequence": "T", + } + } + }, + ], + }, + ], + "allele_annotations": [ + { + "assembly_annotation": [ + { + "seq_id": "NC_000010.11", + "genes": [ + { + "name": "transcription factor 7 like 2", + "locus": "TCF7L2", + "rnas": [ + { + "sequence_ontology": [ + {"name": "intron_variant"}, + ] + } + ], + } + ], + } + ] + } + ], + } + } + + +class RefSnpResolutionTests(unittest.TestCase): + def test_refsnp_base_uses_current_numeric_lookup_endpoint(self) -> None: + self.assertEqual( + map_locus_to_gene.REFSNP_BASE, + "https://api.ncbi.nlm.nih.gov/variation/v0/refsnp", + ) + + def test_resolve_refsnp_coordinates_uses_top_level_grch_placements(self) -> None: + with mock.patch.object(map_locus_to_gene, "safe_get_json", return_value=refsnp_payload()): + coords = map_locus_to_gene.resolve_refsnp_coordinates("rs7903146", [], []) + + self.assertEqual(coords["grch38"]["chr"], "10") + self.assertEqual(coords["grch38"]["pos"], 112998590) + self.assertEqual(coords["grch38"]["ref"], "C") + self.assertEqual(coords["grch38"]["alt"], "T") + self.assertEqual(coords["grch37"]["pos"], 114758349) + + def test_fetch_refsnp_annotations_uses_gene_locus_symbols(self) -> None: + with mock.patch.object(map_locus_to_gene, "safe_get_json", return_value=refsnp_payload()): + annotations = map_locus_to_gene.fetch_refsnp_annotations(["rs7903146"], []) + + self.assertEqual(annotations["rs7903146"]["genes"], ["TCF7L2"]) + self.assertIn("intron_variant", annotations["rs7903146"]["consequence_terms"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/plugins/life-science-research/skills/ncbi-datasets-skill/SKILL.md b/plugins/life-science-research/skills/ncbi-datasets-skill/SKILL.md index 38eaafc4..aa80feb4 100644 --- a/plugins/life-science-research/skills/ncbi-datasets-skill/SKILL.md +++ b/plugins/life-science-research/skills/ncbi-datasets-skill/SKILL.md @@ -21,7 +21,7 @@ description: Submit compact NCBI Datasets v2 requests for assembly, genome, taxo - Required field: `path` - Optional fields: `params`, `record_path`, `response_format`, `max_items`, `max_depth`, `timeout_sec`, `save_raw`, `raw_output_path` - Common Datasets patterns: - - `{"path":"genome/taxon/assembly_descriptors","params":{"taxons":"9606"}}` + - `{"path":"genome/taxon/9606/dataset_report","params":{"page_size":10},"record_path":"reports","max_items":10}` - `{"path":"genome/accession/GCF_000001405.40/dataset_report"}` - `{"path":"taxonomy/taxon/9606"}` @@ -32,7 +32,7 @@ description: Submit compact NCBI Datasets v2 requests for assembly, genome, taxo ## Execution ```bash -echo '{"path":"genome/taxon/assembly_descriptors","params":{"taxons":"9606"}}' | python scripts/ncbi_datasets.py +echo '{"path":"genome/taxon/9606/dataset_report","params":{"page_size":10},"record_path":"reports","max_items":10}' | python scripts/ncbi_datasets.py ``` ## References diff --git a/plugins/life-science-research/skills/pharmgkb-skill/SKILL.md b/plugins/life-science-research/skills/pharmgkb-skill/SKILL.md index 49936a63..8a72c644 100644 --- a/plugins/life-science-research/skills/pharmgkb-skill/SKILL.md +++ b/plugins/life-science-research/skills/pharmgkb-skill/SKILL.md @@ -20,7 +20,7 @@ description: Submit compact PharmGKB API requests for genes, variants, clinical - Required fields: `base_url`, `path` - Optional fields: `method`, `params`, `headers`, `json_body`, `form_body`, `record_path`, `response_format`, `max_items`, `max_depth`, `timeout_sec`, `save_raw`, `raw_output_path` - Common PharmGKB patterns: - - `{"base_url":"https://api.pharmgkb.org/v1/data","path":"gene/PA134865140"}` + - `{"base_url":"https://api.pharmgkb.org/v1/data","path":"gene/PA36679"}` - `{"base_url":"https://api.pharmgkb.org/v1/data","path":"clinicalAnnotation","params":{"relatedChemicals.accessionId":"PA449726","limit":10},"max_items":10}` - `{"base_url":"https://api.pharmgkb.org/v1/data","path":"variant/PA166158545"}` @@ -31,7 +31,7 @@ description: Submit compact PharmGKB API requests for genes, variants, clinical ## Execution ```bash -echo '{"base_url":"https://api.pharmgkb.org/v1/data","path":"gene/PA134865140"}' | python scripts/rest_request.py +echo '{"base_url":"https://api.pharmgkb.org/v1/data","path":"gene/PA36679"}' | python scripts/rest_request.py ``` ## References diff --git a/plugins/life-science-research/skills/quickgo-skill/SKILL.md b/plugins/life-science-research/skills/quickgo-skill/SKILL.md index 636b495d..3252c809 100644 --- a/plugins/life-science-research/skills/quickgo-skill/SKILL.md +++ b/plugins/life-science-research/skills/quickgo-skill/SKILL.md @@ -14,6 +14,7 @@ description: Submit compact QuickGO requests for GO terms, annotations, and onto ## Execution behavior - Return concise markdown summaries from the script JSON by default. - Prefer these paths: `ontology/go/terms/`, `annotation/search`, and ontology child or ancestor endpoints. +- Treat `annotation/search` as upstream-fragile when QuickGO's annotation Solr backend is unavailable; fall back to ontology term lookup or UniProt GO annotations when appropriate. - If the user needs the full payload, set `save_raw=true` and report the saved file path. ## Input @@ -31,7 +32,7 @@ description: Submit compact QuickGO requests for GO terms, annotations, and onto ## Execution ```bash -echo '{"base_url":"https://www.ebi.ac.uk/QuickGO/services","path":"annotation/search","params":{"geneProductId":"P04637","limit":10},"headers":{"Accept":"application/json"},"record_path":"results","max_items":10}' | python scripts/rest_request.py +echo '{"base_url":"https://www.ebi.ac.uk/QuickGO/services","path":"ontology/go/terms/GO:0006915","headers":{"Accept":"application/json"},"record_path":"results","max_items":10}' | python scripts/rest_request.py ``` ## References diff --git a/plugins/life-science-research/skills/rnacentral-skill/SKILL.md b/plugins/life-science-research/skills/rnacentral-skill/SKILL.md index ba377543..d399558a 100644 --- a/plugins/life-science-research/skills/rnacentral-skill/SKILL.md +++ b/plugins/life-science-research/skills/rnacentral-skill/SKILL.md @@ -7,20 +7,21 @@ description: Submit compact RNAcentral API requests for RNA entry browsing, sing - Use `scripts/rest_request.py` for all RNAcentral calls. - Use `base_url=https://rnacentral.org/api/v1`. - Keep the trailing slash on collection and record paths to avoid redirects. -- Start with `rna/` for browsing and `rna//` or `rna//xrefs/` for targeted lookups. +- Start with targeted lookups such as `rna//` because broad `rna/` browsing can be slow or time out. - Re-run requests in long conversations instead of relying on older tool output. ## Execution behavior - Return concise markdown summaries from the script JSON by default. - Return raw JSON only if the user explicitly asks for machine-readable output. -- Prefer these paths: `rna/`, `rna//`, and `rna//xrefs/`. +- Prefer these paths: `rna//`, `rna//`, `rna//xrefs/`, and targeted `rna/` searches with `q` plus small `page_size`. ## Input - Read one JSON object from stdin. - Required fields: `base_url`, `path` - Optional fields: `method`, `params`, `headers`, `json_body`, `form_body`, `record_path`, `response_format`, `max_items`, `max_depth`, `timeout_sec`, `save_raw`, `raw_output_path` - Common RNAcentral patterns: - - `{"base_url":"https://rnacentral.org/api/v1","path":"rna/","params":{"page_size":10},"record_path":"results","max_items":10}` + - `{"base_url":"https://rnacentral.org/api/v1","path":"rna/URS000075C808/9606","max_items":10}` + - `{"base_url":"https://rnacentral.org/api/v1","path":"rna/","params":{"q":"TP53","page_size":10},"record_path":"results","max_items":10}` - `{"base_url":"https://rnacentral.org/api/v1","path":"rna/URS0000000001/"}` - `{"base_url":"https://rnacentral.org/api/v1","path":"rna/URS0000000001/xrefs/","record_path":"results","max_items":10}` @@ -31,7 +32,7 @@ description: Submit compact RNAcentral API requests for RNA entry browsing, sing ## Execution ```bash -echo '{"base_url":"https://rnacentral.org/api/v1","path":"rna/","params":{"page_size":10},"record_path":"results","max_items":10}' | python scripts/rest_request.py +echo '{"base_url":"https://rnacentral.org/api/v1","path":"rna/URS000075C808/9606","max_items":10}' | python scripts/rest_request.py ``` ## References