Source code for endoreg_db.models.metadata.sensitive_meta

from django.db import models
# Removed hash utils, datetime, random, os, timezone, sha256 imports
# Removed icecream import (was used in old save logic)
from typing import TYPE_CHECKING, Dict, Any, Type

# Import logic functions
from . import sensitive_meta_logic as logic
# Import models needed for type hints and FKs
from ..state import SensitiveMetaState # Needed for post-save state check

if TYPE_CHECKING:
    from ..administration import (
        Center,
        Examiner,
        Patient,
        FirstName, # Keep for type hinting if needed
        LastName   # Keep for type hinting if needed
    )
    from ..other import Gender
    from ..medical import PatientExamination
    # from ..state import SensitiveMetaState # Already imported above


# SECRET_SALT moved to logic

[docs] class SensitiveMeta(models.Model): """ Stores potentially sensitive information extracted from media. Logic for creation, hashing, pseudo-anonymization, and saving is in sensitive_meta_logic.py. """ examination_date = models.DateField(blank=True, null=True) pseudo_patient = models.ForeignKey( "Patient", on_delete=models.CASCADE, blank=True, null=True, help_text="FK to the pseudo-anonymized Patient record." ) patient_first_name = models.CharField(max_length=255, blank=True, null=True) patient_last_name = models.CharField(max_length=255, blank=True, null=True) patient_dob = models.DateTimeField( blank=True, null=True, help_text="Date of birth (can be auto-generated if missing)." ) pseudo_examination = models.ForeignKey( "PatientExamination", on_delete=models.CASCADE, blank=True, null=True, help_text="FK to the pseudo-anonymized PatientExamination record." ) patient_gender = models.ForeignKey( "Gender", on_delete=models.CASCADE, blank=True, null=True, ) examiners = models.ManyToManyField( "Examiner", blank=True, help_text="Pseudo-anonymized examiner(s) associated with the examination." ) center = models.ForeignKey( "Center", on_delete=models.CASCADE, blank=True, # Should ideally be False if always required before save null=True, # Should ideally be False ) # Raw examiner names stored temporarily until pseudo-examiner is created/linked examiner_first_name = models.CharField(max_length=255, blank=True, null=True, editable=False) examiner_last_name = models.CharField(max_length=255, blank=True, null=True, editable=False) # Hashes calculated and stored by save logic examination_hash = models.CharField(max_length=64, blank=True, null=True, editable=False, db_index=True) # Use 64 for sha256 hex patient_hash = models.CharField(max_length=64, blank=True, null=True, editable=False, db_index=True) # Use 64 for sha256 hex endoscope_type = models.CharField(max_length=255, blank=True, null=True) endoscope_sn = models.CharField(max_length=255, blank=True, null=True) # Removed state_verified property, assuming state is handled via the related SensitiveMetaState model if TYPE_CHECKING: examiners: models.QuerySet["Examiner"] pseudo_patient: "Patient" patient_gender: "Gender" pseudo_examination: "PatientExamination" state: "SensitiveMetaState" # Assuming related_name='state' is defined on SensitiveMetaState.origin center: "Center" @staticmethod def _generate_random_dob(): # Delegate to logic return logic.generate_random_dob() @staticmethod def _generate_random_examination_date(): # Delegate to logic return logic.generate_random_examination_date()
[docs] @classmethod def create_from_dict(cls: Type["SensitiveMeta"], data: Dict[str, Any]): """Creates a SensitiveMeta instance from a dictionary using external logic.""" # Delegate to logic function return logic.create_sensitive_meta_from_dict(cls, data)
# --- Methods related to pseudo-entities are now primarily handled within save logic --- # Keep simple getters if needed, but creation logic is centralized.
[docs] def get_pseudo_examiner(self) -> "Examiner | None": """Returns the linked pseudo examiner, if one exists.""" if self.pk: return self.examiners.first() return None # Cannot determine before saving and linking
# Removed create_pseudo_examiner - logic is now in sensitive_meta_logic.create_pseudo_examiner_logic
[docs] def get_pseudo_patient(self) -> "Patient | None": """Returns the linked pseudo patient, if one exists.""" return self.pseudo_patient # Access the FK directly
# Removed create_pseudo_patient - logic is now in sensitive_meta_logic.get_or_create_pseudo_patient_logic
[docs] def get_pseudo_patient_examination(self) -> "PatientExamination | None": """Returns the linked pseudo patient examination, if one exists.""" return self.pseudo_examination # Access the FK directly
# Removed get_or_create_pseudo_patient_examination - logic is now in sensitive_meta_logic.get_or_create_pseudo_patient_examination_logic # --- Update method delegates to logic ---
[docs] def update_from_dict(self, data: Dict[str, Any]): """Updates the instance from a dictionary using external logic.""" # Delegate to logic function return logic.update_sensitive_meta_from_dict(self, data)
# --- String representation --- def __str__(self): # Keep this method for basic representation, ensure fields are accessed safely center_name = self.center.name if self.center else "None" gender_str = str(self.patient_gender) if self.patient_gender else "None" dob_str = str(self.patient_dob.date()) if self.patient_dob else "None" # Show only date part exam_date_str = str(self.examination_date) if self.examination_date else "None" examiners_str = "[Not saved yet]" if self.pk: try: # Use prefetch_related in queries accessing this for efficiency examiners_str = ", ".join([str(e) for e in self.examiners.all()]) or "[None]" except Exception as e: examiners_str = f"[Error: {e}]" state_verified = "Unknown" if self.pk: try: # Access state verification through the related state object state_verified = str(self.state.is_verified) if hasattr(self, 'state') and self.state else "No State" except SensitiveMetaState.DoesNotExist: state_verified = "No State" except AttributeError: # Handle case where 'state' related name isn't set up or instance not saved state_obj = SensitiveMetaState.objects.filter(origin_id=self.pk).first() state_verified = str(state_obj.is_verified) if state_obj else "No State" else: state_verified = "[Not saved yet]" return ( f"SensitiveMeta(pk={self.pk}): " f"Patient={self.patient_last_name}, {self.patient_first_name} (*{dob_str}, {gender_str}), " f"ExamDate={exam_date_str}, Center={center_name}, " f"Examiners={examiners_str}, StateVerified={state_verified}, " f"PatientHash={str(self.patient_hash)[-8:] if self.patient_hash else 'None'}, " # Show last 8 chars f"ExamHash={str(self.examination_hash)[-8:] if self.examination_hash else 'None'}" # Show last 8 chars ) def __repr__(self): return self.__str__() # --- Hashing methods delegate to logic ---
[docs] def get_patient_hash(self, salt=None): """Calculates the patient hash using external logic.""" # Use default salt from logic if None is passed salt_to_use = salt if salt is not None else logic.SECRET_SALT # Delegate to logic function return logic.calculate_patient_hash(self, salt=salt_to_use)
[docs] def get_patient_examination_hash(self, salt=None): """Calculates the examination hash using external logic.""" salt_to_use = salt if salt is not None else logic.SECRET_SALT # Delegate to logic function return logic.calculate_examination_hash(self, salt=salt_to_use)
# --- Save method orchestrates calls to logic ---
[docs] def save(self, *args, **kwargs): """ Overrides the default save method to ensure data integrity, calculate hashes, link pseudo-entities, and manage related state using external logic. """ # 1. Call the main logic function to perform pre-save checks, data generation, # and creation/linking of pseudo patient/examination FKs. # This function modifies the instance fields (hashes, FKs, dates). # It returns the examiner instance to be linked *after* saving. examiner_to_link = logic.perform_save_logic(self) # Pass only self # 2. Call the original Django save method to save the instance itself # (including updated FKs, hashes, dates). super().save(*args, **kwargs) # Pass original args/kwargs # 3. Ensure SensitiveMetaState exists *after* saving the main instance # Use related name 'state' if defined, otherwise access via manager. if self.pk: # Ensure we have a PK try: # Check if the related state object already exists using the related manager _ = self.state except SensitiveMetaState.DoesNotExist: # If not, create a new one, linking it to the saved instance SensitiveMetaState.objects.create(origin=self) except AttributeError: # Fallback check if 'state' related_name is missing if not SensitiveMetaState.objects.filter(origin=self).exists(): SensitiveMetaState.objects.create(origin=self) # 4. Handle ManyToMany linking (examiners) *after* the instance has a PK. if examiner_to_link and self.pk and not self.examiners.filter(pk=examiner_to_link.pk).exists(): self.examiners.add(examiner_to_link)
# Adding to M2M handles its own DB interaction, no second super().save() needed. @classmethod def _update_name_db(cls, first_name, last_name): # Delegate to logic logic.update_name_db(first_name, last_name)