endoreg-db 0.8.5.1__py3-none-any.whl → 0.8.5.2__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
218
  default_center = Center.objects.get_by_natural_key("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,16 @@ 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":
448
+ def create_sensitive_meta_from_dict(
449
+ cls: Type["SensitiveMeta"], data: Dict[str, Any]
450
+ ) -> "SensitiveMeta":
331
451
  """Logic to create a SensitiveMeta instance from a dictionary."""
332
452
 
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)}
453
+ field_names = {
454
+ f.name
455
+ for f in cls._meta.get_fields()
456
+ if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)
457
+ }
334
458
  selected_data = {k: v for k, v in data.items() if k in field_names}
335
459
 
336
460
  # --- Convert patient_dob if it's a date object ---
@@ -357,9 +481,13 @@ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str,
357
481
  try:
358
482
  import dateparser
359
483
 
360
- parsed_dob = dateparser.parse(dob, languages=["de"], settings={"DATE_ORDER": "DMY"})
484
+ parsed_dob = dateparser.parse(
485
+ dob, languages=["de"], settings={"DATE_ORDER": "DMY"}
486
+ )
361
487
  if parsed_dob:
362
- aware_dob = timezone.make_aware(parsed_dob.replace(hour=0, minute=0, second=0, microsecond=0))
488
+ aware_dob = timezone.make_aware(
489
+ parsed_dob.replace(hour=0, minute=0, second=0, microsecond=0)
490
+ )
363
491
  selected_data["patient_dob"] = aware_dob
364
492
  logger.debug(
365
493
  "Parsed string patient_dob '%s' to aware datetime: %s",
@@ -413,7 +541,9 @@ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str,
413
541
  # Fall back to dateparser for complex formats
414
542
  import dateparser
415
543
 
416
- parsed_date = dateparser.parse(exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"})
544
+ parsed_date = dateparser.parse(
545
+ exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"}
546
+ )
417
547
  if parsed_date:
418
548
  selected_data["examination_date"] = parsed_date.date()
419
549
  logger.debug(
@@ -431,7 +561,9 @@ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str,
431
561
  # Use dateparser for non-ISO formats
432
562
  import dateparser
433
563
 
434
- parsed_date = dateparser.parse(exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"})
564
+ parsed_date = dateparser.parse(
565
+ exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"}
566
+ )
435
567
  if parsed_date:
436
568
  selected_data["examination_date"] = parsed_date.date()
437
569
  logger.debug(
@@ -477,22 +609,34 @@ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str,
477
609
  elif isinstance(patient_gender_input, str):
478
610
  # Input is a string (gender name)
479
611
  try:
480
- selected_data["patient_gender"] = Gender.objects.get(name=patient_gender_input)
612
+ selected_data["patient_gender"] = Gender.objects.get(
613
+ name=patient_gender_input
614
+ )
481
615
  except Gender.DoesNotExist:
482
- logger.warning(f"Gender with name '{patient_gender_input}' provided but not found. Attempting to guess or use default.")
616
+ logger.warning(
617
+ f"Gender with name '{patient_gender_input}' provided but not found. Attempting to guess or use default."
618
+ )
483
619
  # Fall through to guessing logic if provided string name is invalid
484
620
  patient_gender_input = None # Reset to trigger guessing
485
621
 
486
- if not isinstance(selected_data.get("patient_gender"), Gender): # If not already a Gender object (e.g. was None, or string lookup failed)
622
+ if not isinstance(
623
+ selected_data.get("patient_gender"), Gender
624
+ ): # If not already a Gender object (e.g. was None, or string lookup failed)
487
625
  gender_name_to_use = guess_name_gender(first_name)
488
626
  if not gender_name_to_use:
489
- logger.warning(f"Could not guess gender for name '{first_name}'. Setting Gender to unknown.")
627
+ logger.warning(
628
+ f"Could not guess gender for name '{first_name}'. Setting Gender to unknown."
629
+ )
490
630
  gender_name_to_use = "unknown"
491
631
  try:
492
- selected_data["patient_gender"] = Gender.objects.get(name=gender_name_to_use)
632
+ selected_data["patient_gender"] = Gender.objects.get(
633
+ name=gender_name_to_use
634
+ )
493
635
  except Gender.DoesNotExist:
494
636
  # 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.")
637
+ raise ValueError(
638
+ f"Default or guessed gender '{gender_name_to_use}' does not exist in Gender table."
639
+ )
496
640
 
497
641
  # Update name DB
498
642
  update_name_db(first_name, last_name)
@@ -506,12 +650,63 @@ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str,
506
650
  return sensitive_meta
507
651
 
508
652
 
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)}
653
+ def update_sensitive_meta_from_dict(
654
+ instance: "SensitiveMeta", data: Dict[str, Any]
655
+ ) -> "SensitiveMeta":
656
+ """
657
+ Updates a SensitiveMeta instance from a dictionary of new values.
658
+
659
+ **Integration with two-phase save pattern:**
660
+ This function is typically called after initial SensitiveMeta creation when real
661
+ patient data becomes available (e.g., extracted from video OCR, PDF parsing, or
662
+ manual annotation).
663
+
664
+ **Example workflow:**
665
+ ```python
666
+ # Phase 1: Initial creation with defaults
667
+ sm = SensitiveMeta.create_from_dict({"center": center})
668
+ # → patient_first_name = "unknown", hash = sha256("unknown...")
669
+
670
+ # Phase 2: Update with extracted data
671
+ extracted = {
672
+ "patient_first_name": "Max",
673
+ "patient_last_name": "Mustermann",
674
+ "patient_dob": date(1985, 3, 15)
675
+ }
676
+ update_sensitive_meta_from_dict(sm, extracted)
677
+ # → Sets: sm.patient_first_name = "Max", sm.patient_last_name = "Mustermann"
678
+ # → Calls: sm.save()
679
+ # → Triggers: perform_save_logic() again
680
+ # → Result: Hash recalculated with real data, new pseudo-entities created
681
+ ```
682
+
683
+ **Key behaviors:**
684
+ - Updates instance attributes from provided dictionary
685
+ - Handles type conversions (date strings → date objects, gender strings → Gender objects)
686
+ - Tracks patient name changes to update name database
687
+ - Calls save() at the end, triggering full save logic including hash recalculation
688
+ - Default-setting in perform_save_logic() is skipped (fields already populated)
689
+
690
+ Args:
691
+ instance: The existing SensitiveMeta instance to update
692
+ data: Dictionary of field names and new values
693
+
694
+ Returns:
695
+ The updated SensitiveMeta instance
696
+
697
+ Raises:
698
+ Exception: If save fails or required conversions fail
699
+ """
700
+ field_names = {
701
+ f.name
702
+ for f in instance._meta.get_fields()
703
+ if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)
704
+ }
512
705
  # Exclude FKs that should not be updated directly from dict keys (handled separately or via save logic)
513
706
  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}
707
+ selected_data = {
708
+ k: v for k, v in data.items() if k in field_names and k not in excluded_fields
709
+ }
515
710
 
516
711
  # Handle potential Center update
517
712
  center_name = data.get("center_name")
@@ -520,7 +715,9 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
520
715
  center = Center.objects.get_by_natural_key(center_name)
521
716
  instance.center = center # Update center directly
522
717
  except Center.DoesNotExist as exc:
523
- logger.warning(f"Center '{center_name}' not found during update. Keeping existing center.")
718
+ logger.warning(
719
+ f"Center '{center_name}' not found during update. Keeping existing center."
720
+ )
524
721
  selected_data.pop("center", None) # Remove from dict if not found
525
722
 
526
723
  # Set examiner names if provided, before calling save
@@ -540,10 +737,14 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
540
737
  elif isinstance(patient_gender_input, str):
541
738
  gender_input_clean = patient_gender_input.strip()
542
739
  # Try direct case-insensitive DB lookup first
543
- gender_obj = Gender.objects.filter(name__iexact=gender_input_clean).first()
740
+ gender_obj = Gender.objects.filter(
741
+ name__iexact=gender_input_clean
742
+ ).first()
544
743
  if gender_obj:
545
744
  selected_data["patient_gender"] = gender_obj
546
- logger.debug(f"Successfully matched gender string '{patient_gender_input}' to Gender object via iexact lookup")
745
+ logger.debug(
746
+ f"Successfully matched gender string '{patient_gender_input}' to Gender object via iexact lookup"
747
+ )
547
748
  else:
548
749
  # Use mapping helper for fallback
549
750
  mapped = _map_gender_string_to_standard(gender_input_clean)
@@ -551,30 +752,50 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
551
752
  gender_obj = Gender.objects.filter(name__iexact=mapped).first()
552
753
  if gender_obj:
553
754
  selected_data["patient_gender"] = gender_obj
554
- logger.info(f"Mapped gender '{patient_gender_input}' to '{mapped}' via fallback mapping")
755
+ logger.info(
756
+ f"Mapped gender '{patient_gender_input}' to '{mapped}' via fallback mapping"
757
+ )
555
758
  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()
759
+ logger.warning(
760
+ f"Mapped gender '{patient_gender_input}' to '{mapped}', but no such Gender in DB. Trying 'unknown'."
761
+ )
762
+ unknown_gender = Gender.objects.filter(
763
+ name__iexact="unknown"
764
+ ).first()
558
765
  if unknown_gender:
559
766
  selected_data["patient_gender"] = unknown_gender
560
- logger.warning(f"Using 'unknown' gender as fallback for '{patient_gender_input}'")
767
+ logger.warning(
768
+ f"Using 'unknown' gender as fallback for '{patient_gender_input}'"
769
+ )
561
770
  else:
562
- logger.error(f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update.")
771
+ logger.error(
772
+ f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update."
773
+ )
563
774
  selected_data.pop("patient_gender", None)
564
775
  else:
565
776
  # Last resort: try to get 'unknown' gender
566
- unknown_gender = Gender.objects.filter(name__iexact="unknown").first()
777
+ unknown_gender = Gender.objects.filter(
778
+ name__iexact="unknown"
779
+ ).first()
567
780
  if unknown_gender:
568
781
  selected_data["patient_gender"] = unknown_gender
569
- logger.warning(f"Using 'unknown' gender as fallback for '{patient_gender_input}' (no mapping)")
782
+ logger.warning(
783
+ f"Using 'unknown' gender as fallback for '{patient_gender_input}' (no mapping)"
784
+ )
570
785
  else:
571
- logger.error(f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update.")
786
+ logger.error(
787
+ f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update."
788
+ )
572
789
  selected_data.pop("patient_gender", None)
573
790
  else:
574
- logger.warning(f"Unexpected patient_gender type {type(patient_gender_input)}: {patient_gender_input}. Skipping gender update.")
791
+ logger.warning(
792
+ f"Unexpected patient_gender type {type(patient_gender_input)}: {patient_gender_input}. Skipping gender update."
793
+ )
575
794
  selected_data.pop("patient_gender", None)
576
795
  except Exception as e:
577
- logger.exception(f"Error handling patient_gender '{patient_gender_input}': {e}. Skipping gender update.")
796
+ logger.exception(
797
+ f"Error handling patient_gender '{patient_gender_input}': {e}. Skipping gender update."
798
+ )
578
799
  selected_data.pop("patient_gender", None)
579
800
 
580
801
  # Update other attributes from selected_data
@@ -591,7 +812,9 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
591
812
  value_to_set = v
592
813
  if k == "patient_dob":
593
814
  if isinstance(v, date) and not isinstance(v, datetime):
594
- aware_dob = timezone.make_aware(datetime.combine(v, datetime.min.time()))
815
+ aware_dob = timezone.make_aware(
816
+ datetime.combine(v, datetime.min.time())
817
+ )
595
818
  value_to_set = aware_dob
596
819
  logger.debug(
597
820
  "Converted patient_dob from date to aware datetime during update: %s",
@@ -614,9 +837,15 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
614
837
  try:
615
838
  import dateparser
616
839
 
617
- parsed_dob = dateparser.parse(v, languages=["de"], settings={"DATE_ORDER": "DMY"})
840
+ parsed_dob = dateparser.parse(
841
+ v, languages=["de"], settings={"DATE_ORDER": "DMY"}
842
+ )
618
843
  if parsed_dob:
619
- value_to_set = timezone.make_aware(parsed_dob.replace(hour=0, minute=0, second=0, microsecond=0))
844
+ value_to_set = timezone.make_aware(
845
+ parsed_dob.replace(
846
+ hour=0, minute=0, second=0, microsecond=0
847
+ )
848
+ )
620
849
  logger.debug(
621
850
  "Parsed string patient_dob '%s' during update to aware datetime: %s",
622
851
  v,
@@ -651,7 +880,9 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
651
880
  try:
652
881
  import dateparser
653
882
 
654
- parsed_date = dateparser.parse(v, languages=["de"], settings={"DATE_ORDER": "DMY"})
883
+ parsed_date = dateparser.parse(
884
+ v, languages=["de"], settings={"DATE_ORDER": "DMY"}
885
+ )
655
886
  if parsed_date:
656
887
  value_to_set = parsed_date.date()
657
888
  logger.debug(
@@ -675,13 +906,18 @@ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, A
675
906
  # --- End Conversion ---
676
907
 
677
908
  # Check if patient name is changing
678
- if k in ["patient_first_name", "patient_last_name"] and getattr(instance, k) != value_to_set:
909
+ if (
910
+ k in ["patient_first_name", "patient_last_name"]
911
+ and getattr(instance, k) != value_to_set
912
+ ):
679
913
  patient_name_changed = True
680
914
 
681
915
  setattr(instance, k, value_to_set) # Use value_to_set
682
916
 
683
917
  except Exception as e:
684
- logger.error(f"Error setting attribute '{k}' to '{v}': {e}. Skipping this field.")
918
+ logger.error(
919
+ f"Error setting attribute '{k}' to '{v}': {e}. Skipping this field."
920
+ )
685
921
  continue
686
922
 
687
923
  # 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.2
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
@@ -390,7 +390,7 @@ endoreg_db/models/media/video/create_from_file.py,sha256=3n4bbzFteEOFDUuEikP0x-S
390
390
  endoreg_db/models/media/video/pipe_1.py,sha256=ljO3vO2mqqTXLZsKjzMTC6-sW4JRWMVRfJcK0n5CjKg,9740
391
391
  endoreg_db/models/media/video/pipe_2.py,sha256=DnMxW0uOqSsf7-0n9Rlvn7u89U4Jpkv7n6hFpQfUjkQ,4964
392
392
  endoreg_db/models/media/video/refactor_plan.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
393
- endoreg_db/models/media/video/video_file.py,sha256=NMO0ykCpesolX9ov3leOStOQSsgKPiGOKnN5UBGiDCY,26613
393
+ endoreg_db/models/media/video/video_file.py,sha256=AyRmG_HSAgYTF-UWivk3EF7G9RHCpJbWE1PEeqlZJiU,29424
394
394
  endoreg_db/models/media/video/video_file_ai.py,sha256=3ABea52FOF1qlrlxHdYhz_M3Kmqfzqtgq7M0prl-FAo,18819
395
395
  endoreg_db/models/media/video/video_file_anonymize.py,sha256=pet1UfSsbSHJJZxq6gDPifAfBWpGyEpD1jEQuSQi0Gg,16027
396
396
  endoreg_db/models/media/video/video_file_frames.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -468,7 +468,7 @@ endoreg_db/models/metadata/model_meta.py,sha256=F_r-PTLeNi4J-4EaGCQkGIguhdl7Bwba
468
468
  endoreg_db/models/metadata/model_meta_logic.py,sha256=vAbNDaoZygH8xOCulWlXoHoR1T0BSvr9kIloxjzhfjo,12533
469
469
  endoreg_db/models/metadata/pdf_meta.py,sha256=BTmpSgqxmPKi0apcNjyrZAS4AFKCPXVdBd6VBeyyv6E,3174
470
470
  endoreg_db/models/metadata/sensitive_meta.py,sha256=ekLHrW-b5uYcjfkRd0EW5ncx5ef8Bu-K6msDkpWCAbk,13034
471
- endoreg_db/models/metadata/sensitive_meta_logic.py,sha256=BnxWSSGHRlWLvqGgChzZ13qCXMB7onLE6MOBdE5CBhg,32311
471
+ endoreg_db/models/metadata/sensitive_meta_logic.py,sha256=s5wf_oPXhOuk0ReDE7SblRWO_thkq1ixCerc-xwWRB0,40100
472
472
  endoreg_db/models/metadata/video_meta.py,sha256=c6xWdLW3uNqJ5VPJXHCxXA3mbXw-b0uR54-TOS3qL2Q,14966
473
473
  endoreg_db/models/metadata/video_prediction_logic.py,sha256=j5N82mHtiomeeIaf1HA65kT5d0htQfJmbI2bJb8mpxQ,7677
474
474
  endoreg_db/models/metadata/video_prediction_meta.py,sha256=EyfctAAAVcW9L0gf76ZBc9-G8MLMcD-tc2kkjaaLH4w,10592
@@ -603,7 +603,7 @@ endoreg_db/services/pseudonym_service.py,sha256=CJhbtRa6K6SPbphgCZgEMi8AFQtB18CU
603
603
  endoreg_db/services/requirements_object.py,sha256=290zf8AEbVtCoHhW4Jr7_ud-RvrqYmb1Nz9UBHtTnc0,6164
604
604
  endoreg_db/services/segment_sync.py,sha256=YgHvIHkbW4mqCu0ACf3zjRSZnNfxWwt4gh5syUVXuE0,6400
605
605
  endoreg_db/services/storage_aware_video_processor.py,sha256=kKFK64vXLeBSVkp1YJonU3gFDTeXZ8C4qb9QZZB99SE,13420
606
- endoreg_db/services/video_import.py,sha256=N2izuB3jpwH068WY15CYW597CugntKTuXrzOA_IDBdw,52189
606
+ endoreg_db/services/video_import.py,sha256=mtEQlHq-n5x-PE1w8CpeCNnkmKpLhbjWflqtK43nBlw,52237
607
607
  endoreg_db/tasks/upload_tasks.py,sha256=OJq7DhNwcbWdXzHY8jz5c51BCVkPN5gSWOz-6Fx6W5M,7799
608
608
  endoreg_db/tasks/video_ingest.py,sha256=kxFuYkHijINV0VabQKCFVpJRv6eCAw07tviONurDgg8,5265
609
609
  endoreg_db/tasks/video_processing_tasks.py,sha256=rZ7Kr49bAR4Q-vALO2SURebrhcJ5hSFGwjF4aULrOao,14089
@@ -788,7 +788,7 @@ endoreg_db/views/video/video_meta.py,sha256=C1wBMTtQb_yzEUrhFGAy2UHEWMk_CbU75WXX
788
788
  endoreg_db/views/video/video_processing_history.py,sha256=mhFuS8RG5GV8E-lTtuD0qrq-bIpnUFp8vy9aERfC-J8,770
789
789
  endoreg_db/views/video/video_remove_frames.py,sha256=2FmvNrSPM0fUXiBxINN6vBUUDCqDlBkNcGR3WsLDgKo,1696
790
790
  endoreg_db/views/video/video_stream.py,sha256=kLyuf0ORTmsLeYUQkTQ6iRYqlIQozWhMMR3Lhfe_trk,12148
791
- endoreg_db-0.8.5.1.dist-info/METADATA,sha256=PT3KElyA0TF1cY_Q6AJfG5N5M9NVGX911hri5sj0B2k,14719
792
- endoreg_db-0.8.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
793
- endoreg_db-0.8.5.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
794
- endoreg_db-0.8.5.1.dist-info/RECORD,,
791
+ endoreg_db-0.8.5.2.dist-info/METADATA,sha256=U-mn6p7DpJNoCpAn01fAr14bYmJKzpwfbx2nDOuz_Qs,14719
792
+ endoreg_db-0.8.5.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
793
+ endoreg_db-0.8.5.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
794
+ endoreg_db-0.8.5.2.dist-info/RECORD,,