from decimal import Decimal from django.db import models from django.urls import reverse class PilotCall(models.Model): SOURCE_YTEL = 'ytel' SOURCE_RINGCENTRAL = 'ringcentral' SOURCE_MANUAL = 'manual' CALL_SOURCE_CHOICES = [ (SOURCE_YTEL, 'Ytel'), (SOURCE_RINGCENTRAL, 'RingCentral fallback'), (SOURCE_MANUAL, 'Manual/recording test'), ] STATUS_UNCONFIRMED = 'unconfirmed' STATUS_AVAILABLE = 'available' STATUS_BLOCKED = 'blocked' STATUS_PARTNER = 'partner' INTEGRATION_STATUS_CHOICES = [ (STATUS_UNCONFIRMED, 'Not confirmed'), (STATUS_AVAILABLE, 'Available'), (STATUS_BLOCKED, 'Blocked / not exposed'), (STATUS_PARTNER, 'Needs enterprise/partner access'), ] client_name = models.CharField(max_length=140) phone = models.CharField(max_length=40) email = models.EmailField(blank=True) call_source = models.CharField(max_length=24, choices=CALL_SOURCE_CHOICES, default=SOURCE_YTEL) ytel_audio_status = models.CharField(max_length=24, choices=INTEGRATION_STATUS_CHOICES, default=STATUS_UNCONFIRMED) logics_api_status = models.CharField(max_length=24, choices=INTEGRATION_STATUS_CHOICES, default=STATUS_UNCONFIRMED) transcript = models.TextField(help_text='Paste a live transcript segment or recorded-call transcript for the pilot.') irs_debt = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) state_debt = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) tax_years = models.CharField(max_length=120, blank=True) filing_status = models.CharField(max_length=80, blank=True) employment_type = models.CharField(max_length=120, blank=True) occupation = models.CharField(max_length=120, blank=True) monthly_gross_income = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) spouse_income = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) mortgage = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) rent = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) utilities = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) car_payment = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) insurance = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) student_loans = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) credit_cards = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) assets = models.TextField(blank=True) business_expenses = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) irs_payment_plan = models.BooleanField(default=False) levy = models.BooleanField(default=False) garnishment = models.BooleanField(default=False) best_callback_time = models.CharField(max_length=120, blank=True) agent_notes = models.TextField(blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-created_at'] verbose_name = 'pilot call' verbose_name_plural = 'pilot calls' def __str__(self): return f'{self.client_name} ยท {self.get_call_source_display()}' def get_absolute_url(self): return reverse('call_detail', args=[self.pk]) @property def required_fields(self): return [ ('IRS debt', self.irs_debt), ('Tax years', self.tax_years), ('Employment', self.employment_type), ('Monthly income', self.monthly_gross_income), ('Best callback', self.best_callback_time), ] @property def completion_score(self): if not self.required_fields: return 0 completed = sum(1 for _label, value in self.required_fields if self._has_value(value)) return round((completed / len(self.required_fields)) * 100) @property def missing_required_labels(self): return [label for label, value in self.required_fields if not self._has_value(value)] @property def urgent_flags(self): flags = [] if self.levy: flags.append('Levy mentioned') if self.garnishment: flags.append('Wage garnishment mentioned') if self.irs_debt and self.irs_debt > Decimal('50000'): flags.append('Debt over $50,000') if self.monthly_gross_income is not None and self.monthly_gross_income <= 0: flags.append('No monthly income captured') return flags @staticmethod def _has_value(value): if value is None: return False if isinstance(value, str): return bool(value.strip()) return True class WebhookEvent(models.Model): PROVIDER_GENERIC = 'generic' provider = models.CharField(max_length=60, default=PROVIDER_GENERIC) event_type = models.CharField(max_length=120, blank=True) external_id = models.CharField(max_length=160, blank=True) delivery_id = models.CharField(max_length=160, blank=True) source_ip = models.GenericIPAddressField(null=True, blank=True) content_type = models.CharField(max_length=180, blank=True) headers = models.JSONField(default=dict, blank=True) query_params = models.JSONField(default=dict, blank=True) payload = models.JSONField(default=dict, blank=True) raw_body = models.TextField(blank=True) processed = models.BooleanField(default=False) notes = models.TextField(blank=True) received_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-received_at'] indexes = [ models.Index(fields=['provider', 'event_type']), models.Index(fields=['received_at']), ] verbose_name = 'webhook event' verbose_name_plural = 'webhook events' def __str__(self): label = self.event_type or self.external_id or f'event #{self.pk}' return f'{self.provider}: {label}' @property def display_event_type(self): return self.event_type or 'unclassified'