saas-market-analysis-dubai/apps/analytics/models.py
2025-09-17 03:04:22 +05:30

352 lines
14 KiB
Python

"""
Analytics models for Dubai Land Department data.
"""
from django.db import models
from django.contrib.postgres.indexes import BTreeIndex
# JSONField is now in django.db.models in Django 3.1+
from apps.core.models import TimeStampedModel
import uuid
class Broker(TimeStampedModel):
"""Real estate brokers data."""
broker_number = models.CharField(max_length=20, unique=True, db_index=True)
broker_name_en = models.CharField(max_length=255)
gender = models.CharField(max_length=10, choices=[
('male', 'Male'),
('female', 'Female'),
('أنثى', 'Female (Arabic)'),
])
license_start_date = models.DateTimeField()
license_end_date = models.DateTimeField()
webpage = models.URLField(blank=True, null=True)
phone = models.CharField(max_length=50, blank=True)
fax = models.CharField(max_length=50, blank=True)
real_estate_number = models.CharField(max_length=20, blank=True)
real_estate_name_en = models.CharField(max_length=255, blank=True)
class Meta:
db_table = 'brokers'
indexes = [
BTreeIndex(fields=['broker_number']),
BTreeIndex(fields=['real_estate_name_en']),
BTreeIndex(fields=['license_end_date']),
]
def __str__(self):
return f"{self.broker_name_en} ({self.broker_number})"
class Developer(TimeStampedModel):
"""Real estate developers data."""
developer_number = models.CharField(max_length=20, unique=True, db_index=True)
developer_name_en = models.CharField(max_length=255)
class Meta:
db_table = 'developers'
def __str__(self):
return f"{self.developer_name_en} ({self.developer_number})"
class Project(TimeStampedModel):
"""Real estate projects data."""
project_number = models.CharField(max_length=20, unique=True, db_index=True)
project_name_en = models.CharField(max_length=255)
developer = models.ForeignKey(Developer, on_delete=models.CASCADE, related_name='projects')
start_date = models.DateTimeField()
end_date = models.DateTimeField(null=True, blank=True)
adoption_date = models.DateTimeField(null=True, blank=True)
project_type = models.CharField(max_length=50, choices=[
('Normal', 'Normal'),
('Escrow', 'Escrow'),
])
project_value = models.DecimalField(max_digits=15, decimal_places=2, null=True, blank=True)
escrow_account_number = models.CharField(max_length=50, blank=True)
project_status = models.CharField(max_length=20, choices=[
('ACTIVE', 'Active'),
('PENDING', 'Pending'),
('CANCELLED', 'Cancelled'),
('COMPLETED', 'Completed'),
])
percent_completed = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
inspection_date = models.DateTimeField(null=True, blank=True)
completion_date = models.DateTimeField(null=True, blank=True)
description_en = models.TextField(blank=True)
area_en = models.CharField(max_length=100)
zone_en = models.CharField(max_length=100)
count_land = models.IntegerField(default=0)
count_building = models.IntegerField(default=0)
count_villa = models.IntegerField(default=0)
count_unit = models.IntegerField(default=0)
master_project_en = models.CharField(max_length=255, blank=True)
class Meta:
db_table = 'projects'
indexes = [
BTreeIndex(fields=['project_number']),
BTreeIndex(fields=['project_status']),
BTreeIndex(fields=['area_en']),
BTreeIndex(fields=['zone_en']),
BTreeIndex(fields=['developer']),
]
def __str__(self):
return f"{self.project_name_en} ({self.project_number})"
class Land(TimeStampedModel):
"""Land data."""
land_type = models.CharField(max_length=50, choices=[
('Agricultural', 'Agricultural'),
('Commercial', 'Commercial'),
('Residential', 'Residential'),
('Industrial', 'Industrial'),
])
property_sub_type = models.CharField(max_length=50)
actual_area = models.DecimalField(max_digits=10, decimal_places=2)
is_offplan = models.CharField(max_length=10, choices=[
('Ready', 'Ready'),
('Off-Plan', 'Off-Plan'),
])
pre_registration_number = models.CharField(max_length=50, blank=True)
is_freehold = models.CharField(max_length=20, choices=[
('Free Hold', 'Free Hold'),
('Non Free Hold', 'Non Free Hold'),
])
dm_zip_code = models.CharField(max_length=10, blank=True)
master_project = models.CharField(max_length=255, blank=True)
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, related_name='lands')
area_en = models.CharField(max_length=100)
zone_en = models.CharField(max_length=100)
class Meta:
db_table = 'lands'
indexes = [
BTreeIndex(fields=['land_type']),
BTreeIndex(fields=['area_en']),
BTreeIndex(fields=['zone_en']),
BTreeIndex(fields=['is_freehold']),
BTreeIndex(fields=['actual_area']),
]
def __str__(self):
return f"{self.area_en} - {self.land_type} ({self.actual_area} sqft)"
class Transaction(TimeStampedModel):
"""Real estate transactions data."""
transaction_number = models.CharField(max_length=50, unique=True, db_index=True)
instance_date = models.DateTimeField()
group = models.CharField(max_length=50, choices=[
('Mortgage', 'Mortgage'),
('Sale', 'Sale'),
('Rent', 'Rent'),
('Other', 'Other'),
])
procedure = models.CharField(max_length=100)
is_offplan = models.CharField(max_length=10, choices=[
('Off-Plan', 'Off-Plan'),
('Ready', 'Ready'),
])
is_freehold = models.CharField(max_length=20, choices=[
('Free Hold', 'Free Hold'),
('Non Free Hold', 'Non Free Hold'),
])
usage = models.CharField(max_length=50, choices=[
('Residential', 'Residential'),
('Commercial', 'Commercial'),
('Industrial', 'Industrial'),
('Mixed', 'Mixed'),
])
area_en = models.CharField(max_length=100)
property_type = models.CharField(max_length=50, choices=[
('Unit', 'Unit'),
('Villa', 'Villa'),
('Land', 'Land'),
('Building', 'Building'),
])
property_sub_type = models.CharField(max_length=50)
transaction_value = models.DecimalField(max_digits=15, decimal_places=2)
procedure_area = models.DecimalField(max_digits=10, decimal_places=2)
actual_area = models.DecimalField(max_digits=10, decimal_places=2)
rooms = models.CharField(max_length=20, blank=True)
parking = models.CharField(max_length=20, blank=True)
nearest_metro = models.CharField(max_length=100, blank=True)
nearest_mall = models.CharField(max_length=100, blank=True)
nearest_landmark = models.CharField(max_length=100, blank=True)
total_buyer = models.IntegerField(default=0)
total_seller = models.IntegerField(default=0)
master_project = models.CharField(max_length=255, blank=True)
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, related_name='transactions')
class Meta:
db_table = 'transactions'
indexes = [
BTreeIndex(fields=['transaction_number']),
BTreeIndex(fields=['instance_date']),
BTreeIndex(fields=['area_en']),
BTreeIndex(fields=['property_type']),
BTreeIndex(fields=['transaction_value']),
BTreeIndex(fields=['group']),
BTreeIndex(fields=['usage']),
]
def __str__(self):
return f"{self.transaction_number} - {self.area_en} - {self.transaction_value}"
@property
def price_per_sqft(self):
"""Calculate price per square foot."""
if self.actual_area and self.actual_area > 0:
return self.transaction_value / self.actual_area
return None
class Valuation(TimeStampedModel):
"""Property valuations data."""
property_total_value = models.DecimalField(max_digits=15, decimal_places=2)
area_en = models.CharField(max_length=100)
actual_area = models.DecimalField(max_digits=10, decimal_places=2)
procedure_year = models.IntegerField()
procedure_number = models.CharField(max_length=20)
instance_date = models.DateTimeField()
actual_worth = models.DecimalField(max_digits=15, decimal_places=2)
procedure_area = models.DecimalField(max_digits=10, decimal_places=2)
property_type = models.CharField(max_length=50, choices=[
('Unit', 'Unit'),
('Villa', 'Villa'),
('Land', 'Land'),
('Building', 'Building'),
])
property_sub_type = models.CharField(max_length=50)
class Meta:
db_table = 'valuations'
indexes = [
BTreeIndex(fields=['area_en']),
BTreeIndex(fields=['property_type']),
BTreeIndex(fields=['procedure_year']),
BTreeIndex(fields=['instance_date']),
BTreeIndex(fields=['property_total_value']),
]
def __str__(self):
return f"{self.area_en} - {self.property_type} - {self.property_total_value}"
class Rent(TimeStampedModel):
"""Rental data."""
registration_date = models.DateTimeField()
start_date = models.DateTimeField()
end_date = models.DateTimeField()
version = models.CharField(max_length=20, choices=[
('New', 'New'),
('Renewed', 'Renewed'),
])
area_en = models.CharField(max_length=100)
contract_amount = models.DecimalField(max_digits=15, decimal_places=2)
annual_amount = models.DecimalField(max_digits=15, decimal_places=2)
is_freehold = models.CharField(max_length=20, choices=[
('Free Hold', 'Free Hold'),
('Non Free Hold', 'Non Free Hold'),
])
actual_area = models.DecimalField(max_digits=10, decimal_places=2)
property_type = models.CharField(max_length=50, choices=[
('Unit', 'Unit'),
('Villa', 'Villa'),
('Land', 'Land'),
('Building', 'Building'),
])
property_sub_type = models.CharField(max_length=50)
rooms = models.CharField(max_length=20, blank=True)
usage = models.CharField(max_length=50, choices=[
('Residential', 'Residential'),
('Commercial', 'Commercial'),
('Industrial', 'Industrial'),
('Mixed', 'Mixed'),
])
nearest_metro = models.CharField(max_length=100, blank=True)
nearest_mall = models.CharField(max_length=100, blank=True)
nearest_landmark = models.CharField(max_length=100, blank=True)
parking = models.CharField(max_length=20, blank=True)
total_properties = models.IntegerField(default=1)
master_project = models.CharField(max_length=255, blank=True)
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, related_name='rents')
class Meta:
db_table = 'rents'
indexes = [
BTreeIndex(fields=['area_en']),
BTreeIndex(fields=['property_type']),
BTreeIndex(fields=['registration_date']),
BTreeIndex(fields=['start_date']),
BTreeIndex(fields=['end_date']),
BTreeIndex(fields=['contract_amount']),
BTreeIndex(fields=['annual_amount']),
]
def __str__(self):
return f"{self.area_en} - {self.property_type} - {self.annual_amount}"
@property
def rent_per_sqft(self):
"""Calculate rent per square foot."""
if self.actual_area and self.actual_area > 0:
return self.annual_amount / self.actual_area
return None
class Forecast(TimeStampedModel):
"""Property price forecasts."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
area_en = models.CharField(max_length=100)
property_type = models.CharField(max_length=50)
property_sub_type = models.CharField(max_length=50, blank=True)
forecast_date = models.DateTimeField()
predicted_price = models.DecimalField(max_digits=15, decimal_places=2)
confidence_interval_lower = models.DecimalField(max_digits=15, decimal_places=2)
confidence_interval_upper = models.DecimalField(max_digits=15, decimal_places=2)
model_version = models.CharField(max_length=20, default='1.0')
accuracy_score = models.DecimalField(max_digits=5, decimal_places=4, null=True, blank=True)
metadata = models.JSONField(default=dict)
class Meta:
db_table = 'forecasts'
indexes = [
BTreeIndex(fields=['area_en']),
BTreeIndex(fields=['property_type']),
BTreeIndex(fields=['forecast_date']),
BTreeIndex(fields=['predicted_price']),
]
def __str__(self):
return f"{self.area_en} - {self.property_type} - {self.forecast_date}"
class MarketTrend(TimeStampedModel):
"""Market trend analysis data."""
area_en = models.CharField(max_length=100)
property_type = models.CharField(max_length=50)
period_start = models.DateTimeField()
period_end = models.DateTimeField()
average_price = models.DecimalField(max_digits=15, decimal_places=2)
median_price = models.DecimalField(max_digits=15, decimal_places=2)
price_change_percent = models.DecimalField(max_digits=5, decimal_places=2)
transaction_count = models.IntegerField()
volume = models.DecimalField(max_digits=15, decimal_places=2)
price_per_sqft = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
class Meta:
db_table = 'market_trends'
indexes = [
BTreeIndex(fields=['area_en']),
BTreeIndex(fields=['property_type']),
BTreeIndex(fields=['period_start']),
BTreeIndex(fields=['period_end']),
]
def __str__(self):
return f"{self.area_en} - {self.property_type} - {self.period_start} to {self.period_end}"