feat: add /api/push-vitals endpoint for local-to-cloud live telemetry sync
This commit is contained in:
@@ -468,3 +468,114 @@ def get_network_info():
|
|||||||
f"6. The dashboard will show data automatically"
|
f"6. The dashboard will show data automatically"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# === Push Vitals Endpoint (for local-to-cloud sync) ===
|
||||||
|
from schemas import NormalizedVitals
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
@router.post("/push-vitals")
|
||||||
|
async def push_vitals(vitals: NormalizedVitals, api_key: Optional[str] = None, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
Receive vitals pushed from a local instance and store + broadcast them to connected WebSocket clients.
|
||||||
|
"""
|
||||||
|
from config import settings
|
||||||
|
if settings.api_token and api_key != settings.api_token:
|
||||||
|
raise HTTPException(status_code=401, detail="Invalid or missing api_key")
|
||||||
|
|
||||||
|
try:
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
# 1. Device tracking
|
||||||
|
device = db.query(Device).filter(Device.device_id == vitals.device_id).first()
|
||||||
|
if not device:
|
||||||
|
device = Device(
|
||||||
|
device_id=vitals.device_id,
|
||||||
|
ip_address=vitals.ip_address or "remote-push",
|
||||||
|
status="active",
|
||||||
|
last_seen=now
|
||||||
|
)
|
||||||
|
db.add(device)
|
||||||
|
else:
|
||||||
|
if vitals.ip_address:
|
||||||
|
device.ip_address = vitals.ip_address
|
||||||
|
device.status = "active"
|
||||||
|
device.last_seen = now
|
||||||
|
|
||||||
|
# 2. Patient tracking
|
||||||
|
mrn = vitals.patient_id
|
||||||
|
patient = None
|
||||||
|
if mrn and mrn != "UNKNOWN":
|
||||||
|
patient = db.query(Patient).filter(or_(Patient.mrn == mrn, Patient.patient_id == mrn)).first()
|
||||||
|
if not patient:
|
||||||
|
patient = Patient(
|
||||||
|
patient_id=mrn,
|
||||||
|
mrn=mrn,
|
||||||
|
name=vitals.patient_name or "Unknown",
|
||||||
|
gender=vitals.patient_gender,
|
||||||
|
dob=vitals.patient_dob,
|
||||||
|
bed_id=vitals.bed_id,
|
||||||
|
device_id=vitals.device_id
|
||||||
|
)
|
||||||
|
db.add(patient)
|
||||||
|
else:
|
||||||
|
if vitals.patient_name and vitals.patient_name != "Unknown":
|
||||||
|
patient.name = vitals.patient_name
|
||||||
|
if vitals.patient_gender:
|
||||||
|
patient.gender = vitals.patient_gender
|
||||||
|
if vitals.patient_dob:
|
||||||
|
patient.dob = vitals.patient_dob
|
||||||
|
if vitals.bed_id:
|
||||||
|
patient.bed_id = vitals.bed_id
|
||||||
|
patient.device_id = vitals.device_id
|
||||||
|
elif vitals.device_id:
|
||||||
|
# Check if there's already a patient assigned to this device
|
||||||
|
patient = db.query(Patient).filter(Patient.device_id == vitals.device_id, Patient.patient_id != "UNKNOWN").first()
|
||||||
|
if patient:
|
||||||
|
mrn = patient.mrn or patient.patient_id
|
||||||
|
|
||||||
|
# 3. Create VitalReading
|
||||||
|
reading = VitalReading(
|
||||||
|
device_id=vitals.device_id,
|
||||||
|
patient_id=mrn or "UNKNOWN",
|
||||||
|
timestamp=vitals.timestamp or now,
|
||||||
|
heart_rate=vitals.heart_rate,
|
||||||
|
spo2=vitals.spo2,
|
||||||
|
systolic_bp=vitals.systolic_bp,
|
||||||
|
diastolic_bp=vitals.diastolic_bp,
|
||||||
|
map_bp=vitals.map_bp,
|
||||||
|
respiratory_rate=vitals.respiratory_rate,
|
||||||
|
temperature=vitals.temperature,
|
||||||
|
transmitted=True
|
||||||
|
)
|
||||||
|
db.add(reading)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(reading)
|
||||||
|
|
||||||
|
# 4. Broadcast to WebSocket clients
|
||||||
|
vitals_dict = vitals.model_dump(mode="json")
|
||||||
|
vitals_dict["mrn"] = mrn or (patient.mrn if patient else None)
|
||||||
|
vitals_dict["patient_id"] = vitals_dict["mrn"] or vitals.patient_id
|
||||||
|
vitals_dict["name"] = vitals.patient_name or (patient.name if patient else None)
|
||||||
|
vitals_dict["patient_name"] = vitals_dict["name"]
|
||||||
|
vitals_dict["gender"] = vitals.patient_gender or (patient.gender if patient else None)
|
||||||
|
vitals_dict["patient_gender"] = vitals_dict["gender"]
|
||||||
|
vitals_dict["dob"] = vitals.patient_dob or (patient.dob if patient else None)
|
||||||
|
vitals_dict["patient_dob"] = vitals_dict["dob"]
|
||||||
|
vitals_dict["bed_id"] = vitals.bed_id or (patient.bed_id if patient else None)
|
||||||
|
|
||||||
|
await ws_manager.broadcast_vitals(vitals_dict)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"reading_id": reading.id,
|
||||||
|
"device_id": vitals.device_id,
|
||||||
|
"patient_id": vitals_dict["patient_id"]
|
||||||
|
}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Push-vitals error: {e}")
|
||||||
|
db.rollback()
|
||||||
|
raise HTTPException(status_code=500, detail=f"Failed to store vitals: {str(e)}")
|
||||||
|
|
||||||
|
|||||||
@@ -468,3 +468,114 @@ def get_network_info():
|
|||||||
f"6. The dashboard will show data automatically"
|
f"6. The dashboard will show data automatically"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# === Push Vitals Endpoint (for local-to-cloud sync) ===
|
||||||
|
from schemas import NormalizedVitals
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
@router.post("/push-vitals")
|
||||||
|
async def push_vitals(vitals: NormalizedVitals, api_key: Optional[str] = None, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
Receive vitals pushed from a local instance and store + broadcast them to connected WebSocket clients.
|
||||||
|
"""
|
||||||
|
from config import settings
|
||||||
|
if settings.api_token and api_key != settings.api_token:
|
||||||
|
raise HTTPException(status_code=401, detail="Invalid or missing api_key")
|
||||||
|
|
||||||
|
try:
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
# 1. Device tracking
|
||||||
|
device = db.query(Device).filter(Device.device_id == vitals.device_id).first()
|
||||||
|
if not device:
|
||||||
|
device = Device(
|
||||||
|
device_id=vitals.device_id,
|
||||||
|
ip_address=vitals.ip_address or "remote-push",
|
||||||
|
status="active",
|
||||||
|
last_seen=now
|
||||||
|
)
|
||||||
|
db.add(device)
|
||||||
|
else:
|
||||||
|
if vitals.ip_address:
|
||||||
|
device.ip_address = vitals.ip_address
|
||||||
|
device.status = "active"
|
||||||
|
device.last_seen = now
|
||||||
|
|
||||||
|
# 2. Patient tracking
|
||||||
|
mrn = vitals.patient_id
|
||||||
|
patient = None
|
||||||
|
if mrn and mrn != "UNKNOWN":
|
||||||
|
patient = db.query(Patient).filter(or_(Patient.mrn == mrn, Patient.patient_id == mrn)).first()
|
||||||
|
if not patient:
|
||||||
|
patient = Patient(
|
||||||
|
patient_id=mrn,
|
||||||
|
mrn=mrn,
|
||||||
|
name=vitals.patient_name or "Unknown",
|
||||||
|
gender=vitals.patient_gender,
|
||||||
|
dob=vitals.patient_dob,
|
||||||
|
bed_id=vitals.bed_id,
|
||||||
|
device_id=vitals.device_id
|
||||||
|
)
|
||||||
|
db.add(patient)
|
||||||
|
else:
|
||||||
|
if vitals.patient_name and vitals.patient_name != "Unknown":
|
||||||
|
patient.name = vitals.patient_name
|
||||||
|
if vitals.patient_gender:
|
||||||
|
patient.gender = vitals.patient_gender
|
||||||
|
if vitals.patient_dob:
|
||||||
|
patient.dob = vitals.patient_dob
|
||||||
|
if vitals.bed_id:
|
||||||
|
patient.bed_id = vitals.bed_id
|
||||||
|
patient.device_id = vitals.device_id
|
||||||
|
elif vitals.device_id:
|
||||||
|
# Check if there's already a patient assigned to this device
|
||||||
|
patient = db.query(Patient).filter(Patient.device_id == vitals.device_id, Patient.patient_id != "UNKNOWN").first()
|
||||||
|
if patient:
|
||||||
|
mrn = patient.mrn or patient.patient_id
|
||||||
|
|
||||||
|
# 3. Create VitalReading
|
||||||
|
reading = VitalReading(
|
||||||
|
device_id=vitals.device_id,
|
||||||
|
patient_id=mrn or "UNKNOWN",
|
||||||
|
timestamp=vitals.timestamp or now,
|
||||||
|
heart_rate=vitals.heart_rate,
|
||||||
|
spo2=vitals.spo2,
|
||||||
|
systolic_bp=vitals.systolic_bp,
|
||||||
|
diastolic_bp=vitals.diastolic_bp,
|
||||||
|
map_bp=vitals.map_bp,
|
||||||
|
respiratory_rate=vitals.respiratory_rate,
|
||||||
|
temperature=vitals.temperature,
|
||||||
|
transmitted=True
|
||||||
|
)
|
||||||
|
db.add(reading)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(reading)
|
||||||
|
|
||||||
|
# 4. Broadcast to WebSocket clients
|
||||||
|
vitals_dict = vitals.model_dump(mode="json")
|
||||||
|
vitals_dict["mrn"] = mrn or (patient.mrn if patient else None)
|
||||||
|
vitals_dict["patient_id"] = vitals_dict["mrn"] or vitals.patient_id
|
||||||
|
vitals_dict["name"] = vitals.patient_name or (patient.name if patient else None)
|
||||||
|
vitals_dict["patient_name"] = vitals_dict["name"]
|
||||||
|
vitals_dict["gender"] = vitals.patient_gender or (patient.gender if patient else None)
|
||||||
|
vitals_dict["patient_gender"] = vitals_dict["gender"]
|
||||||
|
vitals_dict["dob"] = vitals.patient_dob or (patient.dob if patient else None)
|
||||||
|
vitals_dict["patient_dob"] = vitals_dict["dob"]
|
||||||
|
vitals_dict["bed_id"] = vitals.bed_id or (patient.bed_id if patient else None)
|
||||||
|
|
||||||
|
await ws_manager.broadcast_vitals(vitals_dict)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"reading_id": reading.id,
|
||||||
|
"device_id": vitals.device_id,
|
||||||
|
"patient_id": vitals_dict["patient_id"]
|
||||||
|
}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Push-vitals error: {e}")
|
||||||
|
db.rollback()
|
||||||
|
raise HTTPException(status_code=500, detail=f"Failed to store vitals: {str(e)}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user