Skip to content

Commit c2d2438

Browse files
Merge branch 'main' into sachin/extra-feedback-work
2 parents 88f88ab + 0ccb233 commit c2d2438

32 files changed

+278
-274
lines changed

.snyk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ ignore:
55
SNYK-PYTHON-PYGMENTS-15746419:
66
- '*' :
77
reason: 'No remediation available'
8-
expires: '2026-04-04T17:33:45.004Z'
8+
expires: '2026-04-04T17:33:45.004Z'
9+

Makefile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ down:
181181
@echo "Shutting down all docker compose services..."
182182
@docker compose down
183183

184+
.PHONY: test-down
185+
test-down:
186+
@echo "Shutting down all test docker compose services..."
187+
@docker compose -f compose.test.yml down
188+
184189
.PHONY: test-setup
185190
test-setup:
186191
@echo "Setting up test database..."
@@ -206,7 +211,7 @@ test: test-setup
206211
@$(MAKE) test-frontend
207212

208213
.PHONY: playwright-local
209-
playwright-local: test-system-setup build-frontend-test-assets
214+
playwright-local: test-down test-system-setup build-frontend-test-assets
210215
@cd playwright; \
211216
npx playwright test --project=local
212217

@@ -264,7 +269,7 @@ test-system-setup: test-setup
264269

265270
.PHONY: test-server
266271
test-server: test-system-setup build-frontend-test-assets
267-
bin/npr --test --publish 8008:8008 python manage.py runserver 0.0.0.0:8008
272+
docker compose -f compose.test.yml up django-web
268273

269274
###
270275
# whole project concerns

backend/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Django backend that provides a FHIR API for accessing data from the npd database
1717
- [colima](https://github.com/abiosoft/colima) (if using macOS)
1818
- a postgres database with the npd schema
1919

20-
### Local dev
20+
### Local Dev
2121

2222
1. Ensure that either colima (if using macOS) or the docker service is running
2323
2. Create a `.env` file in this directory, following the template of the `.env_template` file

backend/npdfhir/management/commands/seedsystem.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ def generate_sample_practitioner_roles(self):
7272
individual=DefaultIndividual(id="f1579a55-b5e1-4717-988d-6e014acbe348"),
7373
npi=DefaultNPI(npi=1000000011),
7474
),
75-
organization=DefaultOrganization(npi=DefaultNPI(npi=1000000012)),
75+
organization=DefaultOrganization(
76+
id="893149b6-34de-4030-a2fa-89cc02baccbe", npi=DefaultNPI(npi=1000000012)
77+
),
7678
location=DefaultLocation(has_endpoint=False),
7779
)
7880

@@ -96,16 +98,18 @@ def generate_sample_practitioner_roles(self):
9698
DefaultPractitionerRole(
9799
practitioner=DefaultPractitioner(
98100
individual=DefaultIndividual(
99-
names=[DefaultName(first_name="Test", last_name="Practitioner 1")]
100-
)
101+
id="9b309f46-115e-4eed-bc6e-0e414d5f1215",
102+
names=[DefaultName(first_name="Test", last_name="Practitioner 1")],
103+
),
101104
),
102105
organization=DefaultOrganization(id="0c1f8f84-0502-4444-b636-8fee4ab76e32"),
103106
)
104107
DefaultPractitionerRole(
105108
practitioner=DefaultPractitioner(
106109
individual=DefaultIndividual(
107-
names=[DefaultName(first_name="Test", last_name="Practitioner 2")]
108-
)
110+
id="91cc98f8-8f65-4f6c-8ef2-9dbe829ed5c2",
111+
names=[DefaultName(first_name="Test", last_name="Practitioner 2")],
112+
),
109113
),
110114
organization=DefaultOrganization(id="0c1f8f84-0502-4444-b636-8fee4ab76e32"),
111115
)

backend/npdfhir/mappings.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,21 @@ def to_choices(self):
3131
genderMapping = Mapping({"F": "Female", "M": "Male", "O": "Other"})
3232

3333
addressUseMapping = Mapping({1: "home", 2: "work", 3: "temp", 4: "old", 5: "billing"})
34+
35+
36+
def other_id_type_to_fhir(other_id_type):
37+
fhirIdentifierTypes = {
38+
2: {
39+
"code": "UPIN",
40+
"display": "Medicare/CMS (formerly HCFA)'s Universal Physician Identification numbers",
41+
},
42+
4: {"code": "MCR", "display": "Practitioner Medicare Number"},
43+
5: {"code": "MCD", "display": "Practitioner Medicaid Number"},
44+
6: {"code": "MCR", "display": "Practitioner Medicare Number"},
45+
7: {"code": "MCR", "display": "Practitioner Medicare Number"},
46+
8: {"code": "PPIN", "display": "Medicare/CMS Performing Provider Identification Number"},
47+
}
48+
if other_id_type in fhirIdentifierTypes.keys():
49+
return fhirIdentifierTypes[other_id_type]
50+
else:
51+
return {"code": "OTHER", "display": "Other"}

backend/npdfhir/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ class OrganizationToOtherId(models.Model):
620620
npi = models.ForeignKey(ClinicalOrganization, models.DO_NOTHING, db_column="npi")
621621
other_id = models.CharField(max_length=100)
622622
other_id_type = models.ForeignKey("OtherIdType", models.DO_NOTHING)
623-
state_code = models.CharField(max_length=2)
623+
state_code = models.ForeignKey(FipsState, models.DO_NOTHING, db_column="state_code")
624624
issuer = models.CharField(max_length=200)
625625

626626
class Meta:

backend/npdfhir/serializers.py

Lines changed: 67 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
OrganizationToName,
4040
ProviderToOrganization,
4141
)
42-
from .utils import genReference, get_schema_data, other_id_type_to_fhir
42+
from .utils import genReference, get_schema_data
43+
from .mappings import other_id_type_to_fhir
4344

4445
if "runserver" or "test" in sys.argv:
4546
from .cache import (
@@ -167,9 +168,12 @@ class Meta:
167168
]
168169

169170
def to_representation(self, instance):
171+
assigner_parts = []
172+
if instance.issuer != "" and instance.issuer != " ":
173+
assigner_parts.append(instance.issuer)
174+
assigner_parts.append(instance.state_code.abbreviation)
170175
fhir_type = other_id_type_to_fhir(instance.other_id_type.id)
171176
license_identifier = Identifier(
172-
# system="", TODO: Figure out how to associate a system with each identifier
173177
value=instance.other_id,
174178
type=CodeableConcept(
175179
coding=[
@@ -180,7 +184,7 @@ def to_representation(self, instance):
180184
)
181185
]
182186
),
183-
# use="" TODO: Add use for other identifier
187+
assigner=Reference(display=" - ".join(assigner_parts)),
184188
# period=Period(start=instance.issue_date, end=instance.expiry_date),
185189
)
186190
return license_identifier.model_dump()
@@ -235,6 +239,24 @@ class Meta:
235239
model = Npi
236240
fields = "__all__"
237241

242+
def to_representation(self, instance):
243+
npi = Identifier(
244+
system="http://terminology.hl7.org/NamingSystem/npi",
245+
value=str(instance.npi),
246+
type=CodeableConcept(
247+
coding=[
248+
Coding(
249+
system="http://terminology.hl7.org/CodeSystem/v2-0203",
250+
code="NPI",
251+
display="National Provider Identifier",
252+
)
253+
]
254+
),
255+
use="official",
256+
period=Period(start=instance.enumeration_date, end=instance.deactivation_date),
257+
)
258+
return npi
259+
238260

239261
class IndividualSerializer(serializers.Serializer):
240262
name = NameSerializer(source="individualtoname_set", read_only=True, many=True)
@@ -295,17 +317,30 @@ def to_representation(self, instance):
295317
use="official",
296318
system=instance.system,
297319
value=instance.other_id,
298-
# TODO: Replace with Organization reference
320+
# TODO: Replace with Organization reference?
299321
assigner=Reference(display=str(instance.issuer_id)),
300322
)
301323

302324
return endpoint_identifier.model_dump()
303325

304326

327+
class ClinicalOrganizationSerializer(serializers.Serializer):
328+
other_identifier = OtherIdentifierSerializer(
329+
source="organizationtootherid_set", many=True, read_only=True
330+
)
331+
npi = NPISerializer()
332+
333+
class Meta:
334+
fields = ["other_identifier", "npi"]
335+
336+
305337
class OrganizationSerializer(serializers.Serializer):
306338
name = OrganizationNameSerializer(source="organizationtoname_set", many=True, read_only=True)
307339
authorized_official = IndividualSerializer(read_only=True)
308340
address = AddressSerializer(source="organizationtoaddress_set", many=True, read_only=True)
341+
clinical_organization = ClinicalOrganizationSerializer(
342+
source="clinicalorganization", read_only=True
343+
)
309344

310345
class Meta:
311346
model = Organization
@@ -315,7 +350,6 @@ def to_representation(self, instance):
315350
request = self.context.get("request")
316351
instance = instance.organization
317352
representation = super().to_representation(instance)
318-
319353
organization = FHIROrganization()
320354
organization.id = str(instance.id)
321355
organization.meta = Meta(
@@ -325,82 +359,39 @@ def to_representation(self, instance):
325359
identifiers = []
326360

327361
taxonomies = []
328-
# if instance.ein:
329-
# ein_identifier = Identifier(
330-
# system="https://terminology.hl7.org/NamingSystem-USEIN.html",
331-
# value=str(instance.ein.ein_id),
332-
# type=CodeableConcept(
333-
# coding=[Coding(
334-
# system="http://terminology.hl7.org/CodeSystem/v2-0203",
335-
# code="TAX",
336-
# display="Tax ID number"
337-
# )]
338-
# )
339-
# )
340-
# identifiers.append(ein_identifier)
341362

342363
if hasattr(instance, "clinicalorganization"):
343-
clinical_org = instance.clinicalorganization
344-
if clinical_org and clinical_org.npi:
345-
npi_identifier = Identifier(
346-
system="http://terminology.hl7.org/NamingSystem/npi",
347-
value=str(clinical_org.npi.npi),
348-
type=CodeableConcept(
349-
coding=[
350-
Coding(
351-
system="http://terminology.hl7.org/CodeSystem/v2-0203",
352-
code="NPI",
353-
display="National provider identifier",
354-
)
355-
]
356-
),
357-
use="official",
358-
period=Period(
359-
start=clinical_org.npi.enumeration_date,
360-
end=clinical_org.npi.deactivation_date,
361-
),
362-
)
364+
clinical_org_instance = instance.clinicalorganization
365+
if "clinical_organization" in representation.keys():
366+
clinical_org = representation["clinical_organization"]
367+
npi_identifier = clinical_org["npi"]
363368
identifiers.append(npi_identifier)
369+
if "other_identifier" in clinical_org.keys():
370+
identifiers += clinical_org["other_identifier"]
364371

365-
for other_id in clinical_org.organizationtootherid_set.all():
366-
other_identifier = Identifier(
367-
system=str(other_id.other_id_type_id),
368-
value=other_id.other_id,
369-
type=CodeableConcept(
370-
coding=[
371-
Coding(
372-
system="http://terminology.hl7.org/CodeSystem/v2-0203",
373-
code="test", # do we define this based on the type of id it is?
374-
display="test", # same as above ^
375-
)
376-
]
377-
),
378-
)
379-
identifiers.append(other_identifier)
380-
381-
for taxonomy in clinical_org.organizationtotaxonomy_set.all():
382-
code = CodeableConcept(
383-
coding=[
384-
Coding(
385-
system="http://nucc.org/provider-taxonomy",
386-
code=taxonomy.nucc_code_id,
387-
display=nucc_taxonomy_codes[str(taxonomy.nucc_code_id)],
388-
)
389-
]
390-
)
372+
for taxonomy in clinical_org_instance.organizationtotaxonomy_set.all():
373+
code = CodeableConcept(
374+
coding=[
375+
Coding(
376+
system="http://nucc.org/provider-taxonomy",
377+
code=taxonomy.nucc_code_id,
378+
display=nucc_taxonomy_codes[str(taxonomy.nucc_code_id)],
379+
)
380+
]
381+
)
391382

392-
# Extend the Organization class
393-
# NOTE: fhir.resources really doesn't like when you try to subclass their Pydantic models
383+
# Extend the Organization class
384+
# NOTE: fhir.resources really doesn't like when you try to subclass their Pydantic models
394385

395-
qualification_ext = Extension(
396-
url="https://build.fhir.org/organization-definitions.html#Organization.qualification",
397-
valueCodeableConcept=code,
398-
)
386+
qualification_ext = Extension(
387+
url="https://build.fhir.org/organization-definitions.html#Organization.qualification",
388+
valueCodeableConcept=code,
389+
)
399390

400-
taxonomies.append(qualification_ext)
391+
taxonomies.append(qualification_ext)
401392

402-
if taxonomies:
403-
organization.extension = taxonomies
393+
if taxonomies:
394+
organization.extension = taxonomies
404395

405396
organization.identifier = identifiers
406397

@@ -475,12 +466,6 @@ def to_representation(self, instance):
475466
)
476467
organization_affiliation.participatingOrganization.display = str(instance.organization_name)
477468

478-
# NOTE: Period for OrganizationAffiliation cannot currently be fetched so its blank
479-
480-
# NOTE: Network here means insurance network, per the FHIR spec. We have not begun to incorporate insurance networks
481-
# organization_affiliation.network = [genReference("fhir-organization-detail", instance.id, request)]
482-
# organization_affiliation.network[0].display = str(instance.organization_name)
483-
484469
organization_affiliation.code = [
485470
CodeableConcept(
486471
coding=[
@@ -493,10 +478,6 @@ def to_representation(self, instance):
493478
)
494479
]
495480

496-
# NOTE: not sure how to do specialty yet
497-
498-
endpoints = []
499-
500481
locations = [
501482
genReference("fhir-location-detail", loc_id, request)
502483
for loc_id in instance.location_ids
@@ -539,21 +520,7 @@ def to_representation(self, instance):
539520
practitioner.meta = Meta(
540521
profile=["http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner"]
541522
)
542-
npi_identifier = Identifier(
543-
system="http://terminology.hl7.org/NamingSystem/npi",
544-
value=str(instance.npi.npi),
545-
type=CodeableConcept(
546-
coding=[
547-
Coding(
548-
system="http://terminology.hl7.org/CodeSystem/v2-0203",
549-
code="NPI",
550-
display="National provider identifier",
551-
)
552-
]
553-
),
554-
use="official",
555-
period=Period(start=instance.npi.enumeration_date, end=instance.npi.deactivation_date),
556-
)
523+
npi_identifier = representation["npi"]
557524
if representation["individual"]["telecom"] != []:
558525
practitioner.telecom = representation["individual"]["telecom"]
559526
if (
@@ -593,8 +560,8 @@ def to_representation(self, instance):
593560
else:
594561
location.status = "inactive"
595562
location.name = instance.name
596-
# if 'phone' in representation.keys():
597-
# location.telecom = representation['phone']
563+
if "phone" in representation.keys() and representation["phone"] is not None:
564+
location.telecom = [representation["phone"]]
598565
if "address" in representation.keys():
599566
location.address = representation["address"]
600567
if (
@@ -701,13 +668,6 @@ def to_representation(self, instance):
701668
code=instance.endpoint_connection_type.id,
702669
display=instance.endpoint_connection_type.display,
703670
)
704-
# TODO THIS IS TEMPORARY DUE TO INSUFFICIENT DATA
705-
else:
706-
connection_type = Coding(
707-
system="http://terminology.hl7.org/CodeSystem/endpoint-connection-type",
708-
code="hl7-fhir-rest",
709-
display="HL7 FHIR",
710-
)
711671

712672
## TODO extend base fhir spec to ndh spec
713673
# if instance.environment_type:

backend/npdfhir/tests/fixtures/organization.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def create_if_not_exists(self):
107107
npi=clinical_organization,
108108
other_id=id.other_id,
109109
other_id_type_id=id.other_id_type,
110-
state_code=state_code.id,
110+
state_code=state_code,
111111
)
112112
for taxonomy in self.taxonomies:
113113
OrganizationToTaxonomy.objects.create(

0 commit comments

Comments
 (0)