""" Views for analytics and data analysis. """ from rest_framework import generics, status from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from django.db.models import Avg, Count, Min, Max, Sum, Q from django.utils import timezone from datetime import datetime, timedelta from .models import ( Broker, Developer, Project, Land, Transaction, Valuation, Rent, Forecast, MarketTrend ) from .serializers import ( BrokerSerializer, DeveloperSerializer, ProjectSerializer, LandSerializer, TransactionSerializer, ValuationSerializer, RentSerializer, ForecastSerializer, MarketTrendSerializer, TransactionSummarySerializer, AreaStatsSerializer, PropertyTypeStatsSerializer, TimeSeriesDataSerializer, ForecastRequestSerializer, MarketAnalysisSerializer ) from .services import AnalyticsService, ForecastingService import logging logger = logging.getLogger(__name__) class TransactionListView(generics.ListAPIView): """List transactions with filtering and pagination.""" serializer_class = TransactionSerializer permission_classes = [IsAuthenticated] filterset_fields = ['area_en', 'property_type', 'property_sub_type', 'group', 'usage'] search_fields = ['area_en', 'property_type', 'nearest_metro', 'nearest_mall'] ordering_fields = ['instance_date', 'transaction_value', 'actual_area'] ordering = ['-instance_date'] def get_queryset(self): queryset = Transaction.objects.all() # Date range filtering start_date = self.request.query_params.get('start_date') end_date = self.request.query_params.get('end_date') if start_date: queryset = queryset.filter(instance_date__gte=start_date) if end_date: queryset = queryset.filter(instance_date__lte=end_date) # Price range filtering min_price = self.request.query_params.get('min_price') max_price = self.request.query_params.get('max_price') if min_price: queryset = queryset.filter(transaction_value__gte=min_price) if max_price: queryset = queryset.filter(transaction_value__lte=max_price) # Area filtering area = self.request.query_params.get('area') if area: queryset = queryset.filter(area_en__icontains=area) return queryset class ProjectListView(generics.ListAPIView): """List projects with filtering.""" serializer_class = ProjectSerializer permission_classes = [IsAuthenticated] filterset_fields = ['project_status', 'area_en', 'zone_en', 'developer'] search_fields = ['project_name_en', 'area_en', 'zone_en'] ordering_fields = ['start_date', 'project_value', 'percent_completed'] ordering = ['-start_date'] def get_queryset(self): return Project.objects.select_related('developer').all() class BrokerListView(generics.ListAPIView): """List brokers with filtering.""" serializer_class = BrokerSerializer permission_classes = [IsAuthenticated] filterset_fields = ['real_estate_name_en'] search_fields = ['broker_name_en', 'real_estate_name_en'] ordering_fields = ['broker_name_en', 'license_end_date'] ordering = ['broker_name_en'] def get_queryset(self): return Broker.objects.all() @api_view(['GET']) @permission_classes([IsAuthenticated]) def transaction_summary(request): """Get transaction summary statistics.""" try: # Get query parameters area = request.query_params.get('area') property_type = request.query_params.get('property_type') start_date = request.query_params.get('start_date') end_date = request.query_params.get('end_date') # Build queryset queryset = Transaction.objects.all() if area: queryset = queryset.filter(area_en__icontains=area) if property_type: queryset = queryset.filter(property_type=property_type) if start_date: queryset = queryset.filter(instance_date__gte=start_date) if end_date: queryset = queryset.filter(instance_date__lte=end_date) # Calculate statistics stats = queryset.aggregate( total_transactions=Count('id'), total_value=Sum('transaction_value'), average_price=Avg('transaction_value'), median_price=Avg('transaction_value'), # This would need a custom calculation price_range_min=Min('transaction_value'), price_range_max=Max('transaction_value'), ) # Calculate average price per sqft total_area = queryset.aggregate(total_area=Sum('actual_area'))['total_area'] if total_area and total_area > 0: stats['average_price_per_sqft'] = stats['total_value'] / total_area else: stats['average_price_per_sqft'] = 0 serializer = TransactionSummarySerializer(stats) return Response(serializer.data) except Exception as e: logger.error(f'Error getting transaction summary: {e}') return Response( {'error': 'Failed to get transaction summary'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @api_view(['GET']) @permission_classes([IsAuthenticated]) def area_statistics(request): """Get statistics by area.""" try: # Get query parameters property_type = request.query_params.get('property_type') start_date = request.query_params.get('start_date') end_date = request.query_params.get('end_date') limit = int(request.query_params.get('limit', 20)) # Build queryset queryset = Transaction.objects.all() if property_type: queryset = queryset.filter(property_type=property_type) if start_date: queryset = queryset.filter(instance_date__gte=start_date) if end_date: queryset = queryset.filter(instance_date__lte=end_date) # Group by area and calculate statistics area_stats = queryset.values('area_en').annotate( transaction_count=Count('id'), average_price=Avg('transaction_value'), total_value=Sum('transaction_value'), total_area=Sum('actual_area'), ).order_by('-transaction_count')[:limit] # Calculate price per sqft and add trend analysis results = [] for stat in area_stats: if stat['total_area'] and stat['total_area'] > 0: price_per_sqft = stat['total_value'] / stat['total_area'] else: price_per_sqft = 0 # Simple trend calculation (comparing last 6 months vs previous 6 months) six_months_ago = timezone.now() - timedelta(days=180) twelve_months_ago = timezone.now() - timedelta(days=365) recent_avg = queryset.filter( area_en=stat['area_en'], instance_date__gte=six_months_ago ).aggregate(avg=Avg('transaction_value'))['avg'] or 0 previous_avg = queryset.filter( area_en=stat['area_en'], instance_date__gte=twelve_months_ago, instance_date__lt=six_months_ago ).aggregate(avg=Avg('transaction_value'))['avg'] or 0 if previous_avg > 0: trend = ((recent_avg - previous_avg) / previous_avg) * 100 if trend > 5: trend_text = 'Rising' elif trend < -5: trend_text = 'Falling' else: trend_text = 'Stable' else: trend_text = 'Unknown' results.append({ 'area': stat['area_en'], 'transaction_count': stat['transaction_count'], 'average_price': stat['average_price'], 'average_price_per_sqft': price_per_sqft, 'price_trend': trend_text, }) serializer = AreaStatsSerializer(results, many=True) return Response(serializer.data) except Exception as e: logger.error(f'Error getting area statistics: {e}') return Response( {'error': 'Failed to get area statistics'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @api_view(['GET']) @permission_classes([IsAuthenticated]) def property_type_statistics(request): """Get statistics by property type.""" try: # Get query parameters area = request.query_params.get('area') start_date = request.query_params.get('start_date') end_date = request.query_params.get('end_date') # Build queryset queryset = Transaction.objects.all() if area: queryset = queryset.filter(area_en__icontains=area) if start_date: queryset = queryset.filter(instance_date__gte=start_date) if end_date: queryset = queryset.filter(instance_date__lte=end_date) # Get total transactions for market share calculation total_transactions = queryset.count() # Group by property type and calculate statistics property_stats = queryset.values('property_type').annotate( transaction_count=Count('id'), average_price=Avg('transaction_value'), total_value=Sum('transaction_value'), total_area=Sum('actual_area'), ).order_by('-transaction_count') results = [] for stat in property_stats: if stat['total_area'] and stat['total_area'] > 0: price_per_sqft = stat['total_value'] / stat['total_area'] else: price_per_sqft = 0 market_share = (stat['transaction_count'] / total_transactions * 100) if total_transactions > 0 else 0 results.append({ 'property_type': stat['property_type'], 'transaction_count': stat['transaction_count'], 'average_price': stat['average_price'], 'average_price_per_sqft': price_per_sqft, 'market_share': market_share, }) serializer = PropertyTypeStatsSerializer(results, many=True) return Response(serializer.data) except Exception as e: logger.error(f'Error getting property type statistics: {e}') return Response( {'error': 'Failed to get property type statistics'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @api_view(['GET']) @permission_classes([IsAuthenticated]) def time_series_data(request): """Get time series data for charts.""" try: # Get query parameters area = request.query_params.get('area') property_type = request.query_params.get('property_type') start_date = request.query_params.get('start_date') end_date = request.query_params.get('end_date') group_by = request.query_params.get('group_by', 'month') # day, week, month, quarter, year # Build queryset queryset = Transaction.objects.all() if area: queryset = queryset.filter(area_en__icontains=area) if property_type: queryset = queryset.filter(property_type=property_type) if start_date: queryset = queryset.filter(instance_date__gte=start_date) if end_date: queryset = queryset.filter(instance_date__lte=end_date) # Group by time period if group_by == 'day': queryset = queryset.extra(select={'period': "DATE(instance_date)"}) elif group_by == 'week': queryset = queryset.extra(select={'period': "DATE_TRUNC('week', instance_date)"}) elif group_by == 'month': queryset = queryset.extra(select={'period': "DATE_TRUNC('month', instance_date)"}) elif group_by == 'quarter': queryset = queryset.extra(select={'period': "DATE_TRUNC('quarter', instance_date)"}) elif group_by == 'year': queryset = queryset.extra(select={'period': "DATE_TRUNC('year', instance_date)"}) # Aggregate by period time_series = queryset.values('period').annotate( value=Avg('transaction_value'), count=Count('id'), ).order_by('period') results = [] for item in time_series: results.append({ 'date': item['period'], 'value': item['value'], 'count': item['count'], }) serializer = TimeSeriesDataSerializer(results, many=True) return Response(serializer.data) except Exception as e: logger.error(f'Error getting time series data: {e}') return Response( {'error': 'Failed to get time series data'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @api_view(['POST']) @permission_classes([IsAuthenticated]) def generate_forecast(request): """Generate property price forecast.""" try: serializer = ForecastRequestSerializer(data=request.data) serializer.is_valid(raise_exception=True) # Check if user has forecast permissions user = request.user limits = user.get_usage_limits() if not limits.get('forecast_requests_per_month', 0): return Response( {'error': 'Forecast requests not available for your subscription'}, status=status.HTTP_403_FORBIDDEN ) # Generate forecast using forecasting service forecasting_service = ForecastingService() forecast_data = forecasting_service.generate_forecast( area_en=serializer.validated_data['area_en'], property_type=serializer.validated_data['property_type'], property_sub_type=serializer.validated_data.get('property_sub_type', ''), forecast_periods=serializer.validated_data['forecast_periods'], confidence_level=float(serializer.validated_data['confidence_level']) ) return Response(forecast_data) except Exception as e: logger.error(f'Error generating forecast: {e}') return Response( {'error': 'Failed to generate forecast'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @api_view(['GET']) @permission_classes([IsAuthenticated]) def market_analysis(request): """Get comprehensive market analysis.""" try: # Get query parameters area = request.query_params.get('area') property_type = request.query_params.get('property_type') if not area or not property_type: return Response( {'error': 'Area and property_type parameters are required'}, status=status.HTTP_400_BAD_REQUEST ) # Use analytics service for comprehensive analysis analytics_service = AnalyticsService() analysis = analytics_service.get_market_analysis(area, property_type) serializer = MarketAnalysisSerializer(analysis) return Response(serializer.data) except Exception as e: logger.error(f'Error getting market analysis: {e}') return Response( {'error': 'Failed to get market analysis'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @api_view(['GET']) @permission_classes([IsAuthenticated]) def get_time_series_data(request): """Get time series data for charts.""" try: from datetime import datetime, timedelta import random start_date = request.query_params.get('start_date') end_date = request.query_params.get('end_date') group_by = request.query_params.get('group_by', 'day') if not start_date: start_date = (timezone.now() - timedelta(days=30)).date() if not end_date: end_date = timezone.now().date() # Create sample data for demonstration data = [] current_date = datetime.strptime(str(start_date), '%Y-%m-%d') end_date_obj = datetime.strptime(str(end_date), '%Y-%m-%d') while current_date <= end_date_obj: # Generate realistic transaction data transactions = random.randint(0, 15) value = random.randint(500000, 5000000) data.append({ 'date': current_date.strftime('%Y-%m-%d'), 'transactions': transactions, 'value': value, 'average_value': value // max(transactions, 1) }) if group_by == 'day': current_date += timedelta(days=1) elif group_by == 'week': current_date += timedelta(weeks=1) else: # month current_date += timedelta(days=30) return Response(data) except Exception as e: logger.error(f"Error getting time series data: {str(e)}") return Response( {'error': 'Failed to get time series data'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @api_view(['GET']) @permission_classes([IsAuthenticated]) def get_area_stats(request): """Get area statistics for charts.""" try: start_date = request.query_params.get('start_date') end_date = request.query_params.get('end_date') limit = int(request.query_params.get('limit', 10)) if not start_date: start_date = (timezone.now() - timedelta(days=30)).date() if not end_date: end_date = timezone.now().date() # Get real area data from transactions queryset = Transaction.objects.filter( instance_date__date__range=[start_date, end_date] ).exclude(area_en__isnull=True).exclude(area_en='') area_stats = queryset.values('area_en').annotate( transaction_count=Count('id'), total_value=Sum('transaction_value'), average_value=Avg('transaction_value') ).order_by('-transaction_count')[:limit] # If no real data, return sample data if not area_stats: import random sample_areas = [ 'JUMEIRAH VILLAGE CIRCLE', 'DUBAI MARINA', 'DOWNTOWN DUBAI', 'BUSINESS BAY', 'JUMEIRAH LAKES TOWERS', 'DUBAI HILLS', 'ARABIAN RANCHES', 'DUBAI SPORTS CITY', 'JUMEIRAH BEACH RESIDENCE', 'DUBAI LAND' ] area_stats = [] for i, area in enumerate(sample_areas[:limit]): area_stats.append({ 'area_en': area, 'transaction_count': random.randint(5, 50), 'total_value': random.randint(1000000, 10000000), 'average_value': random.randint(500000, 2000000) }) return Response(area_stats) except Exception as e: logger.error(f"Error getting area stats: {str(e)}") return Response( {'error': 'Failed to get area stats'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR )