Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/program-manager-analytics-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Endpoint доступен менеджерам указанной програм
"participant_funnel": {
"registrations": 4,
"unique_participants": 3,
"onboarded_participants": 2,
"with_team": 2,
"project_creators": 1,
"submitted_project_creators": 1
Expand Down Expand Up @@ -85,6 +86,17 @@ Endpoint доступен менеджерам указанной програм
пользователем.
- `unique_participants` и `summary.participants.total` — уникальные ненулевые
`PartnerProgramUserProfile.user_id`.
- `onboarded_participants` — уникальные ненулевые пользователи, зарегистрированные
в этой программе, у которых `User.onboarding_stage IS NULL`: завершён стартовый
сценарий платформы. Подтверждение почты (`is_active`) и произвольный запрос к API
(`last_activity`) не заменяют этот критерий. Онбординг позволяет пропускать шаги,
поэтому метрика не означает заполненный профиль или регулярное использование.
Это текущее состояние, а не событие с датой: определить, был ли онбординг пройден
до или после регистрации в программе, по этому полю нельзя.
- Этапы участников показывают текущее состояние одной группы зарегистрированных
пользователей, а не последовательную событийную конверсию: `with_team` может
превышать `onboarded_participants`, если команда появилась раньше. Значения
возвращаются без сортировки, ограничения или искусственного уменьшения.
- Участник считается состоящим в команде, если он является руководителем либо
`Collaborator` проекта, связанного с программой через
`PartnerProgramProject`. Поле `PartnerProgramUserProfile.project` не
Expand Down
1 change: 1 addition & 0 deletions partner_programs/serializers/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ProgramAnalyticsSummarySerializer(serializers.Serializer):
class ProgramParticipantFunnelSerializer(serializers.Serializer):
registrations = serializers.IntegerField(min_value=0)
unique_participants = serializers.IntegerField(min_value=0)
onboarded_participants = serializers.IntegerField(min_value=0)
with_team = serializers.IntegerField(min_value=0)
project_creators = serializers.IntegerField(min_value=0)
submitted_project_creators = serializers.IntegerField(min_value=0)
Expand Down
6 changes: 6 additions & 0 deletions partner_programs/services/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ def _get_participant_metrics(program_id: int) -> dict[str, int]:
filter=participant_filter,
distinct=True,
),
onboarded_participants=Count(
"user_id",
filter=participant_filter & Q(user__onboarding_stage__isnull=True),
distinct=True,
),
with_team=Count(
"user_id",
filter=participant_filter & team_filter,
Expand Down Expand Up @@ -351,6 +356,7 @@ def build_program_manager_analytics(program) -> dict:
"participant_funnel": {
"registrations": participants["registrations"],
"unique_participants": participants["unique_participants"],
"onboarded_participants": participants["onboarded_participants"],
"with_team": participants["with_team"],
"project_creators": participants["project_creators"],
"submitted_project_creators": participants["submitted_project_creators"],
Expand Down
36 changes: 36 additions & 0 deletions partner_programs/tests/test_manager_analytics_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,49 @@ def test_summary_participant_funnel_and_regions_use_real_relations(self):
{
"registrations": 4,
"unique_participants": 3,
"onboarded_participants": 0,
"with_team": 2,
"project_creators": 1,
"submitted_project_creators": 1,
},
)
self.assertEqual(response.data["attention"]["participants_without_team"], 1)

def test_onboarding_counts_only_registered_users_with_completed_onboarding(self):
completed = create_user(onboarding_stage=None, is_active=True)
email_verified = create_user(onboarding_stage=0, is_active=True)
started = create_user(onboarding_stage=2, is_active=True)
for user in (completed, email_verified, started):
create_program_member(self.program, user=user)
# A team can exist before onboarding is complete: do not clamp real counts.
for leader in (email_verified, started):
create_program_project(self.program, project=create_project(leader=leader))
# Neither other programs, unregistered leaders, nor deleted users count.
create_program_member(
create_partner_program(), user=create_user(onboarding_stage=None)
)
create_program_project(
self.program,
project=create_project(leader=create_user(onboarding_stage=None)),
)
PartnerProgramUserProfile.objects.create(
user=None, partner_program=self.program, partner_program_data={}
)

response = self.client.get(self.url)

self.assertEqual(response.status_code, 200)
funnel = response.data["participant_funnel"]
self.assertEqual(funnel["registrations"], 4)
self.assertEqual(funnel["unique_participants"], 3)
self.assertEqual(funnel["onboarded_participants"], 1)
self.assertEqual(funnel["with_team"], 2)

started.onboarding_stage = None
started.save(update_fields=["onboarding_stage"])
response = self.client.get(self.url)
self.assertEqual(response.data["participant_funnel"]["onboarded_participants"], 2)

def test_participant_regions_use_only_program_participants_and_preserve_legacy_values(
self,
):
Expand Down
Loading