Skip to content

Conversation

@praffq
Copy link
Contributor

@praffq praffq commented Jan 14, 2026

Proposed Changes

  • added caching for UserSpec in all the used places

Added caching for -

  • UserSpec
  • OrganizationReadSpec
  • FacilityOrganizationReadSpec

Associated Issue

Merge Checklist

  • Tests added/fixed
  • Update docs in /docs
  • Linting Complete
  • Any other necessary step

Only PR's with test cases included and passing lint and test pipelines will be reviewed

@ohcnetwork/care-backend-maintainers @ohcnetwork/care-backend-admins

Summary by CodeRabbit

  • Performance Improvements

    • Introduced cache-backed retrieval for organization, user, and facility data to speed responses and reduce DB load.
  • Refactor

    • Centralized audit-field (created_by, updated_by) serialization across many resources for consistent output.
    • Standardized related-entity serialization to improve maintainability.
  • API / Schema

    • Organization/facility records now include additional metadata (e.g., hierarchy/flags) in responses.

✏️ Tip: You can customize this high-level summary in your review settings.

@praffq praffq requested a review from a team as a code owner January 14, 2026 09:06
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

📝 Walkthrough

Walkthrough

Replaces many explicit spec.serialize(...).to_json() calls with cache-backed model_from_cache(...) lookups, centralizes audit-user serialization into EMRResource.serialize_audit_users, marks organization/facility-organization specs as @cacheable with added fields, and adjusts related-object location retrieval logic and base utilities accordingly.

Changes

Cohort / File(s) Summary
API viewsets: use cache-backed responses
care/emr/api/viewsets/{encounter,location,patient,questionnaire}.py, care/emr/api/viewsets/scheduling/booking.py
Replace inline spec serialization in endpoints with model_from_cache(Spec, id=...) for organizations/users returned by handlers.
Resource specs: consolidate audit-user serialization
care/emr/resources/{allergy_intolerance,condition,location,medication/administration,meta_artifact,notes/*,questionnaire,questionnaire_response,valueset,resource_request,patient}/spec.py
Replace per-field created_by/updated_by serialization with cls.serialize_audit_users(mapping, obj) across many specs.
Resource specs: cache-based related-object lookups
care/emr/resources/{consent,device,encounter,facility,file_upload,observation,resource_request,tag/config_spec,user,inventory/*,healthcare_service,delivery_order}/spec.py
Swap direct ORM serialization for model_from_cache(...) calls by ID for related organizations/users; some modules import model_from_cache.
Cacheable spec classes & API surface additions
care/emr/resources/{facility_organization,organization}/spec.py
Add @cacheable to specs and introduce public fields (e.g., created_by, updated_by, system_generated, level_cache, has_children, parent) and delegate audit handling to helper.
Location / facility retrieval changes
care/emr/resources/{healthcare_service,medication/dispense/dispense_order,scheduling/resource,service_request}/spec.py
Change location handling from bulk/object-direct serialization to per-id or deferred ORM fetch + serialization; skips missing entries for service_request.
Inventory: supplier/origin/destination adjustments
care/emr/resources/inventory/supply_delivery/delivery_order.py, care/emr/resources/inventory/supply_request/request_order.py
Replace organization serialization with model_from_cache(OrganizationReadSpec, id=...); origin/destination now fetched then serialized via FacilityLocationListSpec.
Base utilities: guards and model dump format
care/emr/resources/base.py
Add hasattr guards in serialize_audit_users; change model_from_cache return to data.model_dump(mode="json") (JSON-mode pydantic output).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding model caching for frequently used specs like UserSpec and others.
Description check ✅ Passed The description covers proposed changes and includes an associated issue link, but the merge checklist items remain unchecked with no indication of completion.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch prafful/adding-model-caching-in-specs

Comment @coderabbitai help to get the list of available commands and usage tips.

# Conflicts:
#	care/emr/api/viewsets/encounter.py
#	care/emr/resources/facility/spec.py
#	care/emr/resources/organization/spec.py
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
care/emr/resources/meta_artifact/spec.py (1)

39-45: Missing defaults on created_by and updated_by may cause validation errors.

Unlike the other specs in this PR (e.g., NoteThreadReadSpec, ValueSetReadSpec, QuestionnaireReadSpec), these fields lack default values. The serialize_audit_users helper only sets these keys when the IDs are non-null, which could cause Pydantic validation errors for records where created_by_id or updated_by_id happens to be null.

Suggested fix: add defaults for consistency
-    created_by: UserSpec
-    updated_by: UserSpec
+    created_by: UserSpec = {}
+    updated_by: UserSpec = {}
care/emr/resources/inventory/inventory_item/spec.py (1)

32-33: Incorrect docstring.

The docstring says "Supply delivery read specification" but this class is InventoryItemReadSpec. Someone was perhaps a bit too enthusiastic with copy-paste.

📝 Suggested fix
 class InventoryItemReadSpec(BaseInventoryItemSpec):
-    """Supply delivery read specification"""
+    """Inventory item read specification"""
care/emr/models/location.py (1)

40-57: Handle potential None return from model_from_cache.

If the parent FacilityLocation record is deleted (but the FK reference still exists due to on_delete=models.SET_NULL), model_from_cache will return None with the default quiet=True. Line 51 then attempts temp_data["cache_expiry"], which would raise a TypeError.

While this edge case may be rare, it's worth adding a defensive check.

Suggested fix
             self.parent.get_parent_json()
             temp_data = model_from_cache(FacilityLocationListSpec, id=self.parent.id)
+            if temp_data is None:
+                return {}
             temp_data["cache_expiry"] = str(
                 timezone.now() + timedelta(days=self.cache_expiry_days)
             )
🤖 Fix all issues with AI agents
In `@care/emr/api/viewsets/patient.py`:
- Around line 281-284: The list comprehension building data passes the
ForeignKey object patient_user.user into model_from_cache which expects an id;
change the call inside the comprehension to pass patient_user.user_id instead
(e.g., model_from_cache(UserSpec, id=patient_user.user_id)) so model_from_cache
receives an int/str/None; update the comprehension that assigns data and keep
using UserSpec and patient_users as the referenced symbols.

In `@care/emr/resources/consent/spec.py`:
- Around line 119-124: The loop over obj.verification_details mutates each
verification dict in-place by assigning verification["verified_by"] =
model_from_cache(...); instead, avoid side effects by creating a new list of
verification dicts (e.g., iterate and for each verification create a shallow
copy or new dict), replace the "verified_by" value in the copy using
model_from_cache(UserSpec, external_id=...), and then assign that new list to
mapping["verification_details"] so obj.verification_details and any shared
cached data remain unchanged.

In `@care/emr/resources/device/history_spec.py`:
- Around line 42-44: history.get("updated_by") may be None which causes
model_from_cache(UserSpec, id=user) to raise ValueError before the `or {}` runs;
guard the call by checking / short-circuiting when user is falsy (e.g., if not
user: history["updated_by"] = {}) and only call model_from_cache(UserSpec,
id=user) when user is present, updating the assignment around the history
variable used in edit_history.append to avoid the exception.

In `@care/emr/resources/medication/dispense/dispense_order.py`:
- Around line 64-66: The mapping currently assigns mapping["location"] =
model_from_cache(FacilityLocationListSpec, id=obj.location.id) without guarding
for obj.location or None returns; change this to first check if obj.location is
truthy, then call model_from_cache and only assign mapping["location"] if the
call returns a non-None value — i.e., wrap the call in an if obj.location: block
and then set mapping["location"] = result only when result is not None,
referencing obj.location, mapping["location"], model_from_cache, and
FacilityLocationListSpec.

In `@care/emr/resources/observation/spec.py`:
- Around line 133-137: The code is loading the related User via
obj.data_entered_by when only the FK is needed, causing an extra query; change
the check to use obj.data_entered_by_id and pass that id to model_from_cache
instead of accessing the related object. Update the block around
cls.serialize_audit_users so it uses obj.data_entered_by_id in the conditional
and calls model_from_cache(UserSpec, id=obj.data_entered_by_id) to avoid loading
the User instance.
🧹 Nitpick comments (13)
care/emr/api/viewsets/scheduling/booking.py (1)

276-283: Consider filtering out potential None values from the response.

Per the model_from_cache signature (returns dict[str, Any] | None), if a user cannot be found (and quiet=True by default), the list comprehension will include None entries. While the queryset filters for non-deleted users, it might be worth ensuring the API response doesn't inadvertently contain None values.

🔧 Suggested defensive filter
         return Response(
             {
                 "users": [
-                    model_from_cache(UserSpec, id=user_resource.user_id)
+                    user
                     for user_resource in user_resources
+                    if (user := model_from_cache(UserSpec, id=user_resource.user_id))
+                    is not None
                 ]
             }
         )

Alternatively, if a missing user indicates a data integrity violation that should be surfaced immediately, you could pass quiet=False to raise an exception instead.

care/emr/resources/questionnaire_response/spec.py (1)

48-49: Consider using None as default instead of dict type.

The defaults created_by: UserSpec = dict and updated_by: UserSpec = dict assign the dict type as the default value, not a dict instance. This works because perform_extra_serialization populates these fields before they're needed, but it's an unusual pattern that could confuse future maintainers. Perhaps None with UserSpec | None would express intent more clearly—unless, of course, this is just how things are done around here.

♻️ Suggested improvement
-    created_by: UserSpec = dict
-    updated_by: UserSpec = dict
+    created_by: UserSpec | None = None
+    updated_by: UserSpec | None = None
care/emr/resources/scheduling/resource/spec.py (1)

8-15: Consider applying caching consistently across all resource types.

Lines 10 and 14 now use model_from_cache for practitioners and locations, but line 12 still uses the old serialize(...).to_json() pattern for HealthcareServiceReadSpec. If HealthcareServiceReadSpec isn't cacheable yet, that's perfectly fine—just thought you might want to keep a note of it for future consistency.

care/emr/resources/activity_definition/spec.py (1)

148-154: Redundant existence check before model_from_cache.

The explicit query on lines 150-152 is now redundant since model_from_cache returns None for missing objects (when quiet=True, which is the default). Currently, this could trigger two database queries per location on a cache miss: one for the existence check and one inside model_from_cache.

Consider simplifying by relying on the cache function's behavior:

Suggested simplification
         locations = []
         for location in obj.locations:
-            location_obj = FacilityLocation.objects.filter(id=location).first()
-            if not location_obj:
+            cached_location = model_from_cache(FacilityLocationListSpec, id=location)
+            if not cached_location:
                 continue
-            locations.append(model_from_cache(FacilityLocationListSpec, id=location))
+            locations.append(cached_location)
         mapping["locations"] = locations
care/emr/resources/inventory/supply_request/request_order.py (1)

102-104: Minor type annotation inconsistency.

The supplier field is typed as OrganizationReadSpec | None, but model_from_cache returns dict | None. While Pydantic's model_construct handles this gracefully, you might want to align the type annotation with the other fields for consistency.

💡 Suggested alignment
-    supplier: OrganizationReadSpec | None = None
+    supplier: dict | None = None
care/emr/resources/tag/config_spec.py (1)

172-172: Consider moving import to module level.

The model_from_cache function is already available from care.emr.resources.base (same module as the existing imports at line 14). Moving this import to the module level would be cleaner, unless there's a specific reason for keeping it inline that I'm not seeing.

💡 Suggested change

At line 14, update the import:

-from care.emr.resources.base import EMRResource, cacheable, model_string
+from care.emr.resources.base import EMRResource, cacheable, model_from_cache, model_string

Then remove line 172.

care/emr/resources/user/spec.py (1)

192-194: Variable shadowing: obj reused in list comprehension.

The loop variable obj shadows the method parameter obj: User. While the code works correctly (you want the Organization's id here), this can be confusing for future readers.

Suggested rename for clarity
         mapping["organizations"] = [
-            model_from_cache(OrganizationReadSpec, id=obj.id) for obj in organizations
+            model_from_cache(OrganizationReadSpec, id=org.id) for org in organizations
         ]
care/emr/resources/file_upload/spec.py (1)

115-116: Inconsistent pattern: accessing obj.updated_by.id instead of obj.updated_by_id.

The condition checks obj.updated_by which triggers a database query, and then accesses .id on the result. Meanwhile, lines 95-98 correctly use obj.created_by_id and obj.archived_by_id directly. For consistency and to avoid the extra query, consider using obj.updated_by_id here as well.

♻️ Suggested improvement
-        if obj.updated_by:
-            mapping["updated_by"] = model_from_cache(UserSpec, id=obj.updated_by.id)
+        if obj.updated_by_id:
+            mapping["updated_by"] = model_from_cache(UserSpec, id=obj.updated_by_id)
care/emr/resources/patient/spec.py (1)

267-270: Consider using obj.geo_organization_id to avoid an extra query.

The current code accesses obj.geo_organization.id, which may trigger a database query if geo_organization isn't already loaded. Using obj.geo_organization_id would directly access the foreign key value without the extra fetch.

♻️ Suggested improvement
-        if obj.geo_organization:
-            mapping["geo_organization"] = model_from_cache(
-                OrganizationReadSpec, id=obj.geo_organization.id
-            )
+        if obj.geo_organization_id:
+            mapping["geo_organization"] = model_from_cache(
+                OrganizationReadSpec, id=obj.geo_organization_id
+            )
care/emr/resources/facility/spec.py (2)

111-116: Same FK access pattern issue as elsewhere.

Using obj.geo_organization.id (line 115) instead of obj.geo_organization_id may cause an unnecessary database hit. Since you're already checking for truthiness, switching to the _id field would be more efficient.

♻️ Suggested improvement
         cls.serialize_audit_users(mapping, obj)
         mapping["facility_type"] = REVERSE_FACILITY_TYPES[obj.facility_type]
-        if obj.geo_organization:
+        if obj.geo_organization_id:
             mapping["geo_organization"] = model_from_cache(
-                OrganizationReadSpec, id=obj.geo_organization.id
+                OrganizationReadSpec, id=obj.geo_organization_id
             )

215-218: Same pattern here in FacilityMinimalReadSpec.

For consistency with the suggested change above, this should also use obj.geo_organization_id.

♻️ Suggested improvement
-        if obj.geo_organization:
+        if obj.geo_organization_id:
             mapping["geo_organization"] = model_from_cache(
-                OrganizationReadSpec, id=obj.geo_organization.id
+                OrganizationReadSpec, id=obj.geo_organization_id
             )
care/emr/resources/inventory/supply_delivery/delivery_order.py (1)

80-90: Same FK access pattern applies here.

For consistency with the recommended changes across this PR, consider using the _id fields directly (obj.origin_id, obj.destination_id, obj.supplier_id) instead of accessing through the related object. This avoids potentially unnecessary database queries.

♻️ Suggested improvement
-        if obj.origin:
-            mapping["origin"] = model_from_cache(
-                FacilityLocationListSpec, id=obj.origin.id
-            )
-        mapping["destination"] = model_from_cache(
-            FacilityLocationListSpec, id=obj.destination.id
-        )
-        if obj.supplier:
-            mapping["supplier"] = model_from_cache(
-                OrganizationReadSpec, id=obj.supplier.id
-            )
+        if obj.origin_id:
+            mapping["origin"] = model_from_cache(
+                FacilityLocationListSpec, id=obj.origin_id
+            )
+        mapping["destination"] = model_from_cache(
+            FacilityLocationListSpec, id=obj.destination_id
+        )
+        if obj.supplier_id:
+            mapping["supplier"] = model_from_cache(
+                OrganizationReadSpec, id=obj.supplier_id
+            )
care/emr/resources/resource_request/spec.py (1)

150-151: Consider using assigned_to_id for consistency and efficiency.

The serialize_audit_users helper (which you're using just below) accesses obj.created_by_id directly to avoid fetching the related object. Here, obj.assigned_to triggers a database fetch before extracting .id. It would be just lovely if this followed the same pattern.

♻️ Suggested change
-        if obj.assigned_to:
-            mapping["assigned_to"] = model_from_cache(UserSpec, id=obj.assigned_to.id)
+        if obj.assigned_to_id:
+            mapping["assigned_to"] = model_from_cache(UserSpec, id=obj.assigned_to_id)
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0088f5c and 4414f88.

📒 Files selected for processing (39)
  • care/emr/api/viewsets/encounter.py
  • care/emr/api/viewsets/location.py
  • care/emr/api/viewsets/patient.py
  • care/emr/api/viewsets/questionnaire.py
  • care/emr/api/viewsets/scheduling/booking.py
  • care/emr/models/location.py
  • care/emr/resources/activity_definition/spec.py
  • care/emr/resources/allergy_intolerance/spec.py
  • care/emr/resources/condition/spec.py
  • care/emr/resources/consent/spec.py
  • care/emr/resources/device/history_spec.py
  • care/emr/resources/device/spec.py
  • care/emr/resources/encounter/spec.py
  • care/emr/resources/facility/spec.py
  • care/emr/resources/facility_organization/spec.py
  • care/emr/resources/file_upload/spec.py
  • care/emr/resources/healthcare_service/spec.py
  • care/emr/resources/inventory/inventory_item/spec.py
  • care/emr/resources/inventory/supply_delivery/delivery_order.py
  • care/emr/resources/inventory/supply_request/request_order.py
  • care/emr/resources/location/spec.py
  • care/emr/resources/medication/administration/spec.py
  • care/emr/resources/medication/dispense/dispense_order.py
  • care/emr/resources/medication/dispense/spec.py
  • care/emr/resources/meta_artifact/spec.py
  • care/emr/resources/notes/notes_spec.py
  • care/emr/resources/notes/thread_spec.py
  • care/emr/resources/observation/spec.py
  • care/emr/resources/organization/spec.py
  • care/emr/resources/patient/spec.py
  • care/emr/resources/payment_reconciliation/spec.py
  • care/emr/resources/questionnaire/spec.py
  • care/emr/resources/questionnaire_response/spec.py
  • care/emr/resources/resource_request/spec.py
  • care/emr/resources/scheduling/resource/spec.py
  • care/emr/resources/service_request/spec.py
  • care/emr/resources/tag/config_spec.py
  • care/emr/resources/user/spec.py
  • care/emr/resources/valueset/spec.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (.cursorrules)

**/*.py: Prioritize readability and maintainability; follow Django's coding style guide (PEP 8 compliance).
Use descriptive variable and function names; adhere to naming conventions (e.g., lowercase with underscores for functions and variables).

Files:

  • care/emr/resources/inventory/inventory_item/spec.py
  • care/emr/resources/service_request/spec.py
  • care/emr/resources/condition/spec.py
  • care/emr/resources/notes/thread_spec.py
  • care/emr/resources/medication/administration/spec.py
  • care/emr/resources/questionnaire/spec.py
  • care/emr/resources/encounter/spec.py
  • care/emr/api/viewsets/encounter.py
  • care/emr/resources/valueset/spec.py
  • care/emr/resources/inventory/supply_delivery/delivery_order.py
  • care/emr/resources/patient/spec.py
  • care/emr/resources/inventory/supply_request/request_order.py
  • care/emr/models/location.py
  • care/emr/api/viewsets/patient.py
  • care/emr/resources/scheduling/resource/spec.py
  • care/emr/resources/location/spec.py
  • care/emr/resources/allergy_intolerance/spec.py
  • care/emr/resources/consent/spec.py
  • care/emr/resources/healthcare_service/spec.py
  • care/emr/resources/tag/config_spec.py
  • care/emr/resources/notes/notes_spec.py
  • care/emr/resources/observation/spec.py
  • care/emr/resources/payment_reconciliation/spec.py
  • care/emr/resources/questionnaire_response/spec.py
  • care/emr/api/viewsets/questionnaire.py
  • care/emr/resources/user/spec.py
  • care/emr/resources/file_upload/spec.py
  • care/emr/api/viewsets/scheduling/booking.py
  • care/emr/resources/resource_request/spec.py
  • care/emr/resources/meta_artifact/spec.py
  • care/emr/resources/medication/dispense/spec.py
  • care/emr/resources/facility_organization/spec.py
  • care/emr/resources/organization/spec.py
  • care/emr/resources/facility/spec.py
  • care/emr/api/viewsets/location.py
  • care/emr/resources/activity_definition/spec.py
  • care/emr/resources/device/spec.py
  • care/emr/resources/medication/dispense/dispense_order.py
  • care/emr/resources/device/history_spec.py
**/{models,views,management/commands}/*.py

📄 CodeRabbit inference engine (.cursorrules)

Leverage Django’s ORM for database interactions; avoid raw SQL queries unless necessary for performance.

Files:

  • care/emr/models/location.py
🧠 Learnings (6)
📚 Learning: 2024-11-28T06:16:31.373Z
Learnt from: DraKen0009
Repo: ohcnetwork/care PR: 2620
File: care/facility/models/facility.py:306-311
Timestamp: 2024-11-28T06:16:31.373Z
Learning: In the 'care' project, moving imports of models like `Asset`, `AssetLocation`, `FacilityDefaultAssetLocation`, and `PatientSample` to the module level in `care/facility/models/facility.py` causes circular imports. Therefore, it's acceptable to keep these imports inside the `delete` method of the `Facility` class.

Applied to files:

  • care/emr/resources/inventory/inventory_item/spec.py
  • care/emr/resources/inventory/supply_delivery/delivery_order.py
  • care/emr/resources/inventory/supply_request/request_order.py
  • care/emr/models/location.py
  • care/emr/resources/location/spec.py
  • care/emr/resources/healthcare_service/spec.py
  • care/emr/resources/payment_reconciliation/spec.py
  • care/emr/resources/resource_request/spec.py
  • care/emr/resources/medication/dispense/spec.py
  • care/emr/resources/facility/spec.py
  • care/emr/api/viewsets/location.py
  • care/emr/resources/activity_definition/spec.py
  • care/emr/resources/device/spec.py
  • care/emr/resources/medication/dispense/dispense_order.py
📚 Learning: 2025-07-15T13:39:29.540Z
Learnt from: NikhilA8606
Repo: ohcnetwork/care PR: 3148
File: care/emr/api/viewsets/consent.py:22-24
Timestamp: 2025-07-15T13:39:29.540Z
Learning: In the care codebase, BaseModel (in care/utils/models/base.py) contains an external_id field which is inherited by all EMR models including Encounter. This allows filtering using field paths like `encounter__external_id` in Django filters.

Applied to files:

  • care/emr/resources/encounter/spec.py
  • care/emr/api/viewsets/encounter.py
  • care/emr/resources/location/spec.py
  • care/emr/resources/consent/spec.py
  • care/emr/resources/observation/spec.py
  • care/emr/resources/device/spec.py
📚 Learning: 2025-07-08T13:27:05.363Z
Learnt from: NikhilA8606
Repo: ohcnetwork/care PR: 3124
File: care/emr/resources/medication/administration/spec.py:163-164
Timestamp: 2025-07-08T13:27:05.363Z
Learning: In the care codebase, all EMR models inherit from EMRBaseModel which inherits from BaseModel (in care/utils/models/base.py). BaseModel provides common fields including created_date and modified_date with auto_now_add=True and auto_now=True respectively. These fields are available on all EMR models through inheritance.

Applied to files:

  • care/emr/api/viewsets/encounter.py
  • care/emr/api/viewsets/patient.py
  • care/emr/resources/consent/spec.py
  • care/emr/resources/observation/spec.py
  • care/emr/resources/file_upload/spec.py
  • care/emr/resources/device/spec.py
  • care/emr/resources/device/history_spec.py
📚 Learning: 2025-10-08T08:08:44.661Z
Learnt from: rithviknishad
Repo: ohcnetwork/care PR: 3302
File: care/emr/resources/scheduling/slot/spec.py:146-151
Timestamp: 2025-10-08T08:08:44.661Z
Learning: In care/emr/resources/scheduling/slot/spec.py and related token booking code, TokenBooking instances always have a patient associated. The codebase prefers a fail-fast approach where missing patients should cause errors rather than being handled gracefully with optional/nullable fields.

Applied to files:

  • care/emr/api/viewsets/patient.py
  • care/emr/api/viewsets/scheduling/booking.py
📚 Learning: 2025-10-08T08:07:56.493Z
Learnt from: rithviknishad
Repo: ohcnetwork/care PR: 3302
File: care/emr/resources/scheduling/slot/spec.py:156-161
Timestamp: 2025-10-08T08:07:56.493Z
Learning: In `care/emr/resources/scheduling/slot/spec.py`, token bookings must always have an associated patient. The maintainers prefer fail-fast behavior (allowing exceptions) over graceful degradation when a token booking lacks a patient, as it indicates a data integrity violation that should be caught immediately.

Applied to files:

  • care/emr/api/viewsets/patient.py
  • care/emr/api/viewsets/scheduling/booking.py
📚 Learning: 2025-03-19T14:53:14.299Z
Learnt from: DraKen0009
Repo: ohcnetwork/care PR: 2931
File: care/emr/migrations/0023_allergyintolerance_allergy_intolerance_type.py:16-16
Timestamp: 2025-03-19T14:53:14.299Z
Learning: For the AllergyIntolerance model, field validations are handled by Pydantic specs (AllergyIntoleranceWriteSpec) using enum types rather than in the Django model definition. The field `allergy_intolerance_type` is validated through the AllergyIntoleranceTypeOptions enum which contains "allergy" and "intolerance" options.

Applied to files:

  • care/emr/resources/allergy_intolerance/spec.py
🧬 Code graph analysis (37)
care/emr/resources/inventory/inventory_item/spec.py (2)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/service_request/spec.py (2)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/condition/spec.py (1)
care/emr/resources/base.py (1)
  • serialize_audit_users (162-168)
care/emr/resources/notes/thread_spec.py (1)
care/emr/resources/base.py (1)
  • serialize_audit_users (162-168)
care/emr/resources/medication/administration/spec.py (1)
care/emr/resources/base.py (1)
  • serialize_audit_users (162-168)
care/emr/resources/questionnaire/spec.py (1)
care/emr/resources/base.py (1)
  • serialize_audit_users (162-168)
care/emr/api/viewsets/encounter.py (2)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/facility_organization/spec.py (1)
  • FacilityOrganizationReadSpec (75-89)
care/emr/resources/valueset/spec.py (1)
care/emr/resources/base.py (1)
  • serialize_audit_users (162-168)
care/emr/resources/inventory/supply_delivery/delivery_order.py (3)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/organization/spec.py (1)
  • OrganizationReadSpec (54-63)
care/emr/resources/patient/spec.py (3)
care/emr/resources/base.py (3)
  • model_from_cache (232-275)
  • perform_extra_serialization (40-41)
  • serialize_audit_users (162-168)
care/emr/resources/organization/spec.py (2)
  • OrganizationReadSpec (54-63)
  • perform_extra_serialization (61-63)
care/emr/resources/user/spec.py (4)
  • perform_extra_serialization (135-138)
  • perform_extra_serialization (148-158)
  • perform_extra_serialization (178-213)
  • perform_extra_serialization (224-226)
care/emr/resources/inventory/supply_request/request_order.py (3)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/organization/spec.py (1)
  • OrganizationReadSpec (54-63)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/models/location.py (2)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/api/viewsets/patient.py (2)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/user/spec.py (1)
  • UserSpec (124-138)
care/emr/resources/scheduling/resource/spec.py (2)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/location/spec.py (1)
care/emr/resources/base.py (4)
  • EMRResource (13-168)
  • cacheable (279-305)
  • model_from_cache (232-275)
  • serialize_audit_users (162-168)
care/emr/resources/allergy_intolerance/spec.py (1)
care/emr/resources/base.py (1)
  • serialize_audit_users (162-168)
care/emr/resources/consent/spec.py (2)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/user/spec.py (1)
  • UserSpec (124-138)
care/emr/resources/healthcare_service/spec.py (3)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/facility_organization/spec.py (1)
  • FacilityOrganizationReadSpec (75-89)
care/emr/resources/tag/config_spec.py (3)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/facility_organization/spec.py (1)
  • FacilityOrganizationReadSpec (75-89)
care/emr/resources/organization/spec.py (1)
  • OrganizationReadSpec (54-63)
care/emr/resources/notes/notes_spec.py (1)
care/emr/resources/base.py (1)
  • serialize_audit_users (162-168)
care/emr/resources/observation/spec.py (2)
care/emr/resources/base.py (3)
  • EMRResource (13-168)
  • model_from_cache (232-275)
  • serialize_audit_users (162-168)
care/emr/resources/user/spec.py (1)
  • UserSpec (124-138)
care/emr/resources/payment_reconciliation/spec.py (2)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/questionnaire_response/spec.py (1)
care/emr/resources/base.py (1)
  • serialize_audit_users (162-168)
care/emr/api/viewsets/questionnaire.py (2)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/organization/spec.py (1)
  • OrganizationReadSpec (54-63)
care/emr/resources/file_upload/spec.py (2)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/user/spec.py (1)
  • UserSpec (124-138)
care/emr/api/viewsets/scheduling/booking.py (2)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/user/spec.py (1)
  • UserSpec (124-138)
care/emr/resources/resource_request/spec.py (2)
care/emr/resources/base.py (3)
  • EMRResource (13-168)
  • model_from_cache (232-275)
  • serialize_audit_users (162-168)
care/emr/resources/user/spec.py (1)
  • UserSpec (124-138)
care/emr/resources/meta_artifact/spec.py (1)
care/emr/resources/base.py (1)
  • serialize_audit_users (162-168)
care/emr/resources/medication/dispense/spec.py (2)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/facility_organization/spec.py (1)
care/emr/resources/base.py (3)
  • EMRResource (13-168)
  • cacheable (279-305)
  • serialize_audit_users (162-168)
care/emr/resources/organization/spec.py (1)
care/emr/resources/base.py (3)
  • EMRResource (13-168)
  • cacheable (279-305)
  • serialize_audit_users (162-168)
care/emr/resources/facility/spec.py (2)
care/emr/resources/base.py (4)
  • EMRResource (13-168)
  • cacheable (279-305)
  • model_from_cache (232-275)
  • serialize_audit_users (162-168)
care/emr/resources/organization/spec.py (1)
  • OrganizationReadSpec (54-63)
care/emr/api/viewsets/location.py (2)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/facility_organization/spec.py (1)
  • FacilityOrganizationReadSpec (75-89)
care/emr/resources/activity_definition/spec.py (2)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/device/spec.py (3)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/facility_organization/spec.py (1)
  • FacilityOrganizationReadSpec (75-89)
care/emr/resources/medication/dispense/dispense_order.py (2)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/device/history_spec.py (2)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/user/spec.py (1)
  • UserSpec (124-138)
🔇 Additional comments (56)
care/emr/api/viewsets/scheduling/booking.py (1)

30-30: LGTM on the import.

The import aligns with the PR objective of using cached model lookups.

care/emr/resources/questionnaire_response/spec.py (2)

64-64: LGTM on the refactor to use centralized audit serialization.

This change correctly delegates to the shared serialize_audit_users() helper, which aligns with the PR's goal of consolidating audit-user serialization logic.


9-9: The UserSpec import is actually in use.

It's referenced as a type hint for the created_by and updated_by attributes (lines 48-49), so keeping it around makes sense.

Likely an incorrect or invalid review comment.

care/emr/resources/scheduling/resource/spec.py (1)

14-14: @cacheable decorator is present on FacilityLocationListSpec — no issues here.

The decorator exists, so model_from_cache will work as intended.

care/emr/resources/allergy_intolerance/spec.py (1)

136-136: LGTM!

Clean refactor to the centralized audit user serialization helper. The field defaults (created_by: dict = {}, updated_by: dict = {}) at lines 126-127 ensure safe handling when IDs are null.

care/emr/resources/notes/thread_spec.py (1)

47-47: LGTM!

Consistent with the centralized audit serialization pattern. Field defaults at lines 39-40 handle the null case appropriately.

care/emr/resources/valueset/spec.py (1)

69-69: LGTM!

Centralized audit serialization applied correctly. Field defaults at lines 63-64 ensure graceful handling of null audit user IDs.

care/emr/resources/questionnaire/spec.py (1)

293-293: LGTM!

Audit serialization properly centralized. Defaults are in place at lines 282-283, so this should work seamlessly.

care/emr/resources/condition/spec.py (1)

128-134: LGTM!

Nicely consolidated audit field serialization using the centralized helper. Clean and consistent with the broader refactoring approach.

care/emr/resources/notes/notes_spec.py (1)

48-51: LGTM!

The refactor to serialize_audit_users keeps things consistent across the codebase.

care/emr/resources/observation/spec.py (1)

7-7: LGTM!

Import addition required for the caching functionality.

care/emr/resources/service_request/spec.py (1)

169-173: LGTM!

The switch to model_from_cache aligns with the caching strategy. The batch query ensures iteration over valid locations, and the cache will help on subsequent requests. Just something to keep in mind: if the cache is cold, each model_from_cache call may hit the database again—but that's the nature of caching warm-up.

care/emr/resources/activity_definition/spec.py (1)

15-15: LGTM!

Import addition required for the caching functionality.

care/emr/resources/medication/administration/spec.py (1)

194-194: LGTM!

The refactor to use cls.serialize_audit_users(mapping, obj) properly centralizes audit field serialization, which is consistent with the pattern being applied across other specs in this PR.

care/emr/resources/inventory/inventory_item/spec.py (1)

43-45: LGTM!

The change to use model_from_cache for location serialization is consistent with the broader caching strategy in this PR.

care/emr/resources/inventory/supply_request/request_order.py (1)

108-121: LGTM!

The serialization logic correctly applies the model_from_cache pattern for supplier, origin, and destination. The conditional checks for optional fields are properly preserved.

care/emr/resources/tag/config_spec.py (1)

180-187: LGTM!

The cache-backed serialization for facility_organization and organization is correctly implemented with appropriate conditional checks for optional fields.

care/emr/resources/medication/dispense/spec.py (1)

215-217: Type annotation doesn't match model_from_cache return type, but practically safe.

The location field is dict (required), while model_from_cache returns dict | None. Since obj.location is a valid foreign key reference that must exist, this won't fail at runtime—though Python's type checker might complain. FacilityLocationListSpec is correctly decorated with @cacheable, so the call is properly validated at runtime.

care/emr/resources/encounter/spec.py (2)

167-170: LGTM — null guard is in place.

The if obj.current_location check at line 167 ensures model_from_cache is only called when a location exists. The change correctly aligns with the caching pattern used elsewhere in this PR.


161-165: The @cacheable decorator is already present on FacilityOrganizationReadSpec.

That said, model_from_cache can still return None when an object isn't found (with quiet=True as default). This means the organizations list could contain None values if an organization is deleted between fetching EncounterOrganization records and the cache lookup—which might not be what you want.

care/emr/resources/payment_reconciliation/spec.py (1)

148-152: LGTM — properly guarded cache lookup.

The null check at line 148 ensures model_from_cache is only invoked when obj.location exists. The change is consistent with the broader caching pattern introduced in this PR.

care/emr/resources/location/spec.py (3)

115-133: Good addition of @cacheable decorator.

The FacilityLocationListSpec is now properly marked as cacheable, which enables the model_from_cache lookups used throughout this PR. The implementation looks correct.

One thing to note: perform_extra_serialization calls obj.get_parent_json() (line 128), which itself now uses model_from_cache(FacilityLocationListSpec, ...) internally. This creates a recursive caching pattern where serializing a location will cache its parent chain. Just something to be aware of for debugging cache-related issues.


140-142: LGTM — cleaner audit serialization.

Using the shared serialize_audit_users helper is more maintainable than manually handling created_by and updated_by fields.


190-195: LGTM — location is a required FK.

Based on the model definition in care/emr/models/location.py (line 153), location is a non-nullable ForeignKey, so the direct access to obj.location.id is safe here.

care/emr/resources/organization/spec.py (2)

53-64: LGTM!

The @cacheable decorator is correctly applied, and the new fields (level_cache, system_generated, has_children, parent) appropriately expand the spec for cache-backed retrieval.


69-74: Nice centralization of audit user serialization.

Using serialize_audit_users instead of inline serialization is a good pattern that keeps things DRY across the codebase.

care/emr/api/viewsets/questionnaire.py (2)

191-194: LGTM!

The switch to model_from_cache for organization serialization is clean and consistent with the caching strategy. OrganizationReadSpec is properly decorated with @cacheable as verified in the organization spec file.


250-255: Consistent caching pattern applied.

Same pattern as get_organizations — well done keeping it uniform.

care/emr/resources/user/spec.py (1)

154-158: LGTM!

Cache-backed retrieval for geo_organization is correctly implemented.

care/emr/resources/healthcare_service/spec.py (2)

81-84: Caching pattern is correctly applied.

FacilityOrganizationReadSpec properly has the @cacheable decorator, so the model_from_cache() usage here is solid.


74-79: No issues here. Both FacilityLocationListSpec and FacilityOrganizationReadSpec are properly decorated with @cacheable, so the model_from_cache calls will work as intended.

Likely an incorrect or invalid review comment.

care/emr/resources/file_upload/spec.py (2)

9-9: LGTM!

Import addition aligns with the caching pattern being adopted across the codebase.


95-98: LGTM!

Nice use of _id fields to avoid unnecessary database queries when fetching cached user data. The conditional checks ensure no cache lookup is attempted for null values.

care/emr/resources/patient/spec.py (2)

271-271: LGTM!

Good use of the centralized serialize_audit_users helper - keeps things DRY and consistent across specs.


263-264: Remove the suggestion to move imports to module level.

The local imports of model_from_cache and OrganizationReadSpec at lines 263-264 are intentional. While facility/spec.py imports these at module level, patient/spec.py deliberately keeps them local—likely to avoid circular dependencies. This is a recognized pattern in this codebase, where local imports inside functions are acceptable when module-level imports would create circular references.

Likely an incorrect or invalid review comment.

care/emr/resources/facility/spec.py (1)

10-10: LGTM!

Clean import at module level - this is the pattern other files should follow.

care/emr/api/viewsets/patient.py (2)

17-17: LGTM!

Import at module level, as it should be.


302-302: LGTM!

Correct usage here - user is already fetched and .id gives the proper primary key value.

care/emr/resources/inventory/supply_delivery/delivery_order.py (1)

10-10: LGTM!

Module-level import is correct.

care/emr/resources/facility_organization/spec.py (3)

6-6: LGTM on the import addition.

The cacheable decorator import aligns with the caching strategy being introduced.


74-83: LGTM on the @cacheable decorator and new fields.

The @cacheable decorator is correctly applied, enabling cache invalidation via post_save signals. The new fields (created_by, updated_by, system_generated, level_cache, has_children) are well-typed and have sensible defaults.


85-89: Nice consolidation of audit user serialization. The delegation to cls.serialize_audit_users(mapping, obj) removes duplication and, as it turns out, does use model_from_cache for consistent cache-backed user serialization, so that concern has been handily addressed.

care/emr/api/viewsets/location.py (3)

19-19: LGTM on the import.

The model_from_cache import is necessary for the cache-backed serialization changes below.


183-189: LGTM on switching to cache-backed serialization.

The change from FacilityOrganizationReadSpec.serialize(...).to_json() to model_from_cache(FacilityOrganizationReadSpec, id=...) is consistent with the PR's caching strategy. Since model_from_cache already returns a dict, the response structure remains compatible.


223-225: LGTM on the organizations_add response.

Consistent with the other cache-backed changes. The response now benefits from caching.

care/emr/api/viewsets/encounter.py (3)

34-34: LGTM on the import.

Adding model_from_cache for the cache-backed organization serialization.


259-265: LGTM on the organizations endpoint change.

Mirrors the pattern used in location.py. The cache-backed approach is consistent across both viewsets, which is... thoughtful.


292-294: LGTM on the organizations_add response.

Consistent cache-backed serialization for the response.

care/emr/resources/device/spec.py (4)

8-8: LGTM on the import.

Required for the cache-backed serialization changes below.


112-115: LGTM on the managing_organization caching.

FacilityOrganizationReadSpec is confirmed cacheable from the changes in this PR.


130-137: Same verification needed for FacilityLocationListSpec.

This usage also depends on FacilityLocationListSpec being cacheable. The verification script above should confirm this.


99-106: FacilityLocationListSpec is properly decorated with @cacheable.

The decorator is in place in care/emr/resources/location/spec.py, so the model_from_cache call is safe from runtime errors. No action needed.

care/emr/resources/resource_request/spec.py (4)

9-9: LGTM on the import.

The model_from_cache import aligns with the caching refactor objective.


153-153: LGTM on centralized audit serialization.

Using serialize_audit_users for the audit fields is consistent with the refactoring goal and the spec properly declares both created_by and updated_by fields.


150-153: No action needed. UserSpec is already decorated with @cacheable(use_base_manager=True) at line 123 of the same file.

Likely an incorrect or invalid review comment.


167-173: The spec's updated_by exclusion is intentional, not an oversight.

ResourceRequestComment inherits both created_by and updated_by fields from EMRBaseModel, so serialize_audit_users won't raise an error when it checks for both. However, since ResourceRequestCommentListSpec only declares created_by (line 168), the serializer will set updated_by in the mapping dict, and Pydantic will silently ignore it. If this is intentional — that is, you want only audit creation data exposed in list responses — it's fine as-is, but consider adding a comment to clarify the design choice.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@care/emr/api/viewsets/questionnaire.py`:
- Around line 190-194: The list comprehension building organizations_serialized
can include None because model_from_cache(OrganizationReadSpec, id=org_id)
returns None when not found; filter out None values (or skip cache misses) so
the response does not contain null entries. Update the comprehension that
iterates over questionnaire_organizations to only include non-None results from
model_from_cache (or add a conditional guard) and ensure
organizations_serialized contains only valid OrganizationReadSpec instances
before returning or serializing.
- Around line 250-255: The list comprehension building organizations_serialized
can include None entries from model_from_cache; update the comprehension that
calls model_from_cache(OrganizationReadSpec, id=org_id) for each org_id from
QuestionnaireOrganization.objects.filter(questionnaire=questionnaire).values_list("organization_id",
flat=True) to filter out None results (e.g., by adding a conditional in the
comprehension or wrapping with a filter) so organizations_serialized contains
only non-None OrganizationReadSpec instances.

In `@care/emr/resources/scheduling/resource/spec.py`:
- Line 14: serialize_resource can return None via model_from_cache(…,
quiet=True), but TokenBookingMinimumReadSpec and TokenRetrieveSpec declare their
resource field as dict (or dict = {}) and assign mapping["resource"] = None when
lookups fail; update the type to allow None (dict | None or Optional[dict]) on
the resource field in both specs (TokenBookingMinimumReadSpec,
TokenRetrieveSpec) and/or add an explicit None-check in their
perform_extra_serialization methods to convert a missing resource to an
acceptable value (e.g., None or an empty dict) so the assigned
mapping["resource"] matches the field type.

In `@care/emr/resources/user/spec.py`:
- Around line 196-199: mapping["organizations"] is being populated by calling
model_from_cache(OrganizationReadSpec, id=org_id) for each org_id but
model_from_cache can return None; update the construction to exclude None
results (e.g., only include items where model_from_cache(...) is not None) so
mapping["organizations"] contains only valid OrganizationReadSpec instances;
locate the list comprehension that builds mapping["organizations"] and add a
conditional filter to skip None values or post-filter the list.
🧹 Nitpick comments (1)
care/emr/resources/scheduling/resource/spec.py (1)

8-15: Uneven application of caching in serialization branches.

Lines 10 and 14 now use model_from_cache(), while line 12 still uses HealthcareServiceReadSpec.serialize(obj.healthcare_service).to_json(). All three Spec classes follow the same pattern with perform_extra_serialization() methods, so one approach should work across all three—unless there's something special about HealthcareServiceReadSpec that requires direct serialization.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e40804 and b571c98.

📒 Files selected for processing (6)
  • care/emr/api/viewsets/questionnaire.py
  • care/emr/resources/file_upload/spec.py
  • care/emr/resources/resource_request/spec.py
  • care/emr/resources/scheduling/resource/spec.py
  • care/emr/resources/service_request/spec.py
  • care/emr/resources/user/spec.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • care/emr/resources/file_upload/spec.py
  • care/emr/resources/resource_request/spec.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursorrules)

**/*.py: Prioritize readability and maintainability; follow Django's coding style guide (PEP 8 compliance).
Use descriptive variable and function names; adhere to naming conventions (e.g., lowercase with underscores for functions and variables).

Files:

  • care/emr/api/viewsets/questionnaire.py
  • care/emr/resources/scheduling/resource/spec.py
  • care/emr/resources/service_request/spec.py
  • care/emr/resources/user/spec.py
🧠 Learnings (2)
📚 Learning: 2024-11-28T06:16:31.373Z
Learnt from: DraKen0009
Repo: ohcnetwork/care PR: 2620
File: care/facility/models/facility.py:306-311
Timestamp: 2024-11-28T06:16:31.373Z
Learning: In the 'care' project, moving imports of models like `Asset`, `AssetLocation`, `FacilityDefaultAssetLocation`, and `PatientSample` to the module level in `care/facility/models/facility.py` causes circular imports. Therefore, it's acceptable to keep these imports inside the `delete` method of the `Facility` class.

Applied to files:

  • care/emr/resources/scheduling/resource/spec.py
📚 Learning: 2025-07-15T13:39:29.540Z
Learnt from: NikhilA8606
Repo: ohcnetwork/care PR: 3148
File: care/emr/api/viewsets/consent.py:22-24
Timestamp: 2025-07-15T13:39:29.540Z
Learning: In the care codebase, BaseModel (in care/utils/models/base.py) contains an external_id field which is inherited by all EMR models including Encounter. This allows filtering using field paths like `encounter__external_id` in Django filters.

Applied to files:

  • care/emr/resources/service_request/spec.py
🧬 Code graph analysis (4)
care/emr/api/viewsets/questionnaire.py (3)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/organization/spec.py (1)
  • OrganizationReadSpec (54-63)
care/emr/models/questionnaire.py (1)
  • QuestionnaireOrganization (134-153)
care/emr/resources/scheduling/resource/spec.py (2)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/service_request/spec.py (2)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/user/spec.py (3)
care/emr/resources/base.py (1)
  • model_from_cache (232-275)
care/emr/resources/organization/spec.py (1)
  • OrganizationReadSpec (54-63)
care/emr/models/organization.py (2)
  • Organization (106-107)
  • OrganizationUser (110-121)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test / Test
🔇 Additional comments (5)
care/emr/api/viewsets/questionnaire.py (1)

19-21: LGTM on the import.

The import for model_from_cache alongside OrganizationReadSpec is appropriate for the caching pattern being introduced.

care/emr/resources/user/spec.py (3)

11-11: LGTM on the import update.

Adding model_from_cache to the existing imports from care.emr.resources.base is clean and follows the existing import pattern.


154-158: Trade-off acknowledged - caching benefits outweigh minor inefficiency.

Since obj.geo_organization is already loaded at this point, calling model_from_cache with just the ID means potentially re-fetching what we already have. That said, the cache population for subsequent requests and codebase consistency make this a reasonable trade-off. Just something to keep in mind.


184-199: The superuser vs non-superuser organization retrieval logic looks correct.

The refactoring to fetch IDs first via values_list and then use model_from_cache is a sensible approach that aligns with the caching pattern being introduced across the codebase. The conditional logic is preserved correctly.

care/emr/resources/service_request/spec.py (1)

168-172: ID mismatch claim is incorrect; however, None entries and N+1 concern remain valid.

ServiceRequest.locations stores ArrayField(IntegerField) — plain DB PKs, not UUIDs — so model_from_cache(..., id=location_id) at line 170 is actually the right call. The type hint list[UUID4] in ServiceRequestWriteSpec is misleading but unrelated.

That said, two real issues remain:

  1. If a location isn't found in cache or DB, model_from_cache returns None (with default quiet=True), which gets appended directly, leaving None entries in the list (breaking the type contract list[dict]).
  2. On cold cache, this becomes one DB query per location instead of a single id__in bulk fetch.
Corrected fix (use the right logic + skip missing items)
-        locations = []
-        for location_id in obj.locations:
-            locations.append(model_from_cache(FacilityLocationListSpec, id=location_id))
+        locations: list[dict] = []
+        for location_id in obj.locations:
+            location = model_from_cache(
+                FacilityLocationListSpec, id=location_id
+            )
+            if location is not None:
+                locations.append(location)

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@care/emr/resources/device/history_spec.py`:
- Around line 41-47: The loop over obj.edit_history may assign None when
model_from_cache(UserSpec, id=user) misses; update the code in the block that
handles history entries (the for loop iterating obj.edit_history and setting
history["updated_by"]) to capture the return value of model_from_cache and, if
it is None, replace it with an empty object (e.g., {}) so updated_by always has
an object shape; keep using UserSpec and preserve the existing edit_history
accumulation (edit_history.append(history)).
♻️ Duplicate comments (1)
care/emr/resources/medication/dispense/dispense_order.py (1)

54-67: Don’t assign a possibly-None cache lookup into a required dict field (and don’t silently omit location).

model_from_cache(...) returns dict | None. Right now location is typed as a required dict, but this code can still end up setting mapping["location"] = None (or leaving it unset), which is a leaky contract for downstream consumers and may produce odd model_construct states.

If location is truly required, make the failure loud; if it’s optional, type it as optional and set None intentionally.

Proposed fix (required location → fail loudly)
 class MedicationDispenseOrderReadSpec(BaseMedicationDispenseOrderSpec):
     patient: dict
     location: dict
@@
     def perform_extra_serialization(cls, mapping, obj):
         mapping["id"] = obj.external_id
         mapping["patient"] = PatientListSpec.serialize(obj.patient).to_json()
-        if obj.location_id:
-            mapping["location"] = model_from_cache(
-                FacilityLocationListSpec, id=obj.location_id
-            )
+        if obj.location_id:
+            mapping["location"] = model_from_cache(
+                FacilityLocationListSpec, id=obj.location_id, quiet=False
+            )
Alternative (optional location → make it explicit)
 class MedicationDispenseOrderReadSpec(BaseMedicationDispenseOrderSpec):
     patient: dict
-    location: dict
+    location: dict | None = None
@@
         if obj.location_id:
-            mapping["location"] = model_from_cache(
-                FacilityLocationListSpec, id=obj.location_id
-            )
+            mapping["location"] = model_from_cache(
+                FacilityLocationListSpec, id=obj.location_id
+            )

Verification script (confirm nullability + expectations):

#!/bin/bash
set -euo pipefail

# 1) Is DispenseOrder.location nullable / blank?
rg -n "class DispenseOrder\b" -n care/emr/models/medication_dispense.py
rg -n "location\s*=\s*models\.(ForeignKey|OneToOneField)\(" -n care/emr/models/medication_dispense.py -n -C2

# 2) Any callers/tests expecting location always present in read payload?
rg -nP --type py "\bMedicationDispenseOrderReadSpec\b|\bdispense order\b" -C2
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b571c98 and 1e68dbe.

📒 Files selected for processing (4)
  • care/emr/api/viewsets/patient.py
  • care/emr/resources/device/history_spec.py
  • care/emr/resources/medication/dispense/dispense_order.py
  • care/emr/resources/observation/spec.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • care/emr/api/viewsets/patient.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursorrules)

**/*.py: Prioritize readability and maintainability; follow Django's coding style guide (PEP 8 compliance).
Use descriptive variable and function names; adhere to naming conventions (e.g., lowercase with underscores for functions and variables).

Files:

  • care/emr/resources/medication/dispense/dispense_order.py
  • care/emr/resources/observation/spec.py
  • care/emr/resources/device/history_spec.py
🧠 Learnings (3)
📚 Learning: 2024-11-28T06:16:31.373Z
Learnt from: DraKen0009
Repo: ohcnetwork/care PR: 2620
File: care/facility/models/facility.py:306-311
Timestamp: 2024-11-28T06:16:31.373Z
Learning: In the 'care' project, moving imports of models like `Asset`, `AssetLocation`, `FacilityDefaultAssetLocation`, and `PatientSample` to the module level in `care/facility/models/facility.py` causes circular imports. Therefore, it's acceptable to keep these imports inside the `delete` method of the `Facility` class.

Applied to files:

  • care/emr/resources/medication/dispense/dispense_order.py
  • care/emr/resources/device/history_spec.py
📚 Learning: 2025-07-08T13:27:05.363Z
Learnt from: NikhilA8606
Repo: ohcnetwork/care PR: 3124
File: care/emr/resources/medication/administration/spec.py:163-164
Timestamp: 2025-07-08T13:27:05.363Z
Learning: In the care codebase, all EMR models inherit from EMRBaseModel which inherits from BaseModel (in care/utils/models/base.py). BaseModel provides common fields including created_date and modified_date with auto_now_add=True and auto_now=True respectively. These fields are available on all EMR models through inheritance.

Applied to files:

  • care/emr/resources/observation/spec.py
  • care/emr/resources/device/history_spec.py
📚 Learning: 2025-07-15T13:39:29.540Z
Learnt from: NikhilA8606
Repo: ohcnetwork/care PR: 3148
File: care/emr/api/viewsets/consent.py:22-24
Timestamp: 2025-07-15T13:39:29.540Z
Learning: In the care codebase, BaseModel (in care/utils/models/base.py) contains an external_id field which is inherited by all EMR models including Encounter. This allows filtering using field paths like `encounter__external_id` in Django filters.

Applied to files:

  • care/emr/resources/observation/spec.py
🧬 Code graph analysis (3)
care/emr/resources/medication/dispense/dispense_order.py (2)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/location/spec.py (1)
  • FacilityLocationListSpec (116-132)
care/emr/resources/observation/spec.py (2)
care/emr/resources/base.py (3)
  • EMRResource (13-168)
  • model_from_cache (232-275)
  • serialize_audit_users (162-168)
care/emr/resources/user/spec.py (1)
  • UserSpec (124-138)
care/emr/resources/device/history_spec.py (2)
care/emr/resources/base.py (2)
  • EMRResource (13-168)
  • model_from_cache (232-275)
care/emr/resources/user/spec.py (1)
  • UserSpec (124-138)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test / Test
🔇 Additional comments (4)
care/emr/resources/device/history_spec.py (1)

6-6: Import change looks fine.

Pulling model_from_cache in alongside EMRResource is consistent with the direction of the PR.

care/emr/resources/medication/dispense/dispense_order.py (1)

9-9: Importing model_from_cache is fine (and at least it’s actually used).

care/emr/resources/observation/spec.py (2)

6-8: Import change looks fine; just keep it consistent.

Pulling in model_from_cache here is reasonable given the new serialization path; nothing exciting, but it does the job.


133-137: The soft-deleted user concern is already mitigated—UserSpec uses @cacheable(use_base_manager=True).

The potential issue you raised about model_from_cache() silently filtering deleted users is valid in principle: CustomUserManager filters deleted=False in its get_queryset(), and without the base-manager bypass, cache lookups would miss soft-deleted users. However, UserSpec at line 123 is already decorated with @cacheable(use_base_manager=True), which sets __cacheable_use_base_manager__ and ensures model_from_cache() uses the unfiltered _base_manager. No changes needed.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@codecov
Copy link

codecov bot commented Jan 14, 2026

Codecov Report

❌ Patch coverage is 73.86364% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.74%. Comparing base (2823dd5) to head (f3b4eb7).

Files with missing lines Patch % Lines
care/emr/resources/observation/spec.py 25.00% 3 Missing ⚠️
care/emr/resources/device/history_spec.py 50.00% 1 Missing and 1 partial ⚠️
care/emr/resources/facility/spec.py 33.33% 2 Missing ⚠️
care/emr/resources/file_upload/spec.py 71.42% 1 Missing and 1 partial ⚠️
...esources/inventory/supply_request/request_order.py 75.00% 0 Missing and 2 partials ⚠️
care/emr/resources/resource_request/spec.py 60.00% 1 Missing and 1 partial ⚠️
care/emr/resources/scheduling/resource/spec.py 33.33% 2 Missing ⚠️
care/emr/api/viewsets/patient.py 50.00% 1 Missing ⚠️
care/emr/resources/device/spec.py 50.00% 1 Missing ⚠️
care/emr/resources/healthcare_service/spec.py 75.00% 1 Missing ⚠️
... and 5 more
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3461      +/-   ##
===========================================
+ Coverage    74.67%   74.74%   +0.06%     
===========================================
  Files          471      471              
  Lines        21507    21478      -29     
  Branches      2225     2198      -27     
===========================================
- Hits         16061    16053       -8     
+ Misses        4955     4945      -10     
+ Partials       491      480      -11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

# Conflicts:
#	care/emr/resources/medication/dispense/dispense_order.py
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@care/emr/resources/user/spec.py`:
- Around line 193-197: The organization_ids list can contain duplicates when a
user has multiple OrganizationUser rows for the same org; update the query that
builds organization_ids (the OrganizationUser.objects.filter(user=obj) call) to
deduplicate results by applying distinct() at the ORM level (e.g., call
.distinct() before .values_list(...)) or by converting to a set after fetching,
so organization_ids contains unique organization IDs only.
♻️ Duplicate comments (1)
care/emr/resources/user/spec.py (1)

198-201: Filter out None from cache lookups.
model_from_cache can return None, and the list comprehension will happily include it.

🔧 Suggested fix
-        mapping["organizations"] = [
-            model_from_cache(OrganizationReadSpec, id=org_id)
-            for org_id in organization_ids
-        ]
+        mapping["organizations"] = [
+            org
+            for org_id in organization_ids
+            if (org := model_from_cache(OrganizationReadSpec, id=org_id)) is not None
+        ]
🧹 Nitpick comments (2)
care/emr/api/viewsets/patient.py (1)

288-291: Consider filtering out potential None values from the result list.

The model_from_cache function returns None when the object isn't found (with quiet=True default). If a PatientUser record exists but its associated User has been deleted or is otherwise missing, this list comprehension would include None values in the response, which might not be the intended behavior for clients.

♻️ Suggested improvement
         data = [
-            model_from_cache(UserSpec, id=patient_user.user_id)
+            user_data
             for patient_user in patient_users
+            if (user_data := model_from_cache(UserSpec, id=patient_user.user_id)) is not None
         ]
care/emr/api/viewsets/scheduling/booking.py (1)

278-285: Same None filtering consideration as in the patient viewset.

While the queryset already filters for user__deleted=False (which helpfully reduces the risk), there's still a theoretical window where a user could be deleted between query execution and cache lookup. For consistency with defensive practices, you might want to filter out None values here as well.

♻️ Suggested improvement
         return Response(
             {
                 "users": [
-                    model_from_cache(UserSpec, id=user_resource.user_id)
+                    user_data
                     for user_resource in user_resources
+                    if (user_data := model_from_cache(UserSpec, id=user_resource.user_id)) is not None
                 ]
             }
         )

Comment on lines +193 to 197
organization_ids = list(
OrganizationUser.objects.filter(user=obj).values_list(
"organization_id", flat=True
)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Deduplicate org IDs to avoid repeated organizations.
If a user has multiple OrganizationUser rows for the same org, the response will now include duplicates (the old id__in path implicitly deduped).

🔧 Proposed fix
-            organization_ids = list(
-                OrganizationUser.objects.filter(user=obj).values_list(
-                    "organization_id", flat=True
-                )
-            )
+            organization_ids = list(
+                OrganizationUser.objects.filter(user=obj)
+                .values_list("organization_id", flat=True)
+                .distinct()
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
organization_ids = list(
OrganizationUser.objects.filter(user=obj).values_list(
"organization_id", flat=True
)
)
organization_ids = list(
OrganizationUser.objects.filter(user=obj)
.values_list("organization_id", flat=True)
.distinct()
)
🤖 Prompt for AI Agents
In `@care/emr/resources/user/spec.py` around lines 193 - 197, The organization_ids
list can contain duplicates when a user has multiple OrganizationUser rows for
the same org; update the query that builds organization_ids (the
OrganizationUser.objects.filter(user=obj) call) to deduplicate results by
applying distinct() at the ORM level (e.g., call .distinct() before
.values_list(...)) or by converting to a set after fetching, so organization_ids
contains unique organization IDs only.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@care/emr/resources/medication/dispense/dispense_order.py`:
- Around line 63-64: Replace the redundant query
FacilityLocation.objects.get(id=obj.location_id) with the relation accessor
obj.location and serialize via
FacilityLocationListSpec.serialize(obj.location).to_json(); also ensure the
viewset that returns these DispenseOrder objects uses select_related("location")
in its get_queryset() to avoid N+1 queries so both implicit access and
query-time optimization are addressed (also update the second occurrence around
the other mapping lines 78–79 similarly).

In `@care/emr/resources/service_request/spec.py`:
- Around line 169-173: Replace the per-id queries with a single bulk fetch:
query FacilityLocation.objects.filter(id__in=obj.locations) once, build a dict
mapping id -> FacilityLocation instance, then iterate the original obj.locations
list to preserve order and append
FacilityLocationListSpec.serialize(location).to_json() only when the id exists
in the dict; update the code around the locations list creation (the loop over
obj.locations and the FacilityLocation.objects.filter call) to use this
approach.
🧹 Nitpick comments (2)
care/emr/resources/inventory/supply_request/request_order.py (1)

114-121: Prefer using existing FK objects to avoid extra queries + possible DoesNotExist.
Right now you always hit the DB and can 500 on a dangling FK. Using the related objects keeps it cheap and safer if the relation is already loaded (or even missing).

♻️ Proposed tweak
-        if obj.origin_id:
-            origin = FacilityLocation.objects.get(id=obj.origin_id)
-            mapping["origin"] = FacilityLocationListSpec.serialize(origin).to_json()
-        if obj.destination_id:
-            destination = FacilityLocation.objects.get(id=obj.destination_id)
-            mapping["destination"] = FacilityLocationListSpec.serialize(
-                destination
-            ).to_json()
+        if obj.origin_id and obj.origin:
+            mapping["origin"] = FacilityLocationListSpec.serialize(obj.origin).to_json()
+        if obj.destination_id and obj.destination:
+            mapping["destination"] = FacilityLocationListSpec.serialize(
+                obj.destination
+            ).to_json()
care/emr/resources/healthcare_service/spec.py (1)

76-82: Overly broad exception handling silently swallows errors.

The except Exception: pass catches and ignores all exceptions, including unexpected ones that might indicate actual bugs or data corruption. If a location ID exists in obj.locations but can't be fetched, it would be helpful to at least know about it.

Consider catching only FacilityLocation.DoesNotExist or adding minimal logging so debugging doesn't become a treasure hunt later.

♻️ Suggested improvement
             try:
                 location_obj = FacilityLocation.objects.get(id=location)
                 locations.append(
                     FacilityLocationListSpec.serialize(location_obj).to_json()
                 )
-            except Exception:  # noqa S110
+            except FacilityLocation.DoesNotExist:  # noqa S110
                 pass

Comment on lines +63 to +64
location = FacilityLocation.objects.get(id=obj.location_id)
mapping["location"] = FacilityLocationListSpec.serialize(location).to_json()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, examine the file around lines 63-64 to understand context
head -n 90 care/emr/resources/medication/dispense/dispense_order.py | tail -n 35

Repository: ohcnetwork/care

Length of output: 1364


🏁 Script executed:

# Search for select_related("location") usage in the codebase
rg -n --type py 'select_related\([^)]*location' care/

Repository: ohcnetwork/care

Length of output: 134


🏁 Script executed:

# Look for the DispenseOrder model and related queryset definitions
rg -n --type py 'class.*DispenseOrder|select_related|prefetch_related' care/emr/resources/medication/dispense/ -A 3

Repository: ohcnetwork/care

Length of output: 2547


🏁 Script executed:

# Check if there's a related_name or ForeignKey definition for location in the model
find care -name '*.py' -type f | xargs grep -l 'class.*DispenseOrder\|class.*Dispense' | head -5

Repository: ohcnetwork/care

Length of output: 296


🏁 Script executed:

# Look for the model definition that contains location_id
rg -n --type py 'location_id|location.*ForeignKey' care/emr/ -B 2 -A 2 | head -50

Repository: ohcnetwork/care

Length of output: 4242


🏁 Script executed:

# Look for the DispenseOrder model definition to understand the location relationship
rg -n --type py 'class DispenseOrder' care/emr/models/ -A 20

Repository: ohcnetwork/care

Length of output: 1587


🏁 Script executed:

# Check the viewset that uses these specs to see if select_related is used
rg -n --type py 'MedicationDispenseOrderReadSpec|MedicationDispenseOrderRetrieveSpec' care/emr/api/viewsets/ -B 5 -A 10

Repository: ohcnetwork/care

Length of output: 3226


🏁 Script executed:

# Check if obj.location is accessible (check if it's a ForeignKey relationship)
rg -n --type py 'location.*=.*ForeignKey\|location.*=.*models\.ForeignKey' care/emr/models/medication_dispense.py -B 1 -A 1

Repository: ohcnetwork/care

Length of output: 41


🏁 Script executed:

# Examine the full DispenseOrder model file to understand the relationships
cat care/emr/models/medication_dispense.py | head -100

Repository: ohcnetwork/care

Length of output: 2560


🏁 Script executed:

# Check if the viewset's get_queryset uses select_related or prefetch_related
rg -n --type py 'get_queryset|select_related|prefetch_related' care/emr/api/viewsets/inventory/dispense_order.py -A 10 -B 2

Repository: ohcnetwork/care

Length of output: 805


🏁 Script executed:

# Also check the base EMRBaseViewSet class to see if it implements queryset optimization
find care -name 'base.py' -path '*/viewsets/*' -type f | xargs grep -l 'EMRBaseViewSet' | head -1 | xargs cat | head -150

Repository: ohcnetwork/care

Length of output: 5982


🏁 Script executed:

# Get the full get_queryset method from the dispense_order viewset
sed -n '104,130p' care/emr/api/viewsets/inventory/dispense_order.py

Repository: ohcnetwork/care

Length of output: 1278


🏁 Script executed:

# Check if EMRBaseViewSet has a get_queryset that adds select_related
rg -n --type py 'class EMRBaseViewSet' care/emr/api/viewsets/ -A 50 | grep -E 'get_queryset|select_related|prefetch_related' -A 5 -B 5

Repository: ohcnetwork/care

Length of output: 1506


🏁 Script executed:

# Look for any parent class or mixin that might add select_related to DispenseOrder
rg -n --type py 'DispenseOrder' care/emr/api/viewsets/inventory/dispense_order.py -B 5 -A 30 | head -60

Repository: ohcnetwork/care

Length of output: 2399


Use the relation accessor instead of a redundant query.

The current code ignores Django's relation cache entirely, forcing a fresh query per row even though location is a ForeignKey on the model. Using obj.location is more idiomatic and respects the relation pattern.

That said, the underlying N+1 problem persists unless the viewset's queryset also adds select_related("location") at query time—otherwise you're still hitting the database, just implicitly via the relation accessor. Consider optimizing both the queryset and the serialization:

🛠️ Suggested changes

In care/emr/api/viewsets/inventory/dispense_order.py, add to get_queryset():

def get_queryset(self):
    facility = self.get_facility_obj()
-   queryset = super().get_queryset().filter(facility=facility)
+   queryset = super().get_queryset().select_related("location").filter(facility=facility)

In care/emr/resources/medication/dispense/dispense_order.py, lines 63–64 and 78–79:

-        location = FacilityLocation.objects.get(id=obj.location_id)
-        mapping["location"] = FacilityLocationListSpec.serialize(location).to_json()
+        mapping["location"] = FacilityLocationListSpec.serialize(obj.location).to_json()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
location = FacilityLocation.objects.get(id=obj.location_id)
mapping["location"] = FacilityLocationListSpec.serialize(location).to_json()
mapping["location"] = FacilityLocationListSpec.serialize(obj.location).to_json()
🤖 Prompt for AI Agents
In `@care/emr/resources/medication/dispense/dispense_order.py` around lines 63 -
64, Replace the redundant query FacilityLocation.objects.get(id=obj.location_id)
with the relation accessor obj.location and serialize via
FacilityLocationListSpec.serialize(obj.location).to_json(); also ensure the
viewset that returns these DispenseOrder objects uses select_related("location")
in its get_queryset() to avoid N+1 queries so both implicit access and
query-time optimization are addressed (also update the second occurrence around
the other mapping lines 78–79 similarly).

Comment on lines 169 to +173
locations = []
for location in FacilityLocation.objects.filter(id__in=obj.locations):
locations.append(FacilityLocationListSpec.serialize(location).to_json())
for location_id in obj.locations:
location = FacilityLocation.objects.filter(id=location_id).first()
if location:
locations.append(FacilityLocationListSpec.serialize(location).to_json())
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid N+1 queries when loading locations.

Right now each location_id triggers its own query, which is… not great on a hot read path. Please switch to a single bulk fetch and preserve order.

🔧 Suggested fix (bulk fetch + order preservation)
-        locations = []
-        for location_id in obj.locations:
-            location = FacilityLocation.objects.filter(id=location_id).first()
-            if location:
-                locations.append(FacilityLocationListSpec.serialize(location).to_json())
+        locations = []
+        if obj.locations:
+            location_map = {
+                loc.id: loc
+                for loc in FacilityLocation.objects.filter(id__in=obj.locations)
+            }
+            for location_id in obj.locations:
+                location = location_map.get(location_id)
+                if location:
+                    locations.append(
+                        FacilityLocationListSpec.serialize(location).to_json()
+                    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
locations = []
for location in FacilityLocation.objects.filter(id__in=obj.locations):
locations.append(FacilityLocationListSpec.serialize(location).to_json())
for location_id in obj.locations:
location = FacilityLocation.objects.filter(id=location_id).first()
if location:
locations.append(FacilityLocationListSpec.serialize(location).to_json())
locations = []
if obj.locations:
location_map = {
loc.id: loc
for loc in FacilityLocation.objects.filter(id__in=obj.locations)
}
for location_id in obj.locations:
location = location_map.get(location_id)
if location:
locations.append(
FacilityLocationListSpec.serialize(location).to_json()
)
🤖 Prompt for AI Agents
In `@care/emr/resources/service_request/spec.py` around lines 169 - 173, Replace
the per-id queries with a single bulk fetch: query
FacilityLocation.objects.filter(id__in=obj.locations) once, build a dict mapping
id -> FacilityLocation instance, then iterate the original obj.locations list to
preserve order and append FacilityLocationListSpec.serialize(location).to_json()
only when the id exists in the dict; update the code around the locations list
creation (the loop over obj.locations and the FacilityLocation.objects.filter
call) to use this approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adding model caching for User and tags for all the relavent specs

2 participants