Rules & events
A rule binds a zone, an object class, and a trigger. When it fires, you get an event: a discrete, timestamped, countable fact about the scene.
Anatomy of a rule#
{
"id": "rule_gate_count",
"zone": "zone_a",
"classes": ["car", "van", "truck"],
"trigger": "enter",
"min_dwell_s": 0
}Four triggers exist today:
| Trigger | Fires when |
|---|---|
enter | An object transitions from outside the zone to inside it |
exit | An object that was inside leaves |
dwell | An object stays inside longer than min_dwell_s |
count | Continuously; maintains a running total of distinct entries |
What counts as inside#
The naive test, box center inside polygon, undercounts. A car can clip the corner of a zone with a third of its body and never put its center inside. Praxis instead samples a 5×5 grid across the object's box and requires at least 25% of the samples to land inside the zone's polygon for that frame. Entries register when a car is genuinely in the zone, including partial passes along an edge.
One car, one event#
The counting failure modes come in mirrored pairs, and both get handled explicitly:
- Double counting. A track ID switch mid-crossing looks like two entries. Candidate duplicate events get merged, but only when their two tracks never coexist in the same frames. Coexistence proves two physical objects.
- Undercounting. Two cars entering side by side at the same moment are the classic casualty of aggressive merging. The coexistence test keeps them separate: both tracks were alive simultaneously, so both entries stand.
The event payload#
{
"id": "evt_9f2c",
"type": "zone.entry",
"rule": "rule_gate_count",
"zone": "zone_a",
"feed": "src_boulevard",
"t": 7.307,
"track": 41,
"class": "car",
"position": [0.626, 0.741],
"evidence": {
"crop": "/evidence/evt_9f2c/crop.jpg",
"track_history": "/evidence/evt_9f2c/track.json",
"zone_geometry": "/evidence/evt_9f2c/zone.json"
}
}Positions are normalized to frame size. The evidence block is not optional decoration; see Evidence for why every event carries it.