# documentation/models.py 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 class NoteType(models.TextChoices): ADMISSION = "ADMISSION", "Admission Note" PROGRESS = "PROGRESS", "Progress Note" DISCHARGE = "DISCHARGE", "Discharge Summary" RADIOLOGY_REPORT = "RADIOLOGY_REPORT", "Radiology Report" LAB_REPORT = "LAB_REPORT", "Laboratory Narrative" PROCEDURE = "PROCEDURE", "Procedure Note" ANESTHESIA = "ANESTHESIA", "Anesthesia Note" OTHER = "OTHER", "Other" class Document(models.Model): """ Canonical clinical document (note/report). Stores content, metadata, provenance, and links to any domain object. """ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) tenant = models.ForeignKey("core.Tenant", on_delete=models.CASCADE, related_name="documents") patient = models.ForeignKey("core.Patient", on_delete=models.CASCADE, related_name="documents") encounter = models.ForeignKey("core.Encounter", on_delete=models.SET_NULL, null=True, blank=True, related_name="documents") doc_type = models.CharField(max_length=40, choices=NoteType.choices) title = models.CharField(max_length=255) status = models.CharField(max_length=20, default="final") # draft | amended | final | entered-in-error authored_at = models.DateTimeField(auto_now_add=True) authored_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name="documents_authored") signed_at = models.DateTimeField(null=True, blank=True) signed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="documents_signed") # Content # Store both rich text and structured JSON to support templates, tokens, smart fields. body_markdown = models.TextField(blank=True, default="") body_json = models.JSONField(blank=True, null=True) # optional structured representation # FHIR mapping hints (optional) fhir_profile = models.CharField(max_length=120, blank=True, default="") # e.g., Composition/DiagnosticReport code = models.CharField(max_length=64, blank=True, default="") # LOINC/SNOMED if used # Full-text search (enable GIN index in migration) search_vector = models.TextField(blank=True, default="") # or use a dedicated SearchVector field # Soft flags is_confidential = models.BooleanField(default=False) is_amendment = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class DocumentVersion(models.Model): document = models.ForeignKey(Document, on_delete=models.CASCADE, related_name="versions") version = models.PositiveIntegerField() snapshot_markdown = models.TextField(blank=True, default="") snapshot_json = models.JSONField(blank=True, null=True) changed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True) changed_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = [("document", "version")] ordering = ["-version"] class DocumentLink(models.Model): """ Generic relation to ANY object (lab order, study, admission, procedure, etc.) Allows many-to-many linking without embedding foreign keys in every domain app. """ document = models.ForeignKey(Document, on_delete=models.CASCADE, related_name="links") content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.CharField(max_length=64) target = GenericForeignKey("content_type", "object_id") role = models.CharField(max_length=40, blank=True, default="context") # context | source | result | followup created_at = models.DateTimeField(auto_now_add=True) class DocumentAttachment(models.Model): document = models.ForeignKey(Document, on_delete=models.CASCADE, related_name="attachments") file = models.FileField(upload_to="documents/%Y/%m/") title = models.CharField(max_length=255, blank=True, default="") created_at = models.DateTimeField(auto_now_add=True)