wfr2/species-synonym-api
0
1"""2fuzzy_search.py3 4Fuzzy species name lookup against the GBIF Backbone Taxonomy.5 6 from scripts.utils.fuzzy_search import fuzzy_search7 8 fuzzy_search("Amanita muscaria") # exact match -> ["Amanita muscaria"]9 fuzzy_search("Amanita muscara") # no exact match -> ['Amanita muscaria', 'Amanitaria muscaria']10"""11 12import requests13 14GBIF_BASE = "https://api.gbif.org/v1"15 16 17def fuzzy_search(query):18 """19 Search the GBIF Backbone Taxonomy for a species name.20 21 Always returns a list. Returns a single-item list for exact species matches,22 or a deduplicated list of suggestions from /species/suggest otherwise.23 """24 # strict=false allows fuzzy matching, not just exact.25 match_resp = requests.get(26 f"{GBIF_BASE}/species/match",27 params={"name": query, "strict": "false"},28 timeout=30,29 )30 match_resp.raise_for_status()31 match = match_resp.json()32 33 match_type = match.get("matchType")34 35 # Case: EXACT at species rank or FUZZY — return single-item list with the resolved name.36 if (37 match_type == "EXACT" and match.get("rank") == "SPECIES"38 ) or match_type == "FUZZY":39 return [match.get("canonicalName")]40 41 suggest_query = query42 43 # All other cases (HIGHERRANK, NONE, or EXACT above species rank) fall through to /species/suggest.44 # /species/suggest is an independent prefix search and can surface candidates even when the backbone match fails entirely.45 suggest_resp = requests.get(46 f"{GBIF_BASE}/species/suggest",47 params={"q": suggest_query, "limit": 10, "rank": "SPECIES"},48 timeout=30,49 )50 suggest_resp.raise_for_status()51 52 # Build a deduplicated list of candidate names.53 # The suggest endpoint can return the same species under multiple checklists.54 seen = set()55 suggestions = []56 for s in suggest_resp.json():57 name = s.get("canonicalName", "")58 if name and name not in seen:59 seen.add(name)60 suggestions.append(name)61 62 return suggestions63 