You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
244 lines
8.6 KiB
Python
244 lines
8.6 KiB
Python
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 |