SmartHeal commited on
Commit
990c773
·
verified ·
1 Parent(s): c9c6cf5

Update src/database.py

Browse files
Files changed (1) hide show
  1. src/database.py +94 -48
src/database.py CHANGED
@@ -1,10 +1,10 @@
1
- # database.py (SmartHeal) — dataset ID hardcoded
2
 
3
  import os
4
  import json
5
  import logging
6
  from datetime import datetime
7
- from typing import Optional, Dict, Any, List
8
 
9
  import mysql.connector
10
  from mysql.connector import Error
@@ -52,8 +52,8 @@ def _file_url(path: str) -> str:
52
  class DatabaseManager:
53
  """
54
  Database operations manager for SmartHeal
55
- - Keeps patient_id (INT) consistent for questionnaire_responses / ai_analyses joins
56
- - Uses patients.uuid (CHAR 36) in wounds / wound_images where those tables store VARCHAR
57
  - Saves images locally OR to a hardcoded Hugging Face dataset and stores the URL
58
  """
59
 
@@ -171,6 +171,12 @@ class DatabaseManager:
171
  (patient_id,)
172
  )
173
 
 
 
 
 
 
 
174
  def get_patient_by_name_age_gender(self, name: str, age: Any, gender: str) -> Optional[Dict[str, Any]]:
175
  age_val = self._normalize_age(age)
176
  if age_val is None:
@@ -185,6 +191,30 @@ class DatabaseManager:
185
  (name, age_val, gender),
186
  )
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  def create_patient(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
189
  conn = None
190
  cur = None
@@ -195,8 +225,8 @@ class DatabaseManager:
195
  import uuid as _uuid
196
  p_uuid = str(_uuid.uuid4())
197
  cur.execute("""
198
- INSERT INTO patients (uuid, name, age, gender, illness, allergy, notes, created_at)
199
- VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
200
  """, (
201
  p_uuid,
202
  data.get("patient_name"),
@@ -205,6 +235,7 @@ class DatabaseManager:
205
  data.get("medical_history", ""),
206
  data.get("allergies", ""),
207
  data.get("additional_notes", ""),
 
208
  _now()
209
  ))
210
  conn.commit()
@@ -333,27 +364,36 @@ class DatabaseManager:
333
  pass
334
 
335
  # ------------------------------- wounds -------------------------------
336
- def create_wound(self, patient_uuid: str, questionnaire_data: Dict[str, Any]) -> Optional[str]:
337
- """Create wound (returns wound_uuid). Stores patient_uuid in wounds.patient_id (VARCHAR)."""
 
 
 
338
  conn = None
339
  cur = None
340
  try:
 
 
 
 
 
341
  conn = self.get_connection()
342
  if not conn: return None
343
  cur = conn.cursor()
344
  import uuid as _uuid
345
  wound_uuid = str(_uuid.uuid4())
346
  cur.execute("""
347
- INSERT INTO wounds (uuid, patient_id, position, category, moisture, infection, notes, created_at)
348
- VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
349
  """, (
350
  wound_uuid,
351
- patient_uuid,
352
  questionnaire_data.get("wound_location") or "",
353
  "Assessment",
354
  questionnaire_data.get("moisture_level") or "",
355
  questionnaire_data.get("infection_signs") or "",
356
  questionnaire_data.get("additional_notes") or "",
 
357
  _now()
358
  ))
359
  conn.commit()
@@ -409,24 +449,28 @@ class DatabaseManager:
409
  logging.error(f"HF upload failed: {e}")
410
  return None
411
 
412
- def _insert_wound_image_row(self, conn, cur, patient_uuid: str, wound_uuid: Optional[str], image_path_or_url: str) -> Optional[int]:
413
- """Insert one row in wound_images; width/height detected for local files only."""
 
 
 
414
  width, height = (None, None)
415
- if not (image_path_or_url.startswith("http://") or image_path_or_url.startswith("https://")):
416
  width, height = self._read_image_size(image_path_or_url)
417
 
418
  import uuid as _uuid
419
  img_uuid = str(_uuid.uuid4())
420
  cur.execute("""
421
- INSERT INTO wound_images (uuid, patient_id, wound_id, image, width, height, created_at)
422
- VALUES (%s,%s,%s,%s,%s,%s,%s)
423
  """, (
424
  img_uuid,
425
- patient_uuid,
426
  wound_uuid,
427
  image_path_or_url,
428
  int(width) if width else None,
429
  int(height) if height else None,
 
430
  _now()
431
  ))
432
  conn.commit()
@@ -434,22 +478,32 @@ class DatabaseManager:
434
 
435
  def save_wound_images_bundle(
436
  self,
437
- patient_uuid: str,
438
  original_path: str,
439
  analysis_result: Dict[str, Any],
440
  wound_uuid: Optional[str] = None
441
  ) -> Dict[str, Any]:
442
  """
443
  Save original + detection + segmentation images to wound_images.
444
- Stores **HF dataset URLs** when configured, else local paths.
 
 
445
  Returns display URLs for UI.
446
  """
447
  out = {
448
  "original_id": None, "original_url": None,
449
  "detection_id": None, "detection_url": None,
450
- "segmentation_id": None, "segmentation_url": None
 
451
  }
452
 
 
 
 
 
 
 
 
453
  conn = self.get_connection()
454
  if not conn: return out
455
  cur = conn.cursor()
@@ -460,16 +514,17 @@ class DatabaseManager:
460
  import uuid as _uuid
461
  wound_uuid = str(_uuid.uuid4())
462
  cur.execute("""
463
- INSERT INTO wounds (uuid, patient_id, category, notes, created_at)
464
- VALUES (%s,%s,%s,%s,%s)
465
- """, (wound_uuid, patient_uuid, "Assessment", "", _now()))
466
  conn.commit()
 
467
 
468
  # 1) Original
469
  local_orig = self._copy_to_uploads(original_path, "original")
470
  url_orig = self._hf_upload_and_url(local_orig, subdir="original") if self.use_dataset else None
471
  store_orig = url_orig or local_orig
472
- img_id = self._insert_wound_image_row(conn, cur, patient_uuid, wound_uuid, store_orig)
473
  out["original_id"] = img_id
474
  out["original_url"] = _file_url(store_orig)
475
 
@@ -482,7 +537,7 @@ class DatabaseManager:
482
  local_det = self._copy_to_uploads(det, "detect")
483
  url_det = self._hf_upload_and_url(local_det, subdir="detect") if self.use_dataset else None
484
  store_det = url_det or local_det
485
- did = self._insert_wound_image_row(conn, cur, patient_uuid, wound_uuid, store_det)
486
  out["detection_id"] = did
487
  out["detection_url"] = _file_url(store_det)
488
 
@@ -490,7 +545,7 @@ class DatabaseManager:
490
  local_seg = self._copy_to_uploads(seg, "segment")
491
  url_seg = self._hf_upload_and_url(local_seg, subdir="segment") if self.use_dataset else None
492
  store_seg = url_seg or local_seg
493
- sid = self._insert_wound_image_row(conn, cur, patient_uuid, wound_uuid, store_seg)
494
  out["segmentation_id"] = sid
495
  out["segmentation_url"] = _file_url(store_seg)
496
 
@@ -515,7 +570,7 @@ class DatabaseManager:
515
  Back-compat single-image save:
516
  - If `image` is a PIL.Image -> we write to uploads and (optionally) the dataset.
517
  - If `image` is a filepath -> we copy + (optionally) upload.
518
- Stores patients.uuid into wound_images.patient_id (VARCHAR).
519
  """
520
  try:
521
  # Normalize to a temporary local path first
@@ -533,38 +588,28 @@ class DatabaseManager:
533
  logging.error("Invalid image object/path")
534
  return None
535
 
536
- # Resolve patients.uuid
537
- conn = self.get_connection()
538
- if not conn: return None
539
- cur = conn.cursor()
540
-
541
- cur2 = conn.cursor(dictionary=True)
542
- cur2.execute("SELECT uuid FROM patients WHERE id = %s LIMIT 1", (patient_id,))
543
- row = cur2.fetchone()
544
- cur2.close()
545
- if not row:
546
- logging.error("Patient id not found while saving image")
547
- conn.close()
548
- return None
549
- patient_uuid = row["uuid"]
550
-
551
  # Push to uploads/dataset with final name
552
  local_path = self._copy_to_uploads(tmp_local, "original")
553
  url = self._hf_upload_and_url(local_path, subdir="original") if self.use_dataset else None
554
  path_or_url = url or local_path
555
 
556
  width, height = self._read_image_size(local_path)
 
 
 
 
557
  import uuid as _uuid
558
  img_uuid = str(_uuid.uuid4())
559
  cur.execute("""
560
- INSERT INTO wound_images (uuid, patient_id, image, width, height, created_at)
561
- VALUES (%s,%s,%s,%s,%s,%s)
562
  """, (
563
  img_uuid,
564
- patient_uuid,
565
  path_or_url,
566
  int(width) if width else None,
567
  int(height) if height else None,
 
568
  _now()
569
  ))
570
  conn.commit()
@@ -647,7 +692,8 @@ class DatabaseManager:
647
  # ------------------------------- queries for UI -------------------------------
648
  def get_user_history_rows(self, user_id: int) -> List[Dict[str, Any]]:
649
  """
650
- Latest 20 visits across patients; joins ai_analyses.image_id -> wound_images to pull display image.
 
651
  """
652
  return self.execute_query("""
653
  SELECT
@@ -666,7 +712,7 @@ class DatabaseManager:
666
  wi.image AS image_url
667
  FROM questionnaire_responses qr
668
  JOIN patients p ON p.id = qr.patient_id
669
- LEFT JOIN ai_analyses a ON a.questionnaire_id = qr.id
670
  LEFT JOIN wound_images wi ON wi.id = a.image_id
671
  WHERE qr.practitioner_id = %s
672
  ORDER BY qr.submitted_at DESC
@@ -694,7 +740,7 @@ class DatabaseManager:
694
  wi.image AS image_url
695
  FROM questionnaire_responses qr
696
  JOIN patients p ON p.id = qr.patient_id
697
- LEFT JOIN ai_analyses a ON a.questionnaire_id = qr.id
698
  LEFT JOIN wound_images wi ON wi.id = a.image_id
699
  WHERE qr.practitioner_id = %s AND p.id = %s
700
  ORDER BY qr.submitted_at ASC
 
1
+ # database.py (SmartHeal) — dataset ID hardcoded, wound_images.patient_id uses INT id
2
 
3
  import os
4
  import json
5
  import logging
6
  from datetime import datetime
7
+ from typing import Optional, Dict, Any, List, Tuple, Union
8
 
9
  import mysql.connector
10
  from mysql.connector import Error
 
52
  class DatabaseManager:
53
  """
54
  Database operations manager for SmartHeal
55
+ - Keeps patient_id (INT) consistent for questionnaire_responses / joins
56
+ - Uses patients.id (INT) in wounds.patient_id and wound_images.patient_id (stored as string in VARCHAR columns)
57
  - Saves images locally OR to a hardcoded Hugging Face dataset and stores the URL
58
  """
59
 
 
171
  (patient_id,)
172
  )
173
 
174
+ def get_patient_by_uuid(self, patient_uuid: str) -> Optional[Dict[str, Any]]:
175
+ return self.execute_query_one(
176
+ "SELECT id, uuid, name, age, gender FROM patients WHERE uuid = %s LIMIT 1",
177
+ (patient_uuid,)
178
+ )
179
+
180
  def get_patient_by_name_age_gender(self, name: str, age: Any, gender: str) -> Optional[Dict[str, Any]]:
181
  age_val = self._normalize_age(age)
182
  if age_val is None:
 
191
  (name, age_val, gender),
192
  )
193
 
194
+ def _resolve_patient_ids(
195
+ self,
196
+ patient_ref: Union[int, str]
197
+ ) -> Optional[Tuple[int, str]]:
198
+ """
199
+ Accepts either a numeric ID or a UUID and returns (id_int, uuid_str).
200
+ """
201
+ try:
202
+ # numeric ID?
203
+ if isinstance(patient_ref, int) or (isinstance(patient_ref, str) and patient_ref.isdigit()):
204
+ rid = int(patient_ref)
205
+ row = self.get_patient_by_id(rid)
206
+ if row:
207
+ return (row["id"], row["uuid"])
208
+ return None
209
+ # UUID path
210
+ row = self.get_patient_by_uuid(str(patient_ref))
211
+ if row:
212
+ return (row["id"], row["uuid"])
213
+ return None
214
+ except Exception as e:
215
+ logging.error(f"_resolve_patient_ids error: {e}")
216
+ return None
217
+
218
  def create_patient(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
219
  conn = None
220
  cur = None
 
225
  import uuid as _uuid
226
  p_uuid = str(_uuid.uuid4())
227
  cur.execute("""
228
+ INSERT INTO patients (uuid, name, age, gender, illness, allergy, notes, created_at, updated_at)
229
+ VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)
230
  """, (
231
  p_uuid,
232
  data.get("patient_name"),
 
235
  data.get("medical_history", ""),
236
  data.get("allergies", ""),
237
  data.get("additional_notes", ""),
238
+ _now(),
239
  _now()
240
  ))
241
  conn.commit()
 
364
  pass
365
 
366
  # ------------------------------- wounds -------------------------------
367
+ def create_wound(self, patient_ref: Union[int, str], questionnaire_data: Dict[str, Any]) -> Optional[str]:
368
+ """
369
+ Create wound (returns wound_uuid).
370
+ Stores **patients.id** (as string) in wounds.patient_id.
371
+ """
372
  conn = None
373
  cur = None
374
  try:
375
+ resolved = self._resolve_patient_ids(patient_ref)
376
+ if not resolved:
377
+ raise Exception("Could not resolve patient by id/uuid")
378
+ patient_id_int, _patient_uuid = resolved
379
+
380
  conn = self.get_connection()
381
  if not conn: return None
382
  cur = conn.cursor()
383
  import uuid as _uuid
384
  wound_uuid = str(_uuid.uuid4())
385
  cur.execute("""
386
+ INSERT INTO wounds (uuid, patient_id, position, category, moisture, infection, notes, created_at, updated_at)
387
+ VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)
388
  """, (
389
  wound_uuid,
390
+ str(patient_id_int), # <-- store INT id as string
391
  questionnaire_data.get("wound_location") or "",
392
  "Assessment",
393
  questionnaire_data.get("moisture_level") or "",
394
  questionnaire_data.get("infection_signs") or "",
395
  questionnaire_data.get("additional_notes") or "",
396
+ _now(),
397
  _now()
398
  ))
399
  conn.commit()
 
449
  logging.error(f"HF upload failed: {e}")
450
  return None
451
 
452
+ def _insert_wound_image_row(self, conn, cur, patient_id_str: str, wound_uuid: Optional[str], image_path_or_url: str) -> Optional[int]:
453
+ """
454
+ Insert one row in wound_images.
455
+ Stores **patients.id** as string in wound_images.patient_id.
456
+ """
457
  width, height = (None, None)
458
+ if not (str(image_path_or_url).startswith("http://") or str(image_path_or_url).startswith("https://")):
459
  width, height = self._read_image_size(image_path_or_url)
460
 
461
  import uuid as _uuid
462
  img_uuid = str(_uuid.uuid4())
463
  cur.execute("""
464
+ INSERT INTO wound_images (uuid, patient_id, wound_id, image, width, height, created_at, updated_at)
465
+ VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
466
  """, (
467
  img_uuid,
468
+ patient_id_str, # <-- store INT id (as string)
469
  wound_uuid,
470
  image_path_or_url,
471
  int(width) if width else None,
472
  int(height) if height else None,
473
+ _now(),
474
  _now()
475
  ))
476
  conn.commit()
 
478
 
479
  def save_wound_images_bundle(
480
  self,
481
+ patient_ref: Union[int, str],
482
  original_path: str,
483
  analysis_result: Dict[str, Any],
484
  wound_uuid: Optional[str] = None
485
  ) -> Dict[str, Any]:
486
  """
487
  Save original + detection + segmentation images to wound_images.
488
+ - Accepts patient_ref as INT id or UUID
489
+ - Stores **patient_id (INT as string)** in wound_images.patient_id
490
+ - Ensures a wound row exists (wound_uuid) if not provided
491
  Returns display URLs for UI.
492
  """
493
  out = {
494
  "original_id": None, "original_url": None,
495
  "detection_id": None, "detection_url": None,
496
+ "segmentation_id": None, "segmentation_url": None,
497
+ "wound_id": None
498
  }
499
 
500
+ resolved = self._resolve_patient_ids(patient_ref)
501
+ if not resolved:
502
+ logging.error("save_wound_images_bundle: could not resolve patient")
503
+ return out
504
+ patient_id_int, _patient_uuid = resolved
505
+ patient_id_str = str(patient_id_int)
506
+
507
  conn = self.get_connection()
508
  if not conn: return out
509
  cur = conn.cursor()
 
514
  import uuid as _uuid
515
  wound_uuid = str(_uuid.uuid4())
516
  cur.execute("""
517
+ INSERT INTO wounds (uuid, patient_id, category, notes, created_at, updated_at)
518
+ VALUES (%s,%s,%s,%s,%s,%s)
519
+ """, (wound_uuid, patient_id_str, "Assessment", "", _now(), _now()))
520
  conn.commit()
521
+ out["wound_id"] = wound_uuid
522
 
523
  # 1) Original
524
  local_orig = self._copy_to_uploads(original_path, "original")
525
  url_orig = self._hf_upload_and_url(local_orig, subdir="original") if self.use_dataset else None
526
  store_orig = url_orig or local_orig
527
+ img_id = self._insert_wound_image_row(conn, cur, patient_id_str, wound_uuid, store_orig)
528
  out["original_id"] = img_id
529
  out["original_url"] = _file_url(store_orig)
530
 
 
537
  local_det = self._copy_to_uploads(det, "detect")
538
  url_det = self._hf_upload_and_url(local_det, subdir="detect") if self.use_dataset else None
539
  store_det = url_det or local_det
540
+ did = self._insert_wound_image_row(conn, cur, patient_id_str, wound_uuid, store_det)
541
  out["detection_id"] = did
542
  out["detection_url"] = _file_url(store_det)
543
 
 
545
  local_seg = self._copy_to_uploads(seg, "segment")
546
  url_seg = self._hf_upload_and_url(local_seg, subdir="segment") if self.use_dataset else None
547
  store_seg = url_seg or local_seg
548
+ sid = self._insert_wound_image_row(conn, cur, patient_id_str, wound_uuid, store_seg)
549
  out["segmentation_id"] = sid
550
  out["segmentation_url"] = _file_url(store_seg)
551
 
 
570
  Back-compat single-image save:
571
  - If `image` is a PIL.Image -> we write to uploads and (optionally) the dataset.
572
  - If `image` is a filepath -> we copy + (optionally) upload.
573
+ Stores **patients.id** (as string) into wound_images.patient_id.
574
  """
575
  try:
576
  # Normalize to a temporary local path first
 
588
  logging.error("Invalid image object/path")
589
  return None
590
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
  # Push to uploads/dataset with final name
592
  local_path = self._copy_to_uploads(tmp_local, "original")
593
  url = self._hf_upload_and_url(local_path, subdir="original") if self.use_dataset else None
594
  path_or_url = url or local_path
595
 
596
  width, height = self._read_image_size(local_path)
597
+
598
+ conn = self.get_connection()
599
+ if not conn: return None
600
+ cur = conn.cursor()
601
  import uuid as _uuid
602
  img_uuid = str(_uuid.uuid4())
603
  cur.execute("""
604
+ INSERT INTO wound_images (uuid, patient_id, image, width, height, created_at, updated_at)
605
+ VALUES (%s,%s,%s,%s,%s,%s,%s)
606
  """, (
607
  img_uuid,
608
+ str(int(patient_id)), # <-- store INT id as string
609
  path_or_url,
610
  int(width) if width else None,
611
  int(height) if height else None,
612
+ _now(),
613
  _now()
614
  ))
615
  conn.commit()
 
692
  # ------------------------------- queries for UI -------------------------------
693
  def get_user_history_rows(self, user_id: int) -> List[Dict[str, Any]]:
694
  """
695
+ Latest 20 visits across patients; joins ai_analyses (by questionnaire_id -> questionnaires.id)
696
+ and pulls image via a.image_id -> wound_images.id.
697
  """
698
  return self.execute_query("""
699
  SELECT
 
712
  wi.image AS image_url
713
  FROM questionnaire_responses qr
714
  JOIN patients p ON p.id = qr.patient_id
715
+ LEFT JOIN ai_analyses a ON a.questionnaire_id = qr.questionnaire_id
716
  LEFT JOIN wound_images wi ON wi.id = a.image_id
717
  WHERE qr.practitioner_id = %s
718
  ORDER BY qr.submitted_at DESC
 
740
  wi.image AS image_url
741
  FROM questionnaire_responses qr
742
  JOIN patients p ON p.id = qr.patient_id
743
+ LEFT JOIN ai_analyses a ON a.questionnaire_id = qr.questionnaire_id
744
  LEFT JOIN wound_images wi ON wi.id = a.image_id
745
  WHERE qr.practitioner_id = %s AND p.id = %s
746
  ORDER BY qr.submitted_at ASC