352 lines
14 KiB
Python
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}"
|
|
|