11from collections import defaultdict
22
3- from django .db .models import Count , Exists , OuterRef , Sum
3+ from django .db .models import Case , Count , DecimalField , Exists , OuterRef , Sum , When
4+ from django .db .models .functions import Coalesce
45
56from conferences .models .conference import Conference
67from countries import countries
@@ -26,10 +27,12 @@ def calculate(self, conference_id):
2627 """
2728 statuses = Grant .Status .choices
2829 conference = Conference .objects .get (id = conference_id )
29- filtered_grants = Grant .objects .for_conference (conference )
30+ filtered_grants = Grant .objects .for_conference (conference ).annotate (
31+ current_or_pending_status = Coalesce ("pending_status" , "status" )
32+ )
3033
3134 grants_by_country = filtered_grants .values (
32- "departure_country" , "pending_status "
35+ "departure_country" , "current_or_pending_status "
3336 ).annotate (total = Count ("id" ))
3437
3538 (
@@ -99,6 +102,7 @@ def _aggregate_data_by_country(self, grants_by_country, statuses):
99102 totals_per_continent = {}
100103
101104 for data in grants_by_country :
105+ current_or_pending_status : str = data ["current_or_pending_status" ]
102106 country = countries .get (code = data ["departure_country" ])
103107 continent = country .continent .name if country else "Unknown"
104108 country_name = f"{ country .name } { country .emoji } " if country else "Unknown"
@@ -108,13 +112,13 @@ def _aggregate_data_by_country(self, grants_by_country, statuses):
108112 if key not in summary :
109113 summary [key ] = {status [0 ]: 0 for status in statuses }
110114
111- summary [key ][data [ "pending_status" ] ] += data ["total" ]
112- status_totals [data [ "pending_status" ] ] += data ["total" ]
115+ summary [key ][current_or_pending_status ] += data ["total" ]
116+ status_totals [current_or_pending_status ] += data ["total" ]
113117
114118 # Update continent totals
115119 if continent not in totals_per_continent :
116120 totals_per_continent [continent ] = {status [0 ]: 0 for status in statuses }
117- totals_per_continent [continent ][data [ "pending_status" ] ] += data ["total" ]
121+ totals_per_continent [continent ][current_or_pending_status ] += data ["total" ]
118122
119123 return summary , status_totals , totals_per_continent
120124
@@ -123,83 +127,110 @@ def _aggregate_data_by_country_type(self, filtered_grants, statuses):
123127 Aggregates grant data by country type and status.
124128 """
125129 country_type_data = filtered_grants .values (
126- "country_type" , "pending_status "
130+ "country_type" , "current_or_pending_status "
127131 ).annotate (total = Count ("id" ))
128132 country_type_summary = defaultdict (
129133 lambda : {status [0 ]: 0 for status in statuses }
130134 )
131135
132136 for data in country_type_data :
133137 country_type = data ["country_type" ]
134- pending_status = data ["pending_status " ]
138+ current_or_pending_status : str = data ["current_or_pending_status " ]
135139 total = data ["total" ]
136- country_type_summary [country_type ][pending_status ] += total
140+ country_type_summary [country_type ][current_or_pending_status ] += total
137141
138142 return dict (country_type_summary )
139143
140144 def _aggregate_data_by_gender (self , filtered_grants , statuses ):
141145 """
142146 Aggregates grant data by gender and status.
143147 """
144- gender_data = filtered_grants .values ("gender" , "pending_status" ). annotate (
145- total = Count ( "id" )
146- )
148+ gender_data = filtered_grants .values (
149+ "gender" , "current_or_pending_status"
150+ ). annotate ( total = Count ( "id" ))
147151 gender_summary = defaultdict (lambda : {status [0 ]: 0 for status in statuses })
148152
149153 for data in gender_data :
150154 gender = data ["gender" ] if data ["gender" ] else ""
151- pending_status = data ["pending_status " ]
155+ current_or_pending_status : str = data ["current_or_pending_status " ]
152156 total = data ["total" ]
153- gender_summary [gender ][pending_status ] += total
157+ gender_summary [gender ][current_or_pending_status ] += total
154158
155159 return dict (gender_summary )
156160
157161 def _aggregate_financial_data_by_status (self , filtered_grants , statuses ):
158162 """
159- Aggregates financial data (total amounts) by grant status.
163+ Aggregates financial data (total amounts) by grant status
164+ using conditional aggregation in a single query.
160165 """
161- financial_summary = {status [0 ]: 0 for status in statuses }
162- overall_total = 0
166+ reimbursements = GrantReimbursement .objects .filter (
167+ grant__in = filtered_grants
168+ ).annotate (
169+ current_or_pending_status = Coalesce ("grant__pending_status" , "grant__status" )
170+ )
163171
164- for status in statuses :
165- grants_for_status = filtered_grants .filter (pending_status = status [0 ])
166- reimbursements = GrantReimbursement .objects .filter (
167- grant__in = grants_for_status
172+ aggregations : dict [str , Sum ] = {
173+ status_value : Sum (
174+ Case (
175+ When (current_or_pending_status = status_value , then = "granted_amount" ),
176+ default = 0 ,
177+ output_field = DecimalField (),
178+ )
168179 )
169- total = reimbursements .aggregate (total = Sum ("granted_amount" ))["total" ] or 0
170- financial_summary [status [0 ]] = total
171- if status [0 ] in self .BUDGET_STATUSES :
172- overall_total += total
180+ for status_value , _ in statuses
181+ }
182+ result = reimbursements .aggregate (** aggregations )
183+
184+ financial_summary : dict [str , int ] = {
185+ status_value : int (result [status_value ] or 0 ) for status_value , _ in statuses
186+ }
187+ overall_total : int = sum (
188+ amount
189+ for status_value , amount in financial_summary .items ()
190+ if status_value in self .BUDGET_STATUSES
191+ )
173192
174193 return financial_summary , overall_total
175194
176195 def _aggregate_data_by_reimbursement_category (self , filtered_grants , statuses ):
177196 """
178197 Aggregates grant data by reimbursement category and status.
179198 """
180- category_summary = defaultdict (lambda : {status [0 ]: 0 for status in statuses })
181- reimbursements = GrantReimbursement .objects .filter (grant__in = filtered_grants )
182- for r in reimbursements :
183- category = r .category .category
184- status = r .grant .pending_status
185- category_summary [category ][status ] += 1
199+ category_data = (
200+ GrantReimbursement .objects .filter (grant__in = filtered_grants )
201+ .annotate (
202+ current_or_pending_status = Coalesce (
203+ "grant__pending_status" , "grant__status"
204+ )
205+ )
206+ .values ("category__category" , "current_or_pending_status" )
207+ .annotate (total = Count ("id" ))
208+ )
209+ category_summary : dict [str , dict [str , int ]] = defaultdict (
210+ lambda : {status [0 ]: 0 for status in statuses }
211+ )
212+ for data in category_data :
213+ category : str = data ["category__category" ]
214+ current_or_pending_status : str = data ["current_or_pending_status" ]
215+ total : int = data ["total" ]
216+ category_summary [category ][current_or_pending_status ] += total
186217 return dict (category_summary )
187218
188219 def _aggregate_data_by_grant_type (self , filtered_grants , statuses ):
189220 """
190221 Aggregates grant data by grant_type and status.
191222 """
192223 grant_type_data = filtered_grants .values (
193- "grant_type" , "pending_status "
224+ "grant_type" , "current_or_pending_status "
194225 ).annotate (total = Count ("id" ))
195226 grant_type_summary = defaultdict (lambda : {status [0 ]: 0 for status in statuses })
196227
197228 for data in grant_type_data :
198229 grant_types = data ["grant_type" ]
199- pending_status = data ["pending_status " ]
230+ current_or_pending_status : str = data ["current_or_pending_status " ]
200231 total = data ["total" ]
201232 for grant_type in grant_types :
202- grant_type_summary [grant_type ][pending_status ] += total
233+ grant_type_summary [grant_type ][current_or_pending_status ] += total
203234
204235 return dict (grant_type_summary )
205236
@@ -224,13 +255,13 @@ def _aggregate_data_by_speaker_status(self, filtered_grants, statuses):
224255
225256 proposed_speaker_data = (
226257 filtered_grants .filter (is_proposed_speaker = True )
227- .values ("pending_status " )
258+ .values ("current_or_pending_status " )
228259 .annotate (total = Count ("id" ))
229260 )
230261
231262 confirmed_speaker_data = (
232263 filtered_grants .filter (is_confirmed_speaker = True )
233- .values ("pending_status " )
264+ .values ("current_or_pending_status " )
234265 .annotate (total = Count ("id" ))
235266 )
236267
@@ -239,14 +270,18 @@ def _aggregate_data_by_speaker_status(self, filtered_grants, statuses):
239270 )
240271
241272 for data in proposed_speaker_data :
242- pending_status = data ["pending_status " ]
273+ current_or_pending_status : str = data ["current_or_pending_status " ]
243274 total = data ["total" ]
244- speaker_status_summary ["proposed_speaker" ][pending_status ] += total
275+ speaker_status_summary ["proposed_speaker" ][current_or_pending_status ] += (
276+ total
277+ )
245278
246279 for data in confirmed_speaker_data :
247- pending_status = data ["pending_status " ]
280+ current_or_pending_status : str = data ["current_or_pending_status " ]
248281 total = data ["total" ]
249- speaker_status_summary ["confirmed_speaker" ][pending_status ] += total
282+ speaker_status_summary ["confirmed_speaker" ][current_or_pending_status ] += (
283+ total
284+ )
250285
251286 return dict (speaker_status_summary )
252287
@@ -263,13 +298,13 @@ def _aggregate_data_by_requested_needs_summary(self, filtered_grants, statuses):
263298 for field in requested_needs_summary .keys ():
264299 field_data = (
265300 filtered_grants .filter (** {field : True })
266- .values ("pending_status " )
301+ .values ("current_or_pending_status " )
267302 .annotate (total = Count ("id" ))
268303 )
269304 for data in field_data :
270- pending_status = data ["pending_status " ]
305+ current_or_pending_status : str = data ["current_or_pending_status " ]
271306 total = data ["total" ]
272- requested_needs_summary [field ][pending_status ] += total
307+ requested_needs_summary [field ][current_or_pending_status ] += total
273308
274309 return requested_needs_summary
275310
@@ -278,14 +313,14 @@ def _aggregate_data_by_occupation(self, filtered_grants, statuses):
278313 Aggregates grant data by occupation and status.
279314 """
280315 occupation_data = filtered_grants .values (
281- "occupation" , "pending_status "
316+ "occupation" , "current_or_pending_status "
282317 ).annotate (total = Count ("id" ))
283318 occupation_summary = defaultdict (lambda : {status [0 ]: 0 for status in statuses })
284319
285320 for data in occupation_data :
286321 occupation = data ["occupation" ]
287- pending_status = data ["pending_status " ]
322+ current_or_pending_status : str = data ["current_or_pending_status " ]
288323 total = data ["total" ]
289- occupation_summary [occupation ][pending_status ] += total
324+ occupation_summary [occupation ][current_or_pending_status ] += total
290325
291326 return dict (occupation_summary )
0 commit comments