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.
NORM/FHIR_OCR_POC/fhir_module/fhir_repository.py

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