""" Insurance Approvals app models for hospital management system. Provides universal insurance approval request management for all order types. """ import uuid from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.utils import timezone from django.conf import settings from datetime import timedelta from decimal import Decimal class InsuranceApprovalRequest(models.Model): """ Universal insurance approval request for any order type. Uses GenericForeignKey for polymorphic relationships. """ class RequestType(models.TextChoices): LABORATORY = 'LABORATORY', 'Laboratory Test' RADIOLOGY = 'RADIOLOGY', 'Radiology/Imaging' PHARMACY = 'PHARMACY', 'Medication/Prescription' PROCEDURE = 'PROCEDURE', 'Medical Procedure' SURGERY = 'SURGERY', 'Surgical Procedure' THERAPY = 'THERAPY', 'Therapy Services' DME = 'DME', 'Durable Medical Equipment' HOME_HEALTH = 'HOME_HEALTH', 'Home Health Services' OTHER = 'OTHER', 'Other Services' class ApprovalStatus(models.TextChoices): DRAFT = 'DRAFT', 'Draft' PENDING_SUBMISSION = 'PENDING_SUBMISSION', 'Pending Submission' SUBMITTED = 'SUBMITTED', 'Submitted to Insurance' UNDER_REVIEW = 'UNDER_REVIEW', 'Under Review' ADDITIONAL_INFO_REQUESTED = 'ADDITIONAL_INFO_REQUESTED', 'Additional Info Requested' APPROVED = 'APPROVED', 'Approved' PARTIALLY_APPROVED = 'PARTIALLY_APPROVED', 'Partially Approved' DENIED = 'DENIED', 'Denied' APPEALED = 'APPEALED', 'Appealed' APPEAL_APPROVED = 'APPEAL_APPROVED', 'Appeal Approved' APPEAL_DENIED = 'APPEAL_DENIED', 'Appeal Denied' EXPIRED = 'EXPIRED', 'Expired' CANCELLED = 'CANCELLED', 'Cancelled' class Priority(models.TextChoices): ROUTINE = 'ROUTINE', 'Routine' URGENT = 'URGENT', 'Urgent' STAT = 'STAT', 'STAT' EMERGENCY = 'EMERGENCY', 'Emergency' class SubmissionMethod(models.TextChoices): FAX = 'FAX', 'Fax' PHONE = 'PHONE', 'Phone' PORTAL = 'PORTAL', 'Insurance Portal' EMAIL = 'EMAIL', 'Email' MAIL = 'MAIL', 'Mail' EDI = 'EDI', 'Electronic Data Interchange' # Tenant tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='approval_requests', help_text='Organization tenant' ) # Identifiers approval_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique approval identifier' ) approval_number = models.CharField( max_length=30, unique=True, help_text='Approval request number' ) # Patient & Insurance patient = models.ForeignKey( 'patients.PatientProfile', on_delete=models.CASCADE, related_name='approval_requests', help_text='Patient' ) insurance_info = models.ForeignKey( 'patients.InsuranceInfo', on_delete=models.CASCADE, related_name='approval_requests', help_text='Insurance information' ) # Polymorphic relationship to any order (optional - can create approval before order) content_type = models.ForeignKey( ContentType, on_delete=models.CASCADE, null=True, blank=True, help_text='Type of order (Lab, Radiology, Pharmacy, etc.)' ) object_id = models.PositiveIntegerField( null=True, blank=True, help_text='ID of the related order' ) order = GenericForeignKey('content_type', 'object_id') # Request Details request_type = models.CharField( max_length=20, choices=RequestType.choices, help_text='Type of approval request' ) service_description = models.CharField( max_length=500, help_text='Description of service/procedure' ) # Medical Codes procedure_codes = models.JSONField( default=list, help_text='CPT/HCPCS procedure codes' ) diagnosis_codes = models.JSONField( default=list, help_text='ICD-10 diagnosis codes' ) # Clinical Information clinical_justification = models.TextField( help_text='Clinical justification for service' ) medical_necessity = models.TextField( blank=True, null=True, help_text='Medical necessity statement' ) alternative_treatments_tried = models.TextField( blank=True, null=True, help_text='Alternative treatments attempted' ) # Requested Services requested_quantity = models.PositiveIntegerField( default=1, help_text='Quantity requested' ) requested_visits = models.PositiveIntegerField( blank=True, null=True, help_text='Number of visits requested' ) requested_units = models.PositiveIntegerField( blank=True, null=True, help_text='Number of units requested' ) service_start_date = models.DateField( help_text='Requested service start date' ) service_end_date = models.DateField( blank=True, null=True, help_text='Requested service end date' ) # Status & Priority status = models.CharField( max_length=30, choices=ApprovalStatus.choices, default=ApprovalStatus.DRAFT, help_text='Current approval status' ) priority = models.CharField( max_length=20, choices=Priority.choices, default=Priority.ROUTINE, help_text='Request priority' ) # Submission Details submission_method = models.CharField( max_length=20, choices=SubmissionMethod.choices, blank=True, null=True, help_text='Method used to submit request' ) submitted_date = models.DateTimeField( blank=True, null=True, help_text='Date and time submitted to insurance' ) submitted_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='submitted_approvals', help_text='User who submitted the request' ) # Insurance Response decision_date = models.DateTimeField( blank=True, null=True, help_text='Date insurance made decision' ) authorization_number = models.CharField( max_length=100, blank=True, null=True, help_text='Insurance authorization number' ) reference_number = models.CharField( max_length=100, blank=True, null=True, help_text='Insurance reference number' ) # Approved Details approved_quantity = models.PositiveIntegerField( blank=True, null=True, help_text='Approved quantity' ) approved_visits = models.PositiveIntegerField( blank=True, null=True, help_text='Approved number of visits' ) approved_units = models.PositiveIntegerField( blank=True, null=True, help_text='Approved number of units' ) approved_amount = models.DecimalField( max_digits=12, decimal_places=2, blank=True, null=True, help_text='Approved dollar amount' ) # Validity Period effective_date = models.DateField( blank=True, null=True, help_text='Authorization effective date' ) expiration_date = models.DateField( blank=True, null=True, help_text='Authorization expiration date' ) # Denial Information denial_reason = models.TextField( blank=True, null=True, help_text='Reason for denial' ) denial_code = models.CharField( max_length=50, blank=True, null=True, help_text='Insurance denial code' ) # Appeal Information appeal_date = models.DateTimeField( blank=True, null=True, help_text='Date appeal was filed' ) appeal_reason = models.TextField( blank=True, null=True, help_text='Reason for appeal' ) appeal_deadline = models.DateField( blank=True, null=True, help_text='Deadline to file appeal' ) # Assignment & Tracking assigned_to = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='assigned_approvals', help_text='Staff member assigned to this request' ) requesting_provider = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='requested_approvals', help_text='Provider requesting the approval' ) # Communication Log last_contact_date = models.DateTimeField( blank=True, null=True, help_text='Last contact with insurance' ) last_contact_method = models.CharField( max_length=20, blank=True, null=True, help_text='Method of last contact' ) last_contact_notes = models.TextField( blank=True, null=True, help_text='Notes from last contact' ) # Flags is_urgent = models.BooleanField( default=False, help_text='Urgent request' ) is_expedited = models.BooleanField( default=False, help_text='Expedited processing requested' ) requires_peer_review = models.BooleanField( default=False, help_text='Requires peer-to-peer review' ) # Notes internal_notes = models.TextField( blank=True, null=True, help_text='Internal staff notes' ) insurance_notes = models.TextField( blank=True, null=True, help_text='Notes from insurance company' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_approvals', help_text='User who created the request' ) class Meta: db_table = 'insurance_approvals_request' verbose_name = 'Insurance Approval Request' verbose_name_plural = 'Insurance Approval Requests' ordering = ['-created_at'] indexes = [ models.Index(fields=['tenant', 'status']), models.Index(fields=['patient', 'status']), models.Index(fields=['insurance_info', 'status']), models.Index(fields=['approval_number']), models.Index(fields=['authorization_number']), models.Index(fields=['expiration_date']), models.Index(fields=['assigned_to', 'status']), models.Index(fields=['content_type', 'object_id']), models.Index(fields=['priority', 'status']), ] def __str__(self): return f"{self.approval_number} - {self.patient.get_full_name()} - {self.get_request_type_display()}" def save(self, *args, **kwargs): """Generate approval number if not provided.""" if not self.approval_number: today = timezone.now().date() last_approval = InsuranceApprovalRequest.objects.filter( tenant=self.tenant, created_at__date=today ).order_by('-id').first() if last_approval: last_num = int(last_approval.approval_number.split('-')[-1]) self.approval_number = f"AUTH-{today.strftime('%Y%m%d')}-{last_num + 1:04d}" else: self.approval_number = f"AUTH-{today.strftime('%Y%m%d')}-0001" super().save(*args, **kwargs) @property def is_approved(self): """Check if request is approved.""" return self.status in ['APPROVED', 'PARTIALLY_APPROVED', 'APPEAL_APPROVED'] @property def is_denied(self): """Check if request is denied.""" return self.status in ['DENIED', 'APPEAL_DENIED'] @property def is_expired(self): """Check if authorization is expired.""" if self.expiration_date: return timezone.now().date() > self.expiration_date return False @property def days_until_expiry(self): """Calculate days until expiration.""" if self.expiration_date: return (self.expiration_date - timezone.now().date()).days return None @property def is_expiring_soon(self): """Check if expiring within 30 days.""" days = self.days_until_expiry return days is not None and 0 < days <= 30 @property def can_appeal(self): """Check if request can be appealed.""" if not self.is_denied: return False if not self.appeal_deadline: return True return timezone.now().date() <= self.appeal_deadline @property def turnaround_time_days(self): """Calculate turnaround time in days.""" if self.submitted_date and self.decision_date: return (self.decision_date - self.submitted_date).days return None class ApprovalDocument(models.Model): """Supporting documents for approval requests.""" class DocumentType(models.TextChoices): MEDICAL_RECORDS = 'MEDICAL_RECORDS', 'Medical Records' LAB_RESULTS = 'LAB_RESULTS', 'Lab Results' IMAGING_REPORTS = 'IMAGING_REPORTS', 'Imaging Reports' CLINICAL_NOTES = 'CLINICAL_NOTES', 'Clinical Notes' PRESCRIPTION = 'PRESCRIPTION', 'Prescription' LETTER_OF_MEDICAL_NECESSITY = 'LETTER_OF_MEDICAL_NECESSITY', 'Letter of Medical Necessity' PRIOR_AUTH_FORM = 'PRIOR_AUTH_FORM', 'Prior Authorization Form' INSURANCE_CARD = 'INSURANCE_CARD', 'Insurance Card' CONSENT_FORM = 'CONSENT_FORM', 'Consent Form' APPEAL_LETTER = 'APPEAL_LETTER', 'Appeal Letter' PEER_REVIEW = 'PEER_REVIEW', 'Peer Review Documentation' OTHER = 'OTHER', 'Other' approval_request = models.ForeignKey( InsuranceApprovalRequest, on_delete=models.CASCADE, related_name='documents', help_text='Related approval request' ) document_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique document identifier' ) document_type = models.CharField( max_length=30, choices=DocumentType.choices, help_text='Type of document' ) title = models.CharField( max_length=200, help_text='Document title' ) description = models.TextField( blank=True, null=True, help_text='Document description' ) file = models.FileField( upload_to='approval_documents/%Y/%m/', help_text='Document file' ) file_size = models.PositiveIntegerField( help_text='File size in bytes' ) mime_type = models.CharField( max_length=100, help_text='MIME type' ) uploaded_at = models.DateTimeField(auto_now_add=True) uploaded_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='uploaded_approval_documents', help_text='User who uploaded the document' ) class Meta: db_table = 'insurance_approvals_document' verbose_name = 'Approval Document' verbose_name_plural = 'Approval Documents' ordering = ['-uploaded_at'] indexes = [ models.Index(fields=['approval_request', 'document_type']), ] def __str__(self): return f"{self.title} - {self.approval_request.approval_number}" @property def file_size_mb(self): """Get file size in MB.""" return round(self.file_size / (1024 * 1024), 2) class ApprovalStatusHistory(models.Model): """Audit trail for approval status changes.""" approval_request = models.ForeignKey( InsuranceApprovalRequest, on_delete=models.CASCADE, related_name='status_history', help_text='Related approval request' ) from_status = models.CharField( max_length=30, blank=True, null=True, help_text='Previous status' ) to_status = models.CharField( max_length=30, help_text='New status' ) reason = models.TextField( blank=True, null=True, help_text='Reason for status change' ) notes = models.TextField( blank=True, null=True, help_text='Additional notes' ) changed_at = models.DateTimeField(auto_now_add=True) changed_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='approval_status_changes', help_text='User who made the change' ) class Meta: db_table = 'insurance_approvals_status_history' verbose_name = 'Approval Status History' verbose_name_plural = 'Approval Status Histories' ordering = ['-changed_at'] indexes = [ models.Index(fields=['approval_request', 'changed_at']), ] def __str__(self): return f"{self.approval_request.approval_number}: {self.from_status} → {self.to_status}" class ApprovalCommunicationLog(models.Model): """Log of all communications with insurance company.""" class CommunicationType(models.TextChoices): PHONE_CALL = 'PHONE_CALL', 'Phone Call' FAX_SENT = 'FAX_SENT', 'Fax Sent' FAX_RECEIVED = 'FAX_RECEIVED', 'Fax Received' EMAIL_SENT = 'EMAIL_SENT', 'Email Sent' EMAIL_RECEIVED = 'EMAIL_RECEIVED', 'Email Received' PORTAL_MESSAGE = 'PORTAL_MESSAGE', 'Portal Message' MAIL_SENT = 'MAIL_SENT', 'Mail Sent' MAIL_RECEIVED = 'MAIL_RECEIVED', 'Mail Received' IN_PERSON = 'IN_PERSON', 'In Person' approval_request = models.ForeignKey( InsuranceApprovalRequest, on_delete=models.CASCADE, related_name='communications', help_text='Related approval request' ) communication_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique communication identifier' ) communication_type = models.CharField( max_length=20, choices=CommunicationType.choices, help_text='Type of communication' ) contact_person = models.CharField( max_length=200, blank=True, null=True, help_text='Insurance contact person' ) contact_number = models.CharField( max_length=50, blank=True, null=True, help_text='Contact phone/fax number' ) subject = models.CharField( max_length=200, help_text='Communication subject' ) message = models.TextField( help_text='Message content' ) response = models.TextField( blank=True, null=True, help_text='Response received' ) outcome = models.CharField( max_length=200, blank=True, null=True, help_text='Communication outcome' ) follow_up_required = models.BooleanField( default=False, help_text='Follow-up required' ) follow_up_date = models.DateField( blank=True, null=True, help_text='Follow-up date' ) communicated_at = models.DateTimeField(auto_now_add=True) communicated_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='approval_communications', help_text='User who made the communication' ) class Meta: db_table = 'insurance_approvals_communication_log' verbose_name = 'Approval Communication Log' verbose_name_plural = 'Approval Communication Logs' ordering = ['-communicated_at'] indexes = [ models.Index(fields=['approval_request', 'communicated_at']), models.Index(fields=['follow_up_required', 'follow_up_date']), ] def __str__(self): return f"{self.approval_request.approval_number} - {self.get_communication_type_display()} - {self.communicated_at.date()}" class ApprovalTemplate(models.Model): """Templates for common approval requests.""" tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='approval_templates', help_text='Organization tenant' ) template_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique template identifier' ) name = models.CharField( max_length=200, help_text='Template name' ) description = models.TextField( blank=True, null=True, help_text='Template description' ) request_type = models.CharField( max_length=20, help_text='Type of request this template is for' ) insurance_company = models.CharField( max_length=200, blank=True, null=True, help_text='Specific insurance company (optional)' ) # Template content clinical_justification_template = models.TextField( help_text='Template for clinical justification' ) medical_necessity_template = models.TextField( blank=True, null=True, help_text='Template for medical necessity statement' ) required_documents = models.JSONField( default=list, help_text='List of required document types' ) required_codes = models.JSONField( default=dict, help_text='Required procedure/diagnosis codes' ) is_active = models.BooleanField( default=True, help_text='Template is active' ) usage_count = models.PositiveIntegerField( default=0, help_text='Number of times template has been used' ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_approval_templates', help_text='User who created the template' ) class Meta: db_table = 'insurance_approvals_template' verbose_name = 'Approval Template' verbose_name_plural = 'Approval Templates' ordering = ['name'] indexes = [ models.Index(fields=['tenant', 'is_active']), models.Index(fields=['request_type']), ] unique_together = ['tenant', 'name'] def __str__(self): return f"{self.name} ({self.request_type})"