Source code for tooluniverse.panelapp_tool

# panelapp_tool.py
"""PanelApp panel search tool for ToolUniverse.

PanelApp's `/panels/` endpoint silently ignores substring search params --
confirmed live: `search=`, `q=`, and `name__icontains=` all return the
unfiltered, unranked list of all 434 panels regardless of value; only an
exact full-string `name=` match filters anything (its OpenAPI schema
documents no search param at all, only `type` and `page`). Since the API
can't filter server-side, this fetches every panel (paginating the
API's fixed page_size=100) and filters client-side by substring match
against name/disease_group/disease_sub_group.
"""

from typing import Any, Dict

from .base_rest_tool import BaseRESTTool
from .tool_registry import register_tool

PANELS_URL = "https://panelapp.genomicsengland.co.uk/api/v1/panels/"
_MAX_PAGES = 10  # safety cap; ~434 panels / 100 per page = 5 pages today


[docs] @register_tool("PanelAppSearchTool") class PanelAppSearchTool(BaseRESTTool):
[docs] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: search = (arguments.get("search") or "").strip().lower() if not search: return {"status": "error", "error": "'search' is required"} panels = [] url = PANELS_URL params = {"format": "json"} for _ in range(_MAX_PAGES): try: resp = self.session.get(url, params=params, timeout=self.timeout) resp.raise_for_status() page = resp.json() except Exception as e: return {"status": "error", "error": f"PanelApp API error: {e}"} panels.extend(page.get("results", [])) url = page.get("next") params = None # `next` already includes all query params if not url: break def matches(p: Dict[str, Any]) -> bool: haystack = " ".join( str(p.get(k) or "") for k in ("name", "disease_group", "disease_sub_group") ).lower() return search in haystack results = [p for p in panels if matches(p)] return { "status": "success", "data": { "count": len(results), "next": None, "previous": None, "results": results, }, "metadata": { "query": arguments.get("search"), "total_panels_searched": len(panels), "note": ( "PanelApp's API has no server-side search filter, so this " "matches client-side against name/disease_group/" "disease_sub_group across all panels." ), }, }