Skip to main content

heldar_kernel/services/
consumer.rs

1//! The kernel's perception-consumer seam.
2//!
3//! The media + AI-task kernel is domain-agnostic: it samples frames, accepts detections from workers,
4//! and persists them. Anything that *interprets* those detections into domain events — the zone engine
5//! (spatial rules), the ANPR/access-control engine (plate authorization), and other domain
6//! verticals — plug in here as a [`DetectionConsumer`] rather than being wired
7//! into the ingest handler or [`crate::state::AppState`] directly.
8//!
9//! This inverts the dependency: ingest fans a committed batch out to a registry of consumers; a
10//! consumer self-declares which `task_type`s it cares about. New apps are *added* to the registry (and,
11//! after the crate split, linked in by the composing binary or fed over the event stream) — the kernel
12//! never gains an `if task_type == "..."` branch.
13
14use chrono::{DateTime, Utc};
15
16use crate::models::DetectionIngest;
17
18/// One committed batch of detections handed to consumers after it is persisted. Carries the
19/// site/camera/task context so a consumer needs no extra lookups (and so the seam is tenant-aware for
20/// distributed deployments).
21pub struct DetectionBatch<'a> {
22    pub camera_id: &'a str,
23    pub site_id: Option<&'a str>,
24    /// The task type that produced this batch (consumers self-select on it; a vertical may also
25    /// inspect it). Part of the stable seam contract.
26    pub task_type: &'a str,
27    pub detections: &'a [DetectionIngest],
28    /// Worker-supplied capture time (engines that need trustworthy timing use server time instead;
29    /// time-windowing consumers use this).
30    pub timestamp: DateTime<Utc>,
31}
32
33/// A pluggable interpreter of detection batches. Implementors live in their own module/crate (zones is
34/// kernel-open; ANPR/entry is a proprietary app) and are registered into [`crate::state::AppState`].
35#[async_trait::async_trait]
36pub trait DetectionConsumer: Send + Sync {
37    /// Stable name for logs/metrics.
38    fn name(&self) -> &'static str;
39
40    /// Whether this consumer wants batches of the given `task_type`. Return `true` for all types when
41    /// the consumer is task-agnostic (e.g. the zone engine evaluates any tracked detection).
42    fn interested_in(&self, task_type: &str) -> bool;
43
44    /// Process a persisted batch. Must not panic; errors are the consumer's own to log/handle.
45    async fn consume(&self, batch: &DetectionBatch<'_>);
46}