Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search paginations #1646

Merged
merged 4 commits into from
Oct 30, 2018
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
121 changes: 39 additions & 82 deletions search/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,14 @@
Allow us to make search queries
"""
import datetime
from django.db.models import Max
from django.db.models import Max, Min, Count
from django.conf import settings

from opal import models
from opal.core import subrecords
from opal.utils import stringport
from search.search_rules import SearchRule


class PatientSummary(object):
def __init__(self, episode):
self.start = episode.start
self.end = episode.end
self.episode_ids = set([episode.id])
self.patient_id = episode.patient.id
self.categories = set([episode.category_name])
self.id = episode.patient.demographics_set.get().id

def update(self, episode):
if not self.start:
self.start = episode.start
elif episode.start:
if self.start > episode.start:
self.start = episode.start

if not self.end:
self.end = episode.end
elif episode.end:
if self.end < episode.end:
self.end = episode.end

self.episode_ids.add(episode.id)
self.categories.add(episode.category_name)

def to_dict(self):
result = {k: getattr(self, k) for k in [
"patient_id", "start", "end", "id"
]}
result["categories"] = sorted(self.categories)
result["count"] = len(self.episode_ids)
return result


def episodes_for_user(episodes, user):
"""
Given an iterable of EPISODES and a USER, return a filtered
Expand All @@ -58,6 +23,7 @@ class QueryBackend(object):
"""
Base class for search implementations to inherit from
"""

def __init__(self, user, query):
self.user = user
self.query = query
Expand All @@ -74,14 +40,11 @@ def description(self):
def get_patients(self):
raise NotImplementedError()

def get_patient_summaries(self):
def get_patient_summaries(self, patients):
raise NotImplementedError()

def patients_as_json(self):
patients = self.get_patients()
return [
p.to_dict(self.user) for p in patients
]
def sort_patients(self, patients):
raise NotImplementedError()


class DatabaseQuery(QueryBackend):
Expand Down Expand Up @@ -124,36 +87,46 @@ def episodes_for_criteria(self, criteria):
search_rule = SearchRule.get_rule(rule_name, self.user)
return search_rule.query(criteria)

def get_aggregate_patients_from_episodes(self, episodes):
# at the moment we use start/end only
patient_summaries = {}

for episode in episodes:
patient_id = episode.patient_id
if patient_id in patient_summaries:
patient_summaries[patient_id].update(episode)
else:
patient_summaries[patient_id] = PatientSummary(episode)

patients = models.Patient.objects.filter(
id__in=list(patient_summaries.keys())
def get_patients(self):
episodes = self.get_episodes()
patient_ids = set([i.patient_id for i in episodes])
return self.sort_patients(
models.Patient.objects.filter(id__in=patient_ids)
)
patients = patients.prefetch_related("demographics_set")

results = []
def sort_patients(self, patients):
patients = patients.annotate(
max_episode_id=Max('episode__id')
)
return patients.order_by("-max_episode_id")

for patient_id, patient_summary in patient_summaries.items():
patient = next(p for p in patients if p.id == patient_id)
demographic = patient.demographics_set.get()
def get_patient_summary(self, patient):
result = dict()

result = {k: getattr(demographic, k) for k in [
# prefetch queries only work with sad times, even though
# demographics are a one per patient thing.
for demographics in patient.demographics_set.all():
for i in [
"first_name", "surname", "hospital_number", "date_of_birth"
]}

result.update(patient_summary.to_dict())
results.append(result)
]:
result[i] = getattr(demographics, i)
result["start"] = patient.min_start
result["end"] = patient.max_end
result["count"] = patient.episode_count
result["patient_id"] = patient.id
result["categories"] = list(patient.episode_set.order_by(
"category_name"
).values_list(
"category_name", flat=True
).distinct())
return result

return results
def get_patient_summaries(self, patients):
patients = patients.prefetch_related("demographics_set")
patients = patients.annotate(episode_count=Count("episode"))
patients = patients.annotate(min_start=Min("episode__start"))
patients = patients.annotate(max_end=Max("episode__end"))
return [self.get_patient_summary(patient) for patient in patients]

def _episodes_without_restrictions(self):
all_matches = [
Expand All @@ -180,22 +153,6 @@ def get_episodes(self):
return episodes_for_user(
self._episodes_without_restrictions(), self.user)

def get_patient_summaries(self):
eps = self._episodes_without_restrictions()
episode_ids = [e.id for e in eps]

# get all episodes of patients, that have episodes that
# match the criteria
all_eps = models.Episode.objects.filter(
patient__episode__in=episode_ids
)
filtered_eps = episodes_for_user(all_eps, self.user)
return self.get_aggregate_patients_from_episodes(filtered_eps)

def get_patients(self):
patients = set(e.patient for e in self.get_episodes())
return list(patients)

def description(self):
"""
Provide a textual description of the current search
Expand Down
99 changes: 69 additions & 30 deletions search/tests/test_search_query_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
"""
from datetime import date, datetime

from django.db import transaction
from django.contrib.contenttypes.models import ContentType
from mock import patch, MagicMock
import reversion
from opal.tests.episodes import RestrictedEpisodeCategory

from search.search_rules import SearchRule
from opal.models import Synonym, Gender
from opal.models import Synonym, Gender, Patient, Episode

from opal.core.test import OpalTestCase

Expand All @@ -24,27 +22,6 @@
from opal.tests.episodes import RestrictedEpisodeCategory # NOQA


class PatientSummaryTestCase(OpalTestCase):

def test_update_sets_start(self):
patient, episode = self.new_patient_and_episode_please()
summary = queries.PatientSummary(episode)
self.assertEqual(None, summary.start)
the_date = date(day=27, month=1, year=1972)
episode2 = patient.create_episode(start=the_date)
summary.update(episode2)
self.assertEqual(summary.start, the_date)

def test_update_sets_end(self):
patient, episode = self.new_patient_and_episode_please()
summary = queries.PatientSummary(episode)
self.assertEqual(None, summary.start)
the_date = date(day=27, month=1, year=1972)
episode2 = patient.create_episode(end=the_date)
summary.update(episode2)
self.assertEqual(summary.end, the_date)


class QueryBackendTestCase(OpalTestCase):

def test_fuzzy_query(self):
Expand All @@ -65,7 +42,15 @@ def test_get_patients(self):

def test_get_patient_summaries(self):
with self.assertRaises(NotImplementedError):
queries.QueryBackend(self.user, 'aquery').get_patient_summaries()
queries.QueryBackend(self.user, 'aquery').get_patient_summaries(
Patient.objects.all()
)

def test_sort_patients(self):
with self.assertRaises(NotImplementedError):
queries.QueryBackend(self.user, 'aquery').sort_patients(
Patient.objects.all()
)


class DatabaseQueryTestCase(OpalTestCase):
Expand Down Expand Up @@ -122,6 +107,38 @@ def test_episodes_for_number_fields_greater_than(self):
query = queries.DatabaseQuery(self.user, [criteria])
self.assertEqual([self.episode], query.get_episodes())

def test_sort_patients(self):
patient_1, episode_1 = self.new_patient_and_episode_please()
patient_2, episode_2 = self.new_patient_and_episode_please()
patient_1.create_episode()

not_used_patient, _ = self.new_patient_and_episode_please()
query = queries.DatabaseQuery(self.user, [self.name_criteria])
result = query.sort_patients(
Patient.objects.exclude(id=not_used_patient.id)
)

# should start with patient 1, because its got 2 episodes
self.assertEqual(
result[0].id, patient_1.id,
)

self.assertEqual(
result[1].id, patient_2.id,
)

# make sure its true even if we reverse it
result = query.sort_patients(
Patient.objects.exclude(id=not_used_patient.id).order_by("-id")
)
self.assertEqual(
result[0].id, patient_1.id,
)

self.assertEqual(
result[1].id, patient_2.id,
)

def test_episodes_for_number_fields_less_than(self):
testmodels.FavouriteNumber.objects.create(
patient=self.patient, number=10
Expand Down Expand Up @@ -348,6 +365,30 @@ def test_fuzzy_query(self):
[patient_2, patient_3, patient_1]
)

def test_get_patients(self):
patient_1, episode_1 = self.new_patient_and_episode_please()
patient_2, episode_2 = self.new_patient_and_episode_please()
episode_3 = patient_1.create_episode()

# these will not be used
self.new_patient_and_episode_please()

query = queries.DatabaseQuery(self.user, [self.name_criteria])
with patch.object(query, "get_episodes") as get_episodes:
get_episodes.return_value = Episode.objects.filter(
id__in=[episode_1.id, episode_2.id, episode_3.id]
)
found = query.get_patients().values_list("id", flat=True)
self.assertEqual(
2, found.count()
)
self.assertEqual(
found[0], patient_1.id
)
self.assertEqual(
found[1], patient_2.id
)

def test_distinct_episodes_for_m2m_fields_containing_synonsyms_and_names(
self
):
Expand Down Expand Up @@ -719,9 +760,8 @@ def test_get_episodes_searching_episode_subrecord_ft_or_fk_fields(self):

def test_get_patient_summaries(self):
query = queries.DatabaseQuery(self.user, self.name_criteria)
summaries = query.get_patient_summaries()
summaries = query.get_patient_summaries(Patient.objects.all())
expected = [{
'id': self.patient.id,
'count': 1,
'hospital_number': u'0',
'date_of_birth': self.DATE_OF_BIRTH,
Expand All @@ -734,7 +774,7 @@ def test_get_patient_summaries(self):
}]
self.assertEqual(expected, summaries)

def test_update_patient_summaries(self):
def test_get_patient_summaries_for_patient_with_multiple_episodes(self):
""" with a patient with multiple episodes
we expect it to aggregate these into summaries
"""
Expand All @@ -747,9 +787,8 @@ def test_update_patient_summaries(self):
end=end_date
)
query = queries.DatabaseQuery(self.user, self.name_criteria)
summaries = query.get_patient_summaries()
summaries = query.get_patient_summaries(Patient.objects.all())
expected = [{
'id': self.patient.id,
'count': 3,
'hospital_number': u'0',
'date_of_birth': self.DATE_OF_BIRTH,
Expand Down
Loading