Advisors
Alerts
A daily watch on your visibility that raises an alert when something moves enough to be worth reading, and shows why it fired.

What this is
Alerts is a daily comparison the
watchdog runs for every active project, no model involved: the last 7
days measured against the 7 days before them, with a row written when
one of four specific changes crosses its threshold
(api/services/watchdog.py#detect_alerts_for_project). Every project
gets this whatever plan it is on; the only part of it gated by plan is
the email copy of a day's alerts (see Delivery below).
What can trigger one, and at what threshold
Four kinds, each with its own floor and threshold
(api/services/watchdog.py#build_aggregate_alerts,
#build_prompt_alerts):
- Visibility drop: your own visibility across the whole project fell
at least 20% relative to the week before, counted only when that prior
week's own visibility was itself at least 10%
(
#OWN_DROP_THRESHOLD,#OWN_DROP_BASELINE_MIN). - Competitor surge: evaluated only while your own visibility is under
20%. A tracked competitor whose own visibility reached at least 10%
rose at least 30% relative to its own prior week
(
#OWN_VIS_WEAK_CEILING,#COMPETITOR_MIN_VISIBILITY,#COMPETITOR_SURGE_THRESHOLD). - Prompt drop: one prompt's own visibility fell at least 25% relative
to its prior week, counted only when that prompt's prior-week
visibility was itself at least 15%
(
#PROMPT_DROP_THRESHOLD,#PROMPT_DROP_BASELINE_MIN). - Rank slide: a prompt still cites you about as often as before (it
did not also cross the drop threshold above), but your average
citation rank on it was in the top 5 the week before and slid by at
least 2 full positions
(
#RANK_SLIDE_TOP_N,#RANK_SLIDE_MIN_POSITIONS).
Two volume floors before any of that is checked
Before the thresholds above are evaluated at all, the project needs at
least 5 days of measured history in the prior week, and at least 20
completed answers, project-wide, in both the prior week and the current
one; short of either floor the whole project is skipped for that day,
whatever the raw percentages would say
(#BASELINE_MIN_DAYS, #MIN_WINDOW_EXECUTIONS,
#detect_alerts_for_project). A prompt-level check adds a second, its
own: that one prompt itself needs at least 20 answers in both windows,
not just the project as a whole (#_has_window_volume,
#build_prompt_alerts).
Runs once a day, automatically
A systemd timer fires at 04:00 UTC and runs api.cli run-watchdog, which
enqueues one watchdog job for every project that is active and whose
owner is active, with no opt-in setting and no plan check at this step
(discoveredby-cli@run-watchdog.timer, api/cli.py#cmd_run_watchdog,
api/tasks.py#_enqueue_watchdog_for_all_projects). Each job runs as its
own background task and calls the same detection and persistence logic
described above (#run_visibility_watchdog_task,
api/services/watchdog.py#run_watchdog_for_project). The only other path
that reaches it is an admin running it manually for one project or every
project from the internal admin panel, not something a regular user can
do (api/routers/admin.py#admin_trigger_watchdog,
#admin_trigger_watchdog_all).
A repeat is suppressed, not re-shown
A change that would fire the same kind again for the same project, the
same prompt (or none) and the same competitor (or none) within the last 7
days is dropped before it is ever written, so a drop that stays down does
not post a fresh alert every single day
(api/services/watchdog.py#_is_duplicate, #DEDUPE_WINDOW_DAYS).
Why it fired is stored, not recomputed
Everything behind "why it fired", the exact before and after figures, the
engine-by-engine breakdown, whether competitors moved too, is built once,
at the moment the alert is detected, and saved on the row itself
(#run_watchdog_for_project). The detail page you open later reads that
saved record; it does not re-run the comparison against today's data
(api/routers/alerts.py#get_alert, #_to_read).
The boundary with notifications
Notifications is the account's inbox, and
an alert is one of seven things that can land in it, not the only one.
When the watchdog persists one or more fresh alerts for a project, it
writes exactly one notification per project member, summarizing every
alert from that run together, and links each of those alert rows back to
the notification created for the project's owner
(api/services/watchdog.py#run_watchdog_for_project). A day with three
alerts still produces one notification, not three. The reverse does not
hold at all: most
notifications are not about an alert, see
Notifications for the other six.
Delivery
Every fresh alert always appears on this screen and in the notification
above. On top of that, when the project owner is on a paid plan and has
an email on file, the day's alerts, up to the 5 highest by impact, also
go out as one email; without either of those, the alert still exists,
just without the email copy (#run_watchdog_for_project, #DIGEST_TOP_K,
api/services/email.py#send_visibility_alert_email).
What's on the screen
The list comes back ordered by the day each alert was detected, so it opens
on the most recent alert rather than the biggest
(api/routers/alerts.py#list_project_alerts,
frontend/src/routes/(app)/alerts/+page.svelte#latest). Every alert does
carry an impact_score, and all four kinds build it the same way: the size
of the move, times the visibility it moved from, times the log of how many
answers ran in the current week, plus a small constant on the two kinds
that are not about a single prompt
(api/services/watchdog.py#_impact_score, #build_prompt_alerts). What
supplies the size differs, positions for a rank slide and a relative
visibility change for the other three, so the score is what ranks a day's
alerts against each other for the email above, rather than a number to read
on its own.
Left alone, the list opens on the last 28 days, and you can switch it to 7,
28, or 90 days; 180 is the ceiling this screen enforces regardless of what
you put in the URL
(frontend/src/routes/(app)/alerts/+page.server.js#MAX_WINDOW_DAYS). The
API returns up to 200 alerts for the window, and the screen says so once a
list comes back full (#ROW_CAP,
api/routers/alerts.py#list_project_alerts). Below the hero, alerts
group by the day they were detected. Opening one shows the before and
after pair for that kind, the two date ranges being compared, which
engine moved most, whether competitors moved too or this looks specific
to you, and, for a rank slide, the rank movement itself
(frontend/src/routes/(app)/alerts/[id]/+page.svelte#movement,
#category).
Related
- Notifications: the account-wide inbox an alert lands in, and the other six kinds that land there too
- Competitors: the comparison a competitor surge alert draws on
- Prompts: where a prompt drop or rank slide links back to
Last verified 2026-08-11