Skip to main content

The Alert Lifecycle

A fire is reported at a lat/lon. Somewhere out there, some subscribers want to know and others have muted this hour or this area. AlertService is the machine that goes from "a thing happened here" to "these specific people's phones light up" — and getting the "these specific people" part right is the whole job. Send to too many and you train people to ignore alerts; miss the one dispatcher who needed it and the alert failed.

The shape of the lifecycle​

Creating and dispatching an alert is a short pipeline, each step feeding the next:

CreateAlertMessageAsync handles the left half: it turns the incoming point or ESRI polygon into an anomaly location with real geometry (the GetCircleFromPoint math from the SQL lesson), persists the alert message, and associates its recipients. CreateNotificationFromAlertMessage handles the right half — and that's where the interesting decision lives.

The one idea: two filters decide who's notified​

Once geography narrows the field to subscribers whose areas overlap the fire, two more filters run, and a subscriber has to pass both:

// Mirrors AlertService.FilterRecipientsByUrgencyAndDayTime
recipients = recipients.Where(r =>
(r.UrgencyLevels & alertMessage.UrgencyLevel) != 0 && !r.IsPaused);
// …then each surviving recipient must also match a day/time range (or have none).

The first is a bitmask test. Urgency levels are bit flags, and a subscriber is interested if their mask shares any bit with the alert's level — a single & that's zero or not. The second is a schedule test: a subscriber with no time ranges always passes, but one with ranges must have a range whose days include the alert's day and whose window contains the alert's time of day.

You'll internalize this faster by running it than by reading it. Same alert, six subscribers who all already overlap the fire's location — predict who actually gets notified:

📟 Who gets the alert?0 picked

Alert: Critical urgency, fired Wednesday 2:00 PM. These subscribers all overlap the fire's location — tap the ones you think actually get notified.

Three things that game just made concrete are the real semantics: paused beats urgency (a muted subscriber is dropped even when the level matches), the urgency test is a bitwise overlap (not equality — a Warning|Critical subscriber matches a Critical alert), and no time range means always-on (restrictions only ever remove hours, never add them).

After the filter​

Survivors become a Notification plus one UserNotification per recipient. AlertService resolves its own repository dependencies internally when you call AddAlertService — location, subscription, message, notification, and the three alert-message junction repositories — so the lifecycle is self-contained once registered. NotificationService then reads those rows back for the UI, assembling the NotificationDTO with sender, recipients, and the alert's map image and first camera image.

Gotcha: an empty recipient set is a valid, silent outcome

If the urgency/schedule filter removes everyone, CreateNotificationFromAlertMessage has nothing to create — no notification, no error. That's correct behavior (nobody was subscribed and available), but it means "the alert did nothing" and "the alert failed" look identical from the outside. When debugging a missing alert, confirm subscribers survived the filter before suspecting the dispatch.

Wrapping up​

AlertService is a pipeline: geometry defines where the alert applies, an intersection query finds subscribers who overlap it, and a two-part filter — a bitwise urgency overlap plus a day/time schedule, both of which must pass — decides who's actually notified. The survivors become one notification and a row per recipient. Keep the "paused beats urgency" and "overlap, not equality" rules in mind whenever an alert reaches the wrong-sized audience.

Next, how those notifications actually leave the building: Reaching People: Email, SMS, and Phone.