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
2 changes: 1 addition & 1 deletion VALIDATOR_JOURNEY_ARCHITECTURE_v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def update_user_leaderboard_entries(user):
for leaderboard_type in qualified_leaderboards:
update_leaderboard_type_ranks(leaderboard_type)

# Also update waitlist ranks if user graduated (they were removed) FIX: with the fix above this is not necesarry
# Also update waitlist ranks if user graduated (they were removed) FIX: with the fix above this is not necessary
if 'validator-waitlist-graduation' in qualified_leaderboards:
update_leaderboard_type_ranks('validator-waitlist')

Expand Down
4 changes: 4 additions & 0 deletions backend/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ GET /api/v1/leaderboard/user_stats/by-address/{address}/
# Multipliers
GET /api/v1/multipliers/
GET /api/v1/multiplier-periods/

# Steward Submissions (public metrics)
GET /api/v1/steward-submissions/stats/ (public - aggregate stats)
GET /api/v1/steward-submissions/daily-metrics/ (public - time-series data)
```

## Environment Variables
Expand Down
94 changes: 81 additions & 13 deletions backend/api/metrics_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ class ContributionTypesStatsView(APIView):
"""
Get time series data showing how many contribution types have been assigned on each date.
"""

def get(self, request):
from django.db.models.functions import TruncDate
from datetime import date, timedelta

# Get contributions grouped by date and contribution type
daily_contributions = (
Contribution.objects
Expand All @@ -101,51 +101,119 @@ def get(self, request):
)
.order_by('date')
)

# Build cumulative data
data = []
cumulative_types = set()

# Get all contributions to track cumulative unique types
all_contributions = (
Contribution.objects
.values('contribution_date', 'contribution_type')
.order_by('contribution_date')
)

# Group by date and count cumulative types
from collections import defaultdict
contributions_by_date = defaultdict(set)

for contrib in all_contributions:
date_key = contrib['contribution_date'].date()
contributions_by_date[date_key].add(contrib['contribution_type'])

if contributions_by_date:
# Get date range
start_date = min(contributions_by_date.keys())
end_date = max(contributions_by_date.keys())

# Extend to today if needed
today = date.today()
if end_date < today:
end_date = today

# Build continuous time series with cumulative count
current_date = start_date

while current_date <= end_date:
# Add new types for this date
if current_date in contributions_by_date:
cumulative_types.update(contributions_by_date[current_date])

data.append({
'date': current_date.isoformat(),
'count': len(cumulative_types),
'new_types': len(contributions_by_date.get(current_date, set()))
})

# Move to next day
current_date += timedelta(days=1)


return Response({'data': data})


class ParticipantsGrowthView(APIView):
"""
Get time series data for validators, waitlist users, and builders growth over time.
"""

def get(self, request):
from django.db.models.functions import TruncDate
from datetime import date, timedelta
from collections import defaultdict
from validators.models import Validator
from builders.models import Builder

# Get validators by creation date
validators_by_date = defaultdict(int)
for v in Validator.objects.all():
date_key = v.created_at.date()
validators_by_date[date_key] += 1

# Get waitlist users by contribution date (users with validator-waitlist contribution)
waitlist_by_date = defaultdict(int)
try:
waitlist_type = ContributionType.objects.get(slug='validator-waitlist')
for c in Contribution.objects.filter(contribution_type=waitlist_type):
date_key = c.contribution_date.date()
waitlist_by_date[date_key] += 1
except ContributionType.DoesNotExist:
pass

# Get builders by creation date
builders_by_date = defaultdict(int)
for b in Builder.objects.all():
date_key = b.created_at.date()
builders_by_date[date_key] += 1

# Find date range across all sources
all_dates = set(validators_by_date.keys()) | set(waitlist_by_date.keys()) | set(builders_by_date.keys())

if not all_dates:
return Response({'data': []})

start_date = min(all_dates)
end_date = max(max(all_dates), date.today())

# Build cumulative time series
data = []
cum_validators = 0
cum_waitlist = 0
cum_builders = 0

current_date = start_date
while current_date <= end_date:
cum_validators += validators_by_date.get(current_date, 0)
cum_waitlist += waitlist_by_date.get(current_date, 0)
cum_builders += builders_by_date.get(current_date, 0)

data.append({
'date': current_date.isoformat(),
'validators': cum_validators,
'waitlist': cum_waitlist,
'builders': cum_builders,
'total': cum_validators + cum_waitlist + cum_builders
})

current_date += timedelta(days=1)

return Response({'data': data})
3 changes: 2 additions & 1 deletion backend/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from contributions.views import ContributionTypeViewSet, ContributionViewSet, EvidenceViewSet, SubmittedContributionViewSet, StewardSubmissionViewSet, MissionViewSet, StartupRequestViewSet
from leaderboard.views import GlobalLeaderboardMultiplierViewSet, LeaderboardViewSet
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView
from .metrics_views import ActiveValidatorsView, ContributionTypesStatsView
from .metrics_views import ActiveValidatorsView, ContributionTypesStatsView, ParticipantsGrowthView

# Create a router and register our viewsets with it
router = DefaultRouter()
Expand Down Expand Up @@ -37,4 +37,5 @@
# Metrics endpoints
path('metrics/active-validators/', ActiveValidatorsView.as_view(), name='active-validators'),
path('metrics/contribution-types/', ContributionTypesStatsView.as_view(), name='contribution-types-stats'),
path('metrics/participants-growth/', ParticipantsGrowthView.as_view(), name='participants-growth'),
]
Loading