import os import json import uuid import datetime import logging from typing import Dict, Any, List, Optional, Union from pathlib import Path from fhir.resources.resource import Resource from fhir.resources.patient import Patient from fhir.resources.observation import Observation class FHIRRepository: """ Local storage repository for FHIR resources. This is a simplified implementation for the POC, using file storage. In production, this would be replaced with a database. """ def __init__(self, storage_dir: str = 'fhir_storage'): """ Initialize the FHIR repository. Args: storage_dir: Directory to store FHIR resources """ self.storage_dir = storage_dir self.logger = logging.getLogger(__name__) # Create storage directory if it doesn't exist try: os.makedirs(os.path.join(storage_dir, 'Patient'), exist_ok=True) os.makedirs(os.path.join(storage_dir, 'Observation'), exist_ok=True) self.logger.info(f"FHIR storage directories created in {storage_dir}") except Exception as e: self.logger.error(f"Error creating storage directories: {str(e)}") raise def _get_resource_path(self, resource_type: str, resource_id: str) -> str: """ Get the file path for a resource. Args: resource_type: Type of resource resource_id: ID of the resource Returns: Path to the resource file """ return os.path.join(self.storage_dir, resource_type, f"{resource_id}.json") def _read_resource_file(self, file_path: str) -> Dict[str, Any]: """ Read a resource file. Args: file_path: Path to the resource file Returns: Resource data as a dictionary """ try: with open(file_path, 'r') as f: return json.load(f) except Exception as e: self.logger.error(f"Error reading resource file: {str(e)}") raise def _write_resource_file(self, file_path: str, data: Dict[str, Any]) -> None: """ Write a resource file. Args: file_path: Path to the resource file data: Resource data """ try: with open(file_path, 'w') as f: json.dump(data, f, indent=2) except Exception as e: self.logger.error(f"Error writing resource file: {str(e)}") raise def create_resource(self, resource: Resource) -> Dict[str, Any]: """ Create a new FHIR resource. Args: resource: FHIR resource to create Returns: Created resource data """ resource_type = resource.resource_type # Ensure resource has an ID if not resource.id: resource.id = str(uuid.uuid4()) # Convert resource to dictionary resource_data = json.loads(resource.json()) # Add metadata resource_data['meta'] = resource_data.get('meta', {}) resource_data['meta']['lastUpdated'] = datetime.datetime.utcnow().isoformat() # Save resource to file file_path = self._get_resource_path(resource_type, resource.id) self._write_resource_file(file_path, resource_data) self.logger.info(f"Created {resource_type} resource with ID {resource.id}") return resource_data def read_resource(self, resource_type: str, resource_id: str) -> Dict[str, Any]: """ Read a FHIR resource by ID. Args: resource_type: Type of resource resource_id: ID of the resource Returns: Resource data """ file_path = self._get_resource_path(resource_type, resource_id) if not os.path.exists(file_path): self.logger.error(f"Resource not found: {resource_type}/{resource_id}") raise FileNotFoundError(f"Resource not found: {resource_type}/{resource_id}") resource_data = self._read_resource_file(file_path) self.logger.debug(f"Read {resource_type} resource with ID {resource_id}") return resource_data def update_resource(self, resource: Resource) -> Dict[str, Any]: """ Update an existing FHIR resource. Args: resource: FHIR resource to update Returns: Updated resource data """ resource_type = resource.resource_type resource_id = resource.id if not resource_id: self.logger.error("Cannot update resource without ID") raise ValueError("Resource must have an ID for update") # Check if resource exists file_path = self._get_resource_path(resource_type, resource_id) if not os.path.exists(file_path): self.logger.error(f"Resource not found for update: {resource_type}/{resource_id}") raise FileNotFoundError(f"Resource not found for update: {resource_type}/{resource_id}") # Convert resource to dictionary resource_data = json.loads(resource.json()) # Update metadata resource_data['meta'] = resource_data.get('meta', {}) resource_data['meta']['lastUpdated'] = datetime.datetime.utcnow().isoformat() # Save updated resource self._write_resource_file(file_path, resource_data) self.logger.info(f"Updated {resource_type} resource with ID {resource_id}") return resource_data def delete_resource(self, resource_type: str, resource_id: str) -> bool: """ Delete a FHIR resource. Args: resource_type: Type of resource resource_id: ID of the resource Returns: True if deletion was successful """ file_path = self._get_resource_path(resource_type, resource_id) if not os.path.exists(file_path): self.logger.error(f"Resource not found for deletion: {resource_type}/{resource_id}") return False try: os.remove(file_path) self.logger.info(f"Deleted {resource_type} resource with ID {resource_id}") return True except Exception as e: self.logger.error(f"Error deleting resource: {str(e)}") return False def search_resources(self, resource_type: str, params: Dict[str, Any] = None) -> List[Dict[str, Any]]: """ Search for FHIR resources. Args: resource_type: Type of resource to search for params: Search parameters Returns: List of matching resources """ # Check if resource type directory exists resource_dir = os.path.join(self.storage_dir, resource_type) if not os.path.exists(resource_dir): self.logger.error(f"Resource type directory not found: {resource_type}") return [] # Get all resource files of the specified type resources = [] for file_name in os.listdir(resource_dir): if file_name.endswith('.json'): file_path = os.path.join(resource_dir, file_name) resource = self._read_resource_file(file_path) # Filter by parameters if provided if params: matches_all_params = True for key, value in params.items(): # Handle nested properties with dot notation (e.g., "name.family") parts = key.split('.') resource_value = resource for part in parts: if isinstance(resource_value, dict) and part in resource_value: resource_value = resource_value[part] else: resource_value = None break # Check if value matches if resource_value != value: matches_all_params = False break if not matches_all_params: continue resources.append(resource) self.logger.debug(f"Found {len(resources)} {resource_type} resources matching search criteria") return resources