Skip to content

Commit ba7482b

Browse files
authored
Add stats in Grant Summary (#4314)
1 parent 30b411f commit ba7482b

File tree

3 files changed

+514
-103
lines changed

3 files changed

+514
-103
lines changed

backend/grants/summary.py

Lines changed: 194 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
from helpers.constants import GENDERS
44
from countries import countries
55
from .models import Grant
6+
from django.db.models import Exists, OuterRef
7+
from submissions.models import Submission
8+
from schedule.models import ScheduleItem
69

710

811
class GrantSummary:
@@ -31,13 +34,37 @@ def calculate(self, conference_id):
3134
status_totals,
3235
totals_per_continent,
3336
) = self._aggregate_data_by_country(grants_by_country, statuses)
37+
sorted_country_stats = dict(
38+
sorted(country_stats.items(), key=lambda x: (x[0][0], x[0][2]))
39+
)
40+
country_type_summary = self._aggregate_data_by_country_type(
41+
filtered_grants, statuses
42+
)
3443
gender_stats = self._aggregate_data_by_gender(filtered_grants, statuses)
3544
financial_summary, total_amount = self._aggregate_financial_data_by_status(
3645
filtered_grants, statuses
3746
)
38-
39-
sorted_country_stats = dict(
40-
sorted(country_stats.items(), key=lambda x: (x[0][0], x[0][2]))
47+
grant_type_summary = self._aggregate_data_by_grant_type(
48+
filtered_grants, statuses
49+
)
50+
speaker_status_summary = self._aggregate_data_by_speaker_status(
51+
filtered_grants, statuses
52+
)
53+
approved_type_summary = self._aggregate_data_by_approved_type(
54+
filtered_grants, statuses
55+
)
56+
requested_needs_summary = self._aggregate_data_by_requested_needs_summary(
57+
filtered_grants, statuses
58+
)
59+
approved_types = {
60+
approved_type.value: approved_type.label
61+
for approved_type in Grant.ApprovedType
62+
}
63+
country_types = {
64+
country_type.value: country_type.label for country_type in Grant.CountryType
65+
}
66+
occupation_summary = self._aggregate_data_by_occupation(
67+
filtered_grants, statuses
4168
)
4269

4370
return dict(
@@ -52,6 +79,15 @@ def calculate(self, conference_id):
5279
status_totals=status_totals,
5380
totals_per_continent=totals_per_continent,
5481
gender_stats=gender_stats,
82+
preselected_statuses=["approved", "confirmed"],
83+
grant_type_summary=grant_type_summary,
84+
speaker_status_summary=speaker_status_summary,
85+
approved_type_summary=approved_type_summary,
86+
approved_types=approved_types,
87+
requested_needs_summary=requested_needs_summary,
88+
country_type_summary=country_type_summary,
89+
country_types=country_types,
90+
occupation_summary=occupation_summary,
5591
)
5692

5793
def _aggregate_data_by_country(self, grants_by_country, statuses):
@@ -83,6 +119,26 @@ def _aggregate_data_by_country(self, grants_by_country, statuses):
83119

84120
return summary, status_totals, totals_per_continent
85121

122+
def _aggregate_data_by_country_type(self, filtered_grants, statuses):
123+
"""
124+
Aggregates grant data by country type and status.
125+
"""
126+
country_type_data = filtered_grants.values("country_type", "status").annotate(
127+
total=Count("id")
128+
)
129+
country_type_summary = {
130+
country_type: {status[0]: 0 for status in statuses}
131+
for country_type in Grant.CountryType.values
132+
}
133+
134+
for data in country_type_data:
135+
country_type = data["country_type"]
136+
status = data["status"]
137+
total = data["total"]
138+
country_type_summary[country_type][status] += total
139+
140+
return country_type_summary
141+
86142
def _aggregate_data_by_gender(self, filtered_grants, statuses):
87143
"""
88144
Aggregates grant data by gender and status.
@@ -124,3 +180,138 @@ def _aggregate_financial_data_by_status(self, filtered_grants, statuses):
124180
overall_total += total_amount
125181

126182
return financial_summary, overall_total
183+
184+
def _aggregate_data_by_grant_type(self, filtered_grants, statuses):
185+
"""
186+
Aggregates grant data by grant_type and status.
187+
"""
188+
grant_type_data = filtered_grants.values("grant_type", "status").annotate(
189+
total=Count("id")
190+
)
191+
grant_type_summary = {
192+
grant_type: {status[0]: 0 for status in statuses}
193+
for grant_type in Grant.GrantType.values
194+
}
195+
196+
for data in grant_type_data:
197+
grant_types = data["grant_type"]
198+
status = data["status"]
199+
total = data["total"]
200+
for grant_type in grant_types:
201+
grant_type_summary[grant_type][status] += total
202+
203+
return grant_type_summary
204+
205+
def _aggregate_data_by_speaker_status(self, filtered_grants, statuses):
206+
"""
207+
Aggregates grant data by speaker status (proposed and confirmed) and grant status.
208+
"""
209+
filtered_grants = filtered_grants.annotate(
210+
is_proposed_speaker=Exists(
211+
Submission.objects.non_cancelled().filter(
212+
conference_id=OuterRef("conference_id"),
213+
speaker_id=OuterRef("user_id"),
214+
)
215+
),
216+
is_confirmed_speaker=Exists(
217+
ScheduleItem.objects.filter(
218+
conference_id=OuterRef("conference_id"),
219+
submission__speaker_id=OuterRef("user_id"),
220+
)
221+
),
222+
)
223+
224+
proposed_speaker_data = (
225+
filtered_grants.filter(is_proposed_speaker=True)
226+
.values("status")
227+
.annotate(total=Count("id"))
228+
)
229+
230+
confirmed_speaker_data = (
231+
filtered_grants.filter(is_confirmed_speaker=True)
232+
.values("status")
233+
.annotate(total=Count("id"))
234+
)
235+
236+
speaker_status_summary = {
237+
"proposed_speaker": {status[0]: 0 for status in statuses},
238+
"confirmed_speaker": {status[0]: 0 for status in statuses},
239+
}
240+
241+
for data in proposed_speaker_data:
242+
status = data["status"]
243+
total = data["total"]
244+
speaker_status_summary["proposed_speaker"][status] += total
245+
246+
for data in confirmed_speaker_data:
247+
status = data["status"]
248+
total = data["total"]
249+
speaker_status_summary["confirmed_speaker"][status] += total
250+
251+
return speaker_status_summary
252+
253+
def _aggregate_data_by_approved_type(self, filtered_grants, statuses):
254+
"""
255+
Aggregates grant data by approved type and status.
256+
"""
257+
approved_type_data = filtered_grants.values("approved_type", "status").annotate(
258+
total=Count("id")
259+
)
260+
approved_type_summary = {
261+
approved_type: {status[0]: 0 for status in statuses}
262+
for approved_type in Grant.ApprovedType.values
263+
}
264+
approved_type_summary[None] = {
265+
status[0]: 0 for status in statuses
266+
} # For unspecified genders
267+
268+
for data in approved_type_data:
269+
approved_type = data["approved_type"]
270+
status = data["status"]
271+
total = data["total"]
272+
approved_type_summary[approved_type][status] += total
273+
274+
return approved_type_summary
275+
276+
def _aggregate_data_by_requested_needs_summary(self, filtered_grants, statuses):
277+
"""
278+
Aggregates grant data by boolean fields (needs_funds_for_travel, need_visa, need_accommodation) and status.
279+
"""
280+
requested_needs_summary = {
281+
"needs_funds_for_travel": {status[0]: 0 for status in statuses},
282+
"need_visa": {status[0]: 0 for status in statuses},
283+
"need_accommodation": {status[0]: 0 for status in statuses},
284+
}
285+
286+
for field in requested_needs_summary.keys():
287+
field_data = (
288+
filtered_grants.filter(**{field: True})
289+
.values("status")
290+
.annotate(total=Count("id"))
291+
)
292+
for data in field_data:
293+
status = data["status"]
294+
total = data["total"]
295+
requested_needs_summary[field][status] += total
296+
297+
return requested_needs_summary
298+
299+
def _aggregate_data_by_occupation(self, filtered_grants, statuses):
300+
"""
301+
Aggregates grant data by occupation and status.
302+
"""
303+
occupation_data = filtered_grants.values("occupation", "status").annotate(
304+
total=Count("id")
305+
)
306+
occupation_summary = {
307+
occupation: {status[0]: 0 for status in statuses}
308+
for occupation in Grant.Occupation.values
309+
}
310+
311+
for data in occupation_data:
312+
occupation = data["occupation"]
313+
status = data["status"]
314+
total = data["total"]
315+
occupation_summary[occupation][status] += total
316+
317+
return occupation_summary

0 commit comments

Comments
 (0)