Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 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
3 changes: 2 additions & 1 deletion .env_template
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ DJANGO_ALLOWED_HOSTS=['localhost','127.0.0.1','0.0.0.0']
DJANGO_LOGLEVEL='WARNING'
NPD_PROJECT_NAME='[Project Name]'
NPD_READONLY_PASSWORD=''
NPD_LOAD_TESTER_PASSWORD=''
CORS_TRUSTED_ORIGINS=['http://localhost','http://127.0.0.1','http://0.0.0.0']
NPD_LOAD_TESTER_PASSWORD=''
62 changes: 27 additions & 35 deletions backend/npdfhir/filters/practitioner_filter_set.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.contrib.postgres.search import SearchVector
from django.contrib.postgres.search import SearchQuery
from django.db.models import Q
from django_filters import rest_framework as filters

Expand All @@ -8,24 +8,31 @@


class PractitionerFilterSet(filters.FilterSet):
practitioner_table = None

identifier = filters.CharFilter(
method="filter_identifier",
help_text="Filter by identifier (NPI or other). Format: value or system|value",
)

name = filters.CharFilter(
method="filter_name", help_text="Filter by practitioner name (first, last, or full name)"
method="filter_practitioner_name",
help_text="Filter by practitioner name (first, middle, last, or full name). Name filter accepts websearch syntax.",
)

gender = filters.ChoiceFilter(
method="filter_gender", choices=genderMapping.to_choices(), help_text="Filter by gender"
)

practitioner_type = filters.CharFilter(
method="filter_practitioner_type", help_text="Filter by practitioner type/taxonomy"
method="filter_practitioner_type",
help_text="Filter by practitioner type. Practitioner type filter accepts websearch syntax.",
)

address = filters.CharFilter(method="filter_address", help_text="Filter by any part of address")
address = filters.CharFilter(
method="filter_address",
help_text="Filter by any part of address. Address filter accepts websearch syntax.",
)

address_city = filters.CharFilter(method="filter_address_city", help_text="Filter by city name")

Expand Down Expand Up @@ -83,47 +90,32 @@ def filter_identifier(self, queryset, name, value):

return queryset.filter(queries).distinct()

def filter_name(self, queryset, name, value):
return queryset.annotate(
search=SearchVector(
"individual__individualtoname__first_name",
"individual__individualtoname__last_name",
"individual__individualtoname__middle_name",
)
).filter(search=value)
def filter_practitioner_name(self, queryset, name, value):
query = SearchQuery(f"{value.upper()}", search_type="websearch")
return queryset.filter(individual__individualtoname__search_vector=query).distinct()

def filter_practitioner_type(self, queryset, name, value):
return queryset.annotate(
search=SearchVector("providertotaxonomy__nucc_code__display_name")
).filter(search=value)
query = SearchQuery(value, search_type="websearch")
return queryset.filter(providertotaxonomy__nucc_code__search_vector=query)

def filter_address(self, queryset, name, value):
return queryset.annotate(
search=SearchVector(
"individual__individualtoaddress__address__address_us__delivery_line_1",
"individual__individualtoaddress__address__address_us__delivery_line_2",
"individual__individualtoaddress__address__address_us__city_name",
"individual__individualtoaddress__address__address_us__state_code__abbreviation",
"individual__individualtoaddress__address__address_us__zipcode",
)
).filter(search=value)
query = SearchQuery(value, search_type="websearch")
return queryset.filter(
individual__individualtoaddress__address__address_us__search_vector=query
)

def filter_address_city(self, queryset, name, value):
return queryset.annotate(
search=SearchVector("individual__individualtoaddress__address__address_us__city_name")
).filter(search=value)
return queryset.filter(
individual__individualtoaddress__address__address_us__city_name=value
)

def filter_address_state(self, queryset, name, value):
return queryset.annotate(
search=SearchVector(
"individual__individualtoaddress__address__address_us__state_code__abbreviation"
)
).filter(search=value)
return queryset.filter(
individual__individualtoaddress__address__address_us__state_code__abbreviation=value
)

def filter_address_postalcode(self, queryset, name, value):
return queryset.annotate(
search=SearchVector("individual__individualtoaddress__address__address_us__zipcode")
).filter(search=value)
return queryset.filter(individual__individualtoaddress__address__address_us__zipcode=value)

def filter_address_use(self, queryset, name, value):
if value in addressUseMapping.keys():
Expand Down
39 changes: 17 additions & 22 deletions backend/npdfhir/filters/practitioner_role_filter_set.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import D
from django.contrib.postgres.search import SearchVector
from django.contrib.postgres.search import SearchVector, SearchQuery
from django.db.models import Q
from django_filters import rest_framework as filters

Expand All @@ -11,9 +11,11 @@


class PractitionerRoleFilterSet(filters.FilterSet):
practitioner_table = "provider_to_organization"
# practitioner_name = PractitionerFilterSet.name
practitioner_name = filters.CharFilter(
method="filter_practitioner_name",
help_text="Filter by practitioner name (first, last, or full name)",
help_text="Filter by practitioner name (first, middle, last, or full name). Name filter accepts websearch syntax.",
)

practitioner_gender = filters.ChoiceFilter(
Expand Down Expand Up @@ -117,13 +119,10 @@ class Meta:
]

def filter_practitioner_name(self, queryset, name, value):
return queryset.annotate(
search=SearchVector(
"provider_to_organization__individual__individual__individualtoname__first_name",
"provider_to_organization__individual__individual__individualtoname__last_name",
"provider_to_organization__individual__individual__individualtoname__middle_name",
)
).filter(search=value)
query = SearchQuery(f"{value.upper()}", search_type="websearch")
return queryset.filter(
provider_to_organization__individual__individual__individualtoname__search_vector=query
).distinct()

def filter_practitioner_gender(self, queryset, name, value):
if value in genderMapping.keys():
Expand All @@ -132,9 +131,10 @@ def filter_practitioner_gender(self, queryset, name, value):
return queryset

def filter_practitioner_type(self, queryset, name, value):
query = SearchQuery(value, search_type="websearch")
return queryset.filter(
provider_to_organization__individual__providertotaxonomy__nucc_code__code=value
).distinct()
provider_to_organization__individual__providertotaxonomy__nucc_code__search_vector=query
)

def filter_organization_name(self, queryset, name, value):
return queryset.annotate(
Expand Down Expand Up @@ -200,7 +200,9 @@ def filter_specialty(self, queryset, name, value):

def filter_connection_type(self, queryset, name, value):
return queryset.annotate(
search=SearchVector("other_endpoint__endpoint_instance__endpoint_connection_type__id")
search=SearchVector(
"location__locationtoendpointinstance__endpoint_instance__endpoint_connection_type_id"
)
).filter(search=value)

def filter_endpoint_status(self, queryset, name, value):
Expand All @@ -210,7 +212,7 @@ def filter_endpoint_status(self, queryset, name, value):

def filter_payload_type(self, queryset, name, value):
return queryset.filter(
location__locationtoendpointinstance__endpoint_instance__endpointinstancetopayload__payload_type__id=value
location__locationtoendpointinstance__endpoint_instance__endpointinstancetopayload__payload_type_id=value
).distinct()

def filter_endpoint_organization_id(self, queryset, name, value):
Expand All @@ -222,15 +224,8 @@ def filter_endpoint_organization_name(self, queryset, name, value):
return queryset.filter(location__organization__organizationtoname__name=value)

def filter_address(self, queryset, name, value):
return queryset.annotate(
search=SearchVector(
"location__address__address_us__delivery_line_1",
"location__address__address_us__delivery_line_2",
"location__address__address_us__city_name",
"location__address__address_us__state_code__abbreviation",
"location__address__address_us__zipcode",
)
).filter(search=value)
query = SearchQuery(value, search_type="websearch")
return queryset.filter(location__address__address_us__search_vector=query)

def filter_address_city(self, queryset, name, value):
return queryset.annotate(
Expand Down
27 changes: 23 additions & 4 deletions backend/npdfhir/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.db import models
from django.contrib.gis.db import models as geomodels
from django.contrib.postgres.search import SearchVectorField


class Address(models.Model):
Expand Down Expand Up @@ -126,11 +127,17 @@ class AddressUs(models.Model):
suitelink_match = models.CharField(max_length=5, blank=True, null=True)
enhanced_match = models.CharField(max_length=64, blank=True, null=True)
geolocation = geomodels.PointField(srid=4326)
search_vector = SearchVectorField(blank=True, null=True)

class Meta:
managed = False
db_table = "address_us"

def _do_insert(self, manager, using, fields, update_pk, raw):
# Prevents the model from attempting to insert values into the generated search_vector field
fields = [f for f in fields if f.attname != "search_vector"]
return super()._do_insert(manager, using, fields, update_pk, raw)


class ClinicalOrganization(models.Model):
organization = models.OneToOneField("Organization", models.DO_NOTHING, blank=True, null=True)
Expand Down Expand Up @@ -388,20 +395,26 @@ class Meta:

class IndividualToName(models.Model):
pk = models.CompositePrimaryKey("individual_id", "first_name", "last_name", "name_use_id")
individual = models.ForeignKey(Individual, models.DO_NOTHING)
individual = models.ForeignKey(Individual, models.DO_NOTHING, db_index=True)
prefix = models.CharField(max_length=10, blank=True, null=True)
first_name = models.CharField(max_length=50)
first_name = models.CharField(max_length=50, db_index=True)
middle_name = models.CharField(max_length=50, blank=True, null=True)
last_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200, db_index=True)
start_date = models.DateField(blank=True, null=True)
end_date = models.DateField(blank=True, null=True)
name_use = models.ForeignKey(FhirNameUse, models.DO_NOTHING)
suffix = models.CharField(max_length=10, blank=True, null=True)
search_vector = SearchVectorField(blank=True, null=True)

class Meta:
managed = False
db_table = "individual_to_name"

def _do_insert(self, manager, using, fields, update_pk, raw):
# Prevents the model from attempting to insert values into the generated search_vector field
fields = [f for f in fields if f.attname != "search_vector"]
return super()._do_insert(manager, using, fields, update_pk, raw)


class IndividualToPhone(models.Model):
individual = models.ForeignKey(Individual, models.DO_NOTHING)
Expand Down Expand Up @@ -505,11 +518,17 @@ class Nucc(models.Model):
notes = models.TextField(blank=True, null=True)
certifying_board_name = models.TextField(blank=True, null=True)
certifying_board_url = models.TextField(blank=True, null=True)
search_vector = SearchVectorField(blank=True, null=True)

class Meta:
managed = False
db_table = "nucc"

def _do_insert(self, manager, using, fields, update_pk, raw):
# Prevents the model from attempting to insert values into the generated search_vector field
fields = [f for f in fields if f.attname != "search_vector"]
return super()._do_insert(manager, using, fields, update_pk, raw)


class NuccClassification(models.Model):
nucc_code = models.ForeignKey(
Expand Down Expand Up @@ -644,7 +663,7 @@ class Meta:


class Provider(models.Model):
npi = models.OneToOneField(Npi, models.DO_NOTHING, db_column="npi")
npi = models.OneToOneField(Npi, models.DO_NOTHING, db_column="npi", db_index=True)
individual = models.OneToOneField(Individual, models.DO_NOTHING, primary_key=True)

class Meta:
Expand Down
18 changes: 10 additions & 8 deletions backend/npdfhir/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@ def to_representation(self, instance):
]
)
qualification = PractitionerQualification(
identifier=[
Identifier(
value="test",
type=code, # TODO: Replace
period=Period(),
)
],
# identifier=[
# Identifier(
# value="test",
# type=code, # TODO: Replace
# period=Period(),
# )
# ],
code=code,
)
return qualification.model_dump()
Expand Down Expand Up @@ -467,7 +467,9 @@ def to_representation(self, instance):

organization_affiliation.organization = Reference(display=str(instance.ehr_vendor_name))

organization_affiliation.participatingOrganization = genReference("fhir-organization-detail", instance.id, request)
organization_affiliation.participatingOrganization = genReference(
"fhir-organization-detail", instance.id, request
)
organization_affiliation.participatingOrganization.display = str(instance.organization_name)

# NOTE: Period for OrganizationAffiliation cannot currently be fetched so its blank
Expand Down
4 changes: 2 additions & 2 deletions backend/npdfhir/tests/fixtures/practitioner.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def create_full_practitionerrole(
location_id=None,
role_code="PRV",
role_display="Provider Role",
nucc_types=None
nucc_types=None,
):
"""
Creates:
Expand All @@ -143,7 +143,7 @@ def create_full_practitionerrole(
last_name=last_name,
gender=gender,
npi_value=npi_value,
practitioner_types=nucc_types
practitioner_types=nucc_types,
)

org = create_organization(name=org_name)
Expand Down
Loading