Skip to content

Conversation

@praffq
Copy link
Contributor

@praffq praffq commented Jan 28, 2026

Proposed Changes

  • Fixing N+1 issues reported on Sentry
  • added caching for UserSpec and FacilityMinimalReadSpec in all the used places

Added caching for -

  • UserSpec
  • FacilityMinimalReadSpec

Associated Issue

Summary by CodeRabbit

  • Performance
    • Improved API efficiency by eagerly loading related records and using cached lookups for related entities, speeding queries and list/retrieve endpoints.
  • Refactor
    • Consolidated audit-field serialization into a shared path for consistent created_by/updated_by output across resources.
  • Behavior
    • Serialized facility and user references now use cached representations, reducing latency while preserving response structure.

✏️ 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 28, 2026 06:40
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

Eager-loaded related objects in several viewsets, centralized audit-user serialization into a shared helper, replaced direct object serialization with cache-backed model_from_cache lookups for facility and user fields, and adjusted patient identifier config retrieval to use cached model IDs. (50 words)

Changes

Cohort / File(s) Summary
Query optimizations — Viewsets
care/emr/api/viewsets/facility.py, care/emr/api/viewsets/patient.py, care/emr/api/viewsets/scheduling/booking.py
Added select_related(...) for geo_organization and user relationships to eagerly load related rows.
Audit serialization consolidation
care/emr/resources/allergy_intolerance/spec.py, care/emr/resources/condition/spec.py, care/emr/resources/facility_organization/spec.py, care/emr/resources/location/spec.py, care/emr/resources/meta_artifact/spec.py, care/emr/resources/patient/spec.py, care/emr/resources/questionnaire/spec.py, care/emr/resources/questionnaire_response/spec.py, care/emr/resources/valueset/spec.py
Replaced per-field created_by/updated_by serialization with cls.serialize_audit_users(mapping, obj).
Notes audit type changes
care/emr/resources/notes/notes_spec.py, care/emr/resources/notes/thread_spec.py
Changed created_by/updated_by annotations from UserSpec to dict and delegated audit serialization to cls.serialize_audit_users; removed UserSpec imports.
Cache-based facility serialization
care/emr/resources/encounter/spec.py, care/emr/resources/observation_definition/spec.py, care/emr/resources/report/template/spec.py, care/emr/resources/tag/config_spec.py, care/emr/resources/user/spec.py
Switched facility serialization to model_from_cache(FacilityBareMinimumSpec, id=...) instead of serializing in-memory facility objects.
Cache-based user serialization
care/emr/resources/device/history_spec.py, care/emr/resources/facility/spec.py, care/emr/resources/file_upload/spec.py, care/emr/resources/medication/administration/spec.py, care/emr/resources/resource_request/spec.py
Replaced direct User object serialization with model_from_cache(UserSpec, id=...) for audit/assigned/related user fields; adjusted imports accordingly.
Complex observation changes
care/emr/resources/observation/spec.py
Consolidated audit serialization via cls.serialize_audit_users() and added conditional cache-based population for data_entered_by using model_from_cache when data_entered_by_id exists.
Patient model config retrieval
care/emr/models/patient.py
get_config() now queries only the id from PatientIdentifierConfig and uses model_from_cache(...) to obtain the spec instead of serializing the ORM object.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers proposed changes, specifies which components are being cached, and links to associated issues. However, it omits the Merge Checklist section entirely, missing critical items like test updates and linting verification. Add the Merge Checklist section with checkboxes for tests, documentation, linting, and confirmation that the PR passes all required pipelines.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective of fixing N+1 query issues and adding model caching, which aligns with the changeset's focus across multiple files.

✏️ 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 add-model-cache-and-N+1-issues

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

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/models/patient.py`:
- Around line 193-198: The code calls .to_json() on the result of
model_from_cache in the block that populates cls.configs using
PatientIdentifierConfig and PatientIdentifierListSpec, but model_from_cache
already returns a dict causing an AttributeError; remove the .to_json() call and
store the dict directly into cls.configs[config_id] (keep the lookup using
PatientIdentifierConfig.objects.only("id").get(external_id=config_id) and the
model_from_cache(PatientIdentifierListSpec, id=config.id) call).

In `@care/emr/resources/resource_request/spec.py`:
- Around line 170-173: In perform_extra_serialization(cls, mapping, obj) add
mapping["id"] = str(obj.external_id) (when external_id is present) so the method
sets the serialized id for comment objects; locate perform_extra_serialization
in the ResourceRequestComment spec (same file) and mirror the pattern used in
ResourceRequestListSpec and ResourceRequestRetrieveSpec by converting
obj.external_id to a string and assigning it to mapping["id"] alongside the
existing mapping["created_by"] assignment.
🧹 Nitpick comments (4)
care/emr/resources/device/history_spec.py (1)

43-47: Consider handling the case where model_from_cache returns None.

The code handles the "no user ID" edge case nicely with an empty dict on line 46. However, model_from_cache returns None when the user ID exists but the user isn't found in the cache/database (which is the default quiet=True behavior). This would result in history["updated_by"] being None instead of {}, creating slightly inconsistent responses within the same edit_history list.

♻️ Suggested fix for consistent fallback
             user = history.get("updated_by")
             if user:
-                history["updated_by"] = model_from_cache(UserSpec, id=user)
+                history["updated_by"] = model_from_cache(UserSpec, id=user) or {}
             else:
                 history["updated_by"] = {}  # Edge Case
care/emr/resources/file_upload/spec.py (1)

115-116: Cache lookup for updated_by is fine, though updated_by isn't declared as a field.

This works because mapping is a dict that gets passed to model_construct, but I notice FileUploadRetrieveSpec doesn't declare an updated_by field like FileUploadListSpec declares uploaded_by: dict. This means the field won't be part of the Pydantic schema. If that's intentional, no problem — just thought you might want to know.

Consider adding the field declaration for consistency
 class FileUploadRetrieveSpec(FileUploadListSpec):
     signed_url: str | None = None
     read_signed_url: str | None = None
     internal_name: str  # Not sure if this needs to be returned
+    updated_by: dict | None = None
care/emr/resources/observation/spec.py (2)

121-123: Type annotations inconsistent with other files in this PR.

Other files (e.g., thread_spec.py, notes_spec.py) changed created_by and updated_by from UserSpec = {} to dict = {}. This file still uses UserSpec = {}. It would be wonderful if these were consistent across the codebase.

♻️ Suggested consistency fix
 class ObservationReadSpec(BaseObservationSpec):
-    created_by: UserSpec = {}
-    updated_by: UserSpec = {}
+    created_by: dict = {}
+    updated_by: dict = {}
     data_entered_by: UserSpec | None = None

And while we're at it, data_entered_by should probably be dict | None = None as well.


133-139: Local import could be moved to the top.

The model_from_cache import on line 135 is done locally inside the method, but model_from_cache is already imported in other files at the module level without issues. Since UserSpec is already imported at the top of this file (line 19), there's no circular import concern here. Moving this import to line 7 alongside EMRResource would be cleaner.

That said, the cache-based lookup for data_entered_by is a solid improvement for avoiding N+1 queries.

♻️ Suggested import cleanup
-from care.emr.resources.base import EMRResource
+from care.emr.resources.base import EMRResource, model_from_cache

Then remove the local import on line 135-136.

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: 0

Caution

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

⚠️ Outside diff range comments (1)
care/emr/models/patient.py (1)

189-199: Return type annotation no longer matches the actual return value.

get_config now returns a cached dict from model_from_cache, but the annotation still says PatientIdentifierConfig. That mismatch will mislead callers and type checks. Please update the return type (and any caller assumptions) accordingly.

🔧 Suggested change
-    def get_config(cls, config_id) -> PatientIdentifierConfig:
+    def get_config(cls, config_id) -> dict[str, object]:

@codecov
Copy link

codecov bot commented Jan 28, 2026

Codecov Report

❌ Patch coverage is 59.32203% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.46%. Comparing base (df8d470) to head (51a88ec).

Files with missing lines Patch % Lines
care/emr/resources/observation/spec.py 0.00% 4 Missing ⚠️
care/emr/resources/resource_request/spec.py 42.85% 3 Missing and 1 partial ⚠️
care/emr/api/viewsets/patient.py 0.00% 3 Missing ⚠️
care/emr/models/patient.py 0.00% 2 Missing ⚠️
care/emr/resources/file_upload/spec.py 71.42% 1 Missing and 1 partial ⚠️
...re/emr/resources/medication/administration/spec.py 33.33% 2 Missing ⚠️
care/emr/resources/report/template/spec.py 33.33% 2 Missing ⚠️
care/emr/api/viewsets/facility.py 50.00% 1 Missing ⚠️
care/emr/resources/device/history_spec.py 66.66% 0 Missing and 1 partial ⚠️
care/emr/resources/facility/spec.py 0.00% 0 Missing and 1 partial ⚠️
... and 2 more
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3496      +/-   ##
===========================================
+ Coverage    74.43%   74.46%   +0.02%     
===========================================
  Files          471      471              
  Lines        21732    21690      -42     
  Branches      2264     2238      -26     
===========================================
- Hits         16176    16151      -25     
+ Misses        5055     5049       -6     
+ Partials       501      490      -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.

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.

N+1 issues Adding model caching for User and tags for all the relavent specs

2 participants