diff --git a/apps/analytics/api/views.py b/apps/analytics/api/views.py index a2c9471..cb87c21 100644 --- a/apps/analytics/api/views.py +++ b/apps/analytics/api/views.py @@ -89,6 +89,43 @@ def _label(value) -> str: return str(value or UNKNOWN_LABEL) +def _normalize_entry_year(value) -> str: + if value in (None, ""): + return UNKNOWN_LABEL + raw = str(value).strip() + normalized = ( + raw.translate(str.maketrans("۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩", "01234567890123456789")) + .replace(",", "") + .replace("٬", "") + ) + try: + year = int(normalized) + except (TypeError, ValueError): + return UNKNOWN_LABEL + + if 1390 <= year <= 1499: + return str(year) + if 400 <= year <= 499: + return str(1000 + year) + if 90 <= year <= 99: + return str(1300 + year) + return UNKNOWN_LABEL + + +def _point_group_from_rows(rows: list[dict], *, limit: int = TOP_ITEMS_LIMIT): + sorted_rows = sorted(rows, key=lambda item: (-int(item["value"] or 0), str(item["label"]))) + top_rows = sorted_rows[:limit] + other_rows = sorted_rows[limit:] + return { + "top_items": [ + {"label": _label(item["label"]), "value": int(item["value"] or 0)} + for item in top_rows + ], + "other_count": len(other_rows), + "total_count": len(sorted_rows), + } + + def _point_queryset(queryset, label_field: str, *, limit: int = 12): return [ {"label": _label(item.get(label_field)), "value": item["value"]} @@ -122,6 +159,17 @@ def _point_group_queryset(queryset, label_field: str, *, limit: int = TOP_ITEMS_ } +def _entry_year_group_queryset(queryset, *, limit: int = TOP_ITEMS_LIMIT): + buckets: dict[str, int] = {} + for item in queryset.values("year_of_study").annotate(value=Count("id")): + label = _normalize_entry_year(item.get("year_of_study")) + buckets[label] = buckets.get(label, 0) + int(item["value"] or 0) + return _point_group_from_rows( + [{"label": label, "value": value} for label, value in buckets.items()], + limit=limit, + ) + + def _trend_queryset(queryset, field: str, granularity: str): trunc = GRANULARITY_TRUNC[granularity] rows = ( @@ -263,7 +311,7 @@ def admin_users_analytics(request, date_from: str | None = None, date_to: str | "signup_trend": _trend_queryset(users_qs, "date_joined", granularity), "by_major": _point_group_queryset(users_qs, "major__name"), "by_university": _point_group_queryset(users_qs, "university__name"), - "by_year": _point_group_queryset(users_qs, "year_of_study"), + "by_year": _entry_year_group_queryset(users_qs), } @@ -742,7 +790,7 @@ def admin_dashboard( "signup_trend": _trend_queryset(users_qs, "date_joined", selected_granularity), "by_major": _point_queryset(users_qs, "major__name"), "by_university": _point_queryset(users_qs, "university__name"), - "by_year": _point_queryset(users_qs, "year_of_study"), + "by_year": _entry_year_group_queryset(users_qs)["top_items"], }, "events": { "registration_status": registration_status,