Notification & Activity Domain - Workflow Narrative
A conversational guide to understanding event tracking and notifications
What is the Notification & Activity Domain?
This domain is the nervous system of the EMR. When things happen in the system - a patient is registered, an appointment is booked, a lab result arrives - events are created and potentially trigger notifications.
It serves two purposes. First, it provides an activity feed where users can see what's been happening. Second, it delivers real-time notifications via push to keep users informed of important updates.
Activity Events
What is an Activity Event?
An activity event is a record of something that happened. Think of it as a log entry with rich metadata. Each event captures:
- event_type - What happened (PATIENT_CREATED, APPOINTMENT_SCHEDULED, etc.)
- priority - How important (low, normal, high, urgent)
- title - Human-readable summary
- description - Additional details
- actor_user_id - Who performed the action
- organization_id - Which organization
- patient_id - Which patient (if applicable)
- resource_type - Type of affected resource
- resource_id - ID of affected resource
- encounter_id - Which encounter (if applicable)
- notify - Should this trigger notifications?
Event Types
The system defines many event types, organized by domain:
Patient events: PATIENT_CREATED, PATIENT_UPDATED
Encounter events: ENCOUNTER_CREATED, ENCOUNTER_SIGNED
Appointment events: APPOINTMENT_SCHEDULED, APPOINTMENT_CANCELLED
Clinical events: MEDICATION_PRESCRIBED, LAB_RESULT_RECEIVED
Administrative events: USER_INVITED, ROLE_ASSIGNED
Each event type has a default priority and visibility setting.
Creating Activity Events
The Creation Pattern
Throughout the codebase, you'll see services creating activity events after significant operations. The pattern looks like this:
self._create_activity_event_async(
event_type=EventType.ENCOUNTER_CREATED,
priority=EventPriority.NORMAL,
patient_id=patient_id,
organization_id=organization_id,
actor_user_id=created_by_user,
title=f"New encounter created for {patient_name}",
description="Progress note - Follow-up visit",
resource_id=encounter.id,
notify=True
)
Background Thread Execution
Notice "async" in the method name. Activity events are created in background threads so they don't slow down the main API response. The patient creation returns immediately while the event gets recorded asynchronously.
Separate Database Session
The background event creation uses its own database session. This prevents issues if the main transaction rolls back but the event was already committed (or vice versa).
The Activity Feed
What Users See
The activity feed is a timeline of what's happened. Users see events relevant to their role and accessible patients.
When a practitioner opens their dashboard, they might see:
- "New patient registered: John Smith" (5 minutes ago)
- "Appointment scheduled for Jane Doe" (1 hour ago)
- "Lab results received for Mike Johnson" (2 hours ago)
Role-Based Filtering
Not everyone sees everything. Events have visibility rules based on roles:
- Some events are visible to all organization members
- Some are visible only to clinical staff
- Some are visible only to the actor and supervisors
The ActivityEventService.get_activity_feed() method takes user_roles and filters appropriately.
Patient-Specific Timeline
When viewing a specific patient, get_patient_timeline() returns only events for that patient. This is useful on patient charts showing their interaction history.
Notification Flow
From Event to Push Notification
Not all events become notifications. The notify flag determines this. When an event is created with notify=True, it enters the notification pipeline.
The NotificationRulesService takes over:
- Get Pending Events - Find events where notify=True but notification_sent_at is null
- Determine Recipients - Who should be notified?
- Check Preferences - Does each recipient want this notification type?
- Check Quiet Hours - Is it an appropriate time to notify?
- Send Push - Deliver via Firebase Cloud Messaging
- Mark Sent - Update the event with notification_sent_at
Who Gets Notified?
The recipient logic varies by event type:
Practitioner-only events (appointments): Only the assigned practitioner is notified. If Dr. Smith has a new appointment, only Dr. Smith gets the push.
Care team events (clinical changes): All active care team members for the patient are notified. If a lab result arrives, everyone caring for that patient might need to know.
Organization-wide events (urgent announcements): All active users in the organization receive the notification.
Actor exclusion: The person who caused the event typically doesn't get notified. You don't need a push telling you about the appointment you just scheduled.
User Notification Preferences
Preference Settings
Each user can configure their notification preferences:
- push_enabled - Master switch for push notifications
- email_enabled - Whether to receive email notifications
- enabled_types - List of event types they want notifications for
- quiet_hours_start/end - When to suppress notifications
The Defaults
New users get sensible defaults:
- Push enabled for high/urgent priority events
- Common clinical event types enabled
- Quiet hours disabled
Preference Updates
The update_preferences() method allows users to customize. They might disable appointment notifications if they prefer to check their schedule manually.
Quiet Hours
What They Are
Quiet hours are a courtesy feature. Healthcare workers are often on-call but don't want non-urgent notifications at 2 AM.
When quiet hours are configured (say, 10 PM to 7 AM), low and normal priority notifications are held until quiet hours end. High and urgent notifications still come through immediately.
Time Zone Consideration
Currently, quiet hours are specified in UTC. The system checks the current UTC time against the configured window. Future enhancement might add user-specific timezone support.
Push Notification Delivery
Firebase Cloud Messaging
The actual notification delivery uses Firebase Cloud Messaging (FCM). The FCMService handles:
- Formatting the notification payload
- Sending to registered device tokens
- Handling delivery failures
Device Tokens
Users register their devices by providing FCM tokens. Each device (phone, browser) gets its own token. A single user might have multiple tokens for different devices.
Token Management
Tokens can become stale. The system handles:
- Refreshing tokens when devices re-register
- Removing tokens that consistently fail
- Preferring the most recently used token to avoid duplicates
User Notifications (Persistence)
Beyond Push
Push notifications are immediate but ephemeral. If you miss the push, it's gone. The system also persists notifications to a UserNotification table.
This powers the notification bell in the UI. Users can see their recent notifications, mark them as read, and take action on them.
Notification Properties
- user_id - Who this notification is for
- event_id - Link to the source activity event
- is_read - Has the user seen it?
- read_at - When was it marked read?
- resource_type/id - What to navigate to when clicked
Unread Count
The notification bell shows an unread count. This is a simple query: count notifications where user_id matches and is_read is false.
Sending Notifications for Events
The Complete Flow
Here's the full flow when an appointment is created:
AppointmentService.create_appointment()creates the appointment- It calls
_create_activity_event_async()with notify=True - Background thread creates the
ActivityEventin the database - Notification processor picks up events with notify=True
NotificationRulesService.get_users_to_notify()finds the practitionershould_notify_user()checks if they want appointment notificationsFCMService.send_notification()delivers the pushmark_notification_sent()updates the eventUserNotificationrecord is created for in-app history
Processing Pending Notifications
A background job runs periodically to process notifications. process_pending_notifications() handles events that have notify=True but haven't been sent yet.
This ensures notifications aren't lost even if the background thread failed initially.
Urgent Announcements
Special Case: Organization Broadcasts
Urgent announcements bypass normal targeting. When an admin posts an urgent announcement, everyone in the organization gets notified immediately regardless of their normal preferences.
The event type ANNOUNCEMENT_URGENT has special handling in the notification rules. It targets all organization users and respects only the master push_enabled switch.
Performance Considerations
Event Feed Queries
Activity feeds can grow large. The system uses:
- Pagination (skip/limit parameters)
- Index on organization_id and created_at
- Optional date filters to limit scope
Notification Processing
The notification processor handles events in batches to avoid overwhelming FCM. It also tracks failures to prevent infinite retry loops.
Key Takeaways
-
Events are the source - activity events record what happened
-
notify flag triggers delivery - not all events become notifications
-
Recipients vary by event type - practitioner, care team, or org-wide
-
Preferences give control - users configure what they receive
-
Quiet hours suppress non-urgent - respect for off-hours
-
FCM delivers push - Firebase handles the actual delivery
-
Persistence enables history - notifications are also stored in database
Next: Read about the AI Domain to understand clinical intelligence features