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

improved get icd11 diagnoses functions #2566

Closed
31 changes: 25 additions & 6 deletions care/facility/api/viewsets/icd.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
from django.http import Http404
from redis_om import FindQuery
from rest_framework.exceptions import APIException, NotFound, ValidationError
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet

from care.facility.static_data.icd11 import ICD11, get_icd11_diagnosis_object_by_id
from care.utils.exceptions import ICD11DiagnosisNotFoundError, ICD11RedisConnectionError
from care.utils.static_data.helpers import query_builder


class ICDViewSet(ViewSet):
def serialize_data(self, objects: list[ICD11]):
return [diagnosis.get_representation() for diagnosis in objects]

def retrieve(self, request, pk):
obj = get_icd11_diagnosis_object_by_id(pk, as_dict=True)
if not obj:
raise Http404
return Response(obj)
def retrieve(self, request, pk: int):
try:
pk = int(pk)
except ValueError as err:
raise ValidationError(detail="ID must be an integer.") from err
DraKen0009 marked this conversation as resolved.
Show resolved Hide resolved

try:
diagnosis = get_icd11_diagnosis_object_by_id(pk, as_dict=True)
return Response(diagnosis)

except ICD11DiagnosisNotFoundError as e:
error_message = e.message
raise NotFound(detail=error_message) from e

except ICD11RedisConnectionError as e:
error_message = e.message
exception = APIException(error_message)
exception.status_code = 400
raise exception from e

except Exception as e:
error_message = "Internal Server Error"
raise APIException(error_message) from e
DraKen0009 marked this conversation as resolved.
Show resolved Hide resolved

def list(self, request):
try:
Expand Down
40 changes: 37 additions & 3 deletions care/facility/static_data/icd11.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
from typing import TypedDict

from django.core.paginator import Paginator
from redis.exceptions import RedisError
from redis_om import Field, Migrator
from redis_om.model.model import NotFoundError as RedisModelNotFoundError

from care.facility.models.icd11_diagnosis import ICD11Diagnosis
from care.utils.exceptions import ICD11DiagnosisNotFoundError, ICD11RedisConnectionError
from care.utils.static_data.models.base import BaseRedisModel

logger = logging.getLogger(__name__)


DISEASE_CODE_PATTERN = r"^(?:[A-Z]+\d|\d+[A-Z])[A-Z\d.]*\s"


Expand Down Expand Up @@ -59,11 +61,43 @@ def load_icd11_diagnosis():
def get_icd11_diagnosis_object_by_id(
diagnosis_id: int, as_dict=False
) -> ICD11 | ICD11Object | None:
"""
Retrieves ICD11 diagnosis by ID with Redis lookup and DB fallback.
Returns the diagnosis data or raises a custom exception on error.
"""
try:
diagnosis = ICD11.get(diagnosis_id)
return diagnosis.get_representation() if as_dict else diagnosis
except Exception:
return None

except RedisModelNotFoundError:
try:
diagnosis = ICD11Diagnosis.objects.get(id=diagnosis_id)

icd11_obj = ICD11(
id=diagnosis.id,
label=diagnosis.label,
chapter=diagnosis.meta_chapter_short or "null",
has_code=1 if re.match(DISEASE_CODE_PATTERN, diagnosis.label) else 0,
vec=diagnosis.label.replace(".", "\\.", 1),
)
icd11_obj.save()
return icd11_obj.get_representation() if as_dict else icd11_obj

except ICD11Diagnosis.DoesNotExist as e:
error_message = "Diagnosis with the specified ID not found."
raise ICD11DiagnosisNotFoundError(error_message) from e

except RedisError as e:
error_message = "Redis connection issue encountered."
raise ICD11RedisConnectionError(error_message) from e

except Exception as e:
logger.error(
"An unexpected error occurred while retrieving the diagnosis. Details - %s",
e,
)
error_message = "Internal Server Error"
raise Exception(error_message) from e
DraKen0009 marked this conversation as resolved.
Show resolved Hide resolved


def get_icd11_diagnoses_objects_by_ids(diagnoses_ids: list[int]) -> list[ICD11Object]:
Expand Down
45 changes: 45 additions & 0 deletions care/facility/tests/test_icd11_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from unittest.mock import patch

from redis.exceptions import RedisError
from redis_om.model.model import NotFoundError as RedisModelNotFoundError
from rest_framework import status
from rest_framework.test import APITestCase

from care.facility.models import ICD11Diagnosis
from care.utils.tests.test_utils import TestUtils


Expand Down Expand Up @@ -53,4 +58,44 @@ def test_get_icd11_by_valid_id(self):

def test_get_icd11_by_invalid_id(self):
res = self.client.get("/api/v1/icd/invalid/")
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("ID must be an integer.", res.json())

res = self.client.get("/api/v1/icd/0/")
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
self.assertIn(
"Diagnosis with the specified ID not found.", res.json()["detail"]
)

@patch("care.facility.static_data.icd11.ICD11.get")
def test_retrieve_diagnosis_not_found_in_redis_and_db(self, mock_redis_get):
mock_redis_get.side_effect = RedisModelNotFoundError(
"Diagnosis not found in Redis"
)

with patch.object(
ICD11Diagnosis.objects, "get", side_effect=ICD11Diagnosis.DoesNotExist
):
response = self.client.get("/api/v1/icd/123/")
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(
response.json()["detail"], "Diagnosis with the specified ID not found."
)

@patch("care.facility.static_data.icd11.ICD11.get")
def test_retrieve_redis_connection_error(self, mock_redis_get):
mock_redis_get.side_effect = RedisError("Redis connection issue")

response = self.client.get("/api/v1/icd/123/")
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.json()["detail"], "Redis connection issue encountered."
)

@patch("care.facility.static_data.icd11.ICD11.get")
def test_retrieve_unexpected_error(self, mock_redis_get):
mock_redis_get.side_effect = Exception("Unexpected error")

response = self.client.get("/api/v1/icd/123/")
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
self.assertEqual(response.json()["detail"], "Internal Server Error")
16 changes: 16 additions & 0 deletions care/utils/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
class CeleryTaskError(Exception):
pass


class ICD11DiagnosisNotFoundError(Exception):
"""Custom exception for ICD11 diagnosis not found."""

def __init__(self, message):
self.message = message
super().__init__(self.message)


class ICD11RedisConnectionError(Exception):
"""Custom exception for Redis connection issues."""

def __init__(self, message):
self.message = message
super().__init__(self.message)
5 changes: 5 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

import logging
import sys
from datetime import datetime, timedelta
from pathlib import Path

Expand Down Expand Up @@ -353,6 +354,10 @@
"root": {"level": "INFO", "handlers": ["console"]},
}

# Disable logs when running tests
if "test" in sys.argv:
logging.disable(logging.CRITICAL)

# Django Rest Framework
# ------------------------------------------------------------------------------
# https://www.django-rest-framework.org/api-guide/settings/
Expand Down