fix: revive main dashboard analytics
This commit is contained in:
@@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from collections.abc import Iterable
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from apps.core.exceptions import NotFoundError
|
||||
@@ -20,7 +19,11 @@ from apps.organization.availability import (
|
||||
risk_level_for_availability,
|
||||
)
|
||||
from apps.organization.models import IndustryCluster, Organization
|
||||
from apps.organization.scope_utils import filter_queryset_by_scopes
|
||||
from apps.organization.scope_utils import (
|
||||
SCOPE_LABELS,
|
||||
filter_queryset_by_scopes,
|
||||
scopes_from_organization_fields,
|
||||
)
|
||||
from django.db.models import Avg, Case, Count, IntegerField, Q, Sum, When
|
||||
|
||||
ZERO = Decimal("0")
|
||||
@@ -1145,6 +1148,37 @@ class OrganizationAnalyticsService:
|
||||
class DashboardAnalyticsService:
|
||||
"""Cross-organization dashboard aggregations."""
|
||||
|
||||
@staticmethod
|
||||
def _resolve_dashboard_cluster(organization: Organization) -> str:
|
||||
if organization.cluster:
|
||||
return organization.cluster
|
||||
|
||||
scope_codes = scopes_from_organization_fields(
|
||||
gk_code=organization.gk_code,
|
||||
gk_name=organization.gk_name,
|
||||
opk_registry_membership=organization.opk_registry_membership,
|
||||
)
|
||||
for scope_code in scope_codes:
|
||||
if scope_code in {"rosatom", "roscosmos"}:
|
||||
return scope_code
|
||||
return scope_codes[0] if scope_codes else IndustryCluster.OTHER
|
||||
|
||||
@staticmethod
|
||||
def _dashboard_cluster_label(cluster_code: str) -> str:
|
||||
industry_cluster_label = dict(IndustryCluster.choices).get(cluster_code)
|
||||
if industry_cluster_label:
|
||||
return industry_cluster_label
|
||||
return SCOPE_LABELS.get(cluster_code, "Иная")
|
||||
|
||||
@staticmethod
|
||||
def _resolve_executors_total(organizations: list[Organization]) -> int:
|
||||
executors_total = sum(
|
||||
organization.executors_count for organization in organizations
|
||||
)
|
||||
if executors_total:
|
||||
return executors_total
|
||||
return sum(1 for organization in organizations if organization.goz_participation)
|
||||
|
||||
@classmethod
|
||||
def get_dashboard(
|
||||
cls, *, corporation_scope: str | None = None
|
||||
@@ -1165,32 +1199,27 @@ class DashboardAnalyticsService:
|
||||
|
||||
totals_by_cluster: dict[str, list[Organization]] = defaultdict(list)
|
||||
for organization in organizations:
|
||||
cluster = organization.cluster or IndustryCluster.OTHER
|
||||
cluster = cls._resolve_dashboard_cluster(organization)
|
||||
totals_by_cluster[cluster].append(organization)
|
||||
|
||||
total_organizations = len(organizations)
|
||||
current_year = date.today().year
|
||||
previous_year = current_year - 1
|
||||
|
||||
f3_records = (
|
||||
FormF3Record.objects.filter(
|
||||
organization__in=organizations,
|
||||
is_active_version=True,
|
||||
report_year__in=[previous_year, current_year],
|
||||
)
|
||||
.select_related("organization")
|
||||
.order_by(
|
||||
"organization_id", "report_year", "-report_quarter", "-created_at"
|
||||
)
|
||||
)
|
||||
f3_best: dict[tuple[str, int], FormF3Record] = {}
|
||||
f3_best_by_organization: dict[str, dict[int, FormF3Record]] = defaultdict(dict)
|
||||
for record in f3_records:
|
||||
key = (str(record.organization_id), record.report_year)
|
||||
f3_best.setdefault(key, record)
|
||||
|
||||
def cluster_label(cluster_code: str) -> str:
|
||||
return dict(IndustryCluster.choices).get(cluster_code, "Иная")
|
||||
organization_key = str(record.organization_id)
|
||||
f3_best_by_organization[organization_key].setdefault(
|
||||
record.report_year, record
|
||||
)
|
||||
|
||||
total_organizations = len(organizations)
|
||||
distribution_by_cluster = []
|
||||
executors_by_cluster = []
|
||||
headcount_growth_by_cluster = []
|
||||
@@ -1198,15 +1227,21 @@ class DashboardAnalyticsService:
|
||||
|
||||
for cluster_code, cluster_organizations in sorted(totals_by_cluster.items()):
|
||||
cluster_total = len(cluster_organizations)
|
||||
executors_total = sum(org.executors_count for org in cluster_organizations)
|
||||
executors_total = cls._resolve_executors_total(cluster_organizations)
|
||||
bankruptcy_free = sum(
|
||||
1 for org in cluster_organizations if not org.bankruptcy_messages_found
|
||||
)
|
||||
|
||||
growth_values = []
|
||||
for organization in cluster_organizations:
|
||||
current_record = f3_best.get((str(organization.id), current_year))
|
||||
previous_record = f3_best.get((str(organization.id), previous_year))
|
||||
organization_f3_by_year = f3_best_by_organization.get(
|
||||
str(organization.id), {}
|
||||
)
|
||||
report_years = sorted(organization_f3_by_year)
|
||||
if len(report_years) < 2:
|
||||
continue
|
||||
previous_record = organization_f3_by_year[report_years[-2]]
|
||||
current_record = organization_f3_by_year[report_years[-1]]
|
||||
if current_record is None or previous_record is None:
|
||||
continue
|
||||
growth_values.append(
|
||||
@@ -1218,7 +1253,7 @@ class DashboardAnalyticsService:
|
||||
distribution_by_cluster.append(
|
||||
{
|
||||
"cluster": cluster_code,
|
||||
"cluster_label": cluster_label(cluster_code),
|
||||
"cluster_label": cls._dashboard_cluster_label(cluster_code),
|
||||
"organizations_share_percent": round(
|
||||
(cluster_total / total_organizations) * 100, 1
|
||||
),
|
||||
|
||||
@@ -528,6 +528,89 @@ class OrganizationAnalyticsApiTest(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data["corporation_scope"], "roscosmos")
|
||||
|
||||
def test_dashboard_endpoint_groups_blank_clusters_by_corporation_scope(self):
|
||||
OrganizationFactory.create(
|
||||
cluster="",
|
||||
executors_count=0,
|
||||
gk_code="1",
|
||||
gk_name='Госкорпорация "Роскосмос"',
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
"/api/v1/analytics/dashboard/?corporation_scope=roskosmos"
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
response.data["distribution_by_cluster"][0]["cluster"], "roscosmos"
|
||||
)
|
||||
self.assertEqual(
|
||||
response.data["distribution_by_cluster"][0]["cluster_label"],
|
||||
"Госкорпорация «Роскосмос»",
|
||||
)
|
||||
|
||||
def test_dashboard_endpoint_counts_goz_organizations_when_executors_are_missing(
|
||||
self,
|
||||
):
|
||||
OrganizationFactory.create(
|
||||
cluster="",
|
||||
executors_count=0,
|
||||
gk_code="1",
|
||||
gk_name='Госкорпорация "Роскосмос"',
|
||||
goz_participation=True,
|
||||
)
|
||||
OrganizationFactory.create(
|
||||
cluster="",
|
||||
executors_count=0,
|
||||
gk_code="1",
|
||||
gk_name='Госкорпорация "Роскосмос"',
|
||||
goz_participation=False,
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
"/api/v1/analytics/dashboard/?corporation_scope=roskosmos"
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
response.data["executors_by_cluster"][0]["cluster"], "roscosmos"
|
||||
)
|
||||
self.assertEqual(
|
||||
response.data["executors_by_cluster"][0]["executors_count"], 1
|
||||
)
|
||||
|
||||
def test_dashboard_endpoint_uses_latest_available_f3_years_for_growth(self):
|
||||
organization = OrganizationFactory.create(
|
||||
cluster="space",
|
||||
executors_count=0,
|
||||
gk_code="1",
|
||||
gk_name='Госкорпорация "Роскосмос"',
|
||||
)
|
||||
FormF3RecordFactory.create(
|
||||
organization=organization,
|
||||
report_year=2023,
|
||||
report_quarter=4,
|
||||
avg_employees=100,
|
||||
)
|
||||
FormF3RecordFactory.create(
|
||||
organization=organization,
|
||||
report_year=2024,
|
||||
report_quarter=4,
|
||||
avg_employees=150,
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
"/api/v1/analytics/dashboard/?corporation_scope=roskosmos"
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
response.data["headcount_growth_by_cluster"][0]["cluster"], "space"
|
||||
)
|
||||
self.assertEqual(
|
||||
response.data["headcount_growth_by_cluster"][0]["growth_percent"], 50.0
|
||||
)
|
||||
|
||||
def test_analytics_query_validation(self):
|
||||
response = self.client.get(
|
||||
f"/api/v1/organizations/{self.organization.id}/analytics/economics/"
|
||||
|
||||
Reference in New Issue
Block a user