endoreg-db 0.8.5.1__py3-none-any.whl → 0.8.5.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of endoreg-db might be problematic. Click here for more details.

@@ -80,7 +80,9 @@ def parse_any_date(s: str) -> Optional[date]:
80
80
  # Try dateparser with German locale preference
81
81
  import dateparser
82
82
 
83
- dt = dateparser.parse(s, settings={"DATE_ORDER": "DMY", "PREFER_DAY_OF_MONTH": "first"})
83
+ dt = dateparser.parse(
84
+ s, settings={"DATE_ORDER": "DMY", "PREFER_DAY_OF_MONTH": "first"}
85
+ )
84
86
  return dt.date() if dt else None
85
87
  except Exception as e:
86
88
  logger.debug(f"Dateparser fallback failed for '{s}': {e}")
@@ -162,7 +164,7 @@ def calculate_patient_hash(instance: "SensitiveMeta", salt: str = SECRET_SALT) -
162
164
 
163
165
  assert first_name is not None, "First name is required to calculate patient hash."
164
166
  assert last_name is not None, "Last name is required to calculate patient hash."
165
-
167
+
166
168
  hash_str = get_patient_hash(
167
169
  first_name=first_name,
168
170
  last_name=last_name,
@@ -173,7 +175,9 @@ def calculate_patient_hash(instance: "SensitiveMeta", salt: str = SECRET_SALT) -
173
175
  return sha256(hash_str.encode()).hexdigest()
174
176
 
175
177
 
176
- def calculate_examination_hash(instance: "SensitiveMeta", salt: str = SECRET_SALT) -> str:
178
+ def calculate_examination_hash(
179
+ instance: "SensitiveMeta", salt: str = SECRET_SALT
180
+ ) -> str:
177
181
  """Calculates the examination hash for the instance."""
178
182
  dob = instance.patient_dob
179
183
  first_name = instance.patient_first_name
@@ -206,17 +210,25 @@ def create_pseudo_examiner_logic(instance: "SensitiveMeta") -> "Examiner":
206
210
  center = instance.center # Should be set before calling save
207
211
 
208
212
  if not first_name or not last_name or not center:
209
- logger.warning(f"Incomplete examiner info for SensitiveMeta (pk={instance.pk or 'new'}). Using default examiner.")
213
+ logger.warning(
214
+ f"Incomplete examiner info for SensitiveMeta (pk={instance.pk or 'new'}). Using default examiner."
215
+ )
210
216
  # Ensure default center exists or handle appropriately
211
217
  try:
212
- default_center = Center.objects.get_by_natural_key("endoreg_db_demo")
218
+ default_center = Center.objects.get(name="endoreg_db_demo")
213
219
  except Center.DoesNotExist:
214
- logger.error("Default center 'endoreg_db_demo' not found. Cannot create default examiner.")
220
+ logger.error(
221
+ "Default center 'endoreg_db_demo' not found. Cannot create default examiner."
222
+ )
215
223
  raise ValueError("Default center 'endoreg_db_demo' not found.")
216
224
 
217
- examiner, _created = Examiner.custom_get_or_create(first_name="Unknown", last_name="Unknown", center=default_center)
225
+ examiner, _created = Examiner.custom_get_or_create(
226
+ first_name="Unknown", last_name="Unknown", center=default_center
227
+ )
218
228
  else:
219
- examiner, _created = Examiner.custom_get_or_create(first_name=first_name, last_name=last_name, center=center)
229
+ examiner, _created = Examiner.custom_get_or_create(
230
+ first_name=first_name, last_name=last_name, center=center
231
+ )
220
232
 
221
233
  return examiner
222
234
 
@@ -262,11 +274,13 @@ def get_or_create_pseudo_patient_examination_logic(
262
274
  pseudo_patient = get_or_create_pseudo_patient_logic(instance)
263
275
  instance.pseudo_patient_id = pseudo_patient.pk # Assign FK directly
264
276
 
265
- patient_examination, _created = PatientExamination.get_or_create_pseudo_patient_examination_by_hash(
266
- patient_hash=instance.patient_hash,
267
- examination_hash=instance.examination_hash,
268
- # Optionally pass pseudo_patient if the method requires it
269
- # pseudo_patient=instance.pseudo_patient
277
+ patient_examination, _created = (
278
+ PatientExamination.get_or_create_pseudo_patient_examination_by_hash(
279
+ patient_hash=instance.patient_hash,
280
+ examination_hash=instance.examination_hash,
281
+ # Optionally pass pseudo_patient if the method requires it
282
+ # pseudo_patient=instance.pseudo_patient
283
+ )
270
284
  )
271
285
  return patient_examination
272
286
 
@@ -276,33 +290,137 @@ def perform_save_logic(instance: "SensitiveMeta") -> "Examiner":
276
290
  """
277
291
  Contains the core logic for preparing a SensitiveMeta instance for saving.
278
292
  Handles data generation (dates), hash calculation, and linking pseudo-entities.
279
- Returns the Examiner instance to be linked via M2M after the main save.
293
+
294
+ This function is called on every save() operation and implements a two-phase approach:
295
+
296
+ **Phase 1: Initial Creation (with defaults)**
297
+ - When a SensitiveMeta is first created (e.g., via get_or_create_sensitive_meta()),
298
+ it may have missing patient data (names, DOB, etc.)
299
+ - Default values are set to prevent hash calculation errors:
300
+ * patient_first_name: "unknown"
301
+ * patient_last_name: "unknown"
302
+ * patient_dob: random date (1920-2000)
303
+ - A temporary hash is calculated using these defaults
304
+ - Temporary pseudo-entities (Patient, Examination) are created
305
+
306
+ **Phase 2: Update (with extracted data)**
307
+ - When real patient data is extracted (e.g., from video OCR via lx_anonymizer),
308
+ update_from_dict() is called with actual values
309
+ - The instance fields are updated with real data (names, DOB, etc.)
310
+ - save() is called again, triggering this function
311
+ - Default-setting logic is skipped (fields are no longer empty)
312
+ - Hash is RECALCULATED with real data
313
+ - New pseudo-entities are created/retrieved based on new hash
314
+
315
+ **Example Flow:**
316
+ ```
317
+ # Initial creation
318
+ sm = SensitiveMeta.create_from_dict({"center": center})
319
+ # → patient_first_name = "unknown", patient_last_name = "unknown"
320
+ # → hash = sha256("unknown unknown 1990-01-01 ...")
321
+ # → pseudo_patient_temp created
322
+
323
+ # Later update with extracted data
324
+ sm.update_from_dict({"patient_first_name": "Max", "patient_last_name": "Mustermann"})
325
+ # → patient_first_name = "Max", patient_last_name = "Mustermann" (overwrites)
326
+ # → save() triggered → perform_save_logic() called again
327
+ # → Default-setting skipped (names already exist)
328
+ # → hash = sha256("Max Mustermann 1985-03-15 ...") (RECALCULATED)
329
+ # → pseudo_patient_real created/retrieved with new hash
330
+ ```
331
+
332
+ Args:
333
+ instance: The SensitiveMeta instance being saved
334
+
335
+ Returns:
336
+ Examiner: The pseudo examiner instance to be linked via M2M after save
337
+
338
+ Raises:
339
+ ValueError: If required fields (center, gender) cannot be determined
280
340
  """
281
341
 
282
342
  # --- Pre-Save Checks and Data Generation ---
283
343
 
284
344
  # 1. Ensure DOB and Examination Date exist
285
345
  if not instance.patient_dob:
286
- logger.debug(f"SensitiveMeta (pk={instance.pk or 'new'}): Patient DOB missing, generating random.")
346
+ logger.debug(
347
+ f"SensitiveMeta (pk={instance.pk or 'new'}): Patient DOB missing, generating random."
348
+ )
287
349
  instance.patient_dob = generate_random_dob()
288
350
  if not instance.examination_date:
289
- logger.debug(f"SensitiveMeta (pk={instance.pk or 'new'}): Examination date missing, generating random.")
351
+ logger.debug(
352
+ f"SensitiveMeta (pk={instance.pk or 'new'}): Examination date missing, generating random."
353
+ )
290
354
  instance.examination_date = generate_random_examination_date()
291
355
 
292
356
  # 2. Ensure Center exists (should be set before calling save)
293
357
  if not instance.center:
294
358
  raise ValueError("Center must be set before saving SensitiveMeta.")
295
359
 
360
+ # 2.5 CRITICAL: Set default patient names BEFORE hash calculation
361
+ #
362
+ # **Why this is necessary:**
363
+ # Hash calculation (step 4) requires first_name and last_name to be non-None.
364
+ # However, on initial creation (e.g., via get_or_create_sensitive_meta()), these
365
+ # fields may be empty because real patient data hasn't been extracted yet.
366
+ #
367
+ # **Two-phase approach:**
368
+ # - Phase 1 (Initial): Set defaults if names are missing
369
+ # → Allows hash calculation to succeed without errors
370
+ # → Creates temporary pseudo-entities with default hash
371
+ #
372
+ # - Phase 2 (Update): Real data extraction (OCR, manual input)
373
+ # → update_from_dict() sets real names ("Max", "Mustermann")
374
+ # → save() is called again
375
+ # → This block is SKIPPED (names already exist)
376
+ # → Hash is recalculated with real data (step 4)
377
+ # → New pseudo-entities created with correct hash
378
+ #
379
+ # **Example:**
380
+ # Initial: patient_first_name = "unknown" → hash = sha256("unknown unknown...")
381
+ # Updated: patient_first_name = "Max" → hash = sha256("Max Mustermann...")
382
+ #
383
+ if not instance.patient_first_name:
384
+ instance.patient_first_name = DEFAULT_UNKNOWN_NAME
385
+ logger.debug(
386
+ "SensitiveMeta (pk=%s): Patient first name missing, set to default '%s'.",
387
+ instance.pk or "new",
388
+ DEFAULT_UNKNOWN_NAME,
389
+ )
390
+
391
+ if not instance.patient_last_name:
392
+ instance.patient_last_name = DEFAULT_UNKNOWN_NAME
393
+ logger.debug(
394
+ "SensitiveMeta (pk=%s): Patient last name missing, set to default '%s'.",
395
+ instance.pk or "new",
396
+ DEFAULT_UNKNOWN_NAME,
397
+ )
398
+
296
399
  # 3. Ensure Gender exists (should be set before calling save, e.g., during creation/update)
297
400
  if not instance.patient_gender:
298
- # Attempt to guess if names are available
299
- first_name = instance.patient_first_name or DEFAULT_UNKNOWN_NAME
300
- gender = guess_name_gender(first_name)
301
- if not gender:
302
- raise ValueError("Patient gender could not be determined and must be set before saving.")
303
- instance.patient_gender = gender
401
+ # Use the now-guaranteed first_name for gender guessing
402
+ first_name = instance.patient_first_name
403
+ gender_str = guess_name_gender(first_name)
404
+ if not gender_str:
405
+ raise ValueError(
406
+ "Patient gender could not be determined and must be set before saving."
407
+ )
408
+ # Convert string to Gender object
409
+ try:
410
+ gender_obj = Gender.objects.get(name=gender_str)
411
+ instance.patient_gender = gender_obj
412
+ except Gender.DoesNotExist:
413
+ raise ValueError(f"Gender '{gender_str}' not found in database.")
304
414
 
305
415
  # 4. Calculate Hashes (depends on DOB, Exam Date, Center, Names)
416
+ #
417
+ # **IMPORTANT: Hashes are RECALCULATED on every save!**
418
+ # This enables the two-phase update pattern:
419
+ # - Initial save: Hash based on default "unknown unknown" names
420
+ # - Updated save: Hash based on real extracted names ("Max Mustermann")
421
+ #
422
+ # The new hash will link to different pseudo-entities, ensuring proper
423
+ # anonymization while maintaining referential integrity.
306
424
  instance.patient_hash = calculate_patient_hash(instance)
307
425
  instance.examination_hash = calculate_examination_hash(instance)
308
426
 
@@ -327,10 +445,59 @@ def perform_save_logic(instance: "SensitiveMeta") -> "Examiner":
327
445
  return examiner_instance
328
446
 
329
447
 
330
- def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str, Any]) -> "SensitiveMeta":
331
- """Logic to create a SensitiveMeta instance from a dictionary."""
448
+ def create_sensitive_meta_from_dict(
449
+ cls: Type["SensitiveMeta"], data: Dict[str, Any]
450
+ ) -> "SensitiveMeta":
451
+ """
452
+ Create a SensitiveMeta instance from a dictionary.
453
+
454
+ **Center handling:**
455
+ This function accepts TWO ways to specify the center:
456
+ 1. `center` (Center object) - Directly pass a Center instance
457
+ 2. `center_name` (string) - Pass the center name as a string (will be resolved to Center object)
458
+
459
+ At least ONE of these must be provided.
460
+
461
+ **Example usage:**
462
+ ```python
463
+ # Option 1: With Center object
464
+ data = {
465
+ "patient_first_name": "Patient",
466
+ "patient_last_name": "Unknown",
467
+ "patient_dob": date(1990, 1, 1),
468
+ "examination_date": date.today(),
469
+ "center": center_obj, # ← Center object
470
+ }
471
+ sm = SensitiveMeta.create_from_dict(data)
472
+
473
+ # Option 2: With center name string
474
+ data = {
475
+ "patient_first_name": "Patient",
476
+ "patient_last_name": "Unknown",
477
+ "patient_dob": date(1990, 1, 1),
478
+ "examination_date": date.today(),
479
+ "center_name": "university_hospital_wuerzburg", # ← String
480
+ }
481
+ sm = SensitiveMeta.create_from_dict(data)
482
+ ```
483
+
484
+ Args:
485
+ cls: The SensitiveMeta class
486
+ data: Dictionary containing field values
332
487
 
333
- field_names = {f.name for f in cls._meta.get_fields() if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)}
488
+ Returns:
489
+ SensitiveMeta: The created instance
490
+
491
+ Raises:
492
+ ValueError: If neither center nor center_name is provided
493
+ ValueError: If center_name does not match any Center in database
494
+ """
495
+
496
+ field_names = {
497
+ f.name
498
+ for f in cls._meta.get_fields()
499
+ if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)
500
+ }
334
501
  selected_data = {k: v for k, v in data.items() if k in field_names}
335
502
 
336
503
  # --- Convert patient_dob if it's a date object ---
@@ -357,9 +524,13 @@ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str,
357
524
  try:
358
525
  import dateparser
359
526
 
360
- parsed_dob = dateparser.parse(dob, languages=["de"], settings={"DATE_ORDER": "DMY"})
527
+ parsed_dob = dateparser.parse(
528
+ dob, languages=["de"], settings={"DATE_ORDER": "DMY"}
529
+ )
361
530
  if parsed_dob:
362
- aware_dob = timezone.make_aware(parsed_dob.replace(hour=0, minute=0, second=0, microsecond=0))
531
+ aware_dob = timezone.make_aware(
532
+ parsed_dob.replace(hour=0, minute=0, second=0, microsecond=0)
533
+ )
363
534
  selected_data["patient_dob"] = aware_dob
364
535
  logger.debug(
365
536
  "Parsed string patient_dob '%s' to aware datetime: %s",
@@ -413,7 +584,9 @@ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str,
413
584
  # Fall back to dateparser for complex formats
414
585
  import dateparser
415
586
 
416
- parsed_date = dateparser.parse(exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"})
587
+ parsed_date = dateparser.parse(
588
+ exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"}
589
+ )
417
590
  if parsed_date:
418
591
  selected_data["examination_date"] = parsed_date.date()
419
592
  logger.debug(
@@ -431,7 +604,9 @@ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str,
431
604
  # Use dateparser for non-ISO formats
432
605
  import dateparser
433
606
 
434
- parsed_date = dateparser.parse(exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"})
607
+ parsed_date = dateparser.parse(
608
+ exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"}
609
+ )
435
610
  if parsed_date:
436
611
  selected_data["examination_date"] = parsed_date.date()
437
612
  logger.debug(
@@ -453,15 +628,29 @@ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str,
453
628
  )
454
629
  selected_data.pop("examination_date", None)
455
630
 
456
- # Handle Center
631
+ # Handle Center - accept both center_name (string) and center (object)
632
+ from ..administration import Center
633
+
634
+ center = data.get("center") # First try direct Center object
457
635
  center_name = data.get("center_name")
458
- if not center_name:
459
- raise ValueError("center_name is required in data dictionary.")
460
- try:
461
- center = Center.objects.get_by_natural_key(center_name)
636
+
637
+ if center is not None:
638
+ # Center object provided directly - validate it's a Center instance
639
+ if not isinstance(center, Center):
640
+ raise ValueError(f"'center' must be a Center instance, got {type(center)}")
462
641
  selected_data["center"] = center
463
- except Center.DoesNotExist as exc:
464
- raise ValueError(f"Center with name '{center_name}' does not exist.") from exc
642
+ elif center_name:
643
+ # center_name string provided - resolve to Center object
644
+ try:
645
+ center = Center.objects.get(name=center_name)
646
+ selected_data["center"] = center
647
+ except Center.DoesNotExist:
648
+ raise ValueError(f"Center with name '{center_name}' does not exist.")
649
+ else:
650
+ # Neither center nor center_name provided
651
+ raise ValueError(
652
+ "Either 'center' (Center object) or 'center_name' (string) is required in data dictionary."
653
+ )
465
654
 
466
655
  # Handle Names and Gender
467
656
  first_name = selected_data.get("patient_first_name") or DEFAULT_UNKNOWN_NAME
@@ -477,22 +666,34 @@ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str,
477
666
  elif isinstance(patient_gender_input, str):
478
667
  # Input is a string (gender name)
479
668
  try:
480
- selected_data["patient_gender"] = Gender.objects.get(name=patient_gender_input)
669
+ selected_data["patient_gender"] = Gender.objects.get(
670
+ name=patient_gender_input
671
+ )
481
672
  except Gender.DoesNotExist:
482
- logger.warning(f"Gender with name '{patient_gender_input}' provided but not found. Attempting to guess or use default.")
673
+ logger.warning(
674
+ f"Gender with name '{patient_gender_input}' provided but not found. Attempting to guess or use default."
675
+ )
483
676
  # Fall through to guessing logic if provided string name is invalid
484
677
  patient_gender_input = None # Reset to trigger guessing
485
678
 
486
- if not isinstance(selected_data.get("patient_gender"), Gender): # If not already a Gender object (e.g. was None, or string lookup failed)
679
+ if not isinstance(
680
+ selected_data.get("patient_gender"), Gender
681
+ ): # If not already a Gender object (e.g. was None, or string lookup failed)
487
682
  gender_name_to_use = guess_name_gender(first_name)
488
683
  if not gender_name_to_use:
489
- logger.warning(f"Could not guess gender for name '{first_name}'. Setting Gender to unknown.")
684
+ logger.warning(
685
+ f"Could not guess gender for name '{first_name}'. Setting Gender to unknown."
686
+ )
490
687
  gender_name_to_use = "unknown"
491
688
  try:
492
- selected_data["patient_gender"] = Gender.objects.get(name=gender_name_to_use)
689
+ selected_data["patient_gender"] = Gender.objects.get(
690
+ name=gender_name_to_use
691
+ )
493
692
  except Gender.DoesNotExist:
494
693
  # This should ideally not happen if "unknown" gender is guaranteed to exist
495
- raise ValueError(f"Default or guessed gender '{gender_name_to_use}' does not exist in Gender table.")
694
+ raise ValueError(
695
+ f"Default or guessed gender '{gender_name_to_use}' does not exist in Gender table."
696
+ )
496
697
 
497
698
  # Update name DB
498
699
  update_name_db(first_name, last_name)
@@ -506,21 +707,74 @@ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str,
506
707
  return sensitive_meta
507
708
 
508
709
 
509
- def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, Any]) -> "SensitiveMeta":
510
- """Logic to update a SensitiveMeta instance from a dictionary."""
511
- field_names = {f.name for f in instance._meta.get_fields() if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)}
710
+ def update_sensitive_meta_from_dict(
711
+ instance: "SensitiveMeta", data: Dict[str, Any]
712
+ ) -> "SensitiveMeta":
713
+ """
714
+ Updates a SensitiveMeta instance from a dictionary of new values.
715
+
716
+ **Integration with two-phase save pattern:**
717
+ This function is typically called after initial SensitiveMeta creation when real
718
+ patient data becomes available (e.g., extracted from video OCR, PDF parsing, or
719
+ manual annotation).
720
+
721
+ **Example workflow:**
722
+ ```python
723
+ # Phase 1: Initial creation with defaults
724
+ sm = SensitiveMeta.create_from_dict({"center": center})
725
+ # → patient_first_name = "unknown", hash = sha256("unknown...")
726
+
727
+ # Phase 2: Update with extracted data
728
+ extracted = {
729
+ "patient_first_name": "Max",
730
+ "patient_last_name": "Mustermann",
731
+ "patient_dob": date(1985, 3, 15)
732
+ }
733
+ update_sensitive_meta_from_dict(sm, extracted)
734
+ # → Sets: sm.patient_first_name = "Max", sm.patient_last_name = "Mustermann"
735
+ # → Calls: sm.save()
736
+ # → Triggers: perform_save_logic() again
737
+ # → Result: Hash recalculated with real data, new pseudo-entities created
738
+ ```
739
+
740
+ **Key behaviors:**
741
+ - Updates instance attributes from provided dictionary
742
+ - Handles type conversions (date strings → date objects, gender strings → Gender objects)
743
+ - Tracks patient name changes to update name database
744
+ - Calls save() at the end, triggering full save logic including hash recalculation
745
+ - Default-setting in perform_save_logic() is skipped (fields already populated)
746
+
747
+ Args:
748
+ instance: The existing SensitiveMeta instance to update
749
+ data: Dictionary of field names and new values
750
+
751
+ Returns:
752
+ The updated SensitiveMeta instance
753
+
754
+ Raises:
755
+ Exception: If save fails or required conversions fail
756
+ """
757
+ field_names = {
758
+ f.name
759
+ for f in instance._meta.get_fields()
760
+ if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)
761
+ }
512
762
  # Exclude FKs that should not be updated directly from dict keys (handled separately or via save logic)
513
763
  excluded_fields = {"pseudo_patient", "pseudo_examination"}
514
- selected_data = {k: v for k, v in data.items() if k in field_names and k not in excluded_fields}
764
+ selected_data = {
765
+ k: v for k, v in data.items() if k in field_names and k not in excluded_fields
766
+ }
515
767
 
516
768
  # Handle potential Center update
517
769
  center_name = data.get("center_name")
518
770
  if center_name:
519
771
  try:
520
- center = Center.objects.get_by_natural_key(center_name)
772
+ center = Center.objects.get(name=center_name)
521
773
  instance.center = center # Update center directly
522
- except Center.DoesNotExist as exc:
523
- logger.warning(f"Center '{center_name}' not found during update. Keeping existing center.")
774
+ except Center.DoesNotExist:
775
+ logger.warning(
776
+ f"Center '{center_name}' not found during update. Keeping existing center."
777
+ )
524
778
  selected_data.pop("center", None) # Remove from dict if not found
525
779
 
526
780
  # Set examiner names if provided, before calling save
@@ -540,10 +794,14 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
540
794
  elif isinstance(patient_gender_input, str):
541
795
  gender_input_clean = patient_gender_input.strip()
542
796
  # Try direct case-insensitive DB lookup first
543
- gender_obj = Gender.objects.filter(name__iexact=gender_input_clean).first()
797
+ gender_obj = Gender.objects.filter(
798
+ name__iexact=gender_input_clean
799
+ ).first()
544
800
  if gender_obj:
545
801
  selected_data["patient_gender"] = gender_obj
546
- logger.debug(f"Successfully matched gender string '{patient_gender_input}' to Gender object via iexact lookup")
802
+ logger.debug(
803
+ f"Successfully matched gender string '{patient_gender_input}' to Gender object via iexact lookup"
804
+ )
547
805
  else:
548
806
  # Use mapping helper for fallback
549
807
  mapped = _map_gender_string_to_standard(gender_input_clean)
@@ -551,30 +809,50 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
551
809
  gender_obj = Gender.objects.filter(name__iexact=mapped).first()
552
810
  if gender_obj:
553
811
  selected_data["patient_gender"] = gender_obj
554
- logger.info(f"Mapped gender '{patient_gender_input}' to '{mapped}' via fallback mapping")
812
+ logger.info(
813
+ f"Mapped gender '{patient_gender_input}' to '{mapped}' via fallback mapping"
814
+ )
555
815
  else:
556
- logger.warning(f"Mapped gender '{patient_gender_input}' to '{mapped}', but no such Gender in DB. Trying 'unknown'.")
557
- unknown_gender = Gender.objects.filter(name__iexact="unknown").first()
816
+ logger.warning(
817
+ f"Mapped gender '{patient_gender_input}' to '{mapped}', but no such Gender in DB. Trying 'unknown'."
818
+ )
819
+ unknown_gender = Gender.objects.filter(
820
+ name__iexact="unknown"
821
+ ).first()
558
822
  if unknown_gender:
559
823
  selected_data["patient_gender"] = unknown_gender
560
- logger.warning(f"Using 'unknown' gender as fallback for '{patient_gender_input}'")
824
+ logger.warning(
825
+ f"Using 'unknown' gender as fallback for '{patient_gender_input}'"
826
+ )
561
827
  else:
562
- logger.error(f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update.")
828
+ logger.error(
829
+ f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update."
830
+ )
563
831
  selected_data.pop("patient_gender", None)
564
832
  else:
565
833
  # Last resort: try to get 'unknown' gender
566
- unknown_gender = Gender.objects.filter(name__iexact="unknown").first()
834
+ unknown_gender = Gender.objects.filter(
835
+ name__iexact="unknown"
836
+ ).first()
567
837
  if unknown_gender:
568
838
  selected_data["patient_gender"] = unknown_gender
569
- logger.warning(f"Using 'unknown' gender as fallback for '{patient_gender_input}' (no mapping)")
839
+ logger.warning(
840
+ f"Using 'unknown' gender as fallback for '{patient_gender_input}' (no mapping)"
841
+ )
570
842
  else:
571
- logger.error(f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update.")
843
+ logger.error(
844
+ f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update."
845
+ )
572
846
  selected_data.pop("patient_gender", None)
573
847
  else:
574
- logger.warning(f"Unexpected patient_gender type {type(patient_gender_input)}: {patient_gender_input}. Skipping gender update.")
848
+ logger.warning(
849
+ f"Unexpected patient_gender type {type(patient_gender_input)}: {patient_gender_input}. Skipping gender update."
850
+ )
575
851
  selected_data.pop("patient_gender", None)
576
852
  except Exception as e:
577
- logger.exception(f"Error handling patient_gender '{patient_gender_input}': {e}. Skipping gender update.")
853
+ logger.exception(
854
+ f"Error handling patient_gender '{patient_gender_input}': {e}. Skipping gender update."
855
+ )
578
856
  selected_data.pop("patient_gender", None)
579
857
 
580
858
  # Update other attributes from selected_data
@@ -591,7 +869,9 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
591
869
  value_to_set = v
592
870
  if k == "patient_dob":
593
871
  if isinstance(v, date) and not isinstance(v, datetime):
594
- aware_dob = timezone.make_aware(datetime.combine(v, datetime.min.time()))
872
+ aware_dob = timezone.make_aware(
873
+ datetime.combine(v, datetime.min.time())
874
+ )
595
875
  value_to_set = aware_dob
596
876
  logger.debug(
597
877
  "Converted patient_dob from date to aware datetime during update: %s",
@@ -614,9 +894,15 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
614
894
  try:
615
895
  import dateparser
616
896
 
617
- parsed_dob = dateparser.parse(v, languages=["de"], settings={"DATE_ORDER": "DMY"})
897
+ parsed_dob = dateparser.parse(
898
+ v, languages=["de"], settings={"DATE_ORDER": "DMY"}
899
+ )
618
900
  if parsed_dob:
619
- value_to_set = timezone.make_aware(parsed_dob.replace(hour=0, minute=0, second=0, microsecond=0))
901
+ value_to_set = timezone.make_aware(
902
+ parsed_dob.replace(
903
+ hour=0, minute=0, second=0, microsecond=0
904
+ )
905
+ )
620
906
  logger.debug(
621
907
  "Parsed string patient_dob '%s' during update to aware datetime: %s",
622
908
  v,
@@ -651,7 +937,9 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
651
937
  try:
652
938
  import dateparser
653
939
 
654
- parsed_date = dateparser.parse(v, languages=["de"], settings={"DATE_ORDER": "DMY"})
940
+ parsed_date = dateparser.parse(
941
+ v, languages=["de"], settings={"DATE_ORDER": "DMY"}
942
+ )
655
943
  if parsed_date:
656
944
  value_to_set = parsed_date.date()
657
945
  logger.debug(
@@ -675,13 +963,18 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
675
963
  # --- End Conversion ---
676
964
 
677
965
  # Check if patient name is changing
678
- if k in ["patient_first_name", "patient_last_name"] and getattr(instance, k) != value_to_set:
966
+ if (
967
+ k in ["patient_first_name", "patient_last_name"]
968
+ and getattr(instance, k) != value_to_set
969
+ ):
679
970
  patient_name_changed = True
680
971
 
681
972
  setattr(instance, k, value_to_set) # Use value_to_set
682
973
 
683
974
  except Exception as e:
684
- logger.error(f"Error setting attribute '{k}' to '{v}': {e}. Skipping this field.")
975
+ logger.error(
976
+ f"Error setting attribute '{k}' to '{v}': {e}. Skipping this field."
977
+ )
685
978
  continue
686
979
 
687
980
  # Update name DB if patient names changed
@@ -409,8 +409,7 @@ class VideoImportService:
409
409
  # Initialize video specifications
410
410
  video.initialize_video_specs()
411
411
 
412
- # Initialize frame objects in database
413
- video.initialize_frames()
412
+
414
413
 
415
414
  # Extract frames BEFORE processing to prevent pipeline 1 conflicts
416
415
  self.logger.info("Pre-extracting frames to avoid pipeline conflicts...")
@@ -419,6 +418,8 @@ class VideoImportService:
419
418
  if frames_extracted:
420
419
  self.processing_context["frames_extracted"] = True
421
420
  self.logger.info("Frame extraction completed successfully")
421
+ # Initialize frame objects in database
422
+ video.initialize_frames(video.get_frame_paths())
422
423
 
423
424
  # CRITICAL: Immediately save the frames_extracted state to database
424
425
  # to prevent refresh_from_db() in pipeline 1 from overriding it
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: endoreg-db
3
- Version: 0.8.5.1
3
+ Version: 0.8.5.3
4
4
  Summary: EndoReg Db Django App
5
5
  Project-URL: Homepage, https://info.coloreg.de
6
6
  Project-URL: Repository, https://github.com/wg-lux/endoreg-db