diff --git a/apps/core/config_urls.py b/apps/core/config_urls.py index 4c13069..df8fdbc 100644 --- a/apps/core/config_urls.py +++ b/apps/core/config_urls.py @@ -10,4 +10,5 @@ urlpatterns = [ path('', config_views.config_dashboard, name='dashboard'), path('sla/', config_views.sla_config_list, name='sla_config_list'), path('routing/', config_views.routing_rules_list, name='routing_rules_list'), + path('test/',config_views.test, name='test'), ] diff --git a/apps/core/config_views.py b/apps/core/config_views.py index dcf4cdb..bd65591 100644 --- a/apps/core/config_views.py +++ b/apps/core/config_views.py @@ -18,18 +18,18 @@ def config_dashboard(request): from django.shortcuts import redirect messages.error(request, "Only PX Admins can access configuration.") return redirect('dashboard:command-center') - + # Get counts sla_configs_count = PXActionSLAConfig.objects.filter(is_active=True).count() routing_rules_count = RoutingRule.objects.filter(is_active=True).count() hospitals_count = Hospital.objects.filter(status='active').count() - + context = { 'sla_configs_count': sla_configs_count, 'routing_rules_count': routing_rules_count, 'hospitals_count': hospitals_count, } - + return render(request, 'config/dashboard.html', context) @@ -42,39 +42,39 @@ def sla_config_list(request): from django.shortcuts import redirect messages.error(request, "Only PX Admins can access configuration.") return redirect('dashboard:command-center') - + queryset = PXActionSLAConfig.objects.select_related('hospital', 'department') - + # Apply filters hospital_filter = request.GET.get('hospital') if hospital_filter: queryset = queryset.filter(hospital_id=hospital_filter) - + is_active = request.GET.get('is_active') if is_active == 'true': queryset = queryset.filter(is_active=True) elif is_active == 'false': queryset = queryset.filter(is_active=False) - + # Ordering queryset = queryset.order_by('hospital', 'name') - + # Pagination page_size = int(request.GET.get('page_size', 25)) paginator = Paginator(queryset, page_size) page_number = request.GET.get('page', 1) page_obj = paginator.get_page(page_number) - + # Get hospitals for filter hospitals = Hospital.objects.filter(status='active') - + context = { 'page_obj': page_obj, 'sla_configs': page_obj.object_list, 'hospitals': hospitals, 'filters': request.GET, } - + return render(request, 'config/sla_config.html', context) @@ -87,39 +87,50 @@ def routing_rules_list(request): from django.shortcuts import redirect messages.error(request, "Only PX Admins can access configuration.") return redirect('dashboard:command-center') - + queryset = RoutingRule.objects.select_related( 'hospital', 'department', 'assign_to_user', 'assign_to_department' ) - + # Apply filters hospital_filter = request.GET.get('hospital') if hospital_filter: queryset = queryset.filter(hospital_id=hospital_filter) - + is_active = request.GET.get('is_active') if is_active == 'true': queryset = queryset.filter(is_active=True) elif is_active == 'false': queryset = queryset.filter(is_active=False) - + # Ordering queryset = queryset.order_by('-priority', 'name') - + # Pagination page_size = int(request.GET.get('page_size', 25)) paginator = Paginator(queryset, page_size) page_number = request.GET.get('page', 1) page_obj = paginator.get_page(page_number) - + # Get hospitals for filter hospitals = Hospital.objects.filter(status='active') - + context = { 'page_obj': page_obj, 'routing_rules': page_obj.object_list, 'hospitals': hospitals, 'filters': request.GET, } - + return render(request, 'config/routing_rules.html', context) + +from django.views.decorators.csrf import csrf_exempt +from rich import print +@csrf_exempt +def test(request): + import json + from django.http import JsonResponse + + print(json.loads(request.body)) + + return JsonResponse({'status': 'ok'}) \ No newline at end of file diff --git a/apps/core/views.py b/apps/core/views.py index 618433f..0f5b888 100644 --- a/apps/core/views.py +++ b/apps/core/views.py @@ -18,7 +18,7 @@ def health_check(request): 'status': 'ok', 'services': {} } - + # Check database connection try: with connection.cursor() as cursor: @@ -27,7 +27,7 @@ def health_check(request): except Exception as e: health_status['status'] = 'error' health_status['services']['database'] = f'error: {str(e)}' - + # Check Redis/Celery (optional - don't fail if not available) try: from django_celery_beat.models import PeriodicTask @@ -35,8 +35,9 @@ def health_check(request): health_status['services']['celery_beat'] = 'ok' except Exception: health_status['services']['celery_beat'] = 'not_configured' - + # Return appropriate status code status_code = 200 if health_status['status'] == 'ok' else 503 - + return JsonResponse(health_status, status=status_code) + diff --git a/config/settings/dev.py b/config/settings/dev.py index a69c293..c40b2e7 100644 --- a/config/settings/dev.py +++ b/config/settings/dev.py @@ -9,13 +9,19 @@ ALLOWED_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0'] # Database - Use PostgreSQL even in dev for consistency # Override with SQLite if needed for quick local testing -DATABASES = { - 'default': env.db( - 'DATABASE_URL', - default='postgresql://px360:px360@localhost:5432/px360' - ) -} +# DATABASES = { +# 'default': env.db( +# 'DATABASE_URL', +# default='postgresql://px360:px360@localhost:5432/px360' +# ) +# } +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} # Email backend for development EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' @@ -29,7 +35,7 @@ CORS_ALLOW_ALL_ORIGINS = True # Django Debug Toolbar (optional) if DEBUG: INSTALLED_APPS += ['django_extensions'] # noqa - + # Logging - More verbose in development LOGGING['loggers']['django']['level'] = 'DEBUG' # noqa LOGGING['loggers']['apps']['level'] = 'DEBUG' # noqa diff --git a/pyproject.toml b/pyproject.toml index f7cebe9..72f93d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "whitenoise>=6.6.0", "django-extensions>=4.1", "djangorestframework-stubs>=3.16.6", + "rich>=14.2.0", "reportlab>=4.4.7", "openpyxl>=3.1.5", ] diff --git a/run.py b/run.py new file mode 100644 index 0000000..546bb63 --- /dev/null +++ b/run.py @@ -0,0 +1,117 @@ +import json +import time +import random +import uuid +import requests +from datetime import datetime, timedelta + +# Configuration +API_ENDPOINT = "http://localhost:8000/config/test/" # Update with your actual endpoint +SIMULATION_SPEED_FACTOR = 1 # 1 is real-time, higher is faster +BATCH_SIZE = 1 # Number of events to send at once + +# Mock Data Constants +DEPARTMENTS = ["Emergency", "Cardiology", "Pediatrics", "Radiology", "General Medicine"] +VISIT_TYPES = ["Inpatient", "Outpatient", "Emergency"] +STAGES = ["Triage", "Consultation", "Diagnostic Testing", "Observation", "Discharge"] +ROLES = ["Doctor", "Nurse", "Technician", "Admin"] + +# Staff Directory Simulation +STAFF_LIST = [ + { + "staff_id": f"STF-{1000 + i}", + "first_name": f"StaffFirst_{i}", + "last_name": f"StaffLast_{i}", + "role": random.choice(ROLES), + "department": random.choice(DEPARTMENTS) + } for i in range(20) +] + +def generate_patient_journey(): + """Generates a realistic patient journey payload with National ID and MRN.""" + p_id = f"PAT-{random.randint(10000, 99999)}" + mrn = f"MRN-{random.randint(100000, 999999)}" + national_id = f"{random.randint(100, 999)}-{random.randint(10, 99)}-{random.randint(1000, 9999)}" + e_id = f"ENC-{uuid.uuid4().hex[:8].upper()}" + + # Times + checkin = datetime.now() - timedelta(minutes=random.randint(30, 240)) + + payload = { + "patient_info": { + "patient_id": p_id, + "mrn": mrn, + "national_id": national_id, + "first_name": f"Patient_{p_id[-3:]}", + "last_name": "Doe", + "phone": f"+1-555-{random.randint(100, 999)}-{random.randint(1000, 9999)}" + }, + "visit_info": { + "encounter_id": e_id, + "patient_id": p_id, + "visit_type": random.choice(VISIT_TYPES), + "department": random.choice(DEPARTMENTS), + "checkin_time": checkin.isoformat(), + "discharge_time": datetime.now().isoformat() + }, + "journey_steps": [] + } + + # Generate 1 to 4 journey steps + num_steps = random.randint(1, 4) + step_time = checkin + + for i in range(num_steps): + staff = random.choice(STAFF_LIST) + step_time += timedelta(minutes=random.randint(10, 45)) + + step = { + "encounter_id": e_id, + "patient_id": p_id, + "stage": STAGES[min(i, len(STAGES)-1)], + "timestamp": step_time.isoformat(), + "staff_id": staff["staff_id"], + "wait_minutes": random.randint(0, 30) + } + payload["journey_steps"].append(step) + + return payload + +def send_data(payload): + """Sends the JSON payload to the configured endpoint.""" + print(f"[{datetime.now().strftime('%H:%M:%S')}] Sending journey data for Patient MRN: {payload['patient_info']['mrn']}...") + + try: + # Note: In a real scenario, you'd add headers={'Authorization': 'Bearer YOUR_TOKEN'} + response = requests.post(API_ENDPOINT, json=payload, timeout=10) + response.raise_for_status() + + # Simulated success for demonstration + print(f"Successfully sent {len(payload['journey_steps'])} journey steps to {API_ENDPOINT}") + # print(json.dumps(payload, indent=2)) # Uncomment to see full payload + + except requests.exceptions.RequestException as e: + print(f"Error sending data: {e}") + +def run_simulation(): + """Main loop for the simulation.""" + print("Starting PX360 Integration Simulation...") + print(f"Target Endpoint: {API_ENDPOINT}") + print("-" * 50) + + try: + while True: + # Generate and send data + data = generate_patient_journey() + send_data(data) + + # Wait for a random interval between 2 and 10 seconds (scaled) + wait_time = random.uniform(2, 10) / SIMULATION_SPEED_FACTOR + print(f"Next event in {wait_time:.2f} seconds...\n") + time.sleep(wait_time) + + except KeyboardInterrupt: + print("\nSimulation stopped by user.") + +if __name__ == "__main__": + run_simulation() \ No newline at end of file diff --git a/uv.lock b/uv.lock index b85483c..1b6ee58 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" [[package]] @@ -609,6 +609,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/d6/943cf84117cd9ddecf6e1707a3f712a49fc64abdb8ac31b19132871af1dd/kombu-5.6.1-py3-none-any.whl", hash = "sha256:b69e3f5527ec32fc5196028a36376501682973e9620d6175d1c3d4eaf7e95409", size = 214141, upload-time = "2025-11-25T11:07:31.54Z" }, ] +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + [[package]] name = "matplotlib-inline" version = "0.2.1" @@ -621,6 +633,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "openpyxl" version = "3.1.5" @@ -832,6 +853,7 @@ dependencies = [ { name = "pillow" }, { name = "psycopg2-binary" }, { name = "redis" }, + { name = "rich" }, { name = "reportlab" }, { name = "whitenoise" }, ] @@ -866,6 +888,7 @@ requires-dist = [ { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" }, { name = "pytest-django", marker = "extra == 'dev'", specifier = ">=4.7.0" }, { name = "redis", specifier = ">=5.0.0" }, + { name = "rich", specifier = ">=14.2.0" }, { name = "reportlab", specifier = ">=4.4.7" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, { name = "whitenoise", specifier = ">=6.6.0" }, @@ -1050,6 +1073,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, +] + [[package]] name = "rpds-py" version = "0.30.0"