Skip to content

Commit 83ef2ee

Browse files
committed
Initial account list endpoint
This adds an account listing endpoint to an event that serializes the accounts within the event and annotates them with total number of inconsistencies (evaluators hit), and months of data for the account. I've added these annotations to the `AccountActivityQuerySet` object management for `AccountActivity` (which is defined in `evaluate_m2`), so they're available on any `AccountActivity.objects` query.
1 parent b47f574 commit 83ef2ee

4 files changed

Lines changed: 112 additions & 5 deletions

File tree

django/evaluate_m2/managers.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.db import models
2-
from django.db.models import Q
2+
from django.db.models import Count, IntegerField, OuterRef, Q, Subquery, Value
3+
from django.db.models.functions import Coalesce, TruncMonth
34

45

56
class AccountActivityQuerySet(models.QuerySet):
@@ -9,3 +10,49 @@ def no_previous_bankruptcy_indicators(self):
910
Q(previous_values__cons_info_ind_assoc__isnull=True)
1011
) & Q(previous_values__cons_info_ind='')
1112
)
13+
14+
def with_inconsistency_counts(self, event):
15+
from evaluate_m2.models import EvaluatorResult
16+
17+
# Subquery for the number of inconsistencies (evaluators hit) for
18+
# each account. This is just a count of hits.
19+
subquery = EvaluatorResult.objects.filter(
20+
result_summary__event=event,
21+
acct_num=OuterRef("cons_acct_num"),
22+
).order_by().values("acct_num").annotate(
23+
n=Count("result_summary__evaluator_id", distinct=True),
24+
).values("n")[:1]
25+
26+
return self.annotate(
27+
total_inconsistencies=Coalesce(
28+
Subquery(subquery, output_field=IntegerField()),
29+
Value(0),
30+
),
31+
)
32+
33+
def with_months_of_data(self, event):
34+
# Subquery for the number of months of data. This counts **distinct**
35+
# months, not the number of months in the total range.
36+
# I.e. if there's data for Feb, March, and May, but not April, that's
37+
# three months of data, not four.
38+
subquery = self.model._default_manager.filter(
39+
data_file__event=event,
40+
cons_acct_num=OuterRef("cons_acct_num"),
41+
).order_by().annotate(
42+
month=TruncMonth("activity_date"),
43+
).values("cons_acct_num").annotate(
44+
n=Count("month", distinct=True),
45+
).values("n")[:1]
46+
47+
return self.annotate(
48+
months_of_data=Coalesce(
49+
Subquery(subquery, output_field=IntegerField()),
50+
Value(0),
51+
)
52+
)
53+
54+
def distinct_accounts(self):
55+
return self.order_by(
56+
"cons_acct_num",
57+
"-activity_date",
58+
).distinct("cons_acct_num")

django/evaluate_m2/serializers.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
parse_fields_from_csv,
77
plain_to_code_field_map,
88
)
9-
10-
from .models import EvaluatorMetadata, EvaluatorResultSummary
9+
from evaluate_m2.models import EvaluatorMetadata, EvaluatorResultSummary
10+
from parse_m2.serializers import AccountActivitySerializer
1111

1212

1313
class EventsViewSerializer(serializers.ModelSerializer):
@@ -224,3 +224,14 @@ def validate(self, data):
224224
if invalid_fields:
225225
raise serializers.ValidationError(f"Invalid field names: {invalid_fields}")
226226
return data
227+
228+
229+
class AccountListSerializer(AccountActivitySerializer):
230+
total_inconsistencies = serializers.IntegerField(read_only=True)
231+
months_of_data = serializers.IntegerField(read_only=True)
232+
233+
class Meta(AccountActivitySerializer.Meta):
234+
default_fields = list(AccountActivitySerializer.Meta.default_fields) + [
235+
"total_inconsistencies",
236+
"months_of_data",
237+
]

django/evaluate_m2/urls.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
eval_views.download_evaluator_results_csv),
1010
path('<int:event_id>/evaluator/<str:evaluator_id>/',
1111
eval_views.EvaluatorResultsView().as_view()),
12+
path('<int:event_id>/account/',
13+
eval_views.AccountsListView().as_view()),
1214
path('<int:event_id>/account/<str:account_number>/',
13-
eval_views.account_summary_view),
15+
eval_views.account_summary_view),
1416
path('<int:event_id>/account/<str:account_number>/account_holder/',
15-
eval_views.account_pii_view),
17+
eval_views.account_pii_view),
1618
path('<int:event_id>/', eval_views.events_view),
1719
]

django/evaluate_m2/views.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
)
2525
from evaluate_m2.pagination import EvaluatorResultsPaginator
2626
from evaluate_m2.serializers import (
27+
AccountListSerializer,
2728
EvaluatorMetadataSerializer,
2829
EventsViewSerializer,
2930
)
@@ -58,6 +59,7 @@ def download_evaluator_metadata_csv(request):
5859

5960
return response
6061

62+
6163
@api_view(('GET',))
6264
def download_evaluator_results_csv(request, event_id, evaluator_id):
6365
logger = logging.getLogger('views.download_evaluator_results_csv')
@@ -131,6 +133,7 @@ def account_summary_view(request, event_id, account_number):
131133
logger.error(error['message'])
132134
return Response(error, status=status.HTTP_404_NOT_FOUND)
133135

136+
134137
@api_view(('GET',))
135138
def account_pii_view(request, event_id, account_number):
136139
logger = logging.getLogger('views.account_pii_view')
@@ -153,6 +156,7 @@ def account_pii_view(request, event_id, account_number):
153156
logger.error(error['message'])
154157
return Response(error, status=status.HTTP_404_NOT_FOUND)
155158

159+
156160
@api_view()
157161
def events_view(request, event_id):
158162
logger = logging.getLogger('views.evaluator_results_view')
@@ -188,6 +192,7 @@ def events_view(request, event_id):
188192
logger.error(error['message'])
189193
return Response(error, status=status.HTTP_404_NOT_FOUND)
190194

195+
191196
###########################################
192197
## Helper methods for eval results when S3_ENABLED == True
193198
def fetch_csv_results_from_s3(request, event_id, evaluator_id):
@@ -209,6 +214,7 @@ def fetch_csv_results_from_s3(request, event_id, evaluator_id):
209214
logger.error(error['message'])
210215
return Response(error, status=status.HTTP_404_NOT_FOUND)
211216

217+
212218
def fetch_json_results_from_s3(request, event_id, evaluator_id):
213219
logger = logging.getLogger('views.fetch_json_results_from_s3')
214220
s3 = s3_session()
@@ -319,3 +325,44 @@ def list(self, request, *args, **kwargs):
319325
many=True,
320326
)
321327
return self.get_paginated_response(serializer.data)
328+
329+
330+
331+
332+
class AccountsListView(generics.ListAPIView):
333+
filter_backends = [
334+
django_filters.rest_framework.DjangoFilterBackend,
335+
]
336+
# filterset_class = EvaluatorResultFilterSet
337+
338+
def get_queryset(self):
339+
event_id = self.kwargs["event_id"]
340+
event = Metro2Event.objects.get(id=event_id)
341+
342+
queryset = event.get_all_account_activity().select_related(
343+
"k2",
344+
"k4",
345+
"l1"
346+
).with_inconsistency_counts(
347+
event
348+
).with_months_of_data(
349+
event
350+
)
351+
352+
return queryset
353+
354+
def list(self, request, *args, **kwargs):
355+
event_id = self.kwargs["event_id"]
356+
event = Metro2Event.objects.get(id=event_id)
357+
358+
# TODO: replace using DRF permissions/check_permissions()
359+
if not has_permissions_for_request(request, event):
360+
return HttpResponse('Unauthorized', status=401)
361+
362+
queryset = self.filter_queryset(
363+
self.get_queryset()
364+
).distinct_accounts()
365+
366+
# Paginate the results
367+
serializer = AccountListSerializer(queryset, many=True)
368+
return Response(serializer.data)

0 commit comments

Comments
 (0)