Coverage for portality / bll / services / todo.py: 84%
287 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-04 09:41 +0100
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-04 09:41 +0100
1from __future__ import annotations
3from portality import constants
4from portality import models
5from portality.bll import exceptions
6from portality.lib import dates
7from portality.lib.argvalidate import argvalidate
10class TodoService(object):
11 """
12 ~~Todo:Service->DOAJ:Service~~
13 ~~-> ApplicationStatuses:Config~~
14 """
16 def group_stats(self, group_id):
17 # ~~-> EditorGroup:Model~~
18 eg = models.EditorGroup.pull(group_id)
19 stats = {"editor_group": eg.data}
21 # ~~-> Account:Model ~~
22 stats["editors"] = {}
23 editors = [eg.editor] + eg.associates
24 for editor in editors:
25 acc = models.Account.pull(editor)
26 stats["editors"][editor] = {
27 "email": None if acc is None else acc.email
28 }
30 q = GroupStatsQuery(eg.name)
31 resp = models.Application.query(q=q.query())
33 stats["total"] = {"applications": 0, "update_requests": 0}
35 stats["by_editor"] = {}
36 for bucket in resp.get("aggregations", {}).get("editor", {}).get("buckets", []):
37 stats["by_editor"][bucket["key"]] = {"applications": 0, "update_requests": 0}
39 for b in bucket.get("application_type", {}).get("buckets", []):
40 if b["key"] == constants.APPLICATION_TYPE_NEW_APPLICATION:
41 stats["by_editor"][bucket["key"]]["applications"] = b["doc_count"]
42 stats["total"]["applications"] += b["doc_count"]
43 elif b["key"] == constants.APPLICATION_TYPE_UPDATE_REQUEST:
44 stats["by_editor"][bucket["key"]]["update_requests"] = b["doc_count"]
45 stats["total"]["update_requests"] += b["doc_count"]
47 unassigned_buckets = resp.get("aggregations", {}).get("unassigned", {}).get(
48 "application_type", {}).get("buckets", [])
49 stats["unassigned"] = {"applications": 0, "update_requests": 0}
50 for ub in unassigned_buckets:
51 if ub["key"] == constants.APPLICATION_TYPE_NEW_APPLICATION:
52 stats["unassigned"]["applications"] = ub["doc_count"]
53 stats["total"]["applications"] += ub["doc_count"]
54 elif ub["key"] == constants.APPLICATION_TYPE_UPDATE_REQUEST:
55 stats["unassigned"]["update_requests"] = ub["doc_count"]
56 stats["total"]["update_requests"] += ub["doc_count"]
58 stats["by_status"] = {}
59 for bucket in resp.get("aggregations", {}).get("status", {}).get("buckets", []):
60 stats["by_status"][bucket["key"]] = {"applications": 0, "update_requests": 0}
61 for b in bucket.get("application_type", {}).get("buckets", []):
62 if b["key"] == constants.APPLICATION_TYPE_NEW_APPLICATION:
63 stats["by_status"][bucket["key"]]["applications"] = b["doc_count"]
64 elif b["key"] == constants.APPLICATION_TYPE_UPDATE_REQUEST:
65 stats["by_status"][bucket["key"]]["update_requests"] = b["doc_count"]
67 stats["historical_numbers"] = self.group_finished_historical_counts(eg)
69 return stats
71 def group_finished_historical_counts(self, editor_group: models.EditorGroup, year=None):
72 """
73 Get the count of applications in an editor group where
74 Associate Editors set to Completed when they have done their review
75 Editors set them to Ready
76 in a given year (current by default)
77 :param editor_group
78 :param year
79 :return: historical for editor and associate editor in dict
80 """
81 year_for_query = dates.now_str(dates.FMT_YEAR) if year is None else year
82 editor_status = "status:" + constants.APPLICATION_STATUS_READY
83 associate_status = "status:" + constants.APPLICATION_STATUS_COMPLETED
85 stats = {"year": year_for_query}
87 hs = HistoricalNumbersQuery(editor_group.editor, editor_status, editor_group.id)
88 # ~~-> Provenance:Model ~~
89 editor_count = models.Provenance.count(query=hs.query())
91 # ~~-> Account:Model ~~
92 acc = models.Account.pull(editor_group.editor)
93 if acc is not None:
94 stats["editor"] = {"id": acc.id, "count": editor_count}
96 stats["associate_editors"] = []
97 for associate in editor_group.associates:
98 hs = HistoricalNumbersQuery(associate, associate_status, editor_group.id)
99 associate_count = models.Provenance.count(query=hs.query())
100 acc = models.Account.pull(associate)
101 if acc is not None:
102 stats["associate_editors"].append({"id": acc.id, "name": acc.name, "count": associate_count})
104 return stats
106 def user_finished_historical_counts(self, account, year=None):
107 """
108 Get the count of overall applications
109 Associate Editors set to Completed
110 Editors set them to Ready
111 in a given year (current by default)
112 :param account
113 :param year
114 :return:
115 """
116 hs = None
118 if account.has_role("editor"):
119 hs = HistoricalNumbersQuery(account.id, "status:" + constants.APPLICATION_STATUS_READY, year)
120 elif account.has_role("associate_editor"):
121 hs = HistoricalNumbersQuery(account.id, "status:" + constants.APPLICATION_STATUS_COMPLETED, year)
123 if hs:
124 count = models.Provenance.count(query=hs.query())
125 else:
126 count = None
128 return count
130 def top_todo(self, account, size=25, new_applications=True, update_requests=True, on_hold=True) -> list[dict]:
131 """
132 Returns the top number of todo items for a given user
134 :param account:
135 :param size:
136 :return:
137 """
138 # first validate the incoming arguments to ensure that we've got the right thing
139 argvalidate("top_todo", [
140 {"arg": account, "instance": models.Account, "allow_none": False, "arg_name": "account"}
141 ], exceptions.ArgumentException)
143 queries = []
144 if account.has_role("admin"):
145 maned_of = models.EditorGroup.groups_by_maned(account.id)
146 if new_applications:
147 queries.append(TodoRules.maned_follow_up_old(size, maned_of))
148 queries.append(TodoRules.maned_stalled(size, maned_of))
149 queries.append(TodoRules.maned_ready(size, maned_of))
150 queries.append(TodoRules.maned_completed(size, maned_of))
151 queries.append(TodoRules.maned_assign_pending(size, maned_of))
152 #queries.append(TodoRules.urgent_flags_new_applications(account.id, size))
153 #queries.append(TodoRules.regular_flags_new_applications(account.id, size))
154 if update_requests:
155 queries.append(TodoRules.maned_last_month_update_requests(size, maned_of))
156 queries.append(TodoRules.maned_new_update_requests(size, maned_of))
157 #queries.append(TodoRules.urgent_flags_update_requests(account.id, size))
158 #queries.append(TodoRules.regular_flags_update_requests(account.id, size))
159 if on_hold:
160 queries.append(TodoRules.maned_on_hold(size, account.id, maned_of))
161 #queries.append(TodoRules.urgent_flags_onhold(account.id, size))
162 #queries.append(TodoRules.regular_flags_onhold(account.id, size))
164 if new_applications: # editor and associate editor roles only deal with new applications
165 if account.has_role("editor"):
166 groups = [g for g in models.EditorGroup.groups_by_editor(account.id)]
167 regular_groups = [g for g in groups if g.maned != account.id]
168 maned_groups = [g for g in groups if g.maned == account.id]
169 if len(groups) > 0:
170 queries.append(TodoRules.editor_follow_up_old(groups, size))
171 queries.append(TodoRules.editor_stalled(groups, size))
172 queries.append(TodoRules.editor_completed(groups, size))
174 # for groups where the user is not the maned for a group, given them the assign
175 # pending todos at the regular priority
176 if len(regular_groups) > 0:
177 queries.append(TodoRules.editor_assign_pending(regular_groups, size))
179 # for groups where the user IS the maned for a group, give them the assign
180 # pending todos at a lower priority
181 if len(maned_groups) > 0:
182 qi = TodoRules.editor_assign_pending(maned_groups, size)
183 queries.append((constants.TODO_EDITOR_ASSIGN_PENDING_LOW_PRIORITY, qi[1], qi[2], -2))
185 if account.has_role(constants.ROLE_ASSOCIATE_EDITOR):
186 queries.extend([
187 TodoRules.associate_follow_up_old(account.id, size),
188 TodoRules.associate_stalled(account.id, size),
189 TodoRules.associate_start_pending(account.id, size),
190 TodoRules.associate_all_applications(account.id, size)
191 ])
193 todos = []
194 for aid, q, sort, boost in queries:
195 applications = models.Application.object_query(q=q.query())
196 for ap in applications:
197 sort_map = {
198 "last_manual_update": ap.last_manual_update_timestamp,
199 "created_date": ap.date_applied_timestamp,
200 "most_urgent_flag_deadline": ap.most_urgent_flag_deadline_timestamp
201 }
202 todos.append({
203 "date": sort_map.get(sort, ap.date_applied_timestamp),
204 "date_type": sort,
205 "action_id": [aid],
206 "title": ap.bibjson().title,
207 "object_id": ap.id,
208 "object": ap,
209 "boost": boost
210 })
212 todos = self._rationalise_todos(todos, size)
214 return todos
216 def _rationalise_todos(self, todos, size):
217 boost_groups = sorted(list(set([x["boost"] for x in todos])), reverse=True)
219 stds = []
220 for bg in boost_groups:
221 group = list(filter(lambda x: x["boost"] == bg, todos))
222 stds += sorted(group, key=lambda x: x['date'])
224 id_map = {}
225 removals = []
226 for i in range(len(stds)):
227 todo = stds[i]
228 oid = todo["object_id"]
229 if oid in id_map:
230 removals.append(i)
231 stds[id_map[oid]]['action_id'] += todo['action_id']
232 else:
233 id_map[oid] = i
235 removals.reverse()
236 for r in removals:
237 del stds[r]
239 return stds[:size]
242class TodoRules(object):
244 @classmethod
245 def maned_stalled(cls, size, maned_of):
246 sort_date = "created_date"
247 stalled = TodoQuery(
248 musts=[
249 TodoQuery.lmu_older_than(8),
250 TodoQuery.editor_group(maned_of),
251 TodoQuery.is_new_application()
252 ],
253 must_nots=[
254 TodoQuery.status([
255 constants.APPLICATION_STATUS_ACCEPTED,
256 constants.APPLICATION_STATUS_REJECTED,
257 constants.APPLICATION_STATUS_ON_HOLD
258 ])
259 ],
260 sort=sort_date,
261 size=size
262 )
263 return constants.TODO_MANED_STALLED, stalled, sort_date, 0
265 @classmethod
266 def maned_follow_up_old(cls, size, maned_of):
267 sort_date = "created_date"
268 follow_up_old = TodoQuery(
269 musts=[
270 TodoQuery.cd_older_than(10),
271 TodoQuery.editor_group(maned_of),
272 TodoQuery.is_new_application()
273 ],
274 must_nots=[
275 TodoQuery.status([
276 constants.APPLICATION_STATUS_ACCEPTED,
277 constants.APPLICATION_STATUS_REJECTED,
278 constants.APPLICATION_STATUS_ON_HOLD
279 ])
280 ],
281 sort=sort_date,
282 size=size
283 )
284 return constants.TODO_MANED_FOLLOW_UP_OLD, follow_up_old, sort_date, 0
286 @classmethod
287 def maned_ready(cls, size, maned_of):
288 sort_date = "created_date"
289 ready = TodoQuery(
290 musts=[
291 TodoQuery.status([constants.APPLICATION_STATUS_READY]),
292 TodoQuery.editor_group(maned_of),
293 TodoQuery.is_new_application()
294 ],
295 sort=sort_date,
296 size=size
297 )
298 return constants.TODO_MANED_READY, ready, sort_date, 1
300 @classmethod
301 def maned_completed(cls, size, maned_of):
302 sort_date = "created_date"
303 completed = TodoQuery(
304 musts=[
305 TodoQuery.status([constants.APPLICATION_STATUS_COMPLETED]),
306 TodoQuery.lmu_older_than(2),
307 TodoQuery.editor_group(maned_of),
308 TodoQuery.is_new_application()
309 ],
310 sort=sort_date,
311 size=size
312 )
313 return constants.TODO_MANED_COMPLETED, completed, sort_date, 0
315 @classmethod
316 def maned_assign_pending(cls, size, maned_of):
317 sort_date = "created_date"
318 assign_pending = TodoQuery(
319 musts=[
320 TodoQuery.exists("admin.editor_group"),
321 TodoQuery.lmu_older_than(2),
322 TodoQuery.status([constants.APPLICATION_STATUS_PENDING]),
323 TodoQuery.editor_group(maned_of),
324 TodoQuery.is_new_application()
325 ],
326 must_nots=[
327 TodoQuery.exists("admin.editor")
328 ],
329 sort=sort_date,
330 size=size
331 )
332 return constants.TODO_MANED_ASSIGN_PENDING, assign_pending, sort_date, 0
334 @classmethod
335 def maned_last_month_update_requests(cls, size, maned_of):
336 som = dates.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0)
337 now = dates.now()
338 since_som = int((now - som).total_seconds())
340 sort_date = "created_date"
341 assign_pending = TodoQuery(
342 musts=[
343 TodoQuery.exists("admin.editor_group"),
344 TodoQuery.cd_older_than(since_som, unit="s"),
345 # TodoQuery.status([constants.APPLICATION_STATUS_UPDATE_REQUEST]),
346 TodoQuery.editor_group(maned_of),
347 TodoQuery.is_update_request()
348 ],
349 must_nots=[
350 TodoQuery.status([
351 constants.APPLICATION_STATUS_ACCEPTED,
352 constants.APPLICATION_STATUS_REJECTED
353 ])
354 ],
355 sort=sort_date,
356 size=size
357 )
358 return constants.TODO_MANED_LAST_MONTH_UPDATE_REQUEST, assign_pending, sort_date, 2
360 @classmethod
361 def maned_new_update_requests(cls, size, maned_of):
362 sort_date = "created_date"
363 assign_pending = TodoQuery(
364 musts=[
365 TodoQuery.exists("admin.editor_group"),
366 # TodoQuery.cd_older_than(4),
367 # TodoQuery.status([constants.APPLICATION_STATUS_UPDATE_REQUEST]),
368 TodoQuery.editor_group(maned_of),
369 TodoQuery.is_update_request()
370 ],
371 must_nots=[
372 TodoQuery.status([
373 constants.APPLICATION_STATUS_ACCEPTED,
374 constants.APPLICATION_STATUS_REJECTED
375 ])
376 ],
377 sort=sort_date,
378 size=size
379 )
380 return constants.TODO_MANED_NEW_UPDATE_REQUEST, assign_pending, sort_date, -2
382 @classmethod
383 def maned_on_hold(cls, size, account, maned_of):
384 sort_date = "created_date"
385 on_holds = TodoQuery(
386 musts=[
387 TodoQuery.is_new_application(),
388 TodoQuery.status([constants.APPLICATION_STATUS_ON_HOLD])
389 ],
390 ors=[
391 TodoQuery.editor_group(maned_of),
392 TodoQuery.editor(account)
393 ],
394 sort=sort_date,
395 size=size
396 )
397 return constants.TODO_MANED_ON_HOLD, on_holds, sort_date, 0
399 @classmethod
400 def editor_stalled(cls, groups, size):
401 sort_date = "created_date"
402 stalled = TodoQuery(
403 musts=[
404 TodoQuery.lmu_older_than(6),
405 TodoQuery.editor_groups(groups),
406 TodoQuery.is_new_application()
407 ],
408 must_nots=[
409 TodoQuery.status([
410 constants.APPLICATION_STATUS_ACCEPTED,
411 constants.APPLICATION_STATUS_REJECTED,
412 constants.APPLICATION_STATUS_READY,
413 constants.APPLICATION_STATUS_ON_HOLD,
414 constants.APPLICATION_STATUS_REVISIONS_REQUIRED
415 ])
416 ],
417 sort=sort_date,
418 size=size
419 )
420 return constants.TODO_EDITOR_STALLED, stalled, sort_date, 0
422 @classmethod
423 def editor_follow_up_old(cls, groups, size):
424 sort_date = "created_date"
425 follow_up_old = TodoQuery(
426 musts=[
427 TodoQuery.cd_older_than(8),
428 TodoQuery.editor_groups(groups),
429 TodoQuery.is_new_application()
430 ],
431 must_nots=[
432 TodoQuery.status([
433 constants.APPLICATION_STATUS_ACCEPTED,
434 constants.APPLICATION_STATUS_REJECTED,
435 constants.APPLICATION_STATUS_READY,
436 constants.APPLICATION_STATUS_REVISIONS_REQUIRED,
437 constants.APPLICATION_STATUS_ON_HOLD
438 ])
439 ],
440 sort=sort_date,
441 size=size
442 )
443 return constants.TODO_EDITOR_FOLLOW_UP_OLD, follow_up_old, sort_date, 0
445 @classmethod
446 def editor_completed(cls, groups, size):
447 sort_date = "created_date"
448 completed = TodoQuery(
449 musts=[
450 TodoQuery.status([constants.APPLICATION_STATUS_COMPLETED]),
451 TodoQuery.editor_groups(groups),
452 TodoQuery.is_new_application()
453 ],
454 sort=sort_date,
455 size=size
456 )
457 return constants.TODO_EDITOR_COMPLETED, completed, sort_date, 1
459 @classmethod
460 def editor_assign_pending(cls, groups, size):
461 sort_date = "created_date"
462 assign_pending = TodoQuery(
463 musts=[
464 TodoQuery.editor_groups(groups),
465 TodoQuery.status([constants.APPLICATION_STATUS_PENDING]),
466 TodoQuery.is_new_application()
467 ],
468 must_nots=[
469 TodoQuery.exists("admin.editor")
470 ],
471 sort=sort_date,
472 size=size
473 )
474 return constants.TODO_EDITOR_ASSIGN_PENDING, assign_pending, sort_date, 1
476 @classmethod
477 def associate_stalled(cls, acc_id, size):
478 sort_field = "created_date"
479 stalled = TodoQuery(
480 musts=[
481 TodoQuery.lmu_older_than(3),
482 TodoQuery.editor(acc_id),
483 TodoQuery.is_new_application()
484 ],
485 must_nots=[
486 TodoQuery.status([
487 constants.APPLICATION_STATUS_ACCEPTED,
488 constants.APPLICATION_STATUS_REJECTED,
489 constants.APPLICATION_STATUS_READY,
490 constants.APPLICATION_STATUS_COMPLETED,
491 constants.APPLICATION_STATUS_REVISIONS_REQUIRED,
492 constants.APPLICATION_STATUS_ON_HOLD
493 ])
494 ],
495 sort=sort_field,
496 size=size
497 )
498 return constants.TODO_ASSOCIATE_PROGRESS_STALLED, stalled, sort_field, 0
500 @classmethod
501 def associate_follow_up_old(cls, acc_id, size):
502 sort_field = "created_date"
503 follow_up_old = TodoQuery(
504 musts=[
505 TodoQuery.cd_older_than(6),
506 TodoQuery.editor(acc_id),
507 TodoQuery.is_new_application()
508 ],
509 must_nots=[
510 TodoQuery.status([
511 constants.APPLICATION_STATUS_ACCEPTED,
512 constants.APPLICATION_STATUS_REJECTED,
513 constants.APPLICATION_STATUS_READY,
514 constants.APPLICATION_STATUS_COMPLETED,
515 constants.APPLICATION_STATUS_REVISIONS_REQUIRED,
516 constants.APPLICATION_STATUS_ON_HOLD
517 ])
518 ],
519 sort=sort_field,
520 size=size
521 )
522 return constants.TODO_ASSOCIATE_FOLLOW_UP_OLD, follow_up_old, sort_field, 0
524 @classmethod
525 def associate_start_pending(cls, acc_id, size):
526 sort_field = "created_date"
527 assign_pending = TodoQuery(
528 musts=[
529 TodoQuery.editor(acc_id),
530 TodoQuery.status([constants.APPLICATION_STATUS_PENDING]),
531 TodoQuery.is_new_application()
532 ],
533 sort=sort_field,
534 size=size
535 )
536 return constants.TODO_ASSOCIATE_START_PENDING, assign_pending, sort_field, 0
538 @classmethod
539 def associate_all_applications(cls, acc_id, size):
540 sort_field = "created_date"
541 all = TodoQuery(
542 musts=[
543 TodoQuery.editor(acc_id),
544 TodoQuery.is_new_application()
545 ],
546 must_nots=[
547 TodoQuery.status([
548 constants.APPLICATION_STATUS_ACCEPTED,
549 constants.APPLICATION_STATUS_REJECTED,
550 constants.APPLICATION_STATUS_READY,
551 constants.APPLICATION_STATUS_COMPLETED,
552 constants.APPLICATION_STATUS_REVISIONS_REQUIRED,
553 constants.APPLICATION_STATUS_ON_HOLD
554 ])
555 ],
556 sort=sort_field,
557 size=size
558 )
559 return constants.TODO_ASSOCIATE_ALL_APPLICATIONS, all, sort_field, -1
561 # @classmethod
562 # def urgent_flags_all(cls, acc_id, size):
563 # sort_field = "most_urgent_flag_deadline"
564 #
565 # all = TodoQuery(
566 # musts=[
567 # TodoQuery.flagged_to_me(acc_id),
568 # TodoQuery.urgent_flags()
569 # ],
570 # sort=sort_field,
571 # size=size
572 # )
573 # return constants.TODO_URGENT_FLAGS_ALL, all, sort_field, 4
574 #
575 # @classmethod
576 # def regular_flags_all(cls, acc_id, size):
577 # sort_field = "most_urgent_flag_deadline"
578 # all = TodoQuery(
579 # musts=[
580 # TodoQuery.flagged_to_me(acc_id)
581 # ],
582 # must_nots=[
583 # TodoQuery.urgent_flags()
584 # ],
585 # sort=sort_field,
586 # size=size
587 # )
588 # return constants.TODO_REGULAR_FLAGS_ALL, all, sort_field, 3
589 #
590 # @classmethod
591 # def urgent_flags_new_applications(cls, acc_id, size):
592 # sort_field = "most_urgent_flag_deadline"
593 # all = TodoQuery(
594 # musts=[
595 # TodoQuery.flagged_to_me(acc_id),
596 # TodoQuery.urgent_flags(),
597 # TodoQuery.is_new_application()
598 # ],
599 # sort=sort_field,
600 # size=size
601 # )
602 # return constants.TODO_URGENT_FLAGS_NEW_APPLICATIONS, all, sort_field, 4
603 #
604 # @classmethod
605 # def urgent_flags_update_requests(cls, acc_id, size):
606 # sort_field = "most_urgent_flag_deadline"
607 # all = TodoQuery(
608 # musts=[
609 # TodoQuery.flagged_to_me(acc_id),
610 # TodoQuery.urgent_flags(),
611 # TodoQuery.is_update_request()
612 # ],
613 # sort=sort_field,
614 # size=size
615 # )
616 # return constants.TODO_URGENT_FLAGS_UPDATE_REQUESTS, all, sort_field, 4
617 #
618 # @classmethod
619 # def urgent_flags_onhold(cls, acc_id, size):
620 # sort_field = "most_urgent_flag_deadline"
621 # all = TodoQuery(
622 # musts=[
623 # TodoQuery.flagged_to_me(acc_id),
624 # TodoQuery.urgent_flags(),
625 # TodoQuery.is_new_application(),
626 # TodoQuery.status([constants.APPLICATION_STATUS_ON_HOLD])
627 # ],
628 # sort=sort_field,
629 # size=size
630 # )
631 # return constants.TODO_URGENT_FLAGS_ONHOLD, all, sort_field, 4
632 #
633 # @classmethod
634 # def regular_flags_new_applications(cls, acc_id, size):
635 # sort_field = "most_urgent_flag_deadline"
636 # all = TodoQuery(
637 # musts=[
638 # TodoQuery.flagged_to_me(acc_id),
639 # TodoQuery.is_new_application()
640 # ],
641 # must_nots=[
642 # TodoQuery.urgent_flags()
643 # ],
644 # sort=sort_field,
645 # size=size
646 # )
647 # return constants.TODO_REGULAR_FLAGS_NEW_APPLICATIONS, all, sort_field, 3
648 #
649 # @classmethod
650 # def regular_flags_update_requests(cls, acc_id, size):
651 # sort_field = "most_urgent_flag_deadline"
652 # all = TodoQuery(
653 # musts=[
654 # TodoQuery.flagged_to_me(acc_id),
655 # TodoQuery.is_update_request()
656 # ],
657 # must_nots=[
658 # TodoQuery.urgent_flags()
659 # ],
660 # sort=sort_field,
661 # size=size
662 # )
663 # return constants.TODO_REGULAR_FLAGS_UPDATE_REQUESTS, all, sort_field, 3
664 #
665 # @classmethod
666 # def regular_flags_onhold(cls, acc_id, size):
667 # sort_field = "most_urgent_flag_deadline"
668 #
669 # all = TodoQuery(
670 # musts=[
671 # TodoQuery.flagged_to_me(acc_id),
672 # TodoQuery.is_new_application(),
673 # TodoQuery.status([constants.APPLICATION_STATUS_ON_HOLD])
674 # ],
675 # must_nots=[
676 # TodoQuery.urgent_flags()
677 # ],
678 # sort=sort_field,
679 # size=size
680 # )
681 # return constants.TODO_REGULAR_FLAGS_ONHOLD, all, sort_field, 3
684class TodoQuery(object):
685 """
686 ~~->$Todo:Query~~
687 ~~^->Elasticsearch:Technology~~
688 """
689 lmu_sort = {"last_manual_update": {"order": "asc"}}
690 # cd_sort = {"created_date" : {"order" : "asc"}}
691 # NOTE that admin.date_applied and created_date should be the same for applications, but for some reason this is not always the case
692 # therefore, we take a created_date sort to mean a date_applied sort
693 cd_sort = {"admin.date_applied": {"order": "asc"}}
694 flag_sort = {"index.most_urgent_flag_deadline": {"order": "asc"}}
696 sort_map = {
697 "last_manual_update": lmu_sort,
698 "created_date": cd_sort,
699 "most_urgent_flag_deadline": flag_sort
700 }
702 def __init__(self, musts=None, must_nots=None, ors=None, sort="last_manual_update", size=10):
703 self._musts = [] if musts is None else musts
704 self._must_nots = [] if must_nots is None else must_nots
705 self._ors = [] if ors is None else ors
706 self._sort = self.sort_map.get(sort, self.cd_sort)
707 self._size = size
709 def query(self):
710 q = {
711 "query": {
712 "bool": {
713 "must": self._musts,
714 "must_not": self._must_nots
715 }
716 },
717 "sort": [
718 self._sort
719 ],
720 "size": self._size
721 }
723 if len(self._musts) > 0:
724 q["query"]["bool"]["must"] = self._musts
725 if len(self._must_nots) > 0:
726 q["query"]["bool"]["must_not"] = self._must_nots
727 if len(self._ors) > 0:
728 q["query"]["bool"]["should"] = self._ors
729 q["query"]["bool"]["minimum_should_match"] = 1
731 return q
733 @classmethod
734 def is_new_application(cls):
735 return {
736 "term": {
737 "admin.application_type.exact": constants.APPLICATION_TYPE_NEW_APPLICATION
738 }
739 }
741 @classmethod
742 def is_update_request(cls):
743 return {
744 "term": {
745 "admin.application_type.exact": constants.APPLICATION_TYPE_UPDATE_REQUEST
746 }
747 }
749 @classmethod
750 def editor_group(cls, groups):
751 return {
752 "terms": {
753 "admin.editor_group.exact": [g.name for g in groups]
754 }
755 }
757 @classmethod
758 def lmu_older_than(cls, weeks):
759 return {
760 "range": {
761 "last_manual_update": {
762 "lte": "now-" + str(weeks) + "w"
763 }
764 }
765 }
767 @classmethod
768 def cd_older_than(cls, count, unit="w"):
769 return {
770 "range": {
771 "admin.date_applied": {
772 "lte": "now-" + str(count) + unit
773 }
774 }
775 }
777 @classmethod
778 def status(cls, statuses):
779 return {
780 "terms": {
781 "admin.application_status.exact": statuses
782 }
783 }
785 @classmethod
786 def exists(cls, field):
787 return {
788 "exists": {
789 "field": field
790 }
791 }
793 @classmethod
794 def editor_groups(cls, groups):
795 gids = [g.name for g in groups]
796 return {
797 "terms": {
798 "admin.editor_group.exact": gids
799 }
800 }
802 @classmethod
803 def editor(cls, acc_id):
804 return {
805 "terms": {
806 "admin.editor.exact": [acc_id],
807 }
808 }
810 # @classmethod
811 # def flagged_to_me(cls, acc_id):
812 # return {
813 # "terms": {
814 # "index.flag_assignees.exact": [acc_id]
815 # }
816 # }
817 #
818 # @classmethod
819 # def urgent_flags(cls):
820 # return {
821 # "range": {
822 # "index.most_urgent_flag_deadline": {
823 # "lte": "now+7d/d"
824 # }
825 # }
826 # }
827 #
828 # @classmethod
829 # def flags_with_nonurgent_deadline(cls):
830 # return {
831 # "range": {
832 # "index.most_urgent_flag_deadline": {
833 # "gt": "now+7d/d"
834 # }
835 # }
836 # }
839class GroupStatsQuery():
840 """
841 ~~->$GroupStats:Query~~
842 ~~^->Elasticsearch:Technology~~
843 """
845 def __init__(self, group_name, editor_count=10):
846 self.group_name = group_name
847 self.editor_count = editor_count
849 def query(self):
850 return {
851 "track_total_hits": True,
852 "query": {
853 "bool": {
854 "must": [
855 {
856 "term": {
857 "admin.editor_group.exact": self.group_name
858 }
859 }
860 ],
861 "must_not": [
862 {
863 "terms": {
864 "admin.application_status.exact": [
865 constants.APPLICATION_STATUS_ACCEPTED,
866 constants.APPLICATION_STATUS_REJECTED
867 ]
868 }
869 }
870 ]
871 }
872 },
873 "size": 0,
874 "aggs": {
875 "editor": {
876 "terms": {
877 "field": "admin.editor.exact",
878 "size": self.editor_count
879 },
880 "aggs": {
881 "application_type": {
882 "terms": {
883 "field": "admin.application_type.exact",
884 "size": 2
885 }
886 }
887 }
888 },
889 "status": {
890 "terms": {
891 "field": "admin.application_status.exact",
892 "size": len(constants.APPLICATION_STATUSES_ALL)
893 },
894 "aggs": {
895 "application_type": {
896 "terms": {
897 "field": "admin.application_type.exact",
898 "size": 2
899 }
900 }
901 }
902 },
903 "unassigned": {
904 "missing": {
905 "field": "admin.editor.exact"
906 },
907 "aggs": {
908 "application_type": {
909 "terms": {
910 "field": "admin.application_type.exact",
911 "size": 2
912 }
913 }
914 }
915 }
916 }
917 }
920class HistoricalNumbersQuery:
921 """
922 ~~->$HistoricalNumbers:Query~~
923 ~~^->Elasticsearch:Technology~~
924 """
926 def __init__(self, editor, application_status, editor_group=None, year=None):
927 self.editor_group = editor_group
928 self.editor = editor
929 self.application_status = application_status
930 self.year = year
932 def query(self):
933 if self.year is None:
934 date_range = {"gte": "now/y", "lte": "now"}
935 else:
936 date_range = {
937 "gte": f"{self.year}-01-01",
938 "lte": f"{self.year}-12-31"
939 }
940 must_terms = [{"range": {"last_updated": date_range}},
941 {"term": {"type": "suggestion"}},
942 {"term": {"user.exact": self.editor}},
943 {"term": {"action": self.application_status}}
944 ]
946 if self.editor_group:
947 must_terms.append({"term": {"editor_group": self.editor_group}})
949 return {
950 "query": {
951 "bool": {
952 "must": must_terms
953 }
954 }
955 }