""" Integrations Celery tasks This module contains tasks for: 1. Fetching surveys from HIS systems (every 25 minutes) 2. Testing HIS connection """ import logging from datetime import datetime from celery import shared_task from django.utils import timezone logger = logging.getLogger("apps.integrations") # ============================================================================= # HIS Survey Fetching Tasks # ============================================================================= def _parse_his_date(date_str): """Parse HIS date format 'DD-Mon-YYYY HH:MM:SS' to timezone-aware datetime.""" if not date_str: return None for fmt in ("%d-%b-%Y %H:%M:%S", "%d-%b-%Y %H:%M"): try: naive = datetime.strptime(date_str, fmt) return timezone.make_aware(naive) except ValueError: continue return None @shared_task def fetch_his_surveys(from_date_str=None, to_date_str=None): """ Periodic task to fetch patient data from HIS system every 5 minutes. This task: 1. Connects to configured HIS API endpoints 2. Fetches all patients from the last 5 minutes (or custom date range) 3. Saves patient and visit data for every patient (all types) 4. Creates surveys only for patients whose visit is complete: - ED/IP: DischargeDate present - OP: last visit event is older than configured delay hours Args: from_date_str: Optional override - fetch from this date (DD-Mon-YYYY HH:MM:SS) to_date_str: Optional override - fetch until this date (DD-Mon-YYYY HH:MM:SS) Scheduled to run every 5 minutes via Celery Beat. Returns: dict: Summary of fetched and processed data """ from datetime import datetime, timedelta from apps.integrations.services.his_adapter import HISAdapter from apps.integrations.services.his_client import HISClient, HISClientFactory logger.info("Starting HIS patient data fetch task") result = { "success": False, "clients_processed": 0, "total_patients": 0, "visits_saved": 0, "surveys_created": 0, "errors": [], "details": [], } try: clients = HISClientFactory.get_all_active_clients() if not clients: msg = "No active HIS configurations found" logger.warning(msg) result["errors"].append(msg) return result if from_date_str and to_date_str: fetch_since = _parse_his_date(from_date_str) fetch_until = _parse_his_date(to_date_str) if not fetch_since or not fetch_until: msg = f"Invalid date format: from_date={from_date_str}, to_date={to_date_str}" result["errors"].append(msg) return result logger.info(f"Using custom date range: {fetch_since} to {fetch_until}") else: fetch_since = timezone.now() - timedelta(minutes=5) fetch_until = None logger.info(f"Fetching patient data since {fetch_since}") for client in clients: client_result = { "config": client.config.name if client.config else "Default", "total_patients": 0, "visits_saved": 0, "surveys_created": 0, "errors": [], } try: connection_test = client.test_connection() if not connection_test["success"]: error_msg = f"HIS connection failed for {client_result['config']}: {connection_test['message']}" logger.error(error_msg) client_result["errors"].append(error_msg) result["details"].append(client_result) continue logger.info(f"Fetching patient data from {client_result['config']}") his_data = client.fetch_patient_data(since=fetch_since, until=fetch_until) if not his_data: logger.info(f"No data returned from {client_result['config']}") result["details"].append(client_result) continue patient_list = his_data.get("FetchPatientDataTimeStampList", []) client_result["total_patients"] = len(patient_list) result["total_patients"] += len(patient_list) if not patient_list: logger.info(f"No patients found in {client_result['config']}") result["details"].append(client_result) continue logger.info(f"Fetched {len(patient_list)} patients from {client_result['config']}") process_result = HISAdapter.process_his_response(his_data) client_result["visits_saved"] = process_result.get("visits_saved", 0) client_result["surveys_created"] = process_result.get("surveys_created", 0) client_result["errors"] = process_result.get("errors", []) result["visits_saved"] += client_result["visits_saved"] result["surveys_created"] += client_result["surveys_created"] result["errors"].extend(client_result["errors"]) if client.config: client.config.last_sync_at = timezone.now() client.config.save(update_fields=["last_sync_at"]) result["clients_processed"] += 1 except Exception as e: error_msg = f"Error processing HIS client {client_result['config']}: {str(e)}" logger.error(error_msg, exc_info=True) client_result["errors"].append(error_msg) result["details"].append(client_result) result["success"] = True logger.info( f"HIS fetch completed: {result['clients_processed']} clients, " f"{result['total_patients']} patients, " f"{result['visits_saved']} visits saved, " f"{result['surveys_created']} surveys created" ) except Exception as e: error_msg = f"Fatal error in fetch_his_surveys task: {str(e)}" logger.error(error_msg, exc_info=True) result["errors"].append(error_msg) return result @shared_task def test_his_connection(config_id=None): """ Test connectivity to HIS system. Args: config_id: Optional IntegrationConfig ID. If not provided, tests the default HIS configuration. Returns: dict: Connection test results """ from apps.integrations.models import IntegrationConfig, SourceSystem from apps.integrations.services.his_client import HISClient logger.info(f"Testing HIS connection for config_id={config_id}") try: if config_id: config = IntegrationConfig.objects.filter(id=config_id, source_system=SourceSystem.HIS).first() if not config: return {"success": False, "message": f"HIS configuration with ID {config_id} not found"} client = HISClient(config) else: client = HISClient() result = client.test_connection() logger.info(f"HIS connection test result: {result}") return result except Exception as e: error_msg = f"Error testing HIS connection: {str(e)}" logger.error(error_msg, exc_info=True) return {"success": False, "message": error_msg} @shared_task def sync_his_survey_mappings(): """ Sync survey template mappings from HIS system. This task can be scheduled periodically to sync any mapping configurations from the HIS system. Returns: dict: Sync results """ from apps.integrations.models import SurveyTemplateMapping from apps.organizations.models import Hospital logger.info("Starting HIS survey mappings sync") result = {"success": True, "mappings_synced": 0, "errors": []} try: # Get all active mappings mappings = SurveyTemplateMapping.objects.filter(is_active=True) for mapping in mappings: try: # Validate mapping if not mapping.survey_template: result["errors"].append(f"Mapping {mapping.id} has no survey template") continue # Additional sync logic can be added here # e.g., fetching updated mapping rules from HIS result["mappings_synced"] += 1 except Exception as e: result["errors"].append(f"Error syncing mapping {mapping.id}: {str(e)}") logger.info(f"HIS survey mappings sync completed: {result['mappings_synced']} mappings") except Exception as e: error_msg = f"Error syncing HIS mappings: {str(e)}" logger.error(error_msg, exc_info=True) result["success"] = False result["errors"].append(error_msg) return result