support forwarding vitals and syncing patients to multiple configurable URLs

This commit is contained in:
2026-09-21 15:16:09 +05:30
parent a7999add61
commit b0d3ba3eed
3 changed files with 64 additions and 56 deletions
+46 -46
View File
@@ -41,69 +41,54 @@ async def forward_vitals_to_api(vitals: NormalizedVitals) -> bool:
client = get_http_client() client = get_http_client()
# 1. Forward to primary target API (e.g. Injomo Dev Workflow) async def _post_target(url: str, label: str) -> bool:
async def _forward_primary() -> bool: if not url:
primary_url = settings.target_api_url return False
if not primary_url: curr_port = os.getenv("PORT", "8000")
if f":{curr_port}" in url and ("localhost" in url or "127.0.0.1" in url):
return False return False
try: try:
primary_headers = headers.copy() req_headers = headers.copy()
if settings.api_token: if settings.api_token:
primary_headers["Authorization"] = f"Bearer {settings.api_token}" req_headers["Authorization"] = f"Bearer {settings.api_token}"
response = await client.post( url_to_call = url
primary_url, if settings.api_token and "?" not in url and "/api/push-vitals" in url:
json=payload, url_to_call = f"{url}?api_key={settings.api_token}"
headers=primary_headers response = await client.post(url_to_call, json=payload, headers=req_headers)
)
response.raise_for_status() response.raise_for_status()
logger.info(f"Successfully forwarded vitals to primary API for device {vitals.device_id}") logger.info(f"Successfully forwarded vitals to {label} ({url}) for device {vitals.device_id}")
return True return True
except Exception as e: except Exception as e:
err_msg = str(e).strip() or "timeout / connection reset" err_msg = str(e).strip() or "timeout / connection reset"
logger.warning(f"Error forwarding to primary API: {type(e).__name__} ({err_msg})") logger.warning(f"Error forwarding to {label} ({url}): {type(e).__name__} ({err_msg})")
return False return False
# 2. Forward to Cloud Server targets = []
async def _forward_cloud() -> bool: if settings.target_api_url:
targets.append((settings.target_api_url, "Primary Target API"))
cloud_base = get_cloud_base_url() cloud_base = get_cloud_base_url()
cloud_url = f"{cloud_base}/api/push-vitals"
is_on_cloud = os.getenv("RENDER") is not None or os.getenv("PORT") == "10000" or "render.local" in os.getenv("HOSTNAME", "") is_on_cloud = os.getenv("RENDER") is not None or os.getenv("PORT") == "10000" or "render.local" in os.getenv("HOSTNAME", "")
if is_on_cloud and ("onrender.com" in cloud_base): if not (is_on_cloud and "onrender.com" in cloud_base):
return True cloud_push_url = f"{cloud_base}/api/push-vitals" if not cloud_base.endswith("/api/push-vitals") else cloud_base
try: if cloud_push_url not in [t[0] for t in targets]:
url_with_key = cloud_url targets.append((cloud_push_url, "Cloud Server"))
if settings.api_token:
url_with_key = f"{cloud_url}?api_key={settings.api_token}"
response = await client.post( for extra_url in getattr(settings, "forward_urls", []):
url_with_key, if extra_url and extra_url not in [t[0] for t in targets]:
json=payload, targets.append((extra_url, "Configured Forward URL"))
headers=headers
)
response.raise_for_status()
logger.info(f"Successfully forwarded vitals to Cloud API for device {vitals.device_id}")
return True
except Exception as e:
err_msg = str(e).strip() or "timeout / connection reset"
logger.warning(f"Error forwarding to Cloud API: {type(e).__name__} ({err_msg})")
return False
results = await asyncio.gather(_forward_primary(), _forward_cloud(), return_exceptions=True) tasks = [_post_target(url, lbl) for url, lbl in targets]
results = await asyncio.gather(*tasks, return_exceptions=True)
return any(r is True for r in results if not isinstance(r, Exception)) return any(r is True for r in results if not isinstance(r, Exception))
async def sync_patient_assignment_to_cloud(patient_data: dict, device_id: str) -> bool: async def sync_patient_assignment_to_cloud(patient_data: dict, device_id: str) -> bool:
""" """
Synchronizes patient demographics assignment to Cloud Server. Synchronizes patient demographics assignment to Cloud and LAN servers.
Ensures local and remote dashboards always show the exact same patient. Ensures local and remote dashboards always show the exact same patient.
""" """
cloud_base = get_cloud_base_url()
is_on_cloud = os.getenv("RENDER") is not None or os.getenv("PORT") == "10000" or "render.local" in os.getenv("HOSTNAME", "")
if is_on_cloud and ("onrender.com" in cloud_base):
return True
client = get_http_client() client = get_http_client()
cloud_assign_url = f"{cloud_base}/api/patient/assign"
payload = { payload = {
"device_id": device_id, "device_id": device_id,
"mrn": patient_data.get("mrn") or patient_data.get("patient_id") or "", "mrn": patient_data.get("mrn") or patient_data.get("patient_id") or "",
@@ -113,14 +98,29 @@ async def sync_patient_assignment_to_cloud(patient_data: dict, device_id: str) -
"bed_id": patient_data.get("bed_id"), "bed_id": patient_data.get("bed_id"),
"patient_type": patient_data.get("patient_type") "patient_type": patient_data.get("patient_type")
} }
targets = []
cloud_base = get_cloud_base_url()
targets.append(f"{cloud_base}/api/patient/assign")
for extra_url in getattr(settings, "forward_urls", []):
if "/api/push-vitals" in extra_url:
assign_url = extra_url.replace("/api/push-vitals", "/api/patient/assign")
if assign_url not in targets:
targets.append(assign_url)
async def _sync_one(url: str) -> bool:
try: try:
url_with_key = cloud_assign_url url_with_key = url
if settings.api_token: if settings.api_token:
url_with_key = f"{cloud_assign_url}?api_key={settings.api_token}" url_with_key = f"{url}?api_key={settings.api_token}"
resp = await client.post(url_with_key, json=payload, headers={"Content-Type": "application/json"}) resp = await client.post(url_with_key, json=payload, headers={"Content-Type": "application/json"})
resp.raise_for_status() resp.raise_for_status()
logger.info(f"Successfully synced patient {payload['mrn']} ({payload['name']}) to Cloud for {device_id}") logger.info(f"Successfully synced patient {payload['mrn']} to {url} for {device_id}")
return True return True
except Exception as e: except Exception as e:
logger.warning(f"Could not sync patient assignment to Cloud: {type(e).__name__} ({e})") logger.warning(f"Could not sync patient assignment to {url}: {type(e).__name__} ({e})")
return False return False
results = await asyncio.gather(*[_sync_one(u) for u in targets], return_exceptions=True)
return any(r is True for r in results if not isinstance(r, Exception))
+4
View File
@@ -9,6 +9,10 @@
}, },
"target_api_url": "https://services-api.a2zhealth.in/v1/contec-monitor", "target_api_url": "https://services-api.a2zhealth.in/v1/contec-monitor",
"cloud_api_url": "https://services-api.a2zhealth.in/v1/contec-monitor", "cloud_api_url": "https://services-api.a2zhealth.in/v1/contec-monitor",
"forward_urls": [
"http://192.168.100.104:10000/api/push-vitals",
"https://services-api.a2zhealth.in/v1/contec-monitor"
],
"api_token": "", "api_token": "",
"retry_interval_seconds": 60, "retry_interval_seconds": 60,
"log_file": "hl7_forwarder.log", "log_file": "hl7_forwarder.log",
+4
View File
@@ -11,6 +11,10 @@ class Settings(BaseSettings):
device_models: dict = {} device_models: dict = {}
target_api_url: str = "https://services-api.a2zhealth.in/v1/contec-monitor" target_api_url: str = "https://services-api.a2zhealth.in/v1/contec-monitor"
cloud_api_url: str = "https://services-api.a2zhealth.in/v1/contec-monitor" cloud_api_url: str = "https://services-api.a2zhealth.in/v1/contec-monitor"
forward_urls: list = [
"http://192.168.100.104:10000/api/push-vitals",
"https://services-api.a2zhealth.in/v1/contec-monitor"
]
api_token: str = "" api_token: str = ""
retry_interval_seconds: int = 60 retry_interval_seconds: int = 60
log_file: str = "hl7_forwarder.log" log_file: str = "hl7_forwarder.log"