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

Labs #2642

Draft
wants to merge 39 commits into
base: develop
Choose a base branch
from
Draft

Labs #2642

Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
332401e
added service request related valuesets, model and specs
khavinshankar Nov 28, 2024
3b388c3
added specimen related valuesets, model and specs
khavinshankar Nov 30, 2024
d4d57be
added signal to auto create specimen on creation of service request
khavinshankar Dec 4, 2024
5d2ae31
Merge branch 'vigneshhari/health-details' into khavinshankar/labs
khavinshankar Dec 8, 2024
a38f83d
resolved migration conflict and import path errors
khavinshankar Dec 8, 2024
ed50d6a
added diagnostic report related valuesets, model and specs
khavinshankar Dec 8, 2024
97a0fcd
added endpoints to facilitate the labs flow
khavinshankar Dec 9, 2024
96fbfd7
added status flow filter in specimen
khavinshankar Dec 12, 2024
9e57884
added verify and review apis in diagnostic report
khavinshankar Dec 15, 2024
6daf082
added phase filters for diagnostic report
khavinshankar Dec 15, 2024
9a831ca
Merge branch 'develop' into khavinshankar/labs
khavinshankar Jan 4, 2025
f46595c
fixed migration conflict
khavinshankar Jan 5, 2025
20514ae
updated the model references to emr models | created separate specs f…
khavinshankar Jan 5, 2025
8515c24
preserve default values if set manually
khavinshankar Jan 5, 2025
45cee9d
update condition type in specimen
khavinshankar Jan 5, 2025
ac0cbc4
fixed required field issues while using specs in other specs
khavinshankar Jan 5, 2025
86f215e
updated facility type filter to support multiple types
khavinshankar Jan 6, 2025
79db034
serialize subject, encounter and requester in service request list spec
khavinshankar Jan 6, 2025
a7e1bd0
Merge branch 'develop' into khavinshankar/labs
khavinshankar Jan 6, 2025
703d651
cleaned up migrations
khavinshankar Jan 6, 2025
9b6fb2c
Merge branch 'develop' into khavinshankar/labs
khavinshankar Jan 10, 2025
5f1e76e
fixed migration conflicts
khavinshankar Jan 10, 2025
fcbcf2e
fixed receive at lab endpoint
khavinshankar Jan 10, 2025
223f0c7
fixed len issue in diagnostic report retrieve serializer
khavinshankar Jan 10, 2025
c4e043b
added phase field to service request
khavinshankar Jan 10, 2025
8094d8c
use retrieve spec model in endpoints in specimen and diagnostic report
khavinshankar Jan 10, 2025
10273ea
fixed error while retrieving diagnostic report
khavinshankar Jan 10, 2025
e9f06c0
added phase field to service request spec
khavinshankar Jan 10, 2025
1b4a12a
added phase filter and removed flow status filter
khavinshankar Jan 10, 2025
7549d74
fixed typo caused while resolving merge conflict
khavinshankar Jan 10, 2025
5e23ee0
added code rabbit suggestions
khavinshankar Jan 10, 2025
bafcb93
use perform create instead of clean create data
khavinshankar Jan 10, 2025
48a43d9
moved all the request specs into the viewset
khavinshankar Jan 10, 2025
de165f9
added authz
khavinshankar Jan 10, 2025
c23ac0d
Merge branch 'develop' into khavinshankar/labs
khavinshankar Jan 10, 2025
4dbf130
fixed migration conflict
khavinshankar Jan 10, 2025
48f88e6
added based on and subject in diagnostic report list spec
khavinshankar Jan 14, 2025
e56be6c
Add specimen serialization to diagnostic report list spec
yash-learner Jan 17, 2025
a2b7ede
Merge pull request #2748 from yash-learner/khavinshankar/labs
vigneshhari Jan 19, 2025
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
57 changes: 57 additions & 0 deletions care/emr/api/viewsets/diagnostic_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework.decorators import action
from rest_framework.response import Response

from care.emr.api.viewsets.base import EMRModelViewSet
from care.emr.models.diagnostic_report import DiagnosticReport
from care.emr.models.observation import Observation
from care.emr.resources.diagnostic_report.spec import (
DiagnosticReportObservationRequest,
DiagnosticReportReadSpec,
DiagnosticReportSpec,
)
from care.emr.resources.observation.spec import Performer, PerformerType


@extend_schema_view(
create=extend_schema(request=DiagnosticReportSpec),
)
class DiagnosticReportViewSet(EMRModelViewSet):
database_model = DiagnosticReport
pydantic_model = DiagnosticReportSpec
pydantic_read_model = DiagnosticReportReadSpec

@extend_schema(
request=DiagnosticReportObservationRequest,
responses={200: DiagnosticReportReadSpec},
tags=["diagnostic_report"],
)
@action(detail=True, methods=["POST"])
def observations(self, request, *args, **kwargs):
data = DiagnosticReportObservationRequest(**request.data)
khavinshankar marked this conversation as resolved.
Show resolved Hide resolved
report: DiagnosticReport = self.get_object()

observations = []
for observation in data.observations:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is observation valueset validation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The observations are already validated by the specs
image

if not observation.performer:
observation.performer = Performer(
type=PerformerType.user,
id=str(request.user.external_id),
)

observation_instance = observation.de_serialize()
observation_instance.subject_id = report.subject.id
observation_instance.encounter = report.encounter
observation_instance.patient = report.subject

observations.append(observation_instance)

observation_instances = Observation.objects.bulk_create(observations)
report.result.set(observation_instances)
report.save()

return Response(
self.get_read_pydantic_model()
.serialize(report)
.model_dump(exclude=["meta"]),
)
23 changes: 23 additions & 0 deletions care/emr/api/viewsets/service_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from drf_spectacular.utils import extend_schema, extend_schema_view

from care.emr.api.viewsets.base import EMRModelViewSet
from care.emr.models.service_request import ServiceRequest
from care.emr.resources.service_request.spec import (
ServiceRequestReadSpec,
ServiceRequestSpec,
)


@extend_schema_view(
create=extend_schema(request=ServiceRequestSpec),
)
class ServiceRequestViewSet(EMRModelViewSet):
database_model = ServiceRequest
pydantic_model = ServiceRequestSpec
pydantic_read_model = ServiceRequestReadSpec

def clean_create_data(self, request, *args, **kwargs):
clean_data = super().clean_create_data(request, *args, **kwargs)

clean_data["requester"] = request.user.external_id
return clean_data
142 changes: 142 additions & 0 deletions care/emr/api/viewsets/specimen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from datetime import UTC, datetime
khavinshankar marked this conversation as resolved.
Show resolved Hide resolved

from django.db.models import Q
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response

from care.emr.api.viewsets.base import EMRModelViewSet
from care.emr.fhir.schema.base import DateTime
from care.emr.models.specimen import Specimen
from care.emr.resources.specimen.spec import (
SpecimenCollectRequest,
SpecimenProcessRequest,
SpecimenReadSpec,
SpecimenReceiveAtLabRequest,
SpecimenSendToLabRequest,
SpecimenSpec,
StatusChoices,
)


@extend_schema_view(
create=extend_schema(request=SpecimenSpec),
)
class SpecimenViewSet(EMRModelViewSet):
database_model = Specimen
pydantic_model = SpecimenSpec
pydantic_read_model = SpecimenReadSpec

def get_object(self) -> Specimen:
return get_object_or_404(
self.get_queryset(),
Q(external_id__iexact=self.kwargs[self.lookup_field])
| Q(identifier=self.kwargs[self.lookup_field])
| Q(accession_identifier=self.kwargs[self.lookup_field]),
)

@extend_schema(
request=SpecimenCollectRequest,
responses={200: SpecimenReadSpec},
tags=["specimen"],
)
@action(detail=True, methods=["POST"])
def collect(self, request, *args, **kwargs):
data = SpecimenCollectRequest(**request.data)
specimen = self.get_object()

specimen.identifier = data.identifier
specimen.status = StatusChoices.available
specimen.collected_at = datetime.now(UTC)
specimen.collected_by = request.user
specimen.save()

return Response(
self.get_read_pydantic_model()
.serialize(specimen)
.model_dump(exclude=["meta"]),
)

@extend_schema(
request=SpecimenSendToLabRequest,
responses={200: SpecimenReadSpec},
tags=["specimen"],
)
@action(detail=True, methods=["POST"])
def send_to_lab(self, request, *args, **kwargs):
data = SpecimenSendToLabRequest(**request.data)
specimen = self.get_object()
service_request = specimen.request

service_request.location = data.lab
specimen.dispatched_at = datetime.now(UTC)
specimen.dispatched_by = request.user
service_request.save()
specimen.save()

return Response(
self.get_read_pydantic_model()
.serialize(specimen)
.model_dump(exclude=["meta"]),
status=status.HTTP_200_OK,
)

@extend_schema(
request=SpecimenReceiveAtLabRequest,
responses={200: SpecimenReadSpec},
tags=["specimen"],
)
@action(detail=True, methods=["POST"])
def receive_at_lab(self, request, *args, **kwargs):
data = SpecimenReceiveAtLabRequest(**request.data)
specimen = self.get_object()
note = data.note

specimen.accession_identifier = data.accession_identifier
specimen.condition = data.condition
specimen.received_at = datetime.now(UTC)
specimen.received_by = request.user
if note:
note.authorReference = {"id": request.user.external_id}
note.time = DateTime(datetime.now(UTC).isoformat())
specimen.note.append(note.model_dump(mode="json"))
specimen.save()

return Response(
self.get_read_pydantic_model()
.serialize(specimen)
.model_dump(exclude=["meta"]),
status=status.HTTP_200_OK,
)

@extend_schema(
request=SpecimenProcessRequest,
responses={200: SpecimenReadSpec},
tags=["specimen"],
)
@action(detail=True, methods=["POST"])
def process(self, request, *args, **kwargs):
data = SpecimenProcessRequest(**request.data)
specimen = self.get_object()

processes = []
for process in data.process:
if not process.time:
process.time = datetime.now(UTC)

if not process.performer:
process.performer = request.user.external_id

processes.append(process.model_dump(mode="json"))

specimen.processing.extend(processes)
specimen.save()

return Response(
self.get_read_pydantic_model()
.serialize(specimen)
.model_dump(exclude=["meta"]),
status=status.HTTP_200_OK,
)
3 changes: 3 additions & 0 deletions care/emr/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
class EMRConfig(AppConfig):
name = "care.emr"
verbose_name = _("Electronic Medical Record")

def ready(self):
import care.emr.signals # noqa
91 changes: 91 additions & 0 deletions care/emr/fhir/resources/concept_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from enum import Enum

from pydantic.main import BaseModel

from care.emr.fhir.resources.base import ResourceManger
khavinshankar marked this conversation as resolved.
Show resolved Hide resolved
from care.emr.fhir.utils import parse_fhir_parameter_output


class ConceptMapResource(ResourceManger):
allowed_properties = ["system", "code"]
resource = "ConceptMap"

def serialize_lookup(self, result):
structured_output = parse_fhir_parameter_output(result)

return ConceptMapResult(
result=structured_output["result"],
match=[
ConceptMapMatch(
equivalence=match["equivalence"],
concept=ConceptMapConcept(
display=match["concept"]["display"],
code=match["concept"]["code"],
),
source=match["source"],
)
for match in structured_output["match"]
],
)

def translate(self):
if "system" not in self._filters or "code" not in self._filters:
err = "Both system and code are required"
raise ValueError(err)
full_result = self.query("GET", "ConceptMap/$translate", self._filters)

return self.serialize_lookup(full_result["parameter"])


class ConceptMapConcept(BaseModel):
display: str
code: str


class ConceptMapEquivalence(str, Enum):
def __new__(cls, value, priority=None):
obj = str.__new__(cls, value)
obj._value_ = value
obj.priority = priority
return obj

# This is the strongest form of equality.
equal = "equal", 1

# Indicates equivalence, almost identical.
equivalent = "equivalent", 2

# Concepts are related, exact nature unspecified.
relatedto = "relatedto", 3

# The source concept is more specific than the target.
specializes = "specializes", 4

# The source concept is more general and subsumes the target.
subsumes = "subsumes", 5

# The source is broader in meaning than the target.
wider = "wider", 6

# The source is narrower in meaning than the target.
narrower = "narrower", 7

# The relationship is approximate but not exact.
inexact = "inexact", 8

# Indicates a complete lack of relationship or overlap.
disjoint = "disjoint", 9

# No match exists for the source concept in the target system.
unmatched = "unmatched", 10


class ConceptMapMatch(BaseModel):
equivalence: ConceptMapEquivalence
concept: ConceptMapConcept
source: str


class ConceptMapResult(BaseModel):
result: bool
match: list[ConceptMapMatch]
14 changes: 14 additions & 0 deletions care/emr/fhir/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,26 @@ def parse_fhir_parameter_output(parameters):
response = {}
for parameter in parameters:
value = ""

if "valueString" in parameter:
value = parameter["valueString"]
if "valueBoolean" in parameter:
value = parameter["valueBoolean"]
if "valueCode" in parameter:
value = parameter["valueCode"]
if "valueCoding" in parameter:
value = parameter["valueCoding"]
khavinshankar marked this conversation as resolved.
Show resolved Hide resolved

if parameter["name"] == "property":
if "property" not in response:
response["property"] = {}
response["property"].update(parse_fhir_property_part(parameter["part"]))
elif parameter["name"] == "match":
if "match" not in response:
response["match"] = []

response["match"].append(parse_fhir_parameter_output(parameter["part"]))
khavinshankar marked this conversation as resolved.
Show resolved Hide resolved
else:
response[parameter["name"]] = value

return response
Loading
Loading