Context
While working on the false-positive context view of /localize/:sequenceId (#PR), an alert surfaced where the smoke object spanned 12 frames and the false-positive object spanned 13 — reading, at a glance, like one continuous event whose smoke might have persisted into the later frames. It isn't. The two lanes are 119 minutes apart.
That prompted an audit of how lanes under one platform_alert_id relate temporally. This is distinct from #266 (below-threshold sibling drops) and #262 (cross-alert sibling lanes): those are about boxes that should have become lanes. This is about lanes that are correctly formed but grouped into one alert despite belonging to different detection episodes.
Audit run 2026-08-04 against the local stack.
Finding
Of 39 alerts with more than one lane (47 lane pairs), 12 pairs across 9 alerts share zero timestamps.
Ten of those pairs have genuinely disjoint time windows:
| platform_alert_id |
lane A (frames) |
lane B (frames) |
gap |
| 55664 |
57 (12) |
58 (13) |
119 min |
| 55664 |
57 (12) |
59 (11) |
119 min |
| 54283 |
200 (14) |
202 (8) |
44 min |
| 54283 |
201 (8) |
202 (8) |
36 min |
| 53770 |
249 (23) |
250 (7) |
26 min |
| 53084 |
178 (24) |
179 (6) |
13 min |
| 54666 |
100 (19) |
101 (11) |
12 min |
| 54283 |
200 (14) |
201 (8) |
6 min |
| 55077 |
111 (19) |
112 (10) |
4 min |
| 53797 |
243 (6) |
244 (6) |
0 min (adjacent) |
The remaining two (53387 lanes 224/225, 53758 lanes 253/254) have overlapping windows but still zero shared timestamps — interleaved frames rather than a shared frame set.
Worked example, alert 55664:
| Lane |
Classification |
Window |
Frames |
| 57 |
smoke |
06:58:30 → 07:01:31 |
12 |
| 58 |
false positive (cloud) |
09:01:00 → 09:08:01 |
13 |
| 59 |
unsure |
09:01:00 → 09:08:30 |
11 |
Lanes 58 and 59 share 10 timestamps with each other — a normal object split of one episode. Lane 57 shares none with either. It is a separate detection event, two hours earlier, wearing the same platform_alert_id.
Why it matters
-
The collocated screens are built around "one alert = one moment." /classify/:id and /localize/:id both render an alert's lanes side by side against a shared frame union. When lanes are two hours apart, that union is a timeline with a two-hour hole, and the object rows describe things that were never on screen together.
-
It invites a wrong annotation. The false-positive context view exists to answer "is that plume already accounted for?" before someone adds a duplicate object. If the FP object is from a different episode, that question has no meaningful answer — and the reasonable-looking inference ("the smoke object probably continues into those later frames") is false.
-
It distorts alert-level state. The missed-smoke flag, the submit gate, and the "N of M objects localized" badge are all alert-scoped. Two episodes in one alert means one submit decision covering two unrelated events.
What I could not determine
Whether the grouping originates upstream (the alert API assigning one platform_alert_id across a 2-hour relaxation window — note SEQUENCE_RELAXATION_SECONDS = 7200 in object_clustering.py, which is exactly the 119-minute case) or in our import. object_split.py sets platform_alert_id to the parent alert sequence id, so if two episodes arrive under one alert sequence, our split faithfully preserves that. Worth checking against the alert API before deciding where a fix belongs.
Reproduction
WITH lane AS (
SELECT s.platform_alert_id, s.id AS seq_id,
min(d.recorded_at) AS t0, max(d.recorded_at) AS t1, count(*) AS n
FROM sequences s JOIN detections d ON d.sequence_id = s.id GROUP BY 1, 2)
SELECT a.platform_alert_id, a.seq_id AS lane_a, a.n AS n_a, b.seq_id AS lane_b, b.n AS n_b,
CASE WHEN a.t1 < b.t0 OR b.t1 < a.t0 THEN 'disjoint' ELSE 'overlapping' END AS window_rel,
CASE WHEN a.t1 < b.t0 THEN round(extract(epoch FROM (b.t0 - a.t1)) / 60)
WHEN b.t1 < a.t0 THEN round(extract(epoch FROM (a.t0 - b.t1)) / 60) END AS gap_min
FROM lane a
JOIN lane b ON a.platform_alert_id = b.platform_alert_id AND a.seq_id < b.seq_id
WHERE (SELECT count(*) FROM detections d1 JOIN detections d2 ON d1.recorded_at = d2.recorded_at
WHERE d1.sequence_id = a.seq_id AND d2.sequence_id = b.seq_id) = 0
ORDER BY gap_min DESC NULLS LAST;
Possible directions
Not proposing one — the upstream question above should settle it first.
- Split on the temporal gap at import. If lanes within one alert sequence are separated by more than some threshold, emit them under distinct
platform_alert_ids. Cleanest for the UI, but invents alert identity we don't own.
- Leave grouping alone; make the screens honest about it. Show an explicit break in the frame union / timeline when consecutive frames are far apart, so a two-hour hole reads as a hole rather than as adjacency. Cheaper, no data changes, and useful regardless of what upstream does.
- Confirm it's an alert-API artifact and do nothing here. Viable if the platform genuinely intends these as one alert.
Related
Context
While working on the false-positive context view of
/localize/:sequenceId(#PR), an alert surfaced where the smoke object spanned 12 frames and the false-positive object spanned 13 — reading, at a glance, like one continuous event whose smoke might have persisted into the later frames. It isn't. The two lanes are 119 minutes apart.That prompted an audit of how lanes under one
platform_alert_idrelate temporally. This is distinct from #266 (below-threshold sibling drops) and #262 (cross-alert sibling lanes): those are about boxes that should have become lanes. This is about lanes that are correctly formed but grouped into one alert despite belonging to different detection episodes.Audit run 2026-08-04 against the local stack.
Finding
Of 39 alerts with more than one lane (47 lane pairs), 12 pairs across 9 alerts share zero timestamps.
Ten of those pairs have genuinely disjoint time windows:
The remaining two (53387 lanes 224/225, 53758 lanes 253/254) have overlapping windows but still zero shared timestamps — interleaved frames rather than a shared frame set.
Worked example, alert 55664:
Lanes 58 and 59 share 10 timestamps with each other — a normal object split of one episode. Lane 57 shares none with either. It is a separate detection event, two hours earlier, wearing the same
platform_alert_id.Why it matters
The collocated screens are built around "one alert = one moment."
/classify/:idand/localize/:idboth render an alert's lanes side by side against a shared frame union. When lanes are two hours apart, that union is a timeline with a two-hour hole, and the object rows describe things that were never on screen together.It invites a wrong annotation. The false-positive context view exists to answer "is that plume already accounted for?" before someone adds a duplicate object. If the FP object is from a different episode, that question has no meaningful answer — and the reasonable-looking inference ("the smoke object probably continues into those later frames") is false.
It distorts alert-level state. The missed-smoke flag, the submit gate, and the "N of M objects localized" badge are all alert-scoped. Two episodes in one alert means one submit decision covering two unrelated events.
What I could not determine
Whether the grouping originates upstream (the alert API assigning one
platform_alert_idacross a 2-hour relaxation window — noteSEQUENCE_RELAXATION_SECONDS = 7200inobject_clustering.py, which is exactly the 119-minute case) or in our import.object_split.pysetsplatform_alert_idto the parent alert sequence id, so if two episodes arrive under one alert sequence, our split faithfully preserves that. Worth checking against the alert API before deciding where a fix belongs.Reproduction
Possible directions
Not proposing one — the upstream question above should settle it first.
platform_alert_ids. Cleanest for the UI, but invents alert identity we don't own.Related