This commit is contained in:
ismail 2025-12-30 10:46:54 +03:00
parent 655e59c433
commit fef9abdd4f
7 changed files with 204 additions and 31 deletions

View File

@ -10,4 +10,5 @@ urlpatterns = [
path('', config_views.config_dashboard, name='dashboard'), path('', config_views.config_dashboard, name='dashboard'),
path('sla/', config_views.sla_config_list, name='sla_config_list'), path('sla/', config_views.sla_config_list, name='sla_config_list'),
path('routing/', config_views.routing_rules_list, name='routing_rules_list'), path('routing/', config_views.routing_rules_list, name='routing_rules_list'),
path('test/',config_views.test, name='test'),
] ]

View File

@ -18,18 +18,18 @@ def config_dashboard(request):
from django.shortcuts import redirect from django.shortcuts import redirect
messages.error(request, "Only PX Admins can access configuration.") messages.error(request, "Only PX Admins can access configuration.")
return redirect('dashboard:command-center') return redirect('dashboard:command-center')
# Get counts # Get counts
sla_configs_count = PXActionSLAConfig.objects.filter(is_active=True).count() sla_configs_count = PXActionSLAConfig.objects.filter(is_active=True).count()
routing_rules_count = RoutingRule.objects.filter(is_active=True).count() routing_rules_count = RoutingRule.objects.filter(is_active=True).count()
hospitals_count = Hospital.objects.filter(status='active').count() hospitals_count = Hospital.objects.filter(status='active').count()
context = { context = {
'sla_configs_count': sla_configs_count, 'sla_configs_count': sla_configs_count,
'routing_rules_count': routing_rules_count, 'routing_rules_count': routing_rules_count,
'hospitals_count': hospitals_count, 'hospitals_count': hospitals_count,
} }
return render(request, 'config/dashboard.html', context) return render(request, 'config/dashboard.html', context)
@ -42,39 +42,39 @@ def sla_config_list(request):
from django.shortcuts import redirect from django.shortcuts import redirect
messages.error(request, "Only PX Admins can access configuration.") messages.error(request, "Only PX Admins can access configuration.")
return redirect('dashboard:command-center') return redirect('dashboard:command-center')
queryset = PXActionSLAConfig.objects.select_related('hospital', 'department') queryset = PXActionSLAConfig.objects.select_related('hospital', 'department')
# Apply filters # Apply filters
hospital_filter = request.GET.get('hospital') hospital_filter = request.GET.get('hospital')
if hospital_filter: if hospital_filter:
queryset = queryset.filter(hospital_id=hospital_filter) queryset = queryset.filter(hospital_id=hospital_filter)
is_active = request.GET.get('is_active') is_active = request.GET.get('is_active')
if is_active == 'true': if is_active == 'true':
queryset = queryset.filter(is_active=True) queryset = queryset.filter(is_active=True)
elif is_active == 'false': elif is_active == 'false':
queryset = queryset.filter(is_active=False) queryset = queryset.filter(is_active=False)
# Ordering # Ordering
queryset = queryset.order_by('hospital', 'name') queryset = queryset.order_by('hospital', 'name')
# Pagination # Pagination
page_size = int(request.GET.get('page_size', 25)) page_size = int(request.GET.get('page_size', 25))
paginator = Paginator(queryset, page_size) paginator = Paginator(queryset, page_size)
page_number = request.GET.get('page', 1) page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)
# Get hospitals for filter # Get hospitals for filter
hospitals = Hospital.objects.filter(status='active') hospitals = Hospital.objects.filter(status='active')
context = { context = {
'page_obj': page_obj, 'page_obj': page_obj,
'sla_configs': page_obj.object_list, 'sla_configs': page_obj.object_list,
'hospitals': hospitals, 'hospitals': hospitals,
'filters': request.GET, 'filters': request.GET,
} }
return render(request, 'config/sla_config.html', context) return render(request, 'config/sla_config.html', context)
@ -87,39 +87,50 @@ def routing_rules_list(request):
from django.shortcuts import redirect from django.shortcuts import redirect
messages.error(request, "Only PX Admins can access configuration.") messages.error(request, "Only PX Admins can access configuration.")
return redirect('dashboard:command-center') return redirect('dashboard:command-center')
queryset = RoutingRule.objects.select_related( queryset = RoutingRule.objects.select_related(
'hospital', 'department', 'assign_to_user', 'assign_to_department' 'hospital', 'department', 'assign_to_user', 'assign_to_department'
) )
# Apply filters # Apply filters
hospital_filter = request.GET.get('hospital') hospital_filter = request.GET.get('hospital')
if hospital_filter: if hospital_filter:
queryset = queryset.filter(hospital_id=hospital_filter) queryset = queryset.filter(hospital_id=hospital_filter)
is_active = request.GET.get('is_active') is_active = request.GET.get('is_active')
if is_active == 'true': if is_active == 'true':
queryset = queryset.filter(is_active=True) queryset = queryset.filter(is_active=True)
elif is_active == 'false': elif is_active == 'false':
queryset = queryset.filter(is_active=False) queryset = queryset.filter(is_active=False)
# Ordering # Ordering
queryset = queryset.order_by('-priority', 'name') queryset = queryset.order_by('-priority', 'name')
# Pagination # Pagination
page_size = int(request.GET.get('page_size', 25)) page_size = int(request.GET.get('page_size', 25))
paginator = Paginator(queryset, page_size) paginator = Paginator(queryset, page_size)
page_number = request.GET.get('page', 1) page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)
# Get hospitals for filter # Get hospitals for filter
hospitals = Hospital.objects.filter(status='active') hospitals = Hospital.objects.filter(status='active')
context = { context = {
'page_obj': page_obj, 'page_obj': page_obj,
'routing_rules': page_obj.object_list, 'routing_rules': page_obj.object_list,
'hospitals': hospitals, 'hospitals': hospitals,
'filters': request.GET, 'filters': request.GET,
} }
return render(request, 'config/routing_rules.html', context) 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'})

View File

@ -18,7 +18,7 @@ def health_check(request):
'status': 'ok', 'status': 'ok',
'services': {} 'services': {}
} }
# Check database connection # Check database connection
try: try:
with connection.cursor() as cursor: with connection.cursor() as cursor:
@ -27,7 +27,7 @@ def health_check(request):
except Exception as e: except Exception as e:
health_status['status'] = 'error' health_status['status'] = 'error'
health_status['services']['database'] = f'error: {str(e)}' health_status['services']['database'] = f'error: {str(e)}'
# Check Redis/Celery (optional - don't fail if not available) # Check Redis/Celery (optional - don't fail if not available)
try: try:
from django_celery_beat.models import PeriodicTask from django_celery_beat.models import PeriodicTask
@ -35,8 +35,9 @@ def health_check(request):
health_status['services']['celery_beat'] = 'ok' health_status['services']['celery_beat'] = 'ok'
except Exception: except Exception:
health_status['services']['celery_beat'] = 'not_configured' health_status['services']['celery_beat'] = 'not_configured'
# Return appropriate status code # Return appropriate status code
status_code = 200 if health_status['status'] == 'ok' else 503 status_code = 200 if health_status['status'] == 'ok' else 503
return JsonResponse(health_status, status=status_code) return JsonResponse(health_status, status=status_code)

View File

@ -9,13 +9,19 @@ ALLOWED_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0']
# Database - Use PostgreSQL even in dev for consistency # Database - Use PostgreSQL even in dev for consistency
# Override with SQLite if needed for quick local testing # Override with SQLite if needed for quick local testing
DATABASES = { # DATABASES = {
'default': env.db( # 'default': env.db(
'DATABASE_URL', # 'DATABASE_URL',
default='postgresql://px360:px360@localhost:5432/px360' # 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 for development
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
@ -29,7 +35,7 @@ CORS_ALLOW_ALL_ORIGINS = True
# Django Debug Toolbar (optional) # Django Debug Toolbar (optional)
if DEBUG: if DEBUG:
INSTALLED_APPS += ['django_extensions'] # noqa INSTALLED_APPS += ['django_extensions'] # noqa
# Logging - More verbose in development # Logging - More verbose in development
LOGGING['loggers']['django']['level'] = 'DEBUG' # noqa LOGGING['loggers']['django']['level'] = 'DEBUG' # noqa
LOGGING['loggers']['apps']['level'] = 'DEBUG' # noqa LOGGING['loggers']['apps']['level'] = 'DEBUG' # noqa

View File

@ -23,6 +23,7 @@ dependencies = [
"whitenoise>=6.6.0", "whitenoise>=6.6.0",
"django-extensions>=4.1", "django-extensions>=4.1",
"djangorestframework-stubs>=3.16.6", "djangorestframework-stubs>=3.16.6",
"rich>=14.2.0",
"reportlab>=4.4.7", "reportlab>=4.4.7",
"openpyxl>=3.1.5", "openpyxl>=3.1.5",
] ]

117
run.py Normal file
View File

@ -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()

38
uv.lock generated
View File

@ -1,5 +1,5 @@
version = 1 version = 1
revision = 2 revision = 3
requires-python = ">=3.12" requires-python = ">=3.12"
[[package]] [[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" }, { 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]] [[package]]
name = "matplotlib-inline" name = "matplotlib-inline"
version = "0.2.1" 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" }, { 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]] [[package]]
name = "openpyxl" name = "openpyxl"
version = "3.1.5" version = "3.1.5"
@ -832,6 +853,7 @@ dependencies = [
{ name = "pillow" }, { name = "pillow" },
{ name = "psycopg2-binary" }, { name = "psycopg2-binary" },
{ name = "redis" }, { name = "redis" },
{ name = "rich" },
{ name = "reportlab" }, { name = "reportlab" },
{ name = "whitenoise" }, { name = "whitenoise" },
] ]
@ -866,6 +888,7 @@ requires-dist = [
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" },
{ name = "pytest-django", marker = "extra == 'dev'", specifier = ">=4.7.0" }, { name = "pytest-django", marker = "extra == 'dev'", specifier = ">=4.7.0" },
{ name = "redis", specifier = ">=5.0.0" }, { name = "redis", specifier = ">=5.0.0" },
{ name = "rich", specifier = ">=14.2.0" },
{ name = "reportlab", specifier = ">=4.4.7" }, { name = "reportlab", specifier = ">=4.4.7" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" },
{ name = "whitenoise", specifier = ">=6.6.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" }, { 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]] [[package]]
name = "rpds-py" name = "rpds-py"
version = "0.30.0" version = "0.30.0"