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
+55 -55
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:
cloud_base = get_cloud_base_url() targets.append((settings.target_api_url, "Primary Target API"))
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", "")
if is_on_cloud and ("onrender.com" in cloud_base):
return True
try:
url_with_key = cloud_url
if settings.api_token:
url_with_key = f"{cloud_url}?api_key={settings.api_token}"
response = await client.post( cloud_base = get_cloud_base_url()
url_with_key, is_on_cloud = os.getenv("RENDER") is not None or os.getenv("PORT") == "10000" or "render.local" in os.getenv("HOSTNAME", "")
json=payload, if not (is_on_cloud and "onrender.com" in cloud_base):
headers=headers cloud_push_url = f"{cloud_base}/api/push-vitals" if not cloud_base.endswith("/api/push-vitals") else cloud_base
) if cloud_push_url not in [t[0] for t in targets]:
response.raise_for_status() targets.append((cloud_push_url, "Cloud Server"))
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) for extra_url in getattr(settings, "forward_urls", []):
if extra_url and extra_url not in [t[0] for t in targets]:
targets.append((extra_url, "Configured Forward URL"))
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")
} }
try:
url_with_key = cloud_assign_url targets = []
if settings.api_token: cloud_base = get_cloud_base_url()
url_with_key = f"{cloud_assign_url}?api_key={settings.api_token}" targets.append(f"{cloud_base}/api/patient/assign")
resp = await client.post(url_with_key, json=payload, headers={"Content-Type": "application/json"})
resp.raise_for_status() for extra_url in getattr(settings, "forward_urls", []):
logger.info(f"Successfully synced patient {payload['mrn']} ({payload['name']}) to Cloud for {device_id}") if "/api/push-vitals" in extra_url:
return True assign_url = extra_url.replace("/api/push-vitals", "/api/patient/assign")
except Exception as e: if assign_url not in targets:
logger.warning(f"Could not sync patient assignment to Cloud: {type(e).__name__} ({e})") targets.append(assign_url)
return False
async def _sync_one(url: str) -> bool:
try:
url_with_key = url
if 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.raise_for_status()
logger.info(f"Successfully synced patient {payload['mrn']} to {url} for {device_id}")
return True
except Exception as e:
logger.warning(f"Could not sync patient assignment to {url}: {type(e).__name__} ({e})")
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"