Skip to main content
Glama
mrosata
by mrosata
zus_extensions.py10.8 kB
""" Zus Health FHIR API Extensions This module contains Zus-specific functionality for working with Zus FHIR servers. These tools are optional and only relevant when working with Zus Health's FHIR API. For generic FHIR server operations, use the tools in server.py instead. """ from typing import Any import httpx import json import os # Import base FHIR configuration FHIR_BASE_URL = os.getenv("FHIR_BASE_URL", "http://localhost:8080/fhir") FHIR_AUTH_TOKEN = os.getenv("FHIR_AUTH_TOKEN", "") def get_zus_fhir_headers(builder_id: str | None = None) -> dict[str, str]: """ Get headers for Zus FHIR API requests. Args: builder_id: Zus customer/builder ID for multi-tenant access Returns: Dictionary of HTTP headers with Zus-specific additions """ headers = { "Content-Type": "application/fhir+json", "Accept": "application/fhir+json", } if FHIR_AUTH_TOKEN: headers["Authorization"] = f"Bearer {FHIR_AUTH_TOKEN}" if builder_id: headers["Zus-Account"] = builder_id return headers def calculate_name_similarity(patient_name: dict, search_first: str, search_last: str) -> float: """ Calculate similarity score between patient name and search terms. This helper function is used for intelligent name matching when multiple patients are returned from a search. Args: patient_name: FHIR name object from Patient resource search_first: First name being searched for search_last: Last name being searched for Returns: Similarity score between 0.0 and 1.0 (higher is better match) """ if not patient_name: return 0.0 # Extract name components given_names = patient_name.get("given", []) family_name = patient_name.get("family", "") # Convert to lowercase for case-insensitive comparison search_first_lower = search_first.lower().strip() search_last_lower = search_last.lower().strip() family_lower = family_name.lower().strip() # Calculate family name similarity (exact match gets highest score) family_score = 1.0 if family_lower == search_last_lower else 0.0 # Calculate given name similarity given_score = 0.0 if given_names: # Check for exact match in any given name for given in given_names: if given.lower().strip() == search_first_lower: given_score = 1.0 break # If no exact match, check for partial matches if given_score == 0.0: for given in given_names: given_lower = given.lower().strip() # Check for partial matches - allow shorter names to match longer ones # This allows "John" to match "Johnny" when there's no exact match if (search_first_lower in given_lower and len(search_first_lower) >= 3) or \ (given_lower in search_first_lower and len(given_lower) >= 3): given_score = 0.7 # Partial match score break # Weighted combination: family name is more important total_score = (family_score * 0.6) + (given_score * 0.4) return total_score async def get_patient_zus_upid( first_name: str, last_name: str, builder_id: str | None = None, is_http_method_allowed_fn=None, _allowed_methods_set=None ) -> str: """ Get the Zus UPID (Universal Patient ID) for a Patient resource from Zus FHIR server. This is a Zus-specific tool that searches for a Patient by first and last name, optionally filtered by builderID, then extracts the Zus UPID from the Patient's identifiers. **This tool is only relevant when working with Zus Health's FHIR API.** Args: first_name: Patient's first name last_name: Patient's last name builder_id: Optional builder ID (string) to filter the search is_http_method_allowed_fn: Function to check if HTTP method is allowed _allowed_methods_set: Set of allowed HTTP methods Returns: The Zus UPID value or an error message if not found """ # Check if GET method is allowed if is_http_method_allowed_fn and not is_http_method_allowed_fn("GET"): if _allowed_methods_set is not None: return f"Error: HTTP method GET is not allowed. Allowed methods: {', '.join(sorted(_allowed_methods_set))}" else: return "Error: Read operations are not allowed. Set FHIR_ALLOW_READ=true to enable." # Build search parameters search_params = { "name": f"{first_name} {last_name}" } if builder_id: search_params["builderID"] = builder_id url = f"{FHIR_BASE_URL}/Patient" try: async with httpx.AsyncClient(timeout=30.0) as client: response = await client.get( url, params=search_params, headers=get_zus_fhir_headers(builder_id) ) if response.status_code == 200: bundle_data = response.json() entries = bundle_data.get("entry", []) if not entries: return f"Error: No Patient found with name '{first_name} {last_name}'" + (f" and builderID '{builder_id}'" if builder_id else "") # Look for Zus UPID in each patient zus_upid_system = "https://zusapi.com/fhir/identifier/universal-id" found_patients = [] for entry in entries: patient = entry.get("resource", {}) patient_id = patient.get("id", "unknown") identifiers = patient.get("identifier", []) patient_name = patient.get("name", [{}])[0] if patient.get("name") else {} # Look for Zus UPID identifier zus_upid = None for identifier in identifiers: if identifier.get("system") == zus_upid_system: zus_upid = identifier.get("value") break if zus_upid: # Calculate name similarity score similarity_score = calculate_name_similarity(patient_name, first_name, last_name) found_patients.append({ "patient_id": patient_id, "zus_upid": zus_upid, "name": patient_name, "similarity_score": similarity_score }) if not found_patients: return f"Error: No Zus UPID found for Patient(s) with name '{first_name} {last_name}'" + (f" and builderID '{builder_id}'" if builder_id else "") if len(found_patients) == 1: return f"Zus UPID: {found_patients[0]['zus_upid']}" else: # Multiple patients found, find the best match # Sort by similarity score (highest first) found_patients.sort(key=lambda x: x["similarity_score"], reverse=True) best_match = found_patients[0] best_score = best_match["similarity_score"] # If the best match has a good score (>= 0.5), return it as the primary result if best_score >= 0.5: name_parts = best_match["name"] display_name = "" if name_parts: given = name_parts.get("given", []) family = name_parts.get("family", "") display_name = f" {' '.join(given)} {family}".strip() result = f"Zus UPID: {best_match['zus_upid']} (Best match: {display_name})" # If there are other patients with decent scores, mention them other_matches = [p for p in found_patients[1:] if p["similarity_score"] >= 0.3] if other_matches: result += f"\n\nOther matches found:" for patient in other_matches[:3]: # Limit to top 3 other matches name_parts = patient["name"] display_name = "" if name_parts: given = name_parts.get("given", []) family = name_parts.get("family", "") display_name = f" {' '.join(given)} {family}".strip() result += f"\n- Patient ID: {patient['patient_id']} ({display_name}) -> Zus UPID: {patient['zus_upid']}" return result else: # No good matches, return all patients result = f"Found {len(found_patients)} Patient(s) with Zus UPID(s) (no clear name match):\n" for patient in found_patients: name_parts = patient["name"] display_name = "" if name_parts: given = name_parts.get("given", []) family = name_parts.get("family", "") display_name = f" {' '.join(given)} {family}".strip() result += f"- Patient ID: {patient['patient_id']} ({display_name}) -> Zus UPID: {patient['zus_upid']}\n" return result elif response.status_code == 400: try: error_data = response.json() return f"Invalid search parameters (400):\n{json.dumps(error_data, indent=2)}" except: return f"Invalid search parameters (400): {response.text}" elif response.status_code == 401: return ( "Error: Authentication failed. Check FHIR_AUTH_TOKEN configuration." ) elif response.status_code == 403: return "Error: Authorization failed. Insufficient permissions to search Patient resources." elif response.status_code == 404: return ( "Error: Patient resource type not found or not supported." ) else: return f"Server error ({response.status_code}):\n{response.text}" except httpx.TimeoutException: return f"Error: Request to FHIR server timed out after 30 seconds" except httpx.RequestError as e: return f"Error connecting to FHIR server: {str(e)}\nVerify FHIR_BASE_URL is correct: {FHIR_BASE_URL}" except Exception as e: return f"Unexpected error: {str(e)}"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mrosata/mcp-fhir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server