Skip to content

Commit

Permalink
Merge 732c133 into af6cd3b
Browse files Browse the repository at this point in the history
  • Loading branch information
fredkingham committed Aug 7, 2018
2 parents af6cd3b + 732c133 commit 62bc829
Show file tree
Hide file tree
Showing 17 changed files with 387 additions and 39 deletions.
17 changes: 5 additions & 12 deletions elcid/api.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import datetime
import re
from operator import itemgetter
from collections import defaultdict
from django.conf import settings
from django.utils.text import slugify
from django.http import HttpResponseBadRequest
from django.db.models import Q
from intrahospital_api import loader
from rest_framework import viewsets
from opal.core.api import OPALRouter
from opal.core.api import patient_from_pk, LoginRequiredViewset
from opal.core.views import json_response
from opal.core import serialization
from elcid import models as emodels
from lab import models as lmodels
from elcid.utils import timing
from opal import models as omodels


_LAB_TEST_TAGS = {
Expand Down Expand Up @@ -67,16 +65,11 @@ def extract_observation_value(observation_value):
if its >12 return >12
else return None
"""
obs_result = observation_value.split("~")[0]
try:
# we return None if its not numeric
regex = r'^[0-9][0-9.]*$'
obs_result = observation_value.strip()
obs_result = obs_result.split("~")[0].strip("<").strip(">").strip()
if re.match(regex, obs_result):
return float(obs_result)
except ValueError:
try:
float(obs_result.strip("<").strip(">").strip())
return obs_result
except ValueError:
return None


def get_observation_value(observation):
Expand Down
22 changes: 20 additions & 2 deletions elcid/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.db import models
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from lab import models as lmodels
import opal.models as omodels

Expand Down Expand Up @@ -132,6 +134,22 @@ def update_from_dict(self, data, *args, **kwargs):
"""
pass

def set_datetime_ordered(self, value, *args, **kwargs):
if value is None:
self.datetime_ordered = None
elif isinstance(value, datetime.datetime):
self.datetime_ordered = value
else:
input_format = settings.DATETIME_INPUT_FORMATS[0]

# never use DST, if we're an hour back, we're an hour back
with_tz = timezone.make_aware(
datetime.datetime.strptime(value, input_format),
timezone.get_current_timezone(),
is_dst=False
)
self.datetime_ordered = with_tz

def update_from_api_dict(self, patient, data, user):
"""
This is the updateFromDict of the the UpstreamLabTest
Expand Down Expand Up @@ -221,10 +239,10 @@ def get_relevant_tests(self, patient):
"GENTAMICIN LEVEL",
"CLOTTING SCREEN"
]
six_months_ago = datetime.date.today() - datetime.timedelta(6*30)
three_weeks_ago = timezone.now() - datetime.timedelta(3*7)
qs = UpstreamLabTest.objects.filter(
patient=patient,
datetime_ordered__gt=six_months_ago
datetime_ordered__gt=three_weeks_ago
).order_by("datetime_ordered")
return [i for i in qs if i.extras.get("test_name") in relevent_tests]

Expand Down
2 changes: 1 addition & 1 deletion elcid/templates/detail/inpatient.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</div>

{% record_panel models.Line %}
{% record_panel models.Antimicrobial %}
{% include "partials/antimicrobial_panel.html" %}
{% record_panel models.Imaging %}
{% include "partials/blood_culture_result_display.html" %}
{% if permissions.view_lab_test_trends %}
Expand Down
31 changes: 31 additions & 0 deletions elcid/templates/partials/antimicrobial_panel.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% with name=models.Antimicrobial.get_api_name %}
<div ng-repeat="parent in [episode]">
<div class="panel panel-default record-panel antimicrobial-panel">
<div class="panel-heading">
<h3>
<i class="{{ models.Antimicrobial.get_icon }}"></i> {{ models.Antimicrobial.get_display_name }}
<span>
<i class="fa fa-plus-circle edit pull-right pointer"
ng-click="parent.recordEditor.newItem('{{ name }}')"></i>
<i ng-show="parent.{{ name }}.length" class="fa fa-clipboard edit pull-right pointer" clipboard data-clipboard-target=".antimicrobial-panel"></i>
</span>
</h3>
</div>
<ul class="list-group">
<li class="list-group-item" ng-repeat="item in parent.{{ name }}">
<div class="row">
<div class="col-md-9">
{% include models.Antimicrobial.get_display_template %}
</div>
<div class="col-md-3">
<span>
<i class="fa fa-pencil edit pull-right pointer"
ng-click="parent.recordEditor.editItem('{{ name }}', $index)"></i>
</span>
</div>
</div>
</li>
</ul>
</div>
</div>
{% endwith %}
2 changes: 1 addition & 1 deletion elcid/templates/partials/lab_test_sparklines.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ <h2 class="text-center no-results">No results found</h2>
[[ date | shortDate ]]
</th>
<th class="text-center">
3 month trend
3 week trend
</th>
</tr>
</thead>
Expand Down
18 changes: 18 additions & 0 deletions elcid/test/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,27 @@
BloodCultureResultApi, UpstreamBloodCultureApi
)
from elcid import models as emodels
from elcid import api
from django.test import override_settings


class ExtractObservationValueTestCase(OpalTestCase):
def test_extract_observation_value(self):
inputs_to_expected_results = (
("<1", float(1),),
("1>", float(1),),
(" 1 ", float(1),),
("< 1", float(1),),
(" < 1", float(1),),
(".1 ", None),
("0.1 ", 0.1),
("1E", None),
("'1'", None),
)
for input, expected in inputs_to_expected_results:
self.assertEqual(api.extract_observation_value(input), expected)


class UpstreamBloodCultureApiTestCase(OpalTestCase):
def setUp(self):
self.api = UpstreamBloodCultureApi()
Expand Down
91 changes: 90 additions & 1 deletion elcid/test/test_models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import datetime

from django.test import TestCase
from django.utils import timezone
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.conf import settings

from opal.core import exceptions
from opal.core.test import OpalTestCase
Expand Down Expand Up @@ -325,6 +327,42 @@ def test_update_from_api_dict_repeated(self):
found_hl7_result.status, emodels.UpstreamLabTest.COMPLETE
)

def test_set_datetime_ordered_none(self):
lab_test = emodels.UpstreamLabTest(patient=self.patient)
lab_test.datetime_ordered = timezone.now()
lab_test.set_datetime_ordered(None)
self.assertIsNone(lab_test.datetime_ordered)

def test_set_datetime_ordered_datetime(self):
lab_test = emodels.UpstreamLabTest(patient=self.patient)
now = timezone.now()
lab_test.set_datetime_ordered(now)
self.assertEqual(
lab_test.datetime_ordered, now
)

def test_set_datetime_ordered_string(self):
lab_test = emodels.UpstreamLabTest(patient=self.patient)
date_str = "29/10/2017 10:00:00"
lab_test.set_datetime_ordered(date_str)
dt = datetime.datetime(2017, 10, 29, 10, 0)
dt = timezone.make_aware(dt)
self.assertEqual(
lab_test.datetime_ordered,
dt
)

def test_set_datetime_ordered_dst(self):
lab_test = emodels.UpstreamLabTest(patient=self.patient)
date_str = "29/10/2017 1:00:00"
lab_test.set_datetime_ordered(date_str)
as_str = lab_test.datetime_ordered.strftime(
settings.DATETIME_INPUT_FORMATS[0]
)
self.assertEqual(
as_str, "29/10/2017 01:00:00"
)

def test_update_from_api_dict_first_time(self):
update_dict = dict(
external_identifier="1",
Expand Down Expand Up @@ -364,6 +402,57 @@ def test_update_from_dict(self):
# validate that is hasn't been saved
self.assertIsNone(hl7_result.id)

def test_get_relevant_tests(self):
patient, _ = self.new_patient_and_episode_please()
upstream_lab_test = emodels.UpstreamLabTest(patient=patient)
yesterday = timezone.now() - datetime.timedelta(
1
)
upstream_lab_test.datetime_ordered = yesterday
upstream_lab_test.extras = dict(test_name="C REACTIVE PROTEIN")
upstream_lab_test.save()

relevant = emodels.UpstreamLabTest.get_relevant_tests(patient)
self.assertEqual(
len(relevant), 1
)
self.assertEqual(
relevant[0].datetime_ordered, yesterday
)
self.assertEqual(
relevant[0].extras["test_name"], "C REACTIVE PROTEIN"
)

def test_get_relevant_tests_over_three_weeks(self):
patient, _ = self.new_patient_and_episode_please()
upstream_lab_test = emodels.UpstreamLabTest(patient=patient)
four_weeks_ago = timezone.now() - datetime.timedelta(
4 * 7
)
upstream_lab_test.datetime_ordered = four_weeks_ago
upstream_lab_test.extras = dict(test_name="C REACTIVE PROTEIN")
upstream_lab_test.save()

relevant = emodels.UpstreamLabTest.get_relevant_tests(patient)
self.assertEqual(
len(relevant), 0
)

def test_get_not_relevant_tests(self):
patient, _ = self.new_patient_and_episode_please()
upstream_lab_test = emodels.UpstreamLabTest(patient=patient)
yesterday = timezone.now() - datetime.timedelta(
1
)
upstream_lab_test.datetime_ordered = yesterday
upstream_lab_test.extras = dict(test_name="SOME OTHER TEST")
upstream_lab_test.save()

relevant = emodels.UpstreamLabTest.get_relevant_tests(patient)
self.assertEqual(
len(relevant), 0
)


class DiagnosisTest(OpalTestCase, AbstractEpisodeTestCase):

Expand Down
6 changes: 5 additions & 1 deletion etc/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ server {
server_name elcid-rfh-test.openhealthcare.org.uk;
access_log /usr/lib/ohc/log/nginx.log timed_combined;

location /static {
location /himunin {
alias /var/cache/munin/www;
}

location /static {
alias /var/www/media/static;
}

Expand Down
14 changes: 14 additions & 0 deletions intrahospital_api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from opal.core import subrecords
from lab import models as lmodels
from elcid.episode_serialization import serialize
from intrahospital_api import get_api


def patient_to_dict(patient, user):
Expand Down Expand Up @@ -49,6 +50,19 @@ def patient_to_dict(patient, user):
return d


class UpstreamDataViewset(viewsets.ViewSet):
base_name = 'upstream'
permission_classes = (IsAuthenticated,)

def retrieve(self, request, pk=None):
patient = get_object_or_404(
omodels.Patient.objects.all(), pk=pk
)
api = get_api()
hospital_number = patient.demographics_set.first().hospital_number
return json_response(api.results_for_hospital_number(hospital_number))


class PatientViewSet(viewsets.ViewSet):
base_name = 'patient'
permission_classes = (IsAuthenticated,)
Expand Down
21 changes: 12 additions & 9 deletions intrahospital_api/management/commands/load_tests_from_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,20 @@
"""
import json
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from django.db import transaction
from opal import models
from elcid import models as emodels
from lab import models as lmodels
from intrahospital_api import loader
from intrahospital_api import update_lab_tests


class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('patient_id', type=int)
parser.add_argument('file_name', type=str)

def handle(self, patient_id, file_name, *args, **options):
# we assume that there is a user called super
# which there won't be on prod, but we shouldn't
# be running this on prod
user = User.objects.get(username="super")
patient = models.Patient.objects.get(id=patient_id)
@transaction.atomic
def process(self, patient, results):
lmodels.LabTest.objects.filter(
patient=patient
).filter(
Expand All @@ -30,7 +26,14 @@ def handle(self, patient_id, file_name, *args, **options):
emodels.UpstreamBloodCulture.get_display_name(),
]
).delete()
update_lab_tests.update_tests(patient, results)

def handle(self, patient_id, file_name, *args, **options):
# we assume that there is a user called super
# which there won't be on prod, but we shouldn't
# be running this on prod
with open(file_name) as f:
results = json.load(f)
loader.save_lab_tests(patient, results, user)
patient = models.Patient.objects.get(id=patient_id)
self.process(patient, results)
print "success"
7 changes: 6 additions & 1 deletion intrahospital_api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class Meta:


class InitialPatientLoad(PatientLoad, PatientSubrecord):
""" this model is the initial load of a patient
""" This model is the initial load of a patient
future loads are done by the cron batch load
"""

Expand All @@ -96,6 +96,11 @@ def __unicode__(self):
self.started
)

def update_from_dict(self, data, *args, **kwargs):
""" For the purposes of the front end this model is read only.
"""
pass


class BatchPatientLoad(PatientLoad):
""" This is the batch load of all reconciled patients
Expand Down
5 changes: 4 additions & 1 deletion intrahospital_api/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ class IntraHospitalApiPlugin(plugins.OpalPlugin):
'js/intrahospital_api/services/initial_patient_lab_load_status.js'
]
}
apis = [("patient", api.PatientViewSet,)]
apis = [
("patient", api.PatientViewSet,),
("upstream", api.UpstreamDataViewset,)
]
Loading

0 comments on commit 62bc829

Please sign in to comment.