Alerts¶
The alert system flags rota issues that require admin attention -- staffing shortfalls, overworked clinicians, and scheduling constraint violations. Alerts are generated automatically during rota generation and refreshed whenever shifts change.
Alert Model¶
Defined in config/models.py.
| Field | Type | Description |
|---|---|---|
type |
CharField (choices) | Category of alert (see below) |
severity |
CharField (choices) | WARNING or CRITICAL |
date |
DateField (indexed) | The affected date |
message |
CharField(500) | Human-readable description |
details |
JSONField | Machine-readable context (counts, clinician IDs, etc.) |
status |
CharField (indexed) | ACTIVE, RESOLVED, or DISMISSED |
created_at |
DateTimeField | When the alert was created |
resolved_at |
DateTimeField (nullable) | When the alert was resolved or dismissed |
The model has a composite index on (date, status) for efficient active-alert lookups.
Alert Types¶
| Enum Value | Label | Severity | Trigger |
|---|---|---|---|
BELOW_MINIMUM |
Short-staffed | CRITICAL | Scheduled doctors below day's minimum |
AT_WARNING_THRESHOLD |
At Minimum Staffing | WARNING | Scheduled doctors exactly at minimum |
OVERWORKED_DOCTOR |
Overworked Doctor | WARNING | Clinician exceeds 110% of target shifts |
CONSTRAINT_VIOLATION |
Scheduling Concern | CRITICAL | Hard constraint broken during generation |
INSUFFICIENT_DUTY_DOCTORS |
Not Enough Duty Doctors | CRITICAL | Too few duty doctors assigned (esp. post-bank-holiday) |
Status Lifecycle¶
ACTIVE --> RESOLVED (condition fixed, auto or manual)
ACTIVE --> DISMISSED (admin dismissed manually)
Alerts are created with ACTIVE status. They can transition to RESOLVED (the underlying condition was fixed) or DISMISSED (an admin acknowledged and dismissed the alert). Both transitions set resolved_at to the current time.
The refresh_alerts_for_date method takes a different approach: it deletes stale ACTIVE alerts that no longer apply, rather than resolving them. This keeps the alert table clean.
Alert Generation¶
During Rota Generation¶
After every generation phase completes, _generate_alerts_for_phase() in rota_generation/tasks.py calls AlertService.generate_alerts_for_date_range() for the rota date range. This scans all weekdays and creates alerts for:
- Below-minimum staffing
- Warning-threshold staffing (at exactly minimum)
- Insufficient duty doctors
- Overworked clinicians (checked against target shifts for the reporting period)
Weekends and bank holidays are skipped -- no alerts are generated for these dates.
Phase 6 (duty doctor assignment in rota_generation/phases/phase6_duty_doctors.py) also creates INSUFFICIENT_DUTY_DOCTORS alerts directly when it cannot assign enough duty doctors to a day.
During Leave Processing¶
When leave is approved, LeaveRequestProcessor._refresh_alerts_for_leave_dates() iterates through all dates covered by the leave request and calls refresh_alerts_for_date() for each one. The same happens in the admin leave approval view (frontend/views/admin/leave.py).
During Manual Shift Changes¶
The admin shift management views in frontend/views/admin/shifts.py call refresh_alerts_for_date() after:
- Manually adding a shift
- Editing a shift
- Deleting a shift
- Bulk-deleting shifts
- Processing shift swaps
The shift swap service (shift_swap/services.py) also refreshes alerts for both affected dates after an admin approves a swap.
Manual Generation from Dashboard¶
Admins can trigger manual alert generation via the dashboard view at frontend/views/admin/dashboard.py, which provides a form to select a date range and calls generate_alerts_for_date_range().
Alert Resolution¶
Automatic Resolution¶
AlertService.auto_resolve_alerts_for_date() checks active alerts for a date and resolves them if conditions have improved:
BELOW_MINIMUMalerts are resolved when the doctor count meets or exceeds the minimum.AT_WARNING_THRESHOLDalerts are resolved when the doctor count exceeds minimum + 1.
The refresh_alerts_for_date() method takes a more aggressive approach: it deletes active alerts that no longer match the current state, then recreates any that are still valid. This avoids accumulating stale alert records.
Manual Resolution¶
Admins can resolve or dismiss alerts through two interfaces:
- Django admin (
/django-admin/): Standard model admin with list filters for type, severity, status, and date. - REST API:
POST /api/alerts/{id}/resolve/andPOST /api/alerts/{id}/dismiss/
Both actions delegate to AlertService.resolve_alert() and AlertService.dismiss_alert() respectively.
AlertService¶
Defined in alerts/alert_service.py. This is the single entry point for all alert operations.
Key Methods¶
| Method | Purpose |
|---|---|
create_below_minimum_alert(date, doctors_count, minimum_required) |
Creates a below-minimum staffing alert. Skips if one already exists for that date. |
create_warning_threshold_alert(date, doctors_count, minimum_required) |
Creates a warning-threshold alert. Skips if one already exists. |
create_overwork_alert(clinician, date, shifts_worked, target_shifts) |
Creates an overwork alert for a specific clinician. Skips if one already exists for that clinician on that date. |
create_constraint_violation_alert(date, violation_message) |
Creates a constraint violation alert. Skips if one already exists for that date. |
create_insufficient_duty_doctors_alert(date, required, actual, is_post_bank_holiday) |
Creates a duty doctor shortfall alert. Includes post-bank-holiday context when applicable. |
generate_alerts_for_date_range(start_date, end_date, clinicians, reporting_period_start) |
Scans a date range and creates all applicable alerts. Returns counts by type. |
refresh_alerts_for_date(date) |
Re-evaluates all alerts for a single date. Deletes stale active alerts, creates new ones. |
auto_resolve_alerts_for_date(date) |
Resolves alerts whose conditions are now met. |
resolve_alert(alert) |
Sets an alert to RESOLVED status with a timestamp. |
dismiss_alert(alert) |
Sets an alert to DISMISSED status with a timestamp. |
get_active_alerts(type, severity, start_date, end_date) |
Queries active alerts with optional filters. |
All creation methods are idempotent -- they check for existing active alerts of the same type and date before creating a new one.
Admin Interface¶
Django Admin¶
AlertAdmin in config/admin.py provides:
- List display: type, severity, date, status, message
- Filters: type, severity, status, date
- Search: message text
- Date hierarchy: by date
REST API¶
The AlertViewSet in api/viewsets.py exposes:
| Endpoint | Method | Description |
|---|---|---|
/api/alerts/ |
GET | List alerts (active by default; pass ?active=false for all) |
/api/alerts/{id}/ |
GET | Retrieve a single alert |
/api/alerts/{id}/resolve/ |
POST | Resolve an alert |
/api/alerts/{id}/dismiss/ |
POST | Dismiss an alert |
The list endpoint defaults to showing only ACTIVE alerts. Alerts are ordered by date descending, then creation time descending.
Database Indexes¶
The Alert model defines one explicit index:
idx_alert_date_statuson(date, status)-- optimises the common query pattern of finding active alerts for a specific date or date range.