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

Add Devices Spec #2815

Open
wants to merge 20 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
60 changes: 60 additions & 0 deletions care/emr/models/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from django.contrib.postgres.fields import ArrayField
from django.db import models

from care.emr.models import EMRBaseModel


class Device(EMRBaseModel):
# Device Data
identifier = models.CharField(max_length=1024, null=True, blank=True)
status = models.CharField(max_length=14)
availability_status = models.CharField(max_length=14)
manufacturer = models.CharField(max_length=1024)
manufacture_date = models.DateTimeField(null=True, blank=True)
expiration_date = models.DateTimeField(null=True, blank=True)
lot_number = models.CharField(max_length=1024, null=True, blank=True)
serial_number = models.CharField(max_length=1024, null=True, blank=True)
registered_name = models.CharField(max_length=1024, null=True, blank=True)
user_friendly_name = models.CharField(max_length=1024, null=True, blank=True)
model_number = models.CharField(max_length=1024, null=True, blank=True)
part_number = models.CharField(max_length=1024, null=True, blank=True)
contact = models.JSONField(default=dict)
care_type = models.CharField(max_length=1024, null=True, blank=True)

# Relations
facility = models.ForeignKey("facility.Facility", on_delete=models.CASCADE)
managing_organization = models.ForeignKey(
"emr.FacilityOrganization", on_delete=models.SET_NULL, null=True, blank=True
)
current_location = models.ForeignKey(
"emr.FacilityLocation", on_delete=models.SET_NULL, null=True, blank=True
)
current_encounter = models.ForeignKey(
"emr.Encounter", on_delete=models.SET_NULL, null=True, blank=True
)

# metadata
facility_organization_cache = ArrayField(models.IntegerField(), default=list)


class DeviceEncounterHistory(EMRBaseModel):
device = models.ForeignKey("emr.Device", on_delete=models.CASCADE)
encounter = models.ForeignKey("emr.Encounter", on_delete=models.CASCADE)
start = models.DateTimeField()
end = models.DateTimeField(null=True, blank=True)


class DeviceLocationHistory(EMRBaseModel):
device = models.ForeignKey("emr.Device", on_delete=models.CASCADE)
location = models.ForeignKey("emr.FacilityLocation", on_delete=models.CASCADE)
start = models.DateTimeField()
end = models.DateTimeField(null=True, blank=True)


class DeviceServiceHistory(EMRBaseModel):
device = models.ForeignKey(
Device, on_delete=models.PROTECT, null=False, blank=False
)
serviced_on = models.DateField(default=None, null=True, blank=False)
note = models.TextField(default="", null=True, blank=True)
edit_history = models.JSONField(default=list)
Empty file.
43 changes: 43 additions & 0 deletions care/emr/registries/device_type/device_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class DeviceTypeBase:
def handle_create(self, request_data, obj):
"""
Handle Creates, the original source request along with the base object created is passed along.
Update the obj as needed and create any extra metadata needed. This method is called within a transaction
"""
return obj

def handle_update(self, request_data, obj):
"""
Handle Updates, the original source request along with the base object updated is passed along.
Update the obj as needed and create any extra metadata needed. This method is called within a transaction
"""
return obj

def handle_delete(self, obj):
"""
Handle Deletes, the object to be deleted is passed along.
Perform validation or any other changes required here
"""
return obj

def list(self, obj):
"""
Return Extra metadata for the given obj for lists, N+1 queries is okay, caching is recommended for performance
"""
return {}

def retrieve(self, obj):
"""
Return Extra metadata for the given obj during retrieves
"""
return {}


class InternalQuestionnaireRegistry:
_device_types = {}

@classmethod
def register(cls, device_type, device_class) -> None:
if not issubclass(device_class, DeviceTypeBase):
raise ValueError("The provided class is not a subclass of DeviceTypeBase")
cls._device_types[device_type] = device_class
Comment on lines +44 to +50
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 6, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Address potential thread safety concerns with mutable class variables

Modifying a class-level mutable variable like _device_types without synchronization can lead to race conditions in a multi-threaded environment. It might be prudent to implement thread safety measures when registering device types.

You can enhance thread safety by using a threading lock:

+import threading

 class DeviceTypeRegistry:
+    _lock = threading.Lock()
     _device_types = {}

     @classmethod
     def register(cls, device_type, device_class) -> None:
         if not issubclass(device_class, DeviceTypeBase):
             raise ValueError("The provided class is not a subclass of DeviceTypeBase")
+        with cls._lock:
             cls._device_types[device_type] = device_class

Copy link
Member

Choose a reason for hiding this comment

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

@coderabbitai can you give examples of multithreaded use cases

Copy link
Contributor

Choose a reason for hiding this comment

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

@sainak In this context, thread safety might actually be unnecessary. Here's why:

  1. Device type registration typically happens during Django's application startup (import time) which is single-threaded
  2. After startup, the registry is usually read-only, with device types being accessed but not modified during request handling

If you do need runtime registration (which would be unusual), a better pattern would be:

class DeviceTypeRegistry:
    def __init__(self):
        self._device_types = {}
        
    @classmethod
    def get_instance(cls):
        if not hasattr(cls, '_instance'):
            cls._instance = cls()
        return cls._instance

    def register(self, device_type, device_class):
        if not issubclass(device_class, DeviceTypeBase):
            raise ValueError("The provided class is not a subclass of DeviceTypeBase")
        self._device_types[device_type] = device_class

But I'd be curious to know if you have any use cases where you need to register device types after the application has started?


✏️ Learnings added
Learnt from: sainak
PR: ohcnetwork/care#2815
File: care/emr/registries/device_type/device_registry.py:37-43
Timestamp: 2025-02-06T14:33:29.825Z
Learning: In Django applications, device type registration in DeviceTypeRegistry typically occurs during application startup (import time) and remains read-only afterwards. Thread safety for the registry is usually unnecessary unless there are specific runtime registration requirements.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Loading