Design Decisions
Key design constraints, architectural rationale, and extensibility guide for Bill-wise Critical Callout.
Design Decisions & Architecture
Key Design Decisions & Constraints
Order-wise callout as the primary unit
One callout action covers all critical reports in an order simultaneously.
- Previously: user had to action each critical report separately
- Now:
bill_idis the root key across API, bulk manager, and modal - Schema change:
billFK added toCriticalCallout;lab_reportmade nullable
Single bill-level draft record
Exactly one CriticalCallout row is created per bill when is_draft=True (not one per report).
lab_report = Noneon the draft record- Selected test IDs stored in
critical_callout_meta.callout_forfor UI restore - Only one draft can ever exist per order — atomically replaced on every resubmit
CALLOUT_ATTEMPTED as an explicit enum value
CALLOUT_ATTEMPTED = 4 is a first-class status in CriticalValuesEnum, not a flag.
- Worklist can filter and display attempted orders separately from pending
- Export includes it as a distinct
Callout Statuscolumn value - Activity log carries
is_draft: trueindumped_json - Flows through the same badge, tab-filter, and ES-sync paths as
PENDINGandDONE
Draft deletion on every submit
The existing draft record is always deleted inside transaction.atomic() before new records are created.
- Ensures: at most one draft per bill at any time
- Ensures: a completed callout can never coexist with a draft for the same bill
Department-scoped report visibility
FetchCriticalRecordsByBillView applies department filters per session type.
- Doctor login: filtered by assigned departments or
billId__docId - Lab user: filtered via
LabUserDepartmentRelation - Support login: no department filter (elevated access)
Unified modal — action + history in one surface
Callout history is visible in the right panel of the same modal used to perform the callout.
- Avoids context switching during operations
- History loaded via a parallel bulk logs API call on modal open
- Users can see previous attempts before deciding on next action
Communication channels as additive, config-controlled
SMS and WhatsApp were added without touching existing critical_notification_settings keys.
is_email,always_notify,mandatory_commentsetc. are unchanged- New channels use the same
notify_topayload structure - Future channels can be added the same way — no config schema migration needed
always_notify enforcement at the surface level
always_notify is checked individually in ReportEntryFooter and DoctorFooter, not in shared middleware.
- Each surface intercepts its own action (Save & Sign, Approve)
- Condition:
completedTests === 1ANDcriticalValues ∈ CRITICAL_CALLOUT_VALUESANDalways_notify === 1AND no existing logs - Keeps enforcement logic close to the action it guards; easy to modify per surface
Navigation state preservation on worklist
Worklist filter state (date range, department, tab, search) is restored when user returns from patient overview.
- Worklist users process many orders in sequence
- Losing filters on every row click would break the operational flow
Architectural Rationale
CriticalReport as a proxy model, not a new model
CriticalReport is a Django proxy of LabReportRelation — no extra DB table.
- Inherits all
LabReportRelationfields and relationships - Adds behaviour: parameter evaluation, email, activity log, ES sync
- Avoids data duplication; keeps callout logic co-located with the report
BulkCriticalCalloutManager as a standalone class
Bulk callout logic was extracted into a class rather than embedded in the view.
- A single
process_callout()call spans: parameter batching → email → DB transaction → ES sync → activity log - Classmethod
get_critical_report_params_mapperis reused by both the manager andFetchCriticalRecordsByBillView - Improves testability — no HTTP layer needed to unit test the callout flow
Three-query batch fetch for parameter evaluation
get_critical_report_params_mapper() resolves critical parameters for all reports in an order using 3 DB queries regardless of report count.
ReportValue— all values for the report IDsReportFormat— all formats for the format IDsValueRanges— age ranges (only if any formats haveageRangeFlag)
Results are grouped in Python; zero N+1 risk. Runs on every modal open and every callout submit.
Two-path ES sync strategy
| Path | Trigger | Scope |
|---|---|---|
CriticalReport.after_save() | Django save() hook | Single report |
BillSplitManager.sync_lab_reports(bill) | After bulk callout transaction | Full bill |
The two-path design exists because bulk_update skips the Django save() hook — bill-level sync after the transaction guarantees ES consistency without re-implementing after_save for bulk operations.
do_save=False for deferred bulk_create
save_critical_callout(do_save=False) returns an unsaved instance instead of calling INSERT immediately.
- Bulk manager collects all instances, then calls
bulk_createinside one atomic transaction - Avoids N separate
INSERTstatements - Partial failures roll back cleanly — no orphaned callout records
Reusability & Extensibility
| What you can do | How |
|---|---|
| Add a new communication channel | Add to COMMUNICATION_METHODS (frontend) + METHOD_KEY_VALUE_MAPPER + save_critical_callout kwargs (backend) |
| Embed the callout button on a new surface | Import CriticalNotificationButton, pass labReport + onSuccess + visibility condition — no modal changes |
| Add a new callout status | Add to CriticalValuesEnum → CRITICAL_CALLOUT_VALUES → labReportStatusUtils → worklist tab (if needed) |
| Extend draft restore fields | Add field to CriticalCalloutMeta interface → persist in critical_callout_meta JSON → restore in getRestoredCalloutState() |
| Add a new recipient type | Add to RECIPIENT_KEY_VALUE_MAPPER in BulkCriticalCalloutManager + flag in notify_by_email() |
| Change the email template | Edit critical_notification.jinja — context (report_items, is_bulk, show_patient_name, comment) is parameterised |
| Add a new export column | Add field to return object in getCriticalCalloutExportRows() — SheetJS picks up headers dynamically |