Визуальные изменение. Доработки и фиксы багов
This commit is contained in:
@@ -3,13 +3,14 @@
|
||||
"""
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from django.db.models import Count, Q, Min
|
||||
from django.db.models.functions import TruncDate
|
||||
from django.db.models import Count, Q, Min, Sum, F, Subquery, OuterRef
|
||||
from django.db.models.functions import TruncDate, Abs
|
||||
from django.utils import timezone
|
||||
from django.views.generic import TemplateView
|
||||
from django.http import JsonResponse
|
||||
|
||||
from ..models import ObjItem, Source, Satellite, Geo
|
||||
from ..models import ObjItem, Source, Satellite, Geo, SourceRequest, SourceRequestStatusHistory
|
||||
from mapsapp.models import Transponders
|
||||
|
||||
|
||||
class StatisticsView(TemplateView):
|
||||
@@ -209,6 +210,191 @@ class StatisticsView(TemplateView):
|
||||
|
||||
return list(daily)
|
||||
|
||||
def _get_zone_statistics(self, date_from, date_to, location_place):
|
||||
"""
|
||||
Получает статистику по зоне (КР или ДВ).
|
||||
|
||||
Возвращает:
|
||||
- total_coords: общее количество координат ГЛ
|
||||
- new_coords: количество новых координат ГЛ (уникальные имена, появившиеся впервые)
|
||||
- transfer_delta: сумма дельт переносов по новым транспондерам
|
||||
"""
|
||||
# Базовый queryset для зоны
|
||||
zone_qs = ObjItem.objects.filter(
|
||||
geo_obj__isnull=False,
|
||||
geo_obj__timestamp__isnull=False,
|
||||
parameter_obj__id_satellite__location_place=location_place
|
||||
)
|
||||
|
||||
if date_from:
|
||||
zone_qs = zone_qs.filter(geo_obj__timestamp__date__gte=date_from)
|
||||
if date_to:
|
||||
zone_qs = zone_qs.filter(geo_obj__timestamp__date__lte=date_to)
|
||||
|
||||
# Общее количество координат ГЛ
|
||||
total_coords = zone_qs.count()
|
||||
|
||||
# Новые координаты ГЛ (уникальные имена, появившиеся впервые в периоде)
|
||||
new_coords = 0
|
||||
if date_from:
|
||||
# Имена, которые были ДО периода
|
||||
existing_names = set(
|
||||
ObjItem.objects.filter(
|
||||
geo_obj__isnull=False,
|
||||
geo_obj__timestamp__isnull=False,
|
||||
geo_obj__timestamp__date__lt=date_from,
|
||||
parameter_obj__id_satellite__location_place=location_place,
|
||||
name__isnull=False
|
||||
).exclude(name='').values_list('name', flat=True).distinct()
|
||||
)
|
||||
|
||||
# Имена в периоде
|
||||
period_names = set(
|
||||
zone_qs.filter(name__isnull=False).exclude(name='').values_list('name', flat=True).distinct()
|
||||
)
|
||||
|
||||
new_coords = len(period_names - existing_names)
|
||||
|
||||
# Расчёт дельты переносов по новым транспондерам
|
||||
transfer_delta = self._calculate_transfer_delta(date_from, date_to, location_place)
|
||||
|
||||
return {
|
||||
'total_coords': total_coords,
|
||||
'new_coords': new_coords,
|
||||
'transfer_delta': transfer_delta,
|
||||
}
|
||||
|
||||
def _calculate_transfer_delta(self, date_from, date_to, location_place):
|
||||
"""
|
||||
Вычисляет сумму дельт по downlink для новых транспондеров.
|
||||
|
||||
Логика:
|
||||
1. Берём все новые транспондеры за период (по created_at)
|
||||
2. Для каждого ищем предыдущий транспондер с таким же именем, спутником и зоной
|
||||
3. Вычисляем дельту по downlink
|
||||
4. Суммируем все дельты
|
||||
"""
|
||||
if not date_from:
|
||||
return 0.0
|
||||
|
||||
# Новые транспондеры за период для данной зоны
|
||||
new_transponders_qs = Transponders.objects.filter(
|
||||
sat_id__location_place=location_place,
|
||||
created_at__date__gte=date_from
|
||||
)
|
||||
if date_to:
|
||||
new_transponders_qs = new_transponders_qs.filter(created_at__date__lte=date_to)
|
||||
|
||||
total_delta = 0.0
|
||||
|
||||
for transponder in new_transponders_qs:
|
||||
if not transponder.name or not transponder.sat_id or transponder.downlink is None:
|
||||
continue
|
||||
|
||||
# Ищем предыдущий транспондер с таким же именем, спутником и зоной
|
||||
previous = Transponders.objects.filter(
|
||||
name=transponder.name,
|
||||
sat_id=transponder.sat_id,
|
||||
zone_name=transponder.zone_name,
|
||||
created_at__lt=transponder.created_at,
|
||||
downlink__isnull=False
|
||||
).order_by('-created_at').first()
|
||||
|
||||
if previous and previous.downlink is not None:
|
||||
delta = abs(transponder.downlink - previous.downlink)
|
||||
total_delta += delta
|
||||
|
||||
return round(total_delta, 2)
|
||||
|
||||
def _get_kubsat_statistics(self, date_from, date_to):
|
||||
"""
|
||||
Получает статистику по Кубсатам из SourceRequest.
|
||||
|
||||
Возвращает:
|
||||
- planned_count: количество запланированных сеансов
|
||||
- conducted_count: количество проведённых
|
||||
- canceled_gso_count: количество отменённых ГСО
|
||||
- canceled_kub_count: количество отменённых МКА
|
||||
"""
|
||||
# Базовый queryset для заявок
|
||||
requests_qs = SourceRequest.objects.all()
|
||||
|
||||
# Фильтруем по дате создания или planned_at
|
||||
if date_from:
|
||||
requests_qs = requests_qs.filter(
|
||||
Q(created_at__date__gte=date_from) | Q(planned_at__date__gte=date_from)
|
||||
)
|
||||
if date_to:
|
||||
requests_qs = requests_qs.filter(
|
||||
Q(created_at__date__lte=date_to) | Q(planned_at__date__lte=date_to)
|
||||
)
|
||||
|
||||
# Получаем ID заявок, у которых в истории был статус 'planned'
|
||||
# Это заявки, которые были запланированы в выбранном периоде
|
||||
history_qs = SourceRequestStatusHistory.objects.filter(
|
||||
new_status='planned'
|
||||
)
|
||||
if date_from:
|
||||
history_qs = history_qs.filter(changed_at__date__gte=date_from)
|
||||
if date_to:
|
||||
history_qs = history_qs.filter(changed_at__date__lte=date_to)
|
||||
|
||||
planned_request_ids = set(history_qs.values_list('source_request_id', flat=True))
|
||||
|
||||
# Также добавляем заявки, которые были созданы со статусом 'planned' в периоде
|
||||
created_planned_qs = SourceRequest.objects.filter(status='planned')
|
||||
if date_from:
|
||||
created_planned_qs = created_planned_qs.filter(created_at__date__gte=date_from)
|
||||
if date_to:
|
||||
created_planned_qs = created_planned_qs.filter(created_at__date__lte=date_to)
|
||||
|
||||
planned_request_ids.update(created_planned_qs.values_list('id', flat=True))
|
||||
|
||||
planned_count = len(planned_request_ids)
|
||||
|
||||
# Считаем статусы из истории для запланированных заявок
|
||||
conducted_count = 0
|
||||
canceled_gso_count = 0
|
||||
canceled_kub_count = 0
|
||||
|
||||
if planned_request_ids:
|
||||
# Получаем историю статусов для запланированных заявок
|
||||
status_history = SourceRequestStatusHistory.objects.filter(
|
||||
source_request_id__in=planned_request_ids
|
||||
)
|
||||
if date_from:
|
||||
status_history = status_history.filter(changed_at__date__gte=date_from)
|
||||
if date_to:
|
||||
status_history = status_history.filter(changed_at__date__lte=date_to)
|
||||
|
||||
# Считаем уникальные заявки по каждому статусу
|
||||
conducted_ids = set(status_history.filter(new_status='conducted').values_list('source_request_id', flat=True))
|
||||
canceled_gso_ids = set(status_history.filter(new_status='canceled_gso').values_list('source_request_id', flat=True))
|
||||
canceled_kub_ids = set(status_history.filter(new_status='canceled_kub').values_list('source_request_id', flat=True))
|
||||
|
||||
conducted_count = len(conducted_ids)
|
||||
canceled_gso_count = len(canceled_gso_ids)
|
||||
canceled_kub_count = len(canceled_kub_ids)
|
||||
|
||||
return {
|
||||
'planned_count': planned_count,
|
||||
'conducted_count': conducted_count,
|
||||
'canceled_gso_count': canceled_gso_count,
|
||||
'canceled_kub_count': canceled_kub_count,
|
||||
}
|
||||
|
||||
def get_extended_statistics(self, date_from, date_to):
|
||||
"""Получает расширенную статистику по зонам и Кубсатам."""
|
||||
kr_stats = self._get_zone_statistics(date_from, date_to, 'kr')
|
||||
dv_stats = self._get_zone_statistics(date_from, date_to, 'dv')
|
||||
kubsat_stats = self._get_kubsat_statistics(date_from, date_to)
|
||||
|
||||
return {
|
||||
'kr': kr_stats,
|
||||
'dv': dv_stats,
|
||||
'kubsat': kubsat_stats,
|
||||
}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
@@ -230,6 +416,9 @@ class StatisticsView(TemplateView):
|
||||
# Получаем статистику
|
||||
stats = self.get_statistics(date_from, date_to, satellite_ids, location_places)
|
||||
|
||||
# Получаем расширенную статистику
|
||||
extended_stats = self.get_extended_statistics(date_from, date_to)
|
||||
|
||||
# Сериализуем данные для JavaScript
|
||||
daily_data_json = json.dumps([
|
||||
{
|
||||
@@ -241,6 +430,7 @@ class StatisticsView(TemplateView):
|
||||
])
|
||||
|
||||
satellite_stats_json = json.dumps(stats['satellite_stats'])
|
||||
extended_stats_json = json.dumps(extended_stats)
|
||||
|
||||
context.update({
|
||||
'satellites': satellites,
|
||||
@@ -257,6 +447,8 @@ class StatisticsView(TemplateView):
|
||||
'satellite_stats': stats['satellite_stats'],
|
||||
'daily_data': daily_data_json,
|
||||
'satellite_stats_json': satellite_stats_json,
|
||||
'extended_stats': extended_stats,
|
||||
'extended_stats_json': extended_stats_json,
|
||||
})
|
||||
|
||||
return context
|
||||
@@ -270,6 +462,7 @@ class StatisticsAPIView(StatisticsView):
|
||||
satellite_ids = self.get_selected_satellites()
|
||||
location_places = self.get_selected_location_places()
|
||||
stats = self.get_statistics(date_from, date_to, satellite_ids, location_places)
|
||||
extended_stats = self.get_extended_statistics(date_from, date_to)
|
||||
|
||||
# Преобразуем даты в строки для JSON
|
||||
daily_data = []
|
||||
@@ -287,4 +480,19 @@ class StatisticsAPIView(StatisticsView):
|
||||
'new_emission_objects': stats['new_emission_objects'],
|
||||
'satellite_stats': stats['satellite_stats'],
|
||||
'daily_data': daily_data,
|
||||
'extended_stats': extended_stats,
|
||||
})
|
||||
|
||||
|
||||
class ExtendedStatisticsAPIView(StatisticsView):
|
||||
"""API endpoint для получения расширенной статистики в JSON формате."""
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
date_from, date_to, preset = self.get_date_range()
|
||||
extended_stats = self.get_extended_statistics(date_from, date_to)
|
||||
|
||||
return JsonResponse({
|
||||
'extended_stats': extended_stats,
|
||||
'date_from': date_from.isoformat() if date_from else None,
|
||||
'date_to': date_to.isoformat() if date_to else None,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user