dpv007 commited on
Commit
e9514a3
·
verified ·
1 Parent(s): 8367c2d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +180 -56
app.py CHANGED
@@ -1,7 +1,9 @@
1
  # app.py
2
  """
3
  Elderly HealthWatch AI Backend (FastAPI)
4
- Ensure this file contains only Python code — not requirements.txt content.
 
 
5
  """
6
 
7
  import io
@@ -15,8 +17,9 @@ from PIL import Image
15
  import numpy as np
16
  import os
17
  import traceback
 
18
 
19
- # Try facenet-pytorch first, fallback to mtcnn
20
  try:
21
  from facenet_pytorch import MTCNN as FacenetMTCNN
22
  _MTCNN_IMPL = "facenet_pytorch"
@@ -24,6 +27,7 @@ except Exception:
24
  FacenetMTCNN = None
25
  _MTCNN_IMPL = None
26
 
 
27
  if _MTCNN_IMPL is None:
28
  try:
29
  from mtcnn import MTCNN as ClassicMTCNN
@@ -31,15 +35,45 @@ if _MTCNN_IMPL is None:
31
  except Exception:
32
  ClassicMTCNN = None
33
 
34
- def create_mtcnn():
 
 
 
 
 
 
 
 
35
  if _MTCNN_IMPL == "facenet_pytorch" and FacenetMTCNN is not None:
36
- return FacenetMTCNN(keep_all=False, device="cpu")
37
- elif _MTCNN_IMPL == "mtcnn" and ClassicMTCNN is not None:
38
- return ClassicMTCNN()
39
- else:
40
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- mtcnn = create_mtcnn()
 
 
 
 
 
 
 
43
 
44
  app = FastAPI(title="Elderly HealthWatch AI Backend")
45
 
@@ -56,14 +90,13 @@ screenings_db: Dict[str, Dict[str, Any]] = {}
56
  def load_image_from_bytes(bytes_data: bytes) -> Image.Image:
57
  return Image.open(io.BytesIO(bytes_data)).convert("RGB")
58
 
59
- def estimate_eye_openness_from_detection(detection_result: Dict[str, Any]) -> float:
 
 
 
 
60
  try:
61
- if isinstance(detection_result, dict) and "confidence" in detection_result:
62
- conf = float(detection_result.get("confidence", 0.0))
63
- elif isinstance(detection_result, (list, tuple)) and len(detection_result) >= 2:
64
- conf = float(detection_result[1]) if detection_result[1] is not None else 0.0
65
- else:
66
- conf = 0.0
67
  openness = min(max((conf * 1.15), 0.0), 1.0)
68
  return openness
69
  except Exception:
@@ -75,51 +108,67 @@ async def read_root():
75
 
76
  @app.get("/health")
77
  async def health_check():
78
- return {"status": "healthy", "mtcnn_impl": _MTCNN_IMPL}
 
 
 
 
 
 
 
79
 
80
  @app.post("/api/v1/validate-eye-photo")
81
  async def validate_eye_photo(image: UploadFile = File(...)):
 
 
 
 
82
  if mtcnn is None:
83
- raise HTTPException(status_code=500, detail="No MTCNN implementation available in this environment.")
 
 
84
  try:
85
  content = await image.read()
86
  if not content:
87
  raise HTTPException(status_code=400, detail="Empty file uploaded.")
88
  pil_img = load_image_from_bytes(content)
89
- img_arr = np.asarray(pil_img)
90
 
91
- if _MTCNN_IMPL == "facenet_pytorch":
92
- boxes, probs, landmarks = mtcnn.detect(pil_img, landmarks=True)
93
- if boxes is None or len(boxes) == 0:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  return {
95
- "valid": False,
96
- "face_detected": False,
97
- "eye_openness_score": 0.0,
98
- "message_english": "No face detected. Please ensure your face is clearly visible in the frame.",
99
- "message_hindi": "कोई चेहरा नहीं मिला। कृपया सुनिश्चित करें कि आपका चेहरा फ्रेम में स्पष्ट रूप से दिखाई दे रहा है।"
100
- }
101
- prob = float(probs[0]) if probs is not None else 0.0
102
- lm = landmarks[0] if landmarks is not None else None
103
- if lm is not None and len(lm) >= 2:
104
- left_eye = {"x": float(lm[0][0]), "y": float(lm[0][1])}
105
- right_eye = {"x": float(lm[1][0]), "y": float(lm[1][1])}
106
- else:
107
- left_eye = right_eye = None
108
- eye_openness_score = estimate_eye_openness_from_detection((None, prob))
109
- is_valid = eye_openness_score >= 0.3
110
- return {
111
- "valid": bool(is_valid),
112
- "face_detected": True,
113
- "eye_openness_score": round(eye_openness_score, 2),
114
- "message_english": "Photo looks good! Eyes are properly open." if is_valid else "Eyes appear to be closed or partially closed. Please open your eyes wide and try again.",
115
- "message_hindi": "फोटो अच्छी है! आंखें ठीक से खुली हैं।" if is_valid else "आंखें बंद या आंशिक रूप से बंद दिखाई दे रही हैं। कृपया अपनी आंखें चौड़ी खोलें और पुनः प्रयास करें।",
116
- "eye_landmarks": {
117
- "left_eye": left_eye,
118
- "right_eye": right_eye
119
  }
120
- }
 
 
121
 
122
- elif _MTCNN_IMPL == "mtcnn":
 
123
  try:
124
  detections = mtcnn.detect_faces(img_arr)
125
  except Exception:
@@ -137,7 +186,7 @@ async def validate_eye_photo(image: UploadFile = File(...)):
137
  left_eye = keypoints.get("left_eye")
138
  right_eye = keypoints.get("right_eye")
139
  confidence = float(face.get("confidence", 0.0))
140
- eye_openness_score = estimate_eye_openness_from_detection({"confidence": confidence})
141
  is_valid = eye_openness_score >= 0.3
142
  return {
143
  "valid": bool(is_valid),
@@ -145,13 +194,53 @@ async def validate_eye_photo(image: UploadFile = File(...)):
145
  "eye_openness_score": round(eye_openness_score, 2),
146
  "message_english": "Photo looks good! Eyes are properly open." if is_valid else "Eyes appear to be closed or partially closed. Please open your eyes wide and try again.",
147
  "message_hindi": "फोटो अच्छी है! आंखें ठीक से खुली हैं।" if is_valid else "आंखें बंद या आंशिक रूप से बंद दिखाई दे रही हैं। कृपया अपनी आंखें चौड़ी खोलें और पुनः प्रयास करें।",
148
- "eye_landmarks": {
149
- "left_eye": left_eye,
150
- "right_eye": right_eye
151
- }
152
  }
153
- else:
154
- raise HTTPException(status_code=500, detail="No face detector available in this deployment.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  except HTTPException:
156
  raise
157
  except Exception as e:
@@ -246,10 +335,14 @@ async def process_screening(screening_id: str):
246
  raise RuntimeError("Eye image missing")
247
  face_img = Image.open(face_path).convert("RGB")
248
  eye_img = Image.open(eye_path).convert("RGB")
 
 
249
  face_detected = False
250
  face_confidence = 0.0
251
  left_eye_coord = right_eye_coord = None
252
- if mtcnn is not None:
 
 
253
  try:
254
  if _MTCNN_IMPL == "facenet_pytorch":
255
  boxes, probs, landmarks = mtcnn.detect(face_img, landmarks=True)
@@ -272,6 +365,28 @@ async def process_screening(screening_id: str):
272
  right_eye_coord = k.get("right_eye")
273
  except Exception:
274
  traceback.print_exc()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  face_quality_score = 0.85 if face_detected and face_confidence > 0.6 else 0.45
276
  quality_metrics = {
277
  "face_detected": face_detected,
@@ -282,9 +397,12 @@ async def process_screening(screening_id: str):
282
  "face_blur_estimate": int(np.var(np.asarray(face_img.convert("L"))))
283
  }
284
  screenings_db[screening_id]["quality_metrics"] = quality_metrics
 
 
285
  await asyncio.sleep(1)
286
  vlm_face_desc = "Patient appears to have normal facial tone; no severe jaundice visible."
287
  vlm_eye_desc = "Sclera shows mild yellowing."
 
288
  await asyncio.sleep(1)
289
  medical_insights = {
290
  "hemoglobin_estimate": 11.2,
@@ -293,8 +411,10 @@ async def process_screening(screening_id: str):
293
  "jaundice_indicators": ["mild scleral yellowing"],
294
  "confidence": 0.82
295
  }
 
296
  hem = medical_insights["hemoglobin_estimate"]
297
  bil = medical_insights["bilirubin_estimate"]
 
298
  ai_results = {
299
  "hemoglobin_g_dl": hem,
300
  "anemia_status": "Mild Anemia" if hem < 12 else "Normal",
@@ -308,6 +428,7 @@ async def process_screening(screening_id: str):
308
  "processing_time_ms": 1200
309
  }
310
  screenings_db[screening_id]["ai_results"] = ai_results
 
311
  disease_predictions = [
312
  {
313
  "condition": "Iron Deficiency Anemia",
@@ -322,16 +443,19 @@ async def process_screening(screening_id: str):
322
  "confidence": medical_insights["confidence"]
323
  }
324
  ]
 
325
  recommendations = {
326
  "action_needed": "consult" if hem < 12 else "monitor",
327
  "message_english": f"Your hemoglobin is {hem} g/dL. Please consult a doctor within 2 weeks for blood tests.",
328
  "message_hindi": f"आपका हीमोग्लोबिन {hem} g/dL है। कृपया 2 सप्ताह में डॉक्टर से परामर्श करें।"
329
  }
 
330
  screenings_db[screening_id].update({
331
  "status": "completed",
332
  "disease_predictions": disease_predictions,
333
  "recommendations": recommendations
334
  })
 
335
  print(f"[process_screening] Completed {screening_id}")
336
  except Exception as e:
337
  traceback.print_exc()
 
1
  # app.py
2
  """
3
  Elderly HealthWatch AI Backend (FastAPI)
4
+ This variant uses:
5
+ - facenet-pytorch or mtcnn if available
6
+ - otherwise falls back to OpenCV Haar cascades (fast, CPU-only, lightweight)
7
  """
8
 
9
  import io
 
17
  import numpy as np
18
  import os
19
  import traceback
20
+ import cv2 # opencv-python-headless expected installed
21
 
22
+ # Attempt to import facenet-pytorch MTCNN first (recommended)
23
  try:
24
  from facenet_pytorch import MTCNN as FacenetMTCNN
25
  _MTCNN_IMPL = "facenet_pytorch"
 
27
  FacenetMTCNN = None
28
  _MTCNN_IMPL = None
29
 
30
+ # Fallback to the classic mtcnn package
31
  if _MTCNN_IMPL is None:
32
  try:
33
  from mtcnn import MTCNN as ClassicMTCNN
 
35
  except Exception:
36
  ClassicMTCNN = None
37
 
38
+ # We'll create a fallback "opencv" detector if neither is present.
39
+ def create_mtcnn_or_fallback():
40
+ """
41
+ Return:
42
+ - facenet_pytorch.MTCNN instance if available
43
+ - classic mtcnn instance if available
44
+ - dict with OpenCV cascade detector if neither available
45
+ - None if something unexpected happened
46
+ """
47
  if _MTCNN_IMPL == "facenet_pytorch" and FacenetMTCNN is not None:
48
+ try:
49
+ return FacenetMTCNN(keep_all=False, device="cpu")
50
+ except Exception:
51
+ pass
52
+ if _MTCNN_IMPL == "mtcnn" and ClassicMTCNN is not None:
53
+ try:
54
+ return ClassicMTCNN()
55
+ except Exception:
56
+ pass
57
+
58
+ # OpenCV fallback: use Haar cascades (bundled with cv2)
59
+ try:
60
+ face_cascade_path = os.path.join(cv2.data.haarcascades, "haarcascade_frontalface_default.xml")
61
+ eye_cascade_path = os.path.join(cv2.data.haarcascades, "haarcascade_eye.xml")
62
+ if os.path.exists(face_cascade_path) and os.path.exists(eye_cascade_path):
63
+ face_cascade = cv2.CascadeClassifier(face_cascade_path)
64
+ eye_cascade = cv2.CascadeClassifier(eye_cascade_path)
65
+ return {"impl": "opencv", "face_cascade": face_cascade, "eye_cascade": eye_cascade}
66
+ except Exception:
67
+ pass
68
 
69
+ return None
70
+
71
+ mtcnn = create_mtcnn_or_fallback()
72
+ # mtcnn may now be:
73
+ # - FacenetMTCNN instance (facenet_pytorch)
74
+ # - ClassicMTCNN instance (mtcnn)
75
+ # - dict {"impl":"opencv", "face_cascade":..., "eye_cascade":...}
76
+ # - None
77
 
78
  app = FastAPI(title="Elderly HealthWatch AI Backend")
79
 
 
90
  def load_image_from_bytes(bytes_data: bytes) -> Image.Image:
91
  return Image.open(io.BytesIO(bytes_data)).convert("RGB")
92
 
93
+ def estimate_eye_openness_from_detection(confidence: float) -> float:
94
+ """
95
+ Simple mapping from detection confidence to an "eye_openness" heuristic in [0,1].
96
+ (Used by facenet/mtcnn flows)
97
+ """
98
  try:
99
+ conf = float(confidence)
 
 
 
 
 
100
  openness = min(max((conf * 1.15), 0.0), 1.0)
101
  return openness
102
  except Exception:
 
108
 
109
  @app.get("/health")
110
  async def health_check():
111
+ impl = None
112
+ if mtcnn is None:
113
+ impl = "none"
114
+ elif isinstance(mtcnn, dict) and mtcnn.get("impl") == "opencv":
115
+ impl = "opencv_haar_fallback"
116
+ else:
117
+ impl = _MTCNN_IMPL
118
+ return {"status": "healthy", "detector": impl}
119
 
120
  @app.post("/api/v1/validate-eye-photo")
121
  async def validate_eye_photo(image: UploadFile = File(...)):
122
+ """
123
+ Validate an eye photo: detects a face and returns eye_openness_score & landmarks.
124
+ Uses facenet/mtcnn if available; otherwise OpenCV haar cascades.
125
+ """
126
  if mtcnn is None:
127
+ # No detector at all
128
+ raise HTTPException(status_code=500, detail="No face detector available in this deployment.")
129
+
130
  try:
131
  content = await image.read()
132
  if not content:
133
  raise HTTPException(status_code=400, detail="Empty file uploaded.")
134
  pil_img = load_image_from_bytes(content)
135
+ img_arr = np.asarray(pil_img) # RGB
136
 
137
+ # facenet-pytorch branch
138
+ if not isinstance(mtcnn, dict) and _MTCNN_IMPL == "facenet_pytorch":
139
+ try:
140
+ boxes, probs, landmarks = mtcnn.detect(pil_img, landmarks=True)
141
+ if boxes is None or len(boxes) == 0:
142
+ return {
143
+ "valid": False,
144
+ "face_detected": False,
145
+ "eye_openness_score": 0.0,
146
+ "message_english": "No face detected. Please ensure your face is clearly visible in the frame.",
147
+ "message_hindi": "कोई चेहरा नहीं मिला। कृपया सुनिश्चित करें कि आपका चेहरा फ्रेम में स्पष्ट रूप से दिखाई दे रहा है।"
148
+ }
149
+ prob = float(probs[0]) if probs is not None else 0.0
150
+ lm = landmarks[0] if landmarks is not None else None
151
+ if lm is not None and len(lm) >= 2:
152
+ left_eye = {"x": float(lm[0][0]), "y": float(lm[0][1])}
153
+ right_eye = {"x": float(lm[1][0]), "y": float(lm[1][1])}
154
+ else:
155
+ left_eye = right_eye = None
156
+ eye_openness_score = estimate_eye_openness_from_detection(prob)
157
+ is_valid = eye_openness_score >= 0.3
158
  return {
159
+ "valid": bool(is_valid),
160
+ "face_detected": True,
161
+ "eye_openness_score": round(eye_openness_score, 2),
162
+ "message_english": "Photo looks good! Eyes are properly open." if is_valid else "Eyes appear to be closed or partially closed. Please open your eyes wide and try again.",
163
+ "message_hindi": "फोटो अच्छी है! आंखें ठीक से खुली हैं।" if is_valid else "आंखें बंद या आंशिक रूप से बंद दिखाई दे रही हैं। कृपया अपनी आंखें चौड़ी खोलें और पुनः प्रयास करें।",
164
+ "eye_landmarks": {"left_eye": left_eye, "right_eye": right_eye}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  }
166
+ except Exception:
167
+ traceback.print_exc()
168
+ raise HTTPException(status_code=500, detail="Face detector failed during inference.")
169
 
170
+ # classic mtcnn branch
171
+ if not isinstance(mtcnn, dict) and _MTCNN_IMPL == "mtcnn":
172
  try:
173
  detections = mtcnn.detect_faces(img_arr)
174
  except Exception:
 
186
  left_eye = keypoints.get("left_eye")
187
  right_eye = keypoints.get("right_eye")
188
  confidence = float(face.get("confidence", 0.0))
189
+ eye_openness_score = estimate_eye_openness_from_detection(confidence)
190
  is_valid = eye_openness_score >= 0.3
191
  return {
192
  "valid": bool(is_valid),
 
194
  "eye_openness_score": round(eye_openness_score, 2),
195
  "message_english": "Photo looks good! Eyes are properly open." if is_valid else "Eyes appear to be closed or partially closed. Please open your eyes wide and try again.",
196
  "message_hindi": "फोटो अच्छी है! आंखें ठीक से खुली हैं।" if is_valid else "आंखें बंद या आंशिक रूप से बंद दिखाई दे रही हैं। कृपया अपनी आंखें चौड़ी खोलें और पुनः प्रयास करें।",
197
+ "eye_landmarks": {"left_eye": left_eye, "right_eye": right_eye}
 
 
 
198
  }
199
+
200
+ # OpenCV Haar cascade fallback
201
+ if isinstance(mtcnn, dict) and mtcnn.get("impl") == "opencv":
202
+ try:
203
+ gray = cv2.cvtColor(img_arr, cv2.COLOR_RGB2GRAY)
204
+ face_cascade = mtcnn["face_cascade"]
205
+ eye_cascade = mtcnn["eye_cascade"]
206
+ faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=4, minSize=(60, 60))
207
+ if len(faces) == 0:
208
+ return {
209
+ "valid": False,
210
+ "face_detected": False,
211
+ "eye_openness_score": 0.0,
212
+ "message_english": "No face detected. Please ensure your face is clearly visible in the frame.",
213
+ "message_hindi": "कोई चेहरा नहीं मिला। कृपया सुनिश्चित करें कि आपका चेहरा फ्रेम में स्पष्ट रूप से दिखाई दे रहा है।"
214
+ }
215
+ # Use first face
216
+ (x, y, w, h) = faces[0]
217
+ roi_gray = gray[y:y+h, x:x+w]
218
+ eyes = eye_cascade.detectMultiScale(roi_gray, scaleFactor=1.1, minNeighbors=5, minSize=(20, 10))
219
+ # Heuristic openness: if eyes detected => open
220
+ eye_openness_score = 1.0 if len(eyes) >= 1 else 0.0
221
+ is_valid = eye_openness_score >= 0.3
222
+ # estimate coordinates relative to full image
223
+ left_eye = None
224
+ right_eye = None
225
+ if len(eyes) >= 1:
226
+ ex, ey, ew, eh = eyes[0]
227
+ # convert to image coords center
228
+ cx = float(x + ex + ew/2)
229
+ cy = float(y + ey + eh/2)
230
+ left_eye = {"x": cx, "y": cy}
231
+ return {
232
+ "valid": bool(is_valid),
233
+ "face_detected": True,
234
+ "eye_openness_score": round(eye_openness_score, 2),
235
+ "message_english": "Photo looks good! Eyes are detected." if is_valid else "Eyes not detected. Please open your eyes wide and try again.",
236
+ "message_hindi": "फोटो अच्छी है! आंखें मिलीं।" if is_valid else "आंखें नहीं मिलीं। कृपया अपनी आंखें चौड़ी खोलें और पुनः प्रयास करें।",
237
+ "eye_landmarks": {"left_eye": left_eye, "right_eye": right_eye}
238
+ }
239
+ except Exception:
240
+ traceback.print_exc()
241
+ raise HTTPException(status_code=500, detail="OpenCV fallback detector failed.")
242
+ # Should not reach here
243
+ raise HTTPException(status_code=500, detail="Invalid detector configuration.")
244
  except HTTPException:
245
  raise
246
  except Exception as e:
 
335
  raise RuntimeError("Eye image missing")
336
  face_img = Image.open(face_path).convert("RGB")
337
  eye_img = Image.open(eye_path).convert("RGB")
338
+
339
+ # Basic detection using whichever detector is available; populate quality_metrics
340
  face_detected = False
341
  face_confidence = 0.0
342
  left_eye_coord = right_eye_coord = None
343
+
344
+ # facenet/mtcnn path
345
+ if not isinstance(mtcnn, dict) and (_MTCNN_IMPL == "facenet_pytorch" or _MTCNN_IMPL == "mtcnn"):
346
  try:
347
  if _MTCNN_IMPL == "facenet_pytorch":
348
  boxes, probs, landmarks = mtcnn.detect(face_img, landmarks=True)
 
365
  right_eye_coord = k.get("right_eye")
366
  except Exception:
367
  traceback.print_exc()
368
+
369
+ # OpenCV fallback path
370
+ if isinstance(mtcnn, dict) and mtcnn.get("impl") == "opencv":
371
+ try:
372
+ arr = np.asarray(face_img)
373
+ gray = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY)
374
+ face_cascade = mtcnn["face_cascade"]
375
+ eye_cascade = mtcnn["eye_cascade"]
376
+ faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=4, minSize=(60, 60))
377
+ if len(faces) > 0:
378
+ face_detected = True
379
+ # crude confidence proxy by face size ratio
380
+ (x, y, w, h) = faces[0]
381
+ face_confidence = min(1.0, (w*h) / (arr.shape[0]*arr.shape[1]) * 4.0)
382
+ roi_gray = gray[y:y+h, x:x+w]
383
+ eyes = eye_cascade.detectMultiScale(roi_gray, scaleFactor=1.1, minNeighbors=5, minSize=(20, 10))
384
+ if len(eyes) >= 1:
385
+ ex, ey, ew, eh = eyes[0]
386
+ left_eye_coord = {"x": float(x + ex + ew/2), "y": float(y + ey + eh/2)}
387
+ except Exception:
388
+ traceback.print_exc()
389
+
390
  face_quality_score = 0.85 if face_detected and face_confidence > 0.6 else 0.45
391
  quality_metrics = {
392
  "face_detected": face_detected,
 
397
  "face_blur_estimate": int(np.var(np.asarray(face_img.convert("L"))))
398
  }
399
  screenings_db[screening_id]["quality_metrics"] = quality_metrics
400
+
401
+ # Simulate VLM/medical model steps (kept short)
402
  await asyncio.sleep(1)
403
  vlm_face_desc = "Patient appears to have normal facial tone; no severe jaundice visible."
404
  vlm_eye_desc = "Sclera shows mild yellowing."
405
+
406
  await asyncio.sleep(1)
407
  medical_insights = {
408
  "hemoglobin_estimate": 11.2,
 
411
  "jaundice_indicators": ["mild scleral yellowing"],
412
  "confidence": 0.82
413
  }
414
+
415
  hem = medical_insights["hemoglobin_estimate"]
416
  bil = medical_insights["bilirubin_estimate"]
417
+
418
  ai_results = {
419
  "hemoglobin_g_dl": hem,
420
  "anemia_status": "Mild Anemia" if hem < 12 else "Normal",
 
428
  "processing_time_ms": 1200
429
  }
430
  screenings_db[screening_id]["ai_results"] = ai_results
431
+
432
  disease_predictions = [
433
  {
434
  "condition": "Iron Deficiency Anemia",
 
443
  "confidence": medical_insights["confidence"]
444
  }
445
  ]
446
+
447
  recommendations = {
448
  "action_needed": "consult" if hem < 12 else "monitor",
449
  "message_english": f"Your hemoglobin is {hem} g/dL. Please consult a doctor within 2 weeks for blood tests.",
450
  "message_hindi": f"आपका हीमोग्लोबिन {hem} g/dL है। कृपया 2 सप्ताह में डॉक्टर से परामर्श करें।"
451
  }
452
+
453
  screenings_db[screening_id].update({
454
  "status": "completed",
455
  "disease_predictions": disease_predictions,
456
  "recommendations": recommendations
457
  })
458
+
459
  print(f"[process_screening] Completed {screening_id}")
460
  except Exception as e:
461
  traceback.print_exc()