-
Notifications
You must be signed in to change notification settings - Fork 537
Data points for patient level report builder #3419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b5e031c
d1c32f8
e92ca41
c6259cd
c0fc569
cc31a3b
a56d17c
58bbf22
1059a00
79bc839
341137d
33a5588
ceda4d8
a36c741
41fe726
d75afa0
d9c841c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,18 @@ | ||
| from . import discharge_summary | ||
| from .account import AccountReportAuthorizer | ||
| from .base import BaseReportAuthorizer | ||
| from .discharge_summary import DischargeSummaryReportAuthorizer | ||
| from .discharge_summary import ( | ||
| DischargeSummaryReportAuthorizer, | ||
| ) | ||
| from .encounter import EncounterReportAuthorizer | ||
| from .patient import PatientReportAuthorizer | ||
| from .utils import report_authorizer | ||
|
|
||
| __all__ = [ | ||
| "AccountReportAuthorizer", | ||
| "BaseReportAuthorizer", | ||
| "DischargeSummaryReportAuthorizer", | ||
| "EncounterReportAuthorizer", | ||
| "PatientReportAuthorizer", | ||
| "report_authorizer", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| from care.emr.models.account import Account | ||
| from care.emr.reports.authorizers.base import BaseReportAuthorizer | ||
| from care.security.authorization.base import AuthorizationController | ||
| from care.utils.shortcuts import get_object_or_404 | ||
|
|
||
|
|
||
| class AccountReportAuthorizer(BaseReportAuthorizer): | ||
| def authorize_read(self, user, associating_id: str) -> bool: | ||
| account_obj = get_object_or_404(Account, external_id=associating_id) | ||
| return AuthorizationController.call( | ||
| "can_read_account_in_facility", user, account_obj | ||
| ) | ||
|
|
||
| def authorize_write(self, user, associating_id: str) -> bool: | ||
| account_obj = get_object_or_404(Account, external_id=associating_id) | ||
| return AuthorizationController.call( | ||
| "can_update_account_in_facility", user, account_obj | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| from care.emr.models.patient import Patient | ||
| from care.emr.reports.authorizers.base import BaseReportAuthorizer | ||
| from care.security.authorization import AuthorizationController | ||
| from care.utils.shortcuts import get_object_or_404 | ||
|
|
||
|
|
||
| class PatientReportAuthorizer(BaseReportAuthorizer): | ||
| def authorize_read(self, 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: | ||
| patient_obj = get_object_or_404(Patient, external_id=associating_id) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be clinical write permission, or encounter write permission (Not encounter create either) |
||
| return AuthorizationController.call("can_write_patient_obj", user, patient_obj) | ||
nandkishorr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,3 @@ | ||
| from .data_points.encounter import * # noqa F403 | ||
| from .data_points.patient import * # noqa F403 | ||
| from .data_points.account import * # noqa F403 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| from care.emr.models.account import Account | ||
| from care.emr.reports.context_builder.data_point_registry import DataPointRegistry | ||
| from care.emr.reports.context_builder.data_points.base import ( | ||
| Field, | ||
| SingleObjectContextBuilder, | ||
| ) | ||
| from care.emr.reports.context_builder.data_points.charge_items import ( | ||
| AccountChargeItemContextBuilder, | ||
| ) | ||
| from care.emr.reports.context_builder.data_points.invoice import ( | ||
| AccountInvoiceContextBuilder, | ||
| ) | ||
| from care.emr.reports.context_builder.data_points.monetary_component import ( | ||
| MonetaryComponentContextBuilder, | ||
| ) | ||
| from care.emr.reports.context_builder.data_points.payment_reconciliation import ( | ||
| PaymentReconciliationContextBuilder, | ||
| ) | ||
|
|
||
| STATUS_DISPLAY = { | ||
| "active": "Active", | ||
| "inactive": "Inactive", | ||
| "entered_in_error": "Entered in Error", | ||
| "on_hold": "On Hold", | ||
| } | ||
| BILLING_STATUS_DISPLAY = { | ||
| "open": "Open", | ||
| "carecomplete_notbilled": "CareComplete Not Billed", | ||
| "billing": "Billing", | ||
| "closed_baddebt": "Closed Bad Debt", | ||
| "closed_voided": "Closed Voided", | ||
| "closed_completed": "Closed Completed", | ||
| "closed_combined": "Closed Combined", | ||
| } | ||
|
|
||
|
|
||
| class BaseAccountContextBuilder(SingleObjectContextBuilder): | ||
| name = Field( | ||
| display="Account Title", | ||
| preview_value="General Checkup Account", | ||
| description="Title of the account", | ||
| ) | ||
| status = Field( | ||
| display="Account Status", | ||
| preview_value="Active", | ||
| mapping=lambda a: STATUS_DISPLAY.get(a.status, a.status.title()) | ||
| if 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 "", | ||
| description="Billing status of the account", | ||
| ) | ||
| description = Field( | ||
| display="Account Description", | ||
| preview_value="Account for general health checkup", | ||
| description="Detailed description of the account", | ||
| ) | ||
| total_net = Field( | ||
| display="Total Net Amount", | ||
| preview_value="150.00", | ||
| description="Total net amount for the account", | ||
| ) | ||
| total_gross = Field( | ||
| display="Total Gross Amount", | ||
| preview_value="180.00", | ||
| description="Total gross amount for the account", | ||
| ) | ||
| total_paid = Field( | ||
| display="Total Paid Amount", | ||
| preview_value="100.00", | ||
| description="Total amount paid towards the account", | ||
| ) | ||
| total_balance = Field( | ||
| display="Total Balance Amount", | ||
| preview_value="80.00", | ||
| description="Total balance amount remaining for the account", | ||
| ) | ||
| total_price_components = Field( | ||
| display="Total Price Components", | ||
| preview_value="", | ||
| target_context=MonetaryComponentContextBuilder, | ||
| description="Breakdown of total price components for the account", | ||
| ) | ||
| invoices = Field( | ||
| display="Associated Invoices", | ||
| preview_value="", | ||
| target_context=AccountInvoiceContextBuilder, | ||
| description="Invoices linked to the account", | ||
| ) | ||
| charge_items = Field( | ||
| display="Billable Charge Items", | ||
| preview_value="", | ||
| target_context=AccountChargeItemContextBuilder, | ||
| description="Chargeable items associated with the account", | ||
| ) | ||
| payment_reconciliations = Field( | ||
| display="Payment Reconciliations", | ||
| preview_value="", | ||
| target_context=PaymentReconciliationContextBuilder, | ||
| description="Payment reconciliations for the account", | ||
| ) | ||
| created_date = Field( | ||
| display="Account Created Date", | ||
| preview_value="2023-01-15T10:30:00Z", | ||
| description="Date when the account was created", | ||
| ) | ||
| calculated_at = Field( | ||
| display="Account Calculated At", | ||
| preview_value="2023-01-20T15:45:00Z", | ||
| description="Date when the account totals were last calculated", | ||
| ) | ||
|
|
||
|
|
||
| class PatientAccountContextBuilder(BaseAccountContextBuilder): | ||
| def get_context(self): | ||
| accounts = Account.objects.filter(patient=self.parent_context) | ||
| return accounts.first() | ||
nandkishorr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| class AccountContextBuilder(BaseAccountContextBuilder): | ||
| standalone_context = True | ||
| __slug__ = "account_base" | ||
| __associating_model__ = Account | ||
| __display_name__ = "Account Report" | ||
| __description__ = "Report context for account-based reports" | ||
| context_key = "account" | ||
|
|
||
|
|
||
| DataPointRegistry.register(AccountContextBuilder) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| from django_filters import rest_framework as filters | ||
|
|
||
| from care.emr.models.charge_item import ChargeItem | ||
| from care.emr.reports.context_builder.data_points.base import ( | ||
| Field, | ||
| QuerysetContextBuilder, | ||
| ) | ||
| from care.emr.reports.context_builder.data_points.monetary_component import ( | ||
| MonetaryComponentContextBuilder, | ||
| UnitPriceMonetaryComponentContextBuilder, | ||
| ) | ||
|
|
||
| CHARGE_ITEM_RESOURCE_DISPLAY = { | ||
| "service_request": "Service Request", | ||
| "medication_dispense": "Medication Dispense", | ||
| "appointment": "Appointment", | ||
| "bed_association": "Bed Association", | ||
| } | ||
| CHARGE_ITEM_STATUS_DISPLAY = { | ||
| "planned": "Planned", | ||
| "billable": "Billable", | ||
| "not_billable": "Not Billable", | ||
| "aborted": "Aborted", | ||
| "billed": "Billed", | ||
| "paid": "Paid", | ||
| "entered_in_error": "Entered in Error", | ||
| } | ||
|
|
||
|
|
||
| class ChargeItemReportFilter(filters.FilterSet): | ||
| status = filters.CharFilter(lookup_expr="iexact") | ||
| title = filters.CharFilter(lookup_expr="icontains") | ||
| service_resource = filters.CharFilter(lookup_expr="icontains") | ||
|
|
||
|
|
||
| class ChargeItemContextBuilder(QuerysetContextBuilder): | ||
| filterset_class = ChargeItemReportFilter | ||
| __filterset_backends__ = [filters.DjangoFilterBackend] | ||
|
|
||
| title = Field( | ||
| display="Charge Item Title", | ||
| preview_value="General Consultation", | ||
| description="Title of the charge item", | ||
| ) | ||
| status = Field( | ||
| display="Charge Item Status", | ||
| preview_value="Active", | ||
| mapping=lambda ci: CHARGE_ITEM_STATUS_DISPLAY.get( | ||
| ci.status, ci.status.replace("_", " ").title() | ||
| ) | ||
| if ci.status | ||
| else "", | ||
| description="Current status of the charge item", | ||
| ) | ||
| service_resource = Field( | ||
| display="Service Resource", | ||
| preview_value="Consultation Service", | ||
| mapping=lambda ci: CHARGE_ITEM_RESOURCE_DISPLAY.get( | ||
| ci.service_resource, ci.service_resource.replace("_", " ").title() | ||
| ) | ||
| if ci.service_resource | ||
| else "", | ||
| description="Service resource associated with the charge item", | ||
| ) | ||
| quantity = Field( | ||
| display="Quantity", | ||
| preview_value="5", | ||
| description="Quantity of the charge item", | ||
| ) | ||
| unit_price_components = Field( | ||
| display="Unit Price Components", | ||
| preview_value="", | ||
| target_context=UnitPriceMonetaryComponentContextBuilder, | ||
| description="Unit price components of the charge item", | ||
| ) | ||
| total_price = Field( | ||
| display="Total Price", | ||
| preview_value="100.00", | ||
| description="Total price of the charge item", | ||
| ) | ||
| total_price_components = Field( | ||
| display="Total Price Components", | ||
| preview_value="", | ||
| target_context=MonetaryComponentContextBuilder, | ||
| description="Breakdown of total price components of the charge item", | ||
| ) | ||
|
|
||
| paid_on = Field( | ||
| display="Paid On", | ||
| preview_value="2024-01-15T10:30:00Z", | ||
| description="Date and time when the charge item was paid", | ||
| ) | ||
|
|
||
| def get_context(self): | ||
| return ChargeItem.objects.filter(patient=self.parent_context) | ||
|
|
||
|
|
||
| class AccountChargeItemContextBuilder(ChargeItemContextBuilder): | ||
| def get_context(self): | ||
| return ChargeItem.objects.filter(account=self.parent_context) |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,72 @@ | ||||||||||
| from django_filters import rest_framework as filters | ||||||||||
|
|
||||||||||
| from care.emr.models.invoice import Invoice | ||||||||||
| from care.emr.reports.context_builder.data_points.base import ( | ||||||||||
| Field, | ||||||||||
| QuerysetContextBuilder, | ||||||||||
| ) | ||||||||||
| from care.emr.reports.context_builder.data_points.monetary_component import ( | ||||||||||
| MonetaryComponentContextBuilder, | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| STATUS_DISPLAY = { | ||||||||||
| "draft": "Draft", | ||||||||||
| "issued": "Issued", | ||||||||||
| "balanced": "Balanced", | ||||||||||
| "cancelled": "Cancelled", | ||||||||||
| "entered_in_error": "Entered in Error", | ||||||||||
| } | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class InvoiceReportFilter(filters.FilterSet): | ||||||||||
| status = filters.CharFilter(lookup_expr="iexact") | ||||||||||
| title = filters.CharFilter(lookup_expr="icontains") | ||||||||||
| number = filters.CharFilter(lookup_expr="icontains") | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class InvoiceContextBuilder(QuerysetContextBuilder): | ||||||||||
| filterset_class = InvoiceReportFilter | ||||||||||
| __filterset_backends__ = [filters.DjangoFilterBackend] | ||||||||||
|
Comment on lines
+28
to
+29
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 🔎 Proposed fixAdd the import at the top of the file: +from typing import ClassVar
+
from django_filters import rest_framework as filtersThen annotate the class attribute: class InvoiceContextBuilder(QuerysetContextBuilder):
filterset_class = InvoiceReportFilter
- __filterset_backends__ = [filters.DjangoFilterBackend]
+ __filterset_backends__: ClassVar = [filters.DjangoFilterBackend]📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.14.8)29-29: Mutable class attributes should be annotated with (RUF012) 🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| title = Field( | ||||||||||
| display="Invoice Title", | ||||||||||
| preview_value="Medical Services Invoice", | ||||||||||
| description="Title of the invoice", | ||||||||||
| ) | ||||||||||
| status = Field( | ||||||||||
| display="Invoice Status", | ||||||||||
| preview_value="Issued", | ||||||||||
| mapping=lambda i: STATUS_DISPLAY.get(i.status, i.status.title()) | ||||||||||
| if i.status | ||||||||||
| else "", | ||||||||||
| description="Current status of the invoice", | ||||||||||
| ) | ||||||||||
| number = Field( | ||||||||||
| display="Invoice Number", | ||||||||||
| preview_value="INV-1001", | ||||||||||
| description="Unique number of the invoice", | ||||||||||
| ) | ||||||||||
| total_net = Field( | ||||||||||
| display="Total Net Amount", | ||||||||||
| preview_value="150.00", | ||||||||||
| description="Total net amount of the invoice", | ||||||||||
| ) | ||||||||||
| total_gross = Field( | ||||||||||
| display="Total Gross Amount", | ||||||||||
| preview_value="180.00", | ||||||||||
| description="Total gross amount of the invoice", | ||||||||||
| ) | ||||||||||
| total_price_components = Field( | ||||||||||
| display="Total Price Components", | ||||||||||
| preview_value="", | ||||||||||
| target_context=MonetaryComponentContextBuilder, | ||||||||||
| description="Breakdown of total price components of the invoice", | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| def get_context(self): | ||||||||||
| return Invoice.objects.filter(patient=self.parent_context) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class AccountInvoiceContextBuilder(InvoiceContextBuilder): | ||||||||||
| def get_context(self): | ||||||||||
| return Invoice.objects.filter(account=self.parent_context) | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
invoice model doesnt has encounter field