# # core/models.py (all-in-one, flat style) # from django.conf import settings # from django.db import models # from django.contrib.contenttypes.fields import GenericForeignKey # from django.contrib.contenttypes.models import ContentType # import uuid # # # ============================= # # Mixins & Tenant manager # # ============================= # class TimeStampedModel(models.Model): # created_at = models.DateTimeField(auto_now_add=True, db_index=True) # updated_at = models.DateTimeField(auto_now=True, db_index=True) # class Meta: # abstract = True # # class UserStampedModel(models.Model): # created_by = models.ForeignKey( # settings.AUTH_USER_MODEL, null=True, blank=True, # on_delete=models.SET_NULL, related_name="%(class)s_created" # ) # updated_by = models.ForeignKey( # settings.AUTH_USER_MODEL, null=True, blank=True, # on_delete=models.SET_NULL, related_name="%(class)s_updated" # ) # class Meta: # abstract = True # # class ActiveModel(models.Model): # is_active = models.BooleanField(default=True, db_index=True) # class Meta: # abstract = True # # class TenantManager(models.Manager): # def for_tenant(self, tenant): # return super().get_queryset().filter(tenant=tenant) # # class TenantScopedModel(TimeStampedModel, UserStampedModel, ActiveModel): # tenant = models.ForeignKey("core.Tenant", on_delete=models.CASCADE, db_index=True) # objects = TenantManager() # class Meta: # abstract = True # # # ============================= # # Core primitives # # ============================= # class Tenant(TimeStampedModel, ActiveModel): # code = models.SlugField(max_length=64, unique=True) # name = models.CharField(max_length=255) # status = models.CharField(max_length=32, default="active") # active|suspended|closed # def __str__(self): # return f"{self.code} – {self.name}" # # class Facility(TenantScopedModel): # code = models.SlugField(max_length=64) # name = models.CharField(max_length=255) # facility_type = models.CharField(max_length=64, blank=True, default="") # hospital|clinic|lab|... # address_line1 = models.CharField(max_length=255, blank=True, default="") # address_line2 = models.CharField(max_length=255, blank=True, default="") # city = models.CharField(max_length=120, blank=True, default="") # state = models.CharField(max_length=120, blank=True, default="") # postal_code = models.CharField(max_length=30, blank=True, default="") # country = models.CharField(max_length=2, blank=True, default="") # ISO-3166-1 alpha-2 # # class Meta: # unique_together = [("tenant", "code")] # indexes = [ # models.Index(fields=["tenant", "name"]), # models.Index(fields=["tenant", "facility_type"]), # ] # # def __str__(self): # return f"{self.code} – {self.name}" # # class Patient(TenantScopedModel): # uuid = models.UUIDField(unique=True, editable=False, null=True, blank=True, default=uuid.uuid4) # given_name = models.CharField(max_length=120) # family_name = models.CharField(max_length=120, blank=True, default="") # birth_date = models.DateField(null=True, blank=True) # sex = models.CharField(max_length=16, blank=True, default="") # male|female|other|unknown # primary_facility = models.ForeignKey("core.Facility", null=True, blank=True, on_delete=models.SET_NULL) # # class Meta: # indexes = [ # models.Index(fields=["tenant", "family_name", "given_name"]), # models.Index(fields=["tenant", "birth_date"]), # ] # # def __str__(self): # return f"{self.family_name}, {self.given_name}".strip(", ") # # class IdentifierType(TenantScopedModel): # code = models.SlugField(max_length=64) # name = models.CharField(max_length=120) # issuer = models.CharField(max_length=120, blank=True, default="") # e.g., MOH, facility code # # class Meta: # unique_together = [("tenant", "code")] # # def __str__(self): # return self.name # # class Identifier(TenantScopedModel): # patient = models.ForeignKey("core.Patient", on_delete=models.CASCADE, related_name="identifiers") # id_type = models.ForeignKey("core.IdentifierType", on_delete=models.PROTECT) # value = models.CharField(max_length=128) # is_primary = models.BooleanField(default=False) # period_start = models.DateField(null=True, blank=True) # period_end = models.DateField(null=True, blank=True) # # class Meta: # unique_together = [("tenant", "id_type", "value")] # indexes = [ # models.Index(fields=["tenant", "value"]), # models.Index(fields=["tenant", "patient"]), # ] # # def __str__(self): # return f"{self.id_type.code}:{self.value}" # # class Practitioner(TenantScopedModel): # """Clinical identity; keep HR employment in hr.Employee and auth in accounts.User.""" # user = models.ForeignKey("accounts.User", null=True, blank=True, on_delete=models.SET_NULL) # full_name = models.CharField(max_length=255) # license_id = models.CharField(max_length=80, blank=True, default="") # specialty = models.CharField(max_length=120, blank=True, default="") # npi = models.CharField(max_length=20, blank=True, default="") # if relevant in your locale # active_from = models.DateField(null=True, blank=True) # active_to = models.DateField(null=True, blank=True) # # class Meta: # indexes = [ # models.Index(fields=["tenant", "full_name"]), # ] # unique_together = [] # add ("tenant","license_id") if licenses are unique # # def __str__(self): # return self.full_name # # class Department(TenantScopedModel): # facility = models.ForeignKey("core.Facility", on_delete=models.PROTECT) # code = models.SlugField(max_length=64) # name = models.CharField(max_length=255) # class Meta: # unique_together = [("tenant", "facility", "code")] # indexes = [models.Index(fields=["tenant", "facility", "name"])] # def __str__(self): # return f"{self.facility.code}:{self.code}" # # class LocationNode(TenantScopedModel): # """Generic clinical location tree (building/floor/room/chair/etc.).""" # facility = models.ForeignKey("core.Facility", on_delete=models.PROTECT) # parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.CASCADE, related_name="children") # code = models.SlugField(max_length=64) # name = models.CharField(max_length=255) # kind = models.CharField(max_length=32, default="room") # building|floor|zone|room|chair|desk... # class Meta: # unique_together = [("tenant", "facility", "code")] # indexes = [models.Index(fields=["tenant", "facility", "kind"])] # def __str__(self): # return f"{self.facility.code}:{self.code}" # # class Encounter(TenantScopedModel): # class EncounterClass(models.TextChoices): # INPATIENT = "INPATIENT", "Inpatient" # OUTPATIENT = "OUTPATIENT", "Outpatient" # EMERGENCY = "EMERGENCY", "Emergency" # VIRTUAL = "VIRTUAL", "Virtual" # # class EncounterStatus(models.TextChoices): # PLANNED = "PLANNED", "Planned" # IN_PROGRESS = "IN_PROGRESS", "In Progress" # FINISHED = "FINISHED", "Finished" # CANCELLED = "CANCELLED", "Cancelled" # # patient = models.ForeignKey("core.Patient", on_delete=models.PROTECT, related_name="encounters") # facility = models.ForeignKey("core.Facility", on_delete=models.PROTECT, related_name="encounters") # encounter_class = models.CharField(max_length=20, choices=EncounterClass.choices) # status = models.CharField(max_length=20, choices=EncounterStatus.choices, default=EncounterStatus.PLANNED) # start_at = models.DateTimeField() # end_at = models.DateTimeField(null=True, blank=True) # attending = models.ForeignKey("core.Practitioner", null=True, blank=True, on_delete=models.SET_NULL, related_name="encounters_attending") # department = models.ForeignKey("core.Department", null=True, blank=True, on_delete=models.SET_NULL) # location_node = models.ForeignKey("core.LocationNode", null=True, blank=True, on_delete=models.SET_NULL) # # class Meta: # indexes = [ # models.Index(fields=["tenant", "patient", "start_at"]), # models.Index(fields=["tenant", "facility", "start_at"]), # models.Index(fields=["tenant", "encounter_class"]), # models.Index(fields=["tenant", "status"]), # ] # # def __str__(self): # return f"{self.patient_id} @ {self.facility_id} ({self.encounter_class})" # # class Address(TenantScopedModel): # line1 = models.CharField(max_length=255, blank=True, default="") # line2 = models.CharField(max_length=255, blank=True, default="") # city = models.CharField(max_length=120, blank=True, default="") # state = models.CharField(max_length=120, blank=True, default="") # postal_code = models.CharField(max_length=30, blank=True, default="") # country = models.CharField(max_length=2, blank=True, default="") # def __str__(self): # return ", ".join(x for x in [self.line1, self.city, self.state] if x) # # class ContactPoint(TenantScopedModel): # system = models.CharField(max_length=20) # phone|email # value = models.CharField(max_length=120) # use = models.CharField(max_length=20, blank=True, default="") # home|work|mobile # preferred = models.BooleanField(default=False) # class Meta: # indexes = [models.Index(fields=["tenant", "system", "value"])] # def __str__(self): # return f"{self.system}:{self.value}" # # class PatientContact(TenantScopedModel): # patient = models.ForeignKey("core.Patient", on_delete=models.CASCADE, related_name="contacts") # name = models.CharField(max_length=255, blank=True, default="") # relationship = models.CharField(max_length=40, blank=True, default="") # guardian|next-of-kin # address = models.ForeignKey("core.Address", null=True, blank=True, on_delete=models.SET_NULL) # phone = models.ForeignKey("core.ContactPoint", null=True, blank=True, on_delete=models.SET_NULL, related_name="pc_phone") # email = models.ForeignKey("core.ContactPoint", null=True, blank=True, on_delete=models.SET_NULL, related_name="pc_email") # # class Consent(TenantScopedModel): # patient = models.ForeignKey("core.Patient", on_delete=models.CASCADE, related_name="consents") # kind = models.CharField(max_length=40) # treatment|surgery|disclosure|photo # given_at = models.DateTimeField() # revoked_at = models.DateTimeField(null=True, blank=True) # details = models.JSONField(null=True, blank=True) # form id, signer, ip, scope, etc. # class Meta: # indexes = [models.Index(fields=["tenant", "patient", "kind", "given_at"])] # # class PatientMergeEvent(TenantScopedModel): # surviving = models.ForeignKey("core.Patient", on_delete=models.PROTECT, related_name="merges_surviving") # merged = models.ForeignKey("core.Patient", on_delete=models.PROTECT, related_name="merges_merged") # reason = models.CharField(max_length=120, blank=True, default="") # happened_at = models.DateTimeField(auto_now_add=True) # # class Coverage(TenantScopedModel): # """Patient's active insurance coverage (neutral, referenced by billing & insurance_approvals).""" # patient = models.ForeignKey("core.Patient", on_delete=models.CASCADE, related_name="coverages") # payer = models.ForeignKey("billing.Payer", on_delete=models.PROTECT) # keep payer master in billing # member_id = models.CharField(max_length=64) # plan_name = models.CharField(max_length=120, blank=True, default="") # period_start = models.DateField(null=True, blank=True) # period_end = models.DateField(null=True, blank=True) # is_primary = models.BooleanField(default=True) # class Meta: # indexes = [models.Index(fields=["tenant", "patient", "is_primary"])] # unique_together = [] # consider ("tenant","patient","payer","member_id") # # class CareTeam(TenantScopedModel): # patient = models.ForeignKey("core.Patient", on_delete=models.CASCADE) # encounter = models.ForeignKey("core.Encounter", null=True, blank=True, on_delete=models.SET_NULL) # # class CareTeamMember(TenantScopedModel): # careteam = models.ForeignKey("core.CareTeam", on_delete=models.CASCADE, related_name="members") # practitioner = models.ForeignKey("core.Practitioner", on_delete=models.PROTECT) # role = models.CharField(max_length=40, blank=True, default="") # attending|consultant|nurse # class Meta: # indexes = [models.Index(fields=["tenant", "role"])]] # # class Attachment(TenantScopedModel): # file = models.FileField(upload_to="attachments/%Y/%m/") # title = models.CharField(max_length=255, blank=True, default="") # content_type = models.CharField(max_length=120, blank=True, default="") # size_bytes = models.PositiveIntegerField(default=0) # patient = models.ForeignKey("core.Patient", null=True, blank=True, on_delete=models.SET_NULL, related_name="attachments") # encounter = models.ForeignKey("core.Encounter", null=True, blank=True, on_delete=models.SET_NULL, related_name="attachments") # # class Meta: # indexes = [ # models.Index(fields=["tenant", "patient"]), # models.Index(fields=["tenant", "encounter"]), # ] # # def __str__(self): # return self.title or self.file.name # # class AuditEvent(TenantScopedModel): # action = models.CharField(max_length=32) # create|update|delete|read|login|export|import|custom # actor = models.ForeignKey("accounts.User", null=True, blank=True, on_delete=models.SET_NULL, related_name="audit_events") # target_content_type = models.ForeignKey(ContentType, on_delete=models.SET_NULL, null=True, blank=True) # target_object_id = models.CharField(max_length=64, blank=True, default="") # target = GenericForeignKey("target_content_type", "target_object_id") # summary = models.CharField(max_length=255, blank=True, default="") # payload = models.JSONField(null=True, blank=True) # optional diff/extra data # occurred_at = models.DateTimeField(auto_now_add=True) # # class Meta: # indexes = [ # models.Index(fields=["tenant", "action", "occurred_at"]), # models.Index(fields=["tenant", "target_content_type", "target_object_id"]), # ] # # def __str__(self): # return f"[{self.action}] {self.summary or self.id}"