A construction site theft alert is five fields and 90 seconds. Everything else is decoration.
Most construction site theft alerts are useless. They land on the on-call's phone as a paragraph of marketing chrome with a generic "motion detected" tag, no zone, no dwell, no clip, and no way to triage in the 90 seconds before the truck is loaded and gone. By week three the on-call has stopped answering, the GC has stopped paying for the service, and the cameras are back to doing what they were doing in 1998: recording for the police report.
This is what an alert that actually converts to a prevented loss looks like, field by field, second by second, on a real edge AI retrofit running on a CCTV trailer. The walkthrough is from a Cyrano unit, but the shape is what to look for in any vendor in this category. If a vendor cannot tell you what the five fields are, walk away.
Short answer
A useful construction site theft alert is one phone-screen tall, carries five fields, and lets the on-call decide what to do in about 90 seconds. The five fields are: camera identifier, zone name, dwell in seconds, threat level (low_threat or high_threat), and a tap-to-play 10-second clip. Anything less and the on-call cannot triage. Anything more and the text gets ignored.
The system around those five fields is what keeps them useful: a per-zone arming schedule (so the 0430 concrete truck does not fire the lay-down yard), a dispatch tree with timeouts (call, SMS, webhook, email), and a local append-only outbox file so a cellular outage cannot drop a real event. The rest of this page walks through one real alert from T-0 to T+90 seconds.
“At one Class C multifamily property in Fort Worth, Cyrano caught 20 incidents including a break-in attempt in the first month. Customer renewed after 30 days.”
Fort Worth, TX deployment, Class C 180-unit
Multifamily numbers because that is the longest-running deployment. The construction-site shape of the same product runs the same perception, state, and planner tiers on the cameras already wired into the CCTV trailer.
The five fields the on-call has to see
A useful alert text is one screen tall on a phone held above the face at 2 AM. It carries five fields, in this order, and nothing else. If a field is missing the on-call cannot triage. If a field is added the text scrolls and gets ignored.
The five-field rule
- Camera identifier (e.g. rear-fence-04). The on-call has to know which side of the site without thinking.
- Zone name (e.g. fence-line-east). Not coordinates. A name a human assigned during install.
- Dwell in seconds. How long the subject has been continuously inside the zone.
- Threat level (low_threat or high_threat). The router decided this; the on-call does not have to.
- Clip path. A direct link to a 10-second clip starting at T minus 2 seconds. Tap, watch, decide.
Anything that is not one of those five fields belongs in the dashboard, not in the alert. Site address, alert id, line number in the outbox: useful for forensics, useless on a phone at 2 AM. The full row in /var/lib/cyrano/meta/outbox.ndjson carries those forensic fields, the alert text does not.
What the row in the outbox actually looks like
Before any network call leaves the device, the planner writes one JSON line to an append-only file. The text and phone call are triggered by an outbound worker that reads the file. This is the ordering that survives a cellular outage, because the row exists on local disk before anyone knows whether the modem is up.
Three rows in 30 days on a properly tuned fence-line zone. That is the signal density an on-call can stay sharp on. If the row count for a zone is 30 in 30 days, the dwell or polygon is wrong and the on-call is going alert-blind.
One alert, T-0 to T+90 seconds
The same incident as it actually unfolds. Times are wall clock from the moment the subject crosses the fence-line polygon. None of the steps below involve a human watching a monitor; the only human in this loop is the on-call after their phone rings.
T-0: a person crosses the fence-line polygon at 02:14:33
The perception tier emits a detection: class person, bounding box, track id 4811, camera rear-fence-04. The state tier looks up the zone polygon (fence-line-east) and finds the zone is armed (1900 to 0600 schedule). The dwell counter for (track 4811, fence-line-east) starts at zero.
T+6: dwell threshold crossed
Track 4811 has been continuously inside fence-line-east for 6 seconds, the configured dwell for this zone. The state tier emits a transition. The planner runs the four-tool loop (zone, dwell, router, outbox). The router classifies the event as high_threat because the time is 02:14, the zone is fence-line-east, and the dwell is positive.
T+7: outbox.ndjson gets a new line
Before any network call, the planner appends a single JSON line to /var/lib/cyrano/meta/outbox.ndjson with five fields plus metadata: camera, zone, dwell, level (high_threat), clip path. Line number is now 11843. A 10-second clip starting at T-2 is rendered in parallel and saved to /var/lib/cyrano/clips/.
T+9: phone rings on the on-call superintendent
The outbound worker reads line 11843, picks the dispatch tree for high_threat events, and places a phone call to the primary number. The text body of the SMS that fires in parallel is one line: site name, zone name, dwell, clip url. No paragraphs. No marketing chrome. The on-call wakes up.
T+22: on-call opens the clip
The on-call taps the clip url. The clip is served directly from the device over the cellular link, not from a cloud bucket. They see one person walking south to north along the inside of the east fence, near the copper coil pallet. Not a tarp. Not a dog.
T+47: on-call calls non-emergency dispatch
The on-call dials the local non-emergency line, gives the site address, the alert id, and the clip url. Average dispatch on most municipalities is a 6 to 12 minute response on a confirmed in-progress trespass with video.
T+90: subject leaves the lot
Most construction site theft attempts are exploratory. The subject hears the gate motion sensor or sees a flashlight beam coming up the access road and walks. The pallet stays. The schedule does not slip. The on-call goes back to bed and the outbox row gets its delivery_state field updated to acked_by_oncall.
The 90 second budget is not aspirational. It is the time window in which a subject who is hopping a fence to scout copper has not yet committed to a load. Past 90 seconds the truck is being loaded and the calculus shifts from prevention to recovery, which is a different (and much worse) economic event.
The arming schedule the on-call never sees
Site-wide arm/disarm is the wrong primitive for a construction site. The trades arrive in waves between 0530 and 0700. Concrete shows up at 0430 some days. Inspectors come at 1500 on a Tuesday. The fuel area is hands-off all the time. A site-wide arm flag is wrong every 90 minutes, and every wrong moment is either a missed alert or a pointless one.
The right primitive is a per-zone arming schedule. Each zone has its own polygon and its own clock, and the planner consults zone_polygon_lookup before the dwell counter runs. A typical schedule on a small commercial build looks like this.
| Zone | Armed | Dwell | Default level |
|---|---|---|---|
| fence-line-east | 1900 to 0600 | 6 sec | high_threat |
| lay-down-yard | 1730 to 0700 | 12 to 20 sec | low_threat at 12, high at 20 |
| fuel-storage | 24 hours | 0 sec (first frame) | high_threat |
| main-gate | 1700 to 0600 | 8 sec | low_threat (vehicle), high_threat (person) |
| office-trailer | 2000 to 0530, weekends 24h | 4 sec | high_threat |
| copper-coil-pallet | 24 hours | 3 sec | high_threat |
Six zones, six clocks, six dwell budgets. None of this is exposed in the alert text the on-call sees, because none of it is needed at triage time. It is what makes the alert text fire when it should and stay quiet the rest of the time.
The dispatch tree behind every row
One outbox row produces several outbound actions, in a tree, with timeouts. The tree is what survives a single missed phone call. The tree is also what fails gracefully if the cellular link drops mid- event: the row stays in the file, the worker resumes from the last acked line on reconnect.
Dispatch tree for high_threat
- T+9 sec: phone call to on-call site superintendent (primary)
- T+9 sec: SMS to same number with site, zone, dwell, clip url (parallel)
- T+30 sec: SMS to project manager (secondary) if primary unanswered
- T+45 sec: webhook into the GC's central ops dashboard (always fires)
- T+60 sec: email to shared ops inbox if no acknowledgement (tertiary)
- On reconnect: outbound worker resumes from last acked line_no, in order
The same tree fires on a 5-camera trailer or a 25-camera permanent site. The dispatch tree is configured per site in a JSON file the on-call team can tune from the dashboard without a redeploy.
What changes when the alert is structured vs. when it is not
The same incident, two different alert shapes. The left side is what most existing CCTV systems on a job site emit (motion alerts from the DVR, every event, every hour). The right side is what a properly structured edge AI alert looks like.
Motion detected on Camera 04. Tap to view. (sent at 02:14, again at 02:14, again at 02:15, then 41 more times overnight from wind, headlights, and a stray dog.)
- No zone name. The on-call does not know if it is the fence or the gate.
- No dwell. Could be a tarp flap or a person.
- No threat level. Every alert weighs the same.
- No clip. The on-call has to log into the DVR portal to triage.
- By week two the on-call has muted the app entirely.
What to ask any vendor selling jobsite theft alerts
Five questions that separate vendors who have shipped a real alert pipeline from vendors who are reselling a generic NVR app with push notifications. None are gotchas. They are the questions anyone running an on-call rotation knows the answers to.
- What fields are in the alert text the on-call gets? A real answer names them. A non-answer points at a screenshot.
- Is the dwell threshold per zone or global? A real answer is per zone. A global dwell means the lay-down yard and the fuel area share a number, which is wrong on its face.
- What happens if the cellular link drops for two hours? A real answer describes a local append-only file and an in-order replay. A non-answer is "the cloud handles failover."
- Is the per-zone arming schedule a JSON file or a dropdown with three preset modes? A real answer is a file. A dropdown means the schedule is somebody else's problem, and that somebody is you, in the UI, every Friday at 1700.
- What is your typical alerts-per-week number on a tuned 5-camera jobsite? A real answer is in the single digits. If a vendor is proud of hundreds, the on-call has already muted them.
A vendor who can answer all five with specifics is selling something an on-call can stay sharp on. A vendor who cannot is selling a notification firehose.
See one real alert, end to end, on a CCTV trailer
10 minutes on a call. We will pull a recent outbox row from a deployed unit, walk through the five fields, and show the dispatch tree firing on a test event.
Specific questions, specific answers
What is the minimum information a useful construction site theft alert has to carry?
Five fields. Camera identifier, the zone the trigger fired in, the dwell in seconds (how long the subject has been in that zone), the threat level (low_threat or high_threat), and a path to a 10-second clip the on-call can scrub on the phone. Anything less and the on-call cannot decide whether to call dispatch in the 90 seconds before the truck pulls away. Anything more and the text is too long to read on a glance. On a Cyrano unit those five fields are the literal contents of one JSON line in /var/lib/cyrano/meta/outbox.ndjson, written before any network call leaves the device, so the alert exists locally even if the cellular link is down.
Why does dwell matter? A trespasser is a trespasser.
Because most events at a construction site are not trespassers. They are wind moving a tarp across the lay-down yard, a stray dog walking the perimeter, a city worker checking a fire hydrant from the sidewalk side of the fence. Without dwell, every one of those becomes a 2 AM phone call, and the on-call stops answering by week two. Dwell turns the alert from 'something moved' into 'something has been inside the fenced area for 18 seconds and is still there.' On a typical retrofit deployment, fence-line zones use a 6 to 10 second dwell, lay-down yard zones use 12 to 20 seconds, and the fuel storage zone fires on the first frame because nothing legitimate happens there after dark.
What is a per-zone arming schedule and why is it different from arming the whole site?
Site-wide arm/disarm is what alarm panels do. You set the system to 'armed away' when the last person leaves and 'disarmed' when the first crew arrives. Construction sites do not work that way. The trades arrive in waves between 0530 and 0700. Concrete shows up at 0430 some days. Inspectors come at 1500 on a Tuesday. The fuel area is hands-off all the time. The crane operator sleeps in the trailer. A site-wide arm flag is wrong every 90 minutes. A per-zone schedule says fence-line is armed 1900 to 0600, lay-down yard is armed 1730 to 0700, fuel storage is armed 24 hours, gate is armed 1700 to 0600, and the office trailer is armed 2000 to 0530 with a separate weekend rule. Each zone has its own polygon and its own clock, and zone_polygon_lookup decides per event whether to even consider the dwell counter.
Who actually gets the alert, in what order, and what happens if they do not pick up?
The dispatch tree on a typical small-GC retrofit is three deep. Primary is a phone call to the on-call site superintendent. Secondary is an SMS to the same number plus the project manager, fired 25 to 30 seconds after the call rings unanswered. Tertiary is an email to a shared ops inbox plus a webhook into the contractor's central dashboard. If a clip is attached the on-call can decide whether to call the local police non-emergency line or to drive over. The whole tree fires off the same outbox.ndjson row; if the cellular link is down at the device the row stays in the file and the same tree replays in order when the link comes back. The replay is in-order because the file is append-only and each line has its own line_no field.
What does the on-call actually do in the 90 seconds after the phone buzzes?
Three steps that have to fit inside a phone-on-nightstand reach. First, read the text and decide if the threat level is high_threat or low_threat. High_threat means the subject is in a critical zone (fuel, copper coil pile, generator) or has been dwelling long enough that a casual explanation is unlikely. Second, open the attached 10-second clip and confirm the subject is human, not a tarp, not a dog. Third, if confirmed, call the local non-emergency police line, give the address, the alert id, and the clip url. The on-call does not drive to the site. The on-call dispatches. Most theft attempts on a job site are exploratory and the subject leaves the moment a flashlight or a siren shows up at the gate. The whole sequence is designed to fit in 90 seconds because past 90 seconds the truck is already loaded.
How do I keep the on-call from going alert-blind in week three?
Tune three knobs in this order. First, raise dwell on any zone where the on-call confirms more than two false-positive clips in a week; the lay-down yard often needs 18 to 25 seconds. Second, narrow the polygon. A zone drawn around the entire fenced perimeter will fire on every taxi that pulls up to the gate. A zone drawn around the inside of the fence with a 2-meter buffer ignores the sidewalk side. Third, fix the arming clock. If your concrete trucks routinely show up at 0430, push the lay-down yard arming start to 0445 and let the fence-line and fuel zones pick up anything truly anomalous. Properly tuned, a small GC's on-call on a 10-site portfolio gets one or two alerts per week per site, not per day.
What about during a cellular outage? Does the alert get lost?
No, because the alert is written to /var/lib/cyrano/meta/outbox.ndjson on the device before any network call. The file is append-only and lives on the device's local disk. The detection pipeline and the dwell counters do not care whether the cellular modem is up; they keep writing rows. When the link comes back, an outbound worker walks the file from the last acked line_no forward and replays every unsent row in order. The on-call can also pull the file directly with a USB keyboard if they need to. There is no scenario where a real event during an outage simply disappears, which is the failure mode every cloud-only product has by default.
Does the AI run locally or in the cloud, and why does that matter for alerts?
Locally. Object detection, tracking, the per-zone polygon lookup, and the dwell counter all run on the Cyrano unit at the property. No video frame ever leaves the device unless the on-call clicks the clip url and the device serves the clip directly to the phone. This matters for alerts because cloud-based products have a frame-to-cloud-to-back round trip that adds 1 to 4 seconds of latency on a good cellular link and is unbounded on a bad one. On a construction site with a 4G hotspot mounted to the trailer, that round trip is the difference between a 7-second alert and a 35-second alert. 35 seconds past the start of an event is too late.
What threat levels exist and how does the device decide?
Two: low_threat and high_threat. The decision is a function of zone, dwell, time-of-day, and the object class the perception tier returned. A person inside the lay-down yard at 14:00 with a 6-second dwell is low_threat (route to email, no phone call, log it) because trades are on site. The same person at 02:00 with the same dwell is high_threat (phone call, SMS, clip attached). A person inside the fuel storage zone is high_threat at any hour. A vehicle stopped on the access road for more than 30 seconds at any hour is low_threat unless it is also blocking a gate, in which case it is high_threat. The exact rule grammar is per-property and the on-call team can tune any rule from the dashboard without a redeploy because the rules are JSON files on the device.
Can I use this on a job site that already has a CCTV trailer with cameras and a DVR?
Yes, that is the primary deployment shape. A Cyrano device taps the existing DVR's HDMI multiview output (the same composite the on-site monitor in the trailer is already showing), runs the perception, state, and planner tiers on every camera tile in real time, and writes alerts to its own outbox. The CCTV trailer keeps doing what it was doing. The DVR still records. The cameras stay where they are. The only change is one HDMI cable, one power outlet, and a network drop. Install is in the 2 to 5 minute range on most trailers, and rule tuning takes another 30 minutes on the first day.
Adjacent reading on the same architecture
Construction site theft is a downtime problem, not a materials problem
Why $4,000 in copper costs $50,000 to $150,000 in schedule slip, and what changes when AI rides the existing DVR feed.
AI Agent for Security Camera Monitoring: Where the LLM Sits Decides Whether the Product Is Real
The three-tier loop, the four tools the planner is allowed to call, and the three state files those tools read and write.
DVR Motion Alert Classifier: Why The Bolt-On Pipeline Quietly Fails
Bolt-on classifiers inherit the DVR's recall ceiling. Tap the HDMI multiview instead and the classifier owns the alert stream.
Comments (••)
Leave a comment to see what others are saying.Public and anonymous. No signup.