endoreg-db 0.8.5.0__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.
- endoreg_db/models/media/video/video_file.py +255 -147
- endoreg_db/models/metadata/sensitive_meta_logic.py +292 -56
- endoreg_db/services/video_import.py +312 -102
- endoreg_db/views/media/video_media.py +1 -1
- {endoreg_db-0.8.5.0.dist-info → endoreg_db-0.8.5.2.dist-info}/METADATA +1 -1
- {endoreg_db-0.8.5.0.dist-info → endoreg_db-0.8.5.2.dist-info}/RECORD +8 -8
- {endoreg_db-0.8.5.0.dist-info → endoreg_db-0.8.5.2.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.5.0.dist-info → endoreg_db-0.8.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
#
|
|
299
|
-
first_name = instance.patient_first_name
|
|
300
|
-
|
|
301
|
-
if not
|
|
302
|
-
raise ValueError(
|
|
303
|
-
|
|
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(
|
|
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 = {
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
612
|
+
selected_data["patient_gender"] = Gender.objects.get(
|
|
613
|
+
name=patient_gender_input
|
|
614
|
+
)
|
|
481
615
|
except Gender.DoesNotExist:
|
|
482
|
-
logger.warning(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
510
|
-
""
|
|
511
|
-
|
|
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 = {
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
755
|
+
logger.info(
|
|
756
|
+
f"Mapped gender '{patient_gender_input}' to '{mapped}' via fallback mapping"
|
|
757
|
+
)
|
|
555
758
|
else:
|
|
556
|
-
logger.warning(
|
|
557
|
-
|
|
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(
|
|
767
|
+
logger.warning(
|
|
768
|
+
f"Using 'unknown' gender as fallback for '{patient_gender_input}'"
|
|
769
|
+
)
|
|
561
770
|
else:
|
|
562
|
-
logger.error(
|
|
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(
|
|
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(
|
|
782
|
+
logger.warning(
|
|
783
|
+
f"Using 'unknown' gender as fallback for '{patient_gender_input}' (no mapping)"
|
|
784
|
+
)
|
|
570
785
|
else:
|
|
571
|
-
logger.error(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|