Source code for tooluniverse.iucn_tool

"""IUCN Red List conservation-status tool for ToolUniverse.

The IUCN Red List is the authoritative source for species extinction-risk
(conservation) status. The ecology/biodiversity workflows need it, but GBIF and
the other occurrence tools do not return Red List categories.

API: https://api.iucnredlist.org/api/v4/  (free; requires a token, read from the
IUCN_API_KEY environment variable, sent as an Authorization header).
Register: https://api.iucnredlist.org/
"""

import os
from typing import Any, Dict

import requests

from .base_tool import BaseTool
from .tool_registry import register_tool

IUCN_URL = "https://api.iucnredlist.org/api/v4"
_CATEGORIES = {
    "EX": "Extinct",
    "EW": "Extinct in the Wild",
    "CR": "Critically Endangered",
    "EN": "Endangered",
    "VU": "Vulnerable",
    "NT": "Near Threatened",
    "LC": "Least Concern",
    "DD": "Data Deficient",
    "NE": "Not Evaluated",
}


[docs] @register_tool("IUCNTool") class IUCNTool(BaseTool): """Get IUCN Red List conservation status for a species by scientific name."""
[docs] def __init__(self, tool_config: Dict[str, Any]): super().__init__(tool_config) self.timeout = tool_config.get("timeout", 30) self.token = os.environ.get("IUCN_API_KEY", "")
[docs] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: if not self.token: return { "status": "error", "error": ( "IUCN Red List requires a free API token. Register at " "https://api.iucnredlist.org/ and set the IUCN_API_KEY " "environment variable." ), } name = ( arguments.get("scientific_name") or arguments.get("species") or "" ).strip() genus = (arguments.get("genus_name") or "").strip() species = (arguments.get("species_name") or "").strip() if name and not (genus and species): parts = name.split() if len(parts) >= 2: genus, species = parts[0], parts[1] if not (genus and species): return { "status": "error", "error": "Provide scientific_name 'Genus species' (e.g. 'Panthera leo') " "or genus_name + species_name.", } headers = {"Authorization": self.token, "Accept": "application/json"} url = f"{IUCN_URL}/taxa/scientific_name" params = {"genus_name": genus, "species_name": species} try: resp = requests.get( url, params=params, headers=headers, timeout=self.timeout ) if resp.status_code == 401 or resp.status_code == 403: return { "status": "error", "error": "IUCN API rejected the token (HTTP %d)" % resp.status_code, } if resp.status_code == 404: return { "status": "success", "data": None, "metadata": { "source": "IUCN Red List", "query": f"{genus} {species}", "note": "No IUCN assessment found for this scientific name.", }, } if resp.status_code != 200: return { "status": "error", "error": f"IUCN API returned HTTP {resp.status_code}", } data = resp.json() except requests.exceptions.Timeout: return { "status": "error", "error": f"IUCN API timed out after {self.timeout}s", } except requests.exceptions.RequestException as e: return {"status": "error", "error": f"IUCN API request failed: {e}"} assessments = data.get("assessments") or [] # Prefer the latest / globally-scoped assessment. latest = None for a in assessments: if a.get("latest") or latest is None: latest = a code = (latest or {}).get("red_list_category_code") or (latest or {}).get( "category" ) result = { "scientific_name": f"{genus} {species}", "red_list_category_code": code, "red_list_category": _CATEGORIES.get(code, code), "year_published": (latest or {}).get("year_published"), "assessment_id": (latest or {}).get("assessment_id"), "scopes": (latest or {}).get("scopes"), "n_assessments": len(assessments), } return { "status": "success", "data": result, "metadata": { "source": "IUCN Red List", "query": f"{genus} {species}", "interpretation": ( "Threatened categories are CR (Critically Endangered) > EN " "(Endangered) > VU (Vulnerable); NT near-threatened; LC least " "concern; DD data deficient; EX/EW extinct." ), }, }