Skip to content

Conversation

@nandkishorr
Copy link
Contributor

@nandkishorr nandkishorr commented Dec 16, 2025

Proposed Changes

  • Added Data points For Patient Level Report

Patient details,active account,invoices,charge items,payment reconciliations

Architecture changes

  • Remove this section if not used

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

  • New Features
    • Added Patient Summary and Account Report generation capabilities with comprehensive data presentation
    • Account reports now display invoices, charge items, payment reconciliations, and billing details
    • Patient reports now include associated account information and financial summaries
    • Implemented access control for new patient and account report types

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

@nandkishorr nandkishorr self-assigned this Dec 16, 2025
@nandkishorr nandkishorr requested a review from a team as a code owner December 16, 2025 20:21
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 16, 2025

📝 Walkthrough

Walkthrough

This PR adds invoice filter cleanup, introduces authorization and context builder infrastructure for patient and account reports, registers two new report types (patient summary and account report), and implements comprehensive data point context builders for accounts, invoices, charge items, payment reconciliations, and monetary components.

Changes

Cohort / File(s) Summary
Invoice Filter Removal
care/emr/api/viewsets/invoice.py
Removed the encounter__external_id UUID filter from InvoiceFilters.
Authorization Authorizers
care/emr/reports/authorizers/patient.py, care/emr/reports/authorizers/account.py
Added two new authorizer classes: PatientReportAuthorizer (delegates to can_view_clinical_data and can_write_patient_obj) and AccountReportAuthorizer (delegates to can_read_account_in_facility and can_update_account_in_facility).
Authorization Exports
care/emr/reports/authorizers/__init__.py
Exported AccountReportAuthorizer and PatientReportAuthorizer; reformatted DischargeSummaryReportAuthorizer import.
Report Type Registry
care/emr/reports/report_types.py
Registered two new report types: patient_summary (Patient + PatientReportAuthorizer) and account_report (Account + AccountReportAuthorizer).
Context Builder Module Exports
care/emr/reports/context_builder/__init__.py
Added re-exports from patient and account data_points modules alongside existing encounter exports.
Account Data Points
care/emr/reports/context_builder/data_points/account.py
Introduced BaseAccountContextBuilder and AccountContextBuilder with 15 fields (name, status, billing_status, description, totals, components, invoices, charge items, payment reconciliations, dates), plus PatientAccountContextBuilder subclass.
Patient Data Points
care/emr/reports/context_builder/data_points/patient.py
Added PatientContextBuilderBase extending SingleObjectContextBuilder with name, gender, age, and accounts fields; registered with DataPointRegistry.
Invoice Data Points
care/emr/reports/context_builder/data_points/invoice.py
Introduced InvoiceReportFilter with status, title, and number filters; defined InvoiceContextBuilder and AccountInvoiceContextBuilder with six fields (title, status, number, total_net, total_gross, total_price_components).
Charge Item Data Points
care/emr/reports/context_builder/data_points/charge_items.py
Added ChargeItemReportFilter and ChargeItemContextBuilder with eight fields; defined AccountChargeItemContextBuilder filtering by account.
Payment Reconciliation Data Points
care/emr/reports/context_builder/data_points/payment_reconciliation.py
Introduced PaymentReconciliationReportFilter and PaymentReconciliationContextBuilder with nine fields (status, type, amount, reference number, kind, credit note flag, issuer type, outcome, method) and display mappings.
Monetary Component Data Points
care/emr/reports/context_builder/data_points/monetary_component.py
Added MonetaryComponentContextBuilder and UnitPriceMonetaryComponentContextBuilder with four fields (type, code, factor, amount) and type display mapping.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Authorization implementations (patient.py, account.py): Verify correct AuthorizationController action names and object retrieval logic
  • Data point context builders (multiple data_points/ files): Examine field definitions, filtering logic, parent_context relationships, and display mappings—especially cross-references between contexts (e.g., AccountContextBuilder→AccountInvoiceContextBuilder, charge items, payment reconciliations)
  • Report type registration (report_types.py): Confirm model imports and correct wiring to authorizer classes
  • Context builder re-exports (__init__.py): Validate that all necessary data point builders are properly exposed

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description covers proposed changes and mentions key data points added, but the merge checklist remains incomplete with unchecked boxes and no evidence of test coverage or documentation updates provided. Provide details on test cases added, documentation updates in /docs, and confirmation that linting passed before merging.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding data points for patient-level report builders, which aligns with the substantial new modules and context builders introduced.
✨ 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 datapoint/patient

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

🧹 Nitpick comments (2)
care/emr/reports/authorizers/patient.py (1)

8-8: Add type hints for the user parameter.

The user parameter lacks type annotations, which would improve code clarity and enable better IDE support.

If using Django's built-in User model:

+from django.contrib.auth.models import User
+
 class PatientReportAuthorizer(BaseReportAuthorizer):
-    def authorize_read(self, user, associating_id: str) -> bool:
+    def authorize_read(self, user: User, associating_id: str) -> bool:
         patient_obj = get_object_or_404(Patient, external_id=associating_id)
         return AuthorizationController.call("can_view_clinical_data", user, patient_obj)
 
-    def authorize_write(self, user, associating_id: str) -> bool:
+    def authorize_write(self, user: User, associating_id: str) -> bool:
         patient_obj = get_object_or_404(Patient, external_id=associating_id)
         return AuthorizationController.call("can_write_patient_obj", user, patient_obj)

Also applies to: 12-12

care/emr/reports/context_builder/data_points/payment_reconcilation.py (1)

56-58: Annotate the mutable class attribute with ClassVar.

The __filterset_backends__ class attribute should be annotated with typing.ClassVar to make it clear this is a class-level attribute and prevent potential mutation issues.

Based on static analysis tools.

Apply this diff:

+from typing import ClassVar
+
 class PaymentReconciliationContextBuilder(QuerysetContextBuilder):
     filterset_class = PaymentReconciliationReportFilter
-    __filterset_backends__ = [filters.DjangoFilterBackend]
+    __filterset_backends__: ClassVar = [filters.DjangoFilterBackend]
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f04effc and 79bc839.

📒 Files selected for processing (11)
  • care/emr/api/viewsets/invoice.py (0 hunks)
  • care/emr/reports/authorizers/__init__.py (1 hunks)
  • care/emr/reports/authorizers/discharge_summary.py (1 hunks)
  • care/emr/reports/authorizers/patient.py (1 hunks)
  • care/emr/reports/context_builder/__init__.py (1 hunks)
  • care/emr/reports/context_builder/data_points/account.py (1 hunks)
  • care/emr/reports/context_builder/data_points/charge_items.py (1 hunks)
  • care/emr/reports/context_builder/data_points/invoice.py (1 hunks)
  • care/emr/reports/context_builder/data_points/patient.py (1 hunks)
  • care/emr/reports/context_builder/data_points/payment_reconcilation.py (1 hunks)
  • care/emr/reports/report_types.py (2 hunks)
💤 Files with no reviewable changes (1)
  • care/emr/api/viewsets/invoice.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/reports/context_builder/__init__.py
  • care/emr/reports/authorizers/__init__.py
  • care/emr/reports/context_builder/data_points/patient.py
  • care/emr/reports/context_builder/data_points/payment_reconcilation.py
  • care/emr/reports/authorizers/patient.py
  • care/emr/reports/authorizers/discharge_summary.py
  • care/emr/reports/context_builder/data_points/invoice.py
  • care/emr/reports/context_builder/data_points/charge_items.py
  • care/emr/reports/report_types.py
  • care/emr/reports/context_builder/data_points/account.py
🧠 Learnings (1)
📚 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/reports/report_types.py
🧬 Code graph analysis (8)
care/emr/reports/authorizers/__init__.py (3)
care/emr/reports/authorizers/discharge_summary.py (2)
  • DischargeBillReportAuthorizer (9-10)
  • DischargeSummaryReportAuthorizer (5-6)
care/emr/reports/authorizers/encounter.py (1)
  • EncounterReportAuthorizer (7-18)
care/emr/reports/authorizers/utils.py (1)
  • report_authorizer (6-37)
care/emr/reports/context_builder/data_points/patient.py (4)
care/emr/models/patient.py (2)
  • age (77-81)
  • get_age (54-74)
care/emr/reports/context_builder/data_point_registry.py (1)
  • DataPointRegistry (1-31)
care/emr/reports/context_builder/data_points/account.py (1)
  • AccountContextBuilder (42-125)
care/emr/reports/context_builder/data_points/base.py (2)
  • Field (7-54)
  • SingleObjectContextBuilder (128-129)
care/emr/reports/context_builder/data_points/payment_reconcilation.py (5)
care/emr/models/payment_reconciliation.py (1)
  • PaymentReconciliation (6-29)
care/emr/reports/context_builder/data_points/base.py (2)
  • Field (7-54)
  • QuerysetContextBuilder (99-125)
care/emr/reports/context_builder/data_points/account.py (1)
  • get_context (123-125)
care/emr/reports/context_builder/data_points/charge_items.py (2)
  • get_context (77-78)
  • get_context (82-83)
care/emr/reports/context_builder/data_points/invoice.py (2)
  • get_context (57-58)
  • get_context (62-63)
care/emr/reports/authorizers/patient.py (3)
care/emr/reports/authorizers/base.py (1)
  • BaseReportAuthorizer (4-11)
care/security/authorization/base.py (2)
  • AuthorizationController (72-113)
  • call (97-108)
care/utils/shortcuts.py (1)
  • get_object_or_404 (6-15)
care/emr/reports/authorizers/discharge_summary.py (2)
care/emr/reports/authorizers/patient.py (1)
  • PatientReportAuthorizer (7-14)
care/emr/reports/authorizers/encounter.py (1)
  • EncounterReportAuthorizer (7-18)
care/emr/reports/context_builder/data_points/invoice.py (5)
care/emr/models/invoice.py (1)
  • Invoice (7-31)
care/emr/reports/context_builder/data_points/base.py (2)
  • Field (7-54)
  • QuerysetContextBuilder (99-125)
care/emr/reports/context_builder/data_points/account.py (1)
  • get_context (123-125)
care/emr/reports/context_builder/data_points/payment_reconcilation.py (1)
  • get_context (133-134)
care/emr/reports/context_builder/data_points/charge_items.py (2)
  • get_context (77-78)
  • get_context (82-83)
care/emr/reports/context_builder/data_points/charge_items.py (5)
care/emr/models/charge_item.py (1)
  • ChargeItem (7-48)
care/emr/reports/context_builder/data_points/base.py (2)
  • Field (7-54)
  • QuerysetContextBuilder (99-125)
care/emr/reports/context_builder/data_points/account.py (1)
  • get_context (123-125)
care/emr/reports/context_builder/data_points/payment_reconcilation.py (1)
  • get_context (133-134)
care/emr/reports/context_builder/data_points/invoice.py (2)
  • get_context (57-58)
  • get_context (62-63)
care/emr/reports/context_builder/data_points/account.py (4)
care/emr/models/account.py (1)
  • Account (7-28)
care/emr/reports/context_builder/data_points/charge_items.py (3)
  • AccountChargeItemContextBuilder (81-83)
  • get_context (77-78)
  • get_context (82-83)
care/emr/reports/context_builder/data_points/invoice.py (3)
  • AccountInvoiceContextBuilder (61-63)
  • get_context (57-58)
  • get_context (62-63)
care/emr/reports/context_builder/data_points/payment_reconcilation.py (2)
  • PaymentReconciliationContextBuilder (56-134)
  • get_context (133-134)
🪛 Ruff (0.14.8)
care/emr/reports/context_builder/data_points/payment_reconcilation.py

58-58: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

care/emr/reports/context_builder/data_points/invoice.py

26-26: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

care/emr/reports/context_builder/data_points/charge_items.py

34-34: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

care/emr/reports/context_builder/data_points/account.py

44-44: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ 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 (14)
care/emr/reports/context_builder/data_points/patient.py (1)

20-42: Field definitions are well-structured.

The field definitions for patient attributes are clear, include appropriate preview values, and the age field correctly uses the get_age() method from the Patient model.

care/emr/reports/context_builder/data_points/charge_items.py (2)

9-23: Display mappings implemented correctly.

The display mappings for charge item resources and statuses provide appropriate fallback behavior using replace("_", " ").title() when keys aren't found in the dictionaries.


32-83: Context builder implementation follows established patterns.

The ChargeItemContextBuilder and AccountChargeItemContextBuilder are well-structured with appropriate field definitions, filtering, and context resolution methods. The use of lambda mappings with None-checking ensures graceful handling of missing values.

care/emr/reports/context_builder/data_points/invoice.py (1)

9-63: Invoice context builder implementation is consistent and correct.

The implementation follows the same well-established pattern as other context builders in this PR. Field definitions include appropriate display metadata, the status mapping handles missing values gracefully, and both patient-level and account-level context resolution are properly implemented.

care/emr/reports/context_builder/data_points/account.py (1)

42-121: Comprehensive field definitions for account reporting.

The field definitions cover all relevant account attributes with appropriate display names, preview values, and human-readable mappings for status fields. The nested context fields for invoices, charge items, and payment reconciliations properly establish relationships with related entities.

care/emr/reports/context_builder/__init__.py (1)

2-2: Wildcard import follows established pattern.

Adding the patient data points export is consistent with the existing encounter imports and appropriately exposes the patient context builder to the module's public API.

care/emr/reports/authorizers/discharge_summary.py (1)

9-10: Authorizer correctly inherits patient-level permissions.

The DischargeBillReportAuthorizer appropriately extends PatientReportAuthorizer to inherit authorization logic for reading and writing patient-related reports. The empty implementation is consistent with the existing DischargeSummaryReportAuthorizer pattern.

care/emr/reports/authorizers/__init__.py (1)

3-6: Module exports updated correctly.

The new DischargeBillReportAuthorizer is properly imported and added to the __all__ list, making it available to consumers of this module.

Also applies to: 12-12

care/emr/reports/report_types.py (1)

16-22: Report type registration is properly structured.

The new discharge_bill report type registration correctly associates the Patient model with the DischargeBillReportAuthorizer and includes an appropriate description.

care/emr/reports/authorizers/patient.py (2)

1-4: LGTM!

Imports are well-organized and follow Django conventions.


10-10: No action required. The authorization actions can_view_clinical_data and can_write_patient_obj are already properly defined in the registered patient authorization handler.

Likely an incorrect or invalid review comment.

care/emr/reports/context_builder/data_points/payment_reconcilation.py (3)

48-53: LGTM!

The FilterSet implementation correctly uses UUID filters for foreign key relationships and case-insensitive matching for string fields, following established patterns in the codebase.


60-131: Field definitions follow established patterns.

The Field definitions correctly use lambda mappings with fallback logic (dict.get(key, default_value)), which is consistent with other context builders in the codebase. Just remember to update the dictionary references in lines 63, 71, 91, 105, 115, and 125 when you fix the spelling of those mapping dictionaries mentioned earlier.


133-134: LGTM!

The get_context method correctly filters PaymentReconciliation by account, following the same pattern as other account-scoped context builders (InvoiceContextBuilder, ChargeItemContextBuilder).

@codecov
Copy link

codecov bot commented Dec 16, 2025

Codecov Report

❌ Patch coverage is 90.28571% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.72%. Comparing base (d423145) to head (d75afa0).

Files with missing lines Patch % Lines
care/emr/reports/authorizers/account.py 63.63% 4 Missing ⚠️
care/emr/reports/authorizers/patient.py 63.63% 4 Missing ⚠️
...emr/reports/context_builder/data_points/account.py 94.44% 2 Missing ⚠️
...eports/context_builder/data_points/charge_items.py 92.30% 2 Missing ⚠️
...emr/reports/context_builder/data_points/invoice.py 91.30% 2 Missing ⚠️
.../context_builder/data_points/monetary_component.py 83.33% 2 Missing ⚠️
...text_builder/data_points/payment_reconciliation.py 96.55% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3419      +/-   ##
===========================================
+ Coverage    74.60%   74.72%   +0.12%     
===========================================
  Files          449      457       +8     
  Lines        20646    20819     +173     
  Branches      2141     2141              
===========================================
+ Hits         15402    15558     +156     
- Misses        4782     4799      +17     
  Partials       462      462              

☔ 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.

@nandkishorr nandkishorr changed the title Data points For patient level report builder Data points for patient level report builder Dec 16, 2025
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

♻️ Duplicate comments (1)
care/emr/reports/context_builder/data_points/account.py (1)

51-67: Callable mappings will fail if account is None.

The get_context() method returns .first() which yields None when no accounts exist. The lambda mappings for status and billing_status will raise AttributeError when attempting to access a.status or a.billing_status on a None object.

🧹 Nitpick comments (3)
care/emr/reports/context_builder/data_points/payment_reconciliation.py (2)

88-95: Preview value doesn't match any mapped value.

The preview_value is "Credit" but the PAYMENT_RECONCILIATION_KIND mapping contains "Deposit", "Periodic Payment", "Online", and "Kiosk". Using a value that doesn't appear in the actual data might be slightly confusing for users previewing the report.

     kind = Field(
         display="Kind",
-        preview_value="Credit",
+        preview_value="Deposit",
         mapping=lambda p: PAYMENT_RECONCILIATION_KIND.get(p.kind, p.kind.title())
         if p.kind
         else "",
         description="Kind of payment reconciliation",
     )

112-121: Preview value doesn't match any mapped value.

The preview_value is "Success" but PAYMENT_RECONCILIATION_OUTCOME maps to "Queued", "Complete", "Error", and "Partial". I'm sure "Success" makes perfect sense somehow.

     outcome = Field(
         display="Outcome",
-        preview_value="Success",
+        preview_value="Complete",
         mapping=lambda p: PAYMENT_RECONCILIATION_OUTCOME.get(
             p.outcome, p.outcome.title()
         )
         if p.outcome
         else "",
         description="Outcome of the payment reconciliation process",
     )
care/emr/reports/context_builder/data_points/account.py (1)

123-125: Consider documenting the assumption about parent_context.

This implementation assumes parent_context is always a Patient instance. While this may be guaranteed by the report infrastructure, a brief comment clarifying this expectation might help future maintainers understand the contract. Just a thought.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79bc839 and 33a5588.

📒 Files selected for processing (3)
  • care/emr/reports/context_builder/data_points/account.py (1 hunks)
  • care/emr/reports/context_builder/data_points/payment_reconciliation.py (1 hunks)
  • care/emr/reports/report_types.py (2 hunks)
🧰 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/reports/context_builder/data_points/payment_reconciliation.py
  • care/emr/reports/report_types.py
  • care/emr/reports/context_builder/data_points/account.py
🧠 Learnings (1)
📚 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/reports/report_types.py
🧬 Code graph analysis (3)
care/emr/reports/context_builder/data_points/payment_reconciliation.py (3)
care/emr/models/payment_reconciliation.py (1)
  • PaymentReconciliation (6-29)
care/emr/reports/context_builder/data_points/base.py (2)
  • Field (7-54)
  • QuerysetContextBuilder (99-125)
care/emr/reports/context_builder/data_points/account.py (1)
  • get_context (123-125)
care/emr/reports/report_types.py (5)
care/emr/models/patient.py (1)
  • Patient (16-133)
care/emr/reports/authorizers/discharge_summary.py (1)
  • DischargeBillReportAuthorizer (9-10)
care/emr/reports/report_type_registry.py (2)
  • ReportTypeRegistry (23-87)
  • register (27-50)
care/emr/reports/context_builder/data_point_registry.py (1)
  • register (6-7)
care/emr/reports/context_builder/type_registry.py (1)
  • register (5-6)
care/emr/reports/context_builder/data_points/account.py (5)
care/emr/models/account.py (1)
  • Account (7-28)
care/emr/reports/context_builder/data_points/base.py (1)
  • Field (7-54)
care/emr/reports/context_builder/data_points/charge_items.py (3)
  • AccountChargeItemContextBuilder (81-83)
  • get_context (77-78)
  • get_context (82-83)
care/emr/reports/context_builder/data_points/invoice.py (3)
  • AccountInvoiceContextBuilder (61-63)
  • get_context (57-58)
  • get_context (62-63)
care/emr/reports/context_builder/data_points/payment_reconciliation.py (2)
  • PaymentReconciliationContextBuilder (56-134)
  • get_context (133-134)
🪛 Ruff (0.14.8)
care/emr/reports/context_builder/data_points/payment_reconciliation.py

58-58: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

care/emr/reports/context_builder/data_points/account.py

44-44: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ 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/reports/report_types.py (1)

16-22: LGTM!

The new report type registration follows the established pattern and correctly associates the patient bill summary with the Patient model and DischargeBillReportAuthorizer. All required parameters are properly provided.

care/emr/reports/context_builder/data_points/payment_reconciliation.py (1)

133-133: LGTM!

The get_context method correctly returns a queryset filtered by the parent account context, consistent with the pattern used by AccountInvoiceContextBuilder and AccountChargeItemContextBuilder.

care/emr/reports/context_builder/data_points/account.py (3)

1-16: LGTM!

Imports are well-organized and all imported context builders are properly utilized in the Field definitions below.


18-32: LGTM!

Status mapping constants are well-defined with clear, human-readable display values.


94-111: LGTM!

The related context fields for invoices, charge items, and payment reconciliations are correctly wired to their respective context builders using target_context.

@nandkishorr nandkishorr marked this pull request as draft December 18, 2025 11:58
@nandkishorr nandkishorr marked this pull request as ready for review December 22, 2025 08:37
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

♻️ Duplicate comments (3)
care/emr/reports/context_builder/data_points/patient.py (1)

12-42: Missing get_context() method - addressing prior feedback.

This class extends SingleObjectContextBuilder but doesn't implement get_context(). The previous review correctly identified this gap. Without this method, the builder won't know how to retrieve the patient instance when rendering the report context.

🔎 Suggested implementation
     accounts = Field(
         display="Patient Accounts",
         target_context=PatientAccountContextBuilder,
         preview_value="",
         description="Accounts associated with the patient",
     )
+
+    def get_context(self):
+        return self.parent_context
care/emr/reports/context_builder/data_points/account.py (2)

43-60: Lambda mappings will fail when context is None.

I notice the previous review comment about None handling hasn't been addressed yet. The lambda mappings in status and billing_status fields access attributes directly on the context object without checking if it's None. Since PatientAccountContextBuilder.get_context() uses .first() (Line 125), it can return None when no account exists for the patient, causing an AttributeError at runtime.

You'll need to either:

  1. Add None validation in PatientAccountContextBuilder.get_context() to raise a clear exception, or
  2. Update these lambda mappings to handle None gracefully
🔎 Proposed fix for None-safe lambda mappings
 status = Field(
     display="Account Status",
     preview_value="Active",
-    mapping=lambda a: STATUS_DISPLAY.get(a.status, a.status.title())
-    if a.status
-    else "",
+    mapping=lambda a: (
+        STATUS_DISPLAY.get(a.status, a.status.title())
+        if a and a.status
+        else ""
+    ),
     description="Current status of the account",
 )
 billing_status = Field(
     display="Account Billing Status",
     preview_value="Billed",
-    mapping=lambda a: BILLING_STATUS_DISPLAY.get(
-        a.billing_status, a.billing_status.title()
-    )
-    if a.billing_status
-    else "",
+    mapping=lambda a: (
+        BILLING_STATUS_DISPLAY.get(a.billing_status, a.billing_status.title())
+        if a and a.billing_status
+        else ""
+    ),
     description="Billing status of the account",
 )

122-125: Still returning None from get_context without validation.

This method uses .first() which returns None when no account exists for the patient. As previously noted, this will cause downstream AttributeErrors in the field lambda mappings.

Consider one of these approaches:

  1. Validate and raise a clear exception if no account exists
  2. Return a sentinel/empty context object with safe defaults
  3. Ensure all upstream code paths validate account existence before rendering
🔎 Proposed fix - validate account existence
 def get_context(self):
     accounts = Account.objects.filter(patient=self.parent_context)
-    return accounts.first()
+    account = accounts.first()
+    if account is None:
+        raise ValueError(
+            f"No account found for patient {self.parent_context.id}"
+        )
+    return account
🧹 Nitpick comments (3)
care/emr/reports/context_builder/data_points/charge_items.py (1)

36-38: Annotate mutable class attribute with ClassVar.

The static analysis tool (Ruff RUF012) correctly identifies that __filterset_backends__ is a mutable class attribute. While this works at runtime, annotating it with ClassVar improves type-checking clarity and signals that this attribute is shared across instances.

🔎 Suggested fix
+from typing import ClassVar
+
 from django_filters import rest_framework as filters
 
 ...
 
 class ChargeItemContextBuilder(QuerysetContextBuilder):
     filterset_class = ChargeItemReportFilter
-    __filterset_backends__ = [filters.DjangoFilterBackend]
+    __filterset_backends__: ClassVar[list] = [filters.DjangoFilterBackend]
care/emr/reports/context_builder/data_points/invoice.py (2)

21-24: Consider adding Meta class to specify the model.

While the FilterSet defines appropriate filters, it's missing a Meta class with the model attribute. Django FilterSet best practices recommend explicitly declaring the model being filtered.

🔎 Proposed enhancement
 class InvoiceReportFilter(filters.FilterSet):
     status = filters.CharFilter(lookup_expr="iexact")
     title = filters.CharFilter(lookup_expr="icontains")
     number = filters.CharFilter(lookup_expr="icontains")
+
+    class Meta:
+        model = Invoice
+        fields = ["status", "title", "number"]

36-42: Consider adding defensive None check in status mapping.

While the lambda checks if i.status, it doesn't verify that i itself is not None. Though unlikely with QuerysetContextBuilder, defensive coding would make this safer.

🔎 Proposed enhancement
 status = Field(
     display="Invoice Status",
     preview_value="Issued",
-    mapping=lambda i: STATUS_DISPLAY.get(i.status, i.status.title())
-    if i.status
-    else "",
+    mapping=lambda i: (
+        STATUS_DISPLAY.get(i.status, i.status.title())
+        if i and i.status
+        else ""
+    ),
     description="Current status of the invoice",
 )
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33a5588 and d75afa0.

📒 Files selected for processing (9)
  • care/emr/reports/authorizers/__init__.py
  • care/emr/reports/authorizers/account.py
  • care/emr/reports/context_builder/__init__.py
  • care/emr/reports/context_builder/data_points/account.py
  • care/emr/reports/context_builder/data_points/charge_items.py
  • care/emr/reports/context_builder/data_points/invoice.py
  • care/emr/reports/context_builder/data_points/monetary_component.py
  • care/emr/reports/context_builder/data_points/patient.py
  • care/emr/reports/report_types.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • care/emr/reports/authorizers/init.py
  • care/emr/reports/context_builder/init.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/reports/authorizers/account.py
  • care/emr/reports/context_builder/data_points/charge_items.py
  • care/emr/reports/report_types.py
  • care/emr/reports/context_builder/data_points/patient.py
  • care/emr/reports/context_builder/data_points/invoice.py
  • care/emr/reports/context_builder/data_points/account.py
  • care/emr/reports/context_builder/data_points/monetary_component.py
🧠 Learnings (1)
📚 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/reports/report_types.py
🧬 Code graph analysis (6)
care/emr/reports/authorizers/account.py (4)
care/emr/models/account.py (1)
  • Account (7-28)
care/emr/reports/authorizers/base.py (1)
  • BaseReportAuthorizer (4-11)
care/security/authorization/base.py (2)
  • AuthorizationController (72-113)
  • call (97-108)
care/utils/shortcuts.py (1)
  • get_object_or_404 (6-15)
care/emr/reports/context_builder/data_points/charge_items.py (5)
care/emr/models/charge_item.py (1)
  • ChargeItem (7-48)
care/emr/reports/context_builder/data_points/base.py (2)
  • Field (7-54)
  • QuerysetContextBuilder (99-125)
care/emr/reports/context_builder/data_points/monetary_component.py (3)
  • MonetaryComponentContextBuilder (15-45)
  • get_context (44-45)
  • get_context (49-50)
care/emr/reports/context_builder/data_points/account.py (1)
  • get_context (123-125)
care/emr/reports/context_builder/data_points/invoice.py (2)
  • get_context (66-67)
  • get_context (71-72)
care/emr/reports/report_types.py (5)
care/emr/models/account.py (1)
  • Account (7-28)
care/emr/models/patient.py (1)
  • Patient (16-133)
care/emr/reports/authorizers/account.py (1)
  • AccountReportAuthorizer (7-18)
care/emr/reports/authorizers/patient.py (1)
  • PatientReportAuthorizer (7-14)
care/emr/reports/report_type_registry.py (2)
  • ReportTypeRegistry (23-87)
  • register (27-50)
care/emr/reports/context_builder/data_points/invoice.py (5)
care/emr/models/invoice.py (1)
  • Invoice (7-31)
care/emr/reports/context_builder/data_points/base.py (2)
  • Field (7-54)
  • QuerysetContextBuilder (99-125)
care/emr/reports/context_builder/data_points/monetary_component.py (3)
  • MonetaryComponentContextBuilder (15-45)
  • get_context (44-45)
  • get_context (49-50)
care/emr/reports/context_builder/data_points/account.py (1)
  • get_context (123-125)
care/emr/reports/context_builder/data_points/charge_items.py (2)
  • get_context (94-95)
  • get_context (99-100)
care/emr/reports/context_builder/data_points/account.py (5)
care/emr/models/account.py (1)
  • Account (7-28)
care/emr/reports/context_builder/data_point_registry.py (1)
  • DataPointRegistry (1-31)
care/emr/reports/context_builder/data_points/charge_items.py (3)
  • AccountChargeItemContextBuilder (98-100)
  • get_context (94-95)
  • get_context (99-100)
care/emr/reports/context_builder/data_points/invoice.py (3)
  • AccountInvoiceContextBuilder (70-72)
  • get_context (66-67)
  • get_context (71-72)
care/emr/reports/context_builder/data_points/monetary_component.py (2)
  • get_context (44-45)
  • get_context (49-50)
care/emr/reports/context_builder/data_points/monetary_component.py (1)
care/emr/reports/context_builder/data_points/base.py (2)
  • Field (7-54)
  • QuerysetContextBuilder (99-125)
🪛 Ruff (0.14.8)
care/emr/reports/context_builder/data_points/charge_items.py

38-38: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

care/emr/reports/context_builder/data_points/invoice.py

29-29: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ 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 (18)
care/emr/reports/authorizers/account.py (1)

7-18: LGTM!

The AccountReportAuthorizer correctly implements the BaseReportAuthorizer interface, delegating to AuthorizationController with appropriate action strings for facility-scoped permission checks. The use of get_object_or_404 ensures proper 404 handling for invalid external_id values.

care/emr/reports/context_builder/data_points/patient.py (1)

20-42: Field definitions look good.

The field mappings are well-structured. The age field correctly uses get_age() which returns a human-readable string (e.g., "45 years"), and accounts properly links to PatientAccountContextBuilder for nested context resolution.

care/emr/reports/report_types.py (2)

1-9: Imports are correctly organized.

The new imports for Account, Patient, AccountReportAuthorizer, and PatientReportAuthorizer are appropriately added to support the new report type registrations.


18-32: New report type registrations look correct.

Both patient_summary and account_report registrations follow the established pattern, pairing the correct models with their respective authorizers. The descriptions are clear and consistent with the naming convention.

care/emr/reports/context_builder/data_points/charge_items.py (3)

13-27: Display mappings are comprehensive.

The status and resource display mappings cover the expected values with sensible fallback logic for unmapped values. This approach ensures graceful degradation when new statuses or resources are introduced.


40-92: Field definitions are well-structured.

The field mappings handle edge cases appropriately (null-checking ci.status and ci.service_resource before operations). The monetary component fields correctly delegate to their respective context builders.


94-100: Context retrieval is correctly scoped.

ChargeItemContextBuilder.get_context() filters by patient, while AccountChargeItemContextBuilder correctly overrides to filter by account. This follows the same pattern established in invoice.py.

care/emr/reports/context_builder/data_points/monetary_component.py (3)

6-12: Display mapping is clear and complete.

The MONETARY_COMPONENT_TYPE_DISPLAY mapping covers the standard monetary component types with readable labels.


15-42: Field definitions look reasonable, with a minor note on robustness.

The code field mapping assumes mc.get("code") returns a dict with a display key. This should generally hold true given the JSONField definition, but if malformed data ever sneaks in, the inner .get("display", "") call would raise an AttributeError. Just something to keep in mind if data integrity becomes a concern down the road.

Consider adding defensive handling:

mapping=lambda mc: (mc.get("code") or {}).get("display", ""),

44-50: Context methods are correctly implemented.

MonetaryComponentContextBuilder returns total_price_components while the subclass correctly overrides to return unit_price_components. This pattern allows reuse of field definitions while varying the data source.

care/emr/reports/context_builder/data_points/account.py (4)

1-18: LGTM!

Imports are well-organized and follow PEP 8 conventions.


20-34: LGTM!

Display mappings are well-defined and follow naming conventions.


61-119: Field definitions are well-structured.

The remaining field definitions follow a consistent pattern with appropriate display names, preview values, and descriptions. The fields using target_context properly reference related context builders.


128-137: LGTM!

AccountContextBuilder is properly configured as a standalone context with appropriate metadata and registry registration.

care/emr/reports/context_builder/data_points/invoice.py (4)

1-10: LGTM!

Imports are properly organized and follow conventions.


12-18: LGTM!

Status display mapping is well-defined and follows conventions.


44-67: LGTM!

Field definitions are well-structured with appropriate descriptions and preview values. The get_context method properly filters invoices by patient.


70-72: LGTM!

AccountInvoiceContextBuilder appropriately extends InvoiceContextBuilder to filter by account. The implementation is clean and follows the established pattern.

Comment on lines +28 to +29
filterset_class = InvoiceReportFilter
__filterset_backends__ = [filters.DjangoFilterBackend]
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Annotate mutable class attribute with ClassVar.

The static analysis tool correctly identified that __filterset_backends__ is a mutable class attribute (list) that should be annotated with typing.ClassVar to indicate it's a class variable rather than an instance variable.

🔎 Proposed fix

Add the import at the top of the file:

+from typing import ClassVar
+
 from django_filters import rest_framework as filters

Then annotate the class attribute:

 class InvoiceContextBuilder(QuerysetContextBuilder):
     filterset_class = InvoiceReportFilter
-    __filterset_backends__ = [filters.DjangoFilterBackend]
+    __filterset_backends__: ClassVar = [filters.DjangoFilterBackend]
📝 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
filterset_class = InvoiceReportFilter
__filterset_backends__ = [filters.DjangoFilterBackend]
filterset_class = InvoiceReportFilter
__filterset_backends__: ClassVar = [filters.DjangoFilterBackend]
🧰 Tools
🪛 Ruff (0.14.8)

29-29: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🤖 Prompt for AI Agents
In care/emr/reports/context_builder/data_points/invoice.py around lines 28-29,
the mutable class attribute __filterset_backends__ is currently a plain list and
should be annotated as a class variable; add "from typing import ClassVar, List"
to imports and change the attribute annotation to use ClassVar (e.g.,
__filterset_backends__: ClassVar[List] = [filters.DjangoFilterBackend]) so
static analysis knows it’s a class-level mutable constant.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants