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:
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.
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.