Skip to main content

Event Burst Bounds

Before this change, the only thing stopping new detections from stacking onto an existing open event was events.is_active, and the only thing that ever cleared that flag was a 48-hour cron job (fn_deactivate_stale_primary_events). In between, there was no limit at all -- a detection arriving hours or days after the original could still stack onto it as the same incident, and a faulty camera producing a continuous stream of false triggers could pile thousands of detections onto a single event (one observed case reached 11,013 images) before anyone noticed.

Event burst bounds add two independent, database-level limits on how big a single incident (a "burst" of stacked detections) is allowed to get before the next detection starts a new incident instead:

  • A quiet window -- if a burst has gone quiet for long enough, the next detection starts fresh.
  • A burst cap -- if a burst has accumulated too many detections, the next detection starts fresh, and the source camera is flagged as a likely fault.

This doesn't replace the existing 48-hour TTL cron -- it adds two much tighter bounds that act immediately, on every new detection, instead of waiting up to 48 hours for a stale event to be cleared.

No CleverOps UI for the bounds themselves

This feature is live in production (deployed 19 July 2026, migration 20260718j_event_burst_bounds.sql). Both bounds apply to every new detection.

There is no CleverOps screen for the bounds themselves -- no settings page for the platform defaults and no per-site override fields. Those values are read and written directly in the database, so treat this page as a reference for support and engineering until an admin UI is built. The one thing you can already set from the normal site UI is the Continuous-movement Site Mode that suppresses the camera-fault flag -- see Runaway camera flag below.

The flag's result is a different story: once a stream trips it, a "Flooding" badge now surfaces in CleverOps even though the bounds settings themselves still don't. See Where the flag shows up below.

Why two separate bounds​

The two bounds catch two different failure shapes seen in live data:

  • Unrelated incidents glued together. Nothing ever ended a burst on its own, so a detection long after the original could still be treated as part of the same incident.
  • A genuinely continuous flood. A moving branch, an insect on the lens, or a flickering light can keep a camera detecting continuously for hours, with only a few minutes between detections -- too short a gap for any quiet window to ever catch. Only a hard cap on the count stops this one.

Quiet window​

If an event has had no new activity for longer than the quiet window, the next detection starts its own new event instead of stacking onto the old one.

The window is measured from the burst's most recent activity, not from when the burst started -- so a genuinely continuous incident (detections arriving steadily, a few minutes apart, for hours) is never split by the quiet window alone. Only a gap longer than the window between two consecutive detections triggers a split.

Default: 30 minutes, platform-wide (can be overridden per site). This comes from analysing 30 days of live traffic: 99.9% of gaps between consecutive stacked detections were under 30 minutes, and almost none exceeded 4 hours. A 30-minute window only re-groups roughly 0.1% of detections into a new incident that arguably should have stayed with the original.

Burst cap​

Once an event has accumulated the cap number of detections, the next detection starts a new event instead of adding to the same incident.

Default: 200 detections, platform-wide (can be overridden per site). This comes from the same traffic analysis: normal bursts peak around 125 detections, while the small minority of bursts that exceed 200 -- under 0.5% of all bursts -- account for roughly a quarter of every stacked detection recorded platform-wide. The worst single burst observed ran for 8.6 hours with no gap wider than 4.9 minutes between detections -- exactly the kind of runaway flood a quiet window alone could never catch.

Runaway camera flag​

When the burst cap is what triggers the split (the quiet window never does this), the camera that produced the detections is flagged as a likely fault: video_streams.runaway_detected_at is stamped with the current time and video_streams.runaway_burst_event_id records which event it capped (only for detections tied to a camera stream). The reasoning: a camera that produces 200 detections in one uninterrupted burst is very likely pointed at something that shouldn't be triggering detection at all, and someone should go fix the root cause.

Exception: sites in Continuous-movement mode. For sites where constant movement is normal -- yards, thoroughfares, public areas -- rather than typical business or residential premises, the burst cap still applies (an event still can't grow past the cap), but hitting it does not raise the camera-fault flag. Constant detection is the expected condition at those sites, so capping the event there is just housekeeping, not evidence of a broken camera.

This is the Site Mode you already set

The exception reads the existing Continuous-movement Site Mode -- the one set per site on Sites → (site) → Monitoring → Setup in the Mode card, documented in Site Classification. There is no separate "expects continuous motion" flag to maintain: putting a site into Continuous-movement mode is what suppresses the fault flag, and the burst cap keeps applying either way.

Site Mode is stored per control room, so a site watched by two VCRs can carry a different mode in each. If any control room watching the site has it in Continuous-movement mode, no camera-fault flag is raised there -- deliberately the conservative direction, since the flag asserts that a camera is broken.

Where the flag shows up​

The bounds settings still have no UI (see the warning at the top of this page), but the flag's result does, in two places, once a stream has a recent runaway_detected_at:

  • Performance & Reports → Sites → Cameras -- the flagged stream is pulled into the "needs attention" list even when its status and snapshot are otherwise healthy, a dedicated Flooding detections stat card counts flagged streams (independently of the Hub offline / Device offline / Issue detected / Other counts -- a stream can count toward more than one at once), and an amber "⚠ Flooding" badge appears next to the row's status. If the stream has no status string at all, the row now reads "Flooding detections" instead of the misleading "Offline". See Site Quality: Flooding cameras for the details.
  • Site modal → Hardware tab -- the same amber "⚠ Flooding" badge appears on the stream's row, beside the existing Talk / Siren / Strobe deterrence badges.

Both surfaces read runaway_detected_at directly and only show the badge while it's less than 24 hours old -- it clears itself once a camera stops flooding, with no manual dismiss action. A flagged camera is the inverse of an offline one: it's online, reporting a fresh snapshot, and often carries a perfectly healthy status like "Armed" -- which is exactly why it needed a signal of its own instead of relying on the sub-tab's existing offline/stale-snapshot checks.

What happens when a bound is hit​

The detection that would have stacked onto the existing event is instead promoted to its own new primary event -- the same mechanism that makes any event appear as a fresh card in the CleverCommand queue -- so it is worked as an independent incident from that point on.

The event that got capped also receives an internal note, "Incident bounded," recorded on its action trail:

  • For a quiet-window split, the note explains that a new detection started its own incident after the configured number of minutes with no activity.
  • For a burst-cap split, the note explains that the incident reached the configured detection cap and further detections will start a new incident.

That note is internal only -- it's written to the event's history for operators, but it is never shown to the customer or app user.

If writing that note or setting the runaway-camera flag fails for any reason, the split itself still goes ahead. The worst case is a missing breadcrumb, never a lost detection or a blocked insert.

Doesn't currently feed pattern detection

The signal this raises internally (runaway_burst_capped) is deliberately kept outside the Brain's pattern detection allowlist, so hitting a burst cap does not, by itself, create or feed a Review Queue item today.

Settings​

ScopeWhere it livesKey / columnDefaultTo disable
Platform-wide defaultplatform_settings tableevent_quiet_window_minutes30set to 0
Platform-wide defaultplatform_settings tableevent_burst_cap200set to 0
Per-site overridesites tableevent_quiet_window_minutesempty (inherits the platform default)set to 0
Per-site overridesites tableevent_burst_capempty (inherits the platform default)set to 0

A site's effective bounds resolve in this order: the site's own override, then the platform default, then a hard-coded fallback (30 minutes / 200 detections) if platform_settings is somehow missing or unreadable. Setting either the platform default or a site's override to 0 (or a negative number) disables that specific bound -- the quiet window and the burst cap are independent switches, so a site can run with only one of the two active, or neither.

Because the resolver runs on every alarm insert, it's deliberately fail-safe: a malformed or missing platform_settings value falls back to the hard-coded default rather than raising an error and blocking the detection.

Where it's enforced​

The bounds are enforced in a single BEFORE INSERT trigger on the events table, rather than inside any one application code path. That means every writer that inserts a stacked detection is covered the same way -- the normal and duress branches of the core alarm handler, the alarm_panel_webhook_batch edge function, and the v2 alarm event handler all pass through the same check, with no risk of one path being bound and another left unlimited. The trigger is ordered to run after CleverCam's other pre-insert triggers (icon assignment, app-origin verification, AI cross-verification, silent-log downgrades), so it always has the final say on whether a detection joins an existing incident or starts a new one.

  • Auto-complete and Pattern detection -- other features that follow the same default-plus-per-site-override shape, though both already have a screen in CleverOps, unlike this feature today.
  • Site Classification -- where the Continuous-movement Site Mode that suppresses the camera-fault flag is set.
  • Site Quality: Flooding cameras -- where the runaway-camera flag actually surfaces today: a stat card, a row badge, and a status fallback for flagged streams.