pub async fn fan_out(
pool: &SqlitePool,
consumers: &[Arc<dyn DetectionConsumer>],
batch: &DetectionBatch<'_>,
frame_id: Option<&str>,
) -> boolExpand description
Drive every interested consumer for one committed batch, AT MOST ONCE per
(consumer, camera_id, frame_id).
This is the single fan-out path used by both the ingest handler (inline, low-latency) and the
durable drainer (crate::services::fanout, which replays batches whose fan-out didn’t complete
because the process crashed between commit and fan-out). The per-consumer claim row in
consumer_fanout is what makes replay safe: a consumer that already saw this frame is skipped, so
a replayed batch never double-drives it (no double-counted ANPR votes / zone state).
A batch without a frame_id cannot be deduped, so its consumers are driven directly and it is not
eligible for durable replay (the worker opted out of the idempotency key).
Returns true when every interested consumer was driven or was already done — i.e. the batch is
fully fanned out and may be marked complete. Returns false if a dedup-claim write failed for some
consumer, so the caller leaves the batch un-fanned for the drainer to retry (the consumers that
already succeeded stay claimed and will not re-run).