documentverification/verifier/models.py
vickytechkey 418c7e681d
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 30s
feat: add verification models, admin configuration, views, tests, and database migrations
2026-10-17 13:50:16 +05:30

192 lines
8.8 KiB
Python

import uuid
from django.db import models
class VendorConfig(models.Model):
"""
Dynamic configuration for verification vendors stored in database.
Allows toggling active state, priority adjustments, and credential updates via Django admin.
"""
name = models.CharField(max_length=50, unique=True, help_text="Unique vendor identifier, e.g. codeplus")
display_name = models.CharField(max_length=100, help_text="Human-readable name")
is_active = models.BooleanField(default=True)
priority = models.PositiveIntegerField(
default=10,
help_text="Waterfall execution order (lower numbers execute first)",
)
supported_documents = models.JSONField(
default=list,
help_text="List of supported doc types: ['pan', 'aadhaar', 'driving_license', 'voter_id', ...]",
)
base_url = models.CharField(max_length=255, blank=True, null=True)
api_key = models.CharField(max_length=255, blank=True, null=True)
extra_credentials = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["priority", "name"]
verbose_name = "Vendor Configuration"
verbose_name_plural = "Vendor Configurations"
def __str__(self):
return f"{self.display_name} ({self.name}) - Priority: {self.priority} - {'Active' if self.is_active else 'Disabled'}"
class VerificationTransaction(models.Model):
"""
Master record of every document verification request processed by the engine.
"""
STATUS_CHOICES = (
("PENDING", "Pending"),
("SUCCESS", "Success"),
("FAILED", "Failed"),
("ERROR", "Error"),
)
reference_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True)
document_type = models.CharField(max_length=50, db_index=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="PENDING", db_index=True)
successful_vendor = models.CharField(max_length=50, blank=True, null=True)
input_payload = models.JSONField(default=dict, blank=True)
final_data = models.JSONField(default=dict, blank=True)
response_payload = models.JSONField(default=dict, blank=True)
attempts_count = models.PositiveIntegerField(default=0)
total_latency_ms = models.FloatField(default=0.0)
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["-created_at"]
verbose_name = "Verification Transaction"
verbose_name_plural = "Verification Transactions"
def __str__(self):
return f"[{self.status}] {self.document_type} - {self.reference_id} ({self.successful_vendor or 'none'})"
class VendorAttemptLog(models.Model):
"""
Audit log of each individual vendor attempt in the waterfall cascade.
Allows inspection of which vendor failed, why, latency, and full payload details.
"""
transaction = models.ForeignKey(
VerificationTransaction,
related_name="attempts",
on_delete=models.CASCADE,
)
attempt_number = models.PositiveIntegerField()
vendor_name = models.CharField(max_length=50, db_index=True)
status = models.CharField(max_length=20)
is_success = models.BooleanField(default=False)
data = models.JSONField(default=dict, blank=True)
raw_response = models.JSONField(null=True, blank=True)
error_code = models.CharField(max_length=100, blank=True, null=True)
error_message = models.TextField(blank=True, null=True)
latency_ms = models.FloatField(default=0.0)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["attempt_number"]
verbose_name = "Vendor Attempt Log"
verbose_name_plural = "Vendor Attempt Logs"
def __str__(self):
return f"Tx: {self.transaction.reference_id} | #{self.attempt_number} {self.vendor_name} -> {self.status}"
class PANDetails(models.Model):
"""
Dedicated table storing granular fields from Detailed PAN verification
for analysis, reporting, and KYC intelligence.
"""
transaction = models.ForeignKey(
VerificationTransaction,
related_name="pan_details_record",
on_delete=models.SET_NULL,
null=True,
blank=True,
help_text="Associated verification transaction",
)
document_type = models.CharField(max_length=20, default="PAN", db_index=True)
document_id = models.CharField(max_length=20, db_index=True, help_text="10-digit PAN Number (e.g. AVEPV5707G)")
name = models.CharField(max_length=255, db_index=True, help_text="Full Name on PAN card")
first_name = models.CharField(max_length=150, blank=True, null=True)
last_name = models.CharField(max_length=150, blank=True, null=True)
category = models.CharField(max_length=10, blank=True, null=True, help_text="PAN holder category (e.g. P for Person/Individual)")
date_of_birth = models.CharField(max_length=50, blank=True, null=True, help_text="YYYY-MM-DD or DD/MM/YYYY")
gender = models.CharField(max_length=20, blank=True, null=True)
masked_aadhaar_number = models.CharField(max_length=30, blank=True, null=True, help_text="e.g. XXXXXXXX5139")
aadhaar_linked = models.BooleanField(default=False, help_text="Whether Aadhaar is seeded/linked with PAN")
taxpayer_profile = models.BooleanField(default=False, help_text="Active taxpayer profile flag")
# Contact Info
email = models.CharField(max_length=255, blank=True, null=True)
phone = models.CharField(max_length=50, blank=True, null=True)
# Address breakdown columns for regional & geographical analytics
address_line_1 = models.CharField(max_length=255, blank=True, null=True)
street = models.CharField(max_length=255, blank=True, null=True)
city = models.CharField(max_length=150, blank=True, null=True, db_index=True)
state = models.CharField(max_length=100, blank=True, null=True, db_index=True)
pincode = models.CharField(max_length=20, blank=True, null=True, db_index=True)
address_data = models.JSONField(default=dict, blank=True, null=True, help_text="Full structured address object")
# API tracking & Complete JSON payloads for future data science & audit
request_id = models.CharField(max_length=100, blank=True, null=True, db_index=True)
vendor_txn_id = models.CharField(max_length=100, blank=True, null=True, db_index=True)
full_response = models.JSONField(default=dict, blank=True, null=True, help_text="Entire uncompressed response payload from vendor")
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "pan_details"
ordering = ["-created_at"]
verbose_name = "PAN Detail"
verbose_name_plural = "PAN Details"
def __str__(self):
return f"{self.document_id} - {self.name} ({self.city or 'Unknown'}, {self.state or 'Unknown'})"
class SellerBankVerification(models.Model):
"""
Dedicated bank verification record for seller onboarding and settlements,
keyed by seller_id as primary key.
"""
seller_id = models.CharField(max_length=100, primary_key=True, help_text="Unique Seller / Supplier ID")
transaction = models.ForeignKey(
VerificationTransaction,
related_name="seller_bank_verifications",
on_delete=models.SET_NULL,
null=True,
blank=True,
help_text="Associated verification transaction",
)
account_number = models.CharField(max_length=50)
ifsc = models.CharField(max_length=20)
account_ifsc = models.CharField(max_length=20, blank=True, null=True)
beneficiary_name = models.CharField(max_length=255, blank=True, null=True)
is_valid = models.BooleanField(default=False)
account_exists = models.BooleanField(default=False)
failure_reason = models.TextField(blank=True, null=True)
npci_error_code = models.CharField(max_length=100, blank=True, null=True)
tranx_id = models.CharField(max_length=100, blank=True, null=True, db_index=True)
timestamp = models.CharField(max_length=100, blank=True, null=True)
user_consent = models.CharField(max_length=50, default="true")
vendor_name = models.CharField(max_length=50, default="codeplus")
status = models.CharField(max_length=50, default="PENDING")
latency_ms = models.FloatField(default=0.0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "seller_bank_verification"
verbose_name = "Seller Bank Verification"
verbose_name_plural = "Seller Bank Verifications"
def __str__(self):
return f"Seller {self.seller_id} Bank: {self.beneficiary_name or self.account_number} ({'Valid' if self.is_valid else 'Invalid'})"