Skip to content

Commit

Permalink
Merge pull request #1622 from openhealthcare/43-coding-systems
Browse files Browse the repository at this point in the history
43 coding systems
  • Loading branch information
fredkingham committed Oct 30, 2018
2 parents 74fc493 + 777a643 commit 451b402
Show file tree
Hide file tree
Showing 9 changed files with 1,055 additions and 29 deletions.
8 changes: 8 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
### 0.13.0 (Major Release)

#### Coding systems for lookuplists

Lookuplist entries may now have an associated coding system and code value stored against them.

This enables applications to explicitly code entries against e.g. SNOMED value sets.

Note: This will requires a migration to be created for all applications.

#### New date display format helpers

Introduces two new Angular filters: `displayDate` and `displayDateTime`. These format a date
Expand Down
21 changes: 21 additions & 0 deletions doc/docs/guides/referencedata.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,27 @@ Once this data is stored in the lookuplists file, we can batch load it into our
python manage.py load_lookup_lists
```
## Coding lookuplist values
Often applications wish to use coded reference data from a pre-existing terminology such as SNOMED.
This can be easily accomplished by adding coding values to the lookuplist JSON data.
```JSON
{
"name_of_lookuplist": [
{
"name": "Value of lookuplist item",
"synonyms": ["Synonym 1",],
"coding": {
"system": "SNOMED CT",
"code": "3428473"
}
},
]
}
```
## Management commands
Opal ships with some management commands for importing and exporting lookup lists
Expand Down
4 changes: 4 additions & 0 deletions opal/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ class InitializationError(Error):

class MissingTemplateError(Error):
pass


class InvalidDataError(Error):
pass
86 changes: 81 additions & 5 deletions opal/core/lookuplists.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,81 @@
from django.db import models

from opal import utils
from opal.core import exceptions


def get_or_create_lookuplist_item(model, name, code, system):
"""
Given a lookuplist MODEL, the NAME of an entry, and possibly
the associated CODE and SYSTEM pair, return an instance.
If a preexisting uncoded entry with this name/display value
exists, treat this as an opportunity to code it.
If there is a preexisting entry with this code which has a
different name/display value, raise an exception as we can't
guess the correct thing to do in that scenario.
"""
try:
instance = model.objects.get(name=name, code=code, system=system)
return instance, False
except model.DoesNotExist:
if code is not None and system is not None:
if model.objects.filter(code=code, system=system).count() > 0:
msg = 'Tried to create a lookuplist item with value {0} '
msg += 'and code {1} but this code already exists with '
msg += 'value {2} and code {3}'
existing = model.objects.get(code=code, system=system)
msg = msg.format(name, code, existing.name, existing.code)
raise exceptions.InvalidDataError(msg)

try:
instance = model.objects.get(name=name)
instance.code = code
instance.system = system
instance.save()
return instance, False
except model.DoesNotExist:
instance = model(name=name, code=code, system=system)
instance.save()
return instance, True


def load_lookuplist_item(model, item):
"""
Load an individual lookuplist item into the database
Takes a Lookuplist instance and a dictionary in our
expected lookuplist data structure
"""
from opal.models import Synonym
content_type = ContentType.objects.get_for_model(model)
instance, created = model.objects.get_or_create(name=item['name'])
synonyms_created = 0

name = item.get('name', None)
if name is None:
raise exceptions.InvalidDataError(
'Lookuplist entries must have a name'
)

code, system = None, None
if item.get('coding', None):
try:
code = item['coding']['code']
system = item['coding']['system']
except KeyError:
msg = """
Coding entries in lookuplists must contain both `coding` and `system` values
The following lookuplist item was missing one or both values:
{0}
""".format(str(item))
raise exceptions.InvalidDataError(msg)

instance, created = get_or_create_lookuplist_item(
model, name, code, system
)

# Handle user visible synonyms
synonyms_created = 0
content_type = ContentType.objects.get_for_model(model)
for synonym in item.get('synonyms', []):
syn, created_synonym = Synonym.objects.get_or_create(
content_type=content_type,
Expand All @@ -24,6 +91,7 @@ def load_lookuplist_item(model, item):
)
if created_synonym:
synonyms_created += 1

return int(created), synonyms_created


Expand All @@ -41,12 +109,20 @@ def synonym_exists(lookuplist, name):


class LookupList(models.Model):
name = models.CharField(max_length=255, unique=True)
synonyms = GenericRelation('opal.Synonym')
# For the purposes of FHIR CodeableConcept, .name is .display
# We keep it as .name for Opal backwards compatibility
name = models.CharField(max_length=255, unique=True)
synonyms = GenericRelation('opal.Synonym')
system = models.CharField(max_length=255, blank=True, null=True)
code = models.CharField(max_length=255, blank=True, null=True)
# We don't particularly use .version in the current implementation, but we
# include here for the sake of FHIR CodeableConcept compatibility
version = models.CharField(max_length=255, blank=True, null=True)

class Meta:
ordering = ['name']
abstract = True
unique_together = ('code', 'system')

def __unicode__(self):
return self.name
Expand Down
8 changes: 7 additions & 1 deletion opal/management/commands/dump_lookup_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ def handle(self, *args, **options):
synonyms = [s.name for s in
Synonym.objects.filter(content_type=content_type,
object_id=item.id)]
items.append({'name': item.name, 'synonyms': synonyms})
entry = {'name': item.name, 'synonyms': synonyms}
if item.code and item.system:
entry['coding'] = {
'code' : item.code,
'system': item.system
}
items.append(entry)
data[model.__name__.lower()] = items

if options.get('many_files', False):
Expand Down

0 comments on commit 451b402

Please sign in to comment.