Architecture, scoring engine, FEMA pipeline, mobile runtime, and dashboard.
Three sources feed the alert pipeline — FEMA IPAWS CMAS (5 min), FEMA IPAWS EAS (2 min), and NCMEC RSS (30 min). Between alerts, the inference pipeline is fully idle — no continuous capture, no always-on scanning. When a qualifying alert fires, volunteers in the affected area are notified and the pipeline activates per-mission.
CAP XML polled every 5 min. Plate + vehicle profile parsed and written to watchlist.
Push to enrolled volunteers in alert polygon. Mount phone, tap START.
Drone RTMP + phone foreground service feed shared frame queue. ALPR + YOLO run per frame.
5-second window. ALPR + YOLO corroboration. HIGH / PROBABLE / raw classification.
Discord webhook fires with full verdict. Human review before any LE notification.
React Native 0.81. Foreground Service for background scanning. GPS at ~1Hz.
nginx exec_push pipes JPEG frames into the unified worker. Same path as phone.
Native Kotlin module. Mavic 3, Mini 4 Pro, Air 3, Avata.
POST /ingest/frame. JWT auth. slowapi rate limiting. Async SQLAlchemy.
One process. Shared queue across all sources. ALPR + YOLO + scoring.
Events + watchlist + telemetry. Time-windowed retention. No raw video.
FEMA IPAWS CMAS (5 min) · FEMA IPAWS EAS (2 min) · NCMEC RSS (30 min). amber.alert.gov staged — DNS unresolvable.
Full verdict embed: plate + confidence + frame + vehicle match.
Mapbox dark. /map · /admin · /missions · /leaderboard · /debrief.
Every source — drone RTMP, phone background service, phase-two DJI MSDK — feeds into the same in-process queue. No duplicated inference paths, no per-source forks. Non-matching frames are deleted from disk immediately after inference. When a HIGH_CONFIDENCE watchlist match fires, one golden evidence frame is saved and attached to the coordinator Discord alert.
Shared across all sources. Backpressure-aware.
Local inference. Fuzzy-matched against watchlist.
HSV K-means on upper 60% of bbox; confidence per frame.
Make/model enrichment on high-confidence reads only.
5-second window. ALPR + YOLO corroboration. Classification.
Plate-only ALPR is roughly 30% false-positive in real-world conditions. We score every detection against two independent signals: ALPR text quality and YOLO vehicle corroboration. Both must agree before we open a HIGH_CONFIDENCE event.
Stored on every event as raw_summary.vehicle_corroboration — coordinators inspect exactly what contributed to the score.
Detections grouped by drone and plate-prefix get fuzzy-matched together inside a five-second sliding window. The window decides whether the event opens for coordinator review, fires Discord, or stays a raw observation.
≥ 90.0≥ 75.0< 75.0Three background async tasks cover every layer of the US missing-persons alert system. FEMA IPAWS CMAS (5-min poll, fema_connector.py) and FEMA IPAWS EAS (2-min poll, amber_alert_poller.py) — both federal CAP XML, different endpoints, share a single Postgres-backed dedup table so the same alert never fires twice. NCMEC RSS (30-min poll) — all 50 state feeds individually; fires a Discord cross-reference when a new NCMEC case overlaps an active FEMA vehicle target in the same state. amber.alert.gov is implemented but staged — the hostname is currently unresolvable, so it's dark.
Both FEMA pollers share a processed_alerts dedup table backed by Postgres — survives PM2 restarts. Re-poll uses COALESCE upsert; a bare-plate entry gets backfilled with vehicle profile data on a later poll.
# FEMA CMAS — every 300 s (fema_connector.py) async def fema_background_loop(): for alert in parse_cap(await http.get(FEMA_CMAS_URL)): await watchlist.upsert(plate, color, body, make) await dispatcher.notify_volunteers(polygon, cap_code) # FEMA EAS — every 120 s (amber_alert_poller.py) async def amber_background_loop(): for alert in parse_cap(await http.get(FEMA_EAS_URL)): await watchlist.upsert(alert) # shared dedup table # amber.alert.gov — STAGED (DNS unresolvable · AMBER_GOV_URLS = []) # async def poll_ambgov(): ... ← dark until DNS resolves # NCMEC — every 1800 s, 50 state feeds in parallel async def poll_ncmec(): for state in US_STATES: cases = parse_feed(await http.get(NCMEC_URL.format(state))) await db.upsert_cases(cases) await cross_ref_vehicle_targets(cases, state)
When IPAWS issues an alert in the volunteer's polygon, an Expo push notification fires. They see the suspect descriptor, alert ID, last-known direction — and one tap to start.
expo-notificationsCameraScreen with foreground service runningThe flagship piece of the mobile stack. modules/phone-camera runs Camera2 + GPS + upload as an Android Foreground Service so the user can drive with their navigation app open. AA does not need to be visible.
expo-location stay alive when screen off / app backgrounded/ingest/frame with GPS attached at ~1HzA pilot who can't physically respond still contributes. They power on their drone, open AutonomousMissionScreen, and walk away. The coordinator sees the drone on the map at its home GPS, clicks anywhere on the map to place an observation pin, checks the ADS-B airspace advisory, and dispatches. Pilot taps Accept & Launch — the DJI SDK uploads the waypoint and executes. No pilot on scene required.
pending → dispatched → uploading → executing → completed | aborted. Timeout: 30 min unaccepted, 4 h executing.Coordinators see two layers Flock can't give them: (1) exactly where fixed cameras have zero coverage, and (2) which volunteer drones are online right now and where. Click a drone icon → click anywhere on the map to place the observation pin → check the ADS-B airspace advisory → dispatch. Flock DroneSense costs $3 k–$6 k / drone / year and requires an operator on scene. Ours costs $0 and the pilot can be at home.
Every external dependency was chosen so the system is auditable and self-hostable. The ML stack is open-source end-to-end. Self-hostable on any Ubuntu 20.04+ VPS with PostgreSQL, Node 18+, Python 3.10+.
CMAS (5 min) + EAS (2 min). CAP XML. AMBER, Silver, Mattie's, Purple, MIPA, Blue.
50 individual state feeds every 30 min. Missing-child cross-reference to active vehicle targets.
Live ADS-B air traffic. Airspace advisory in dispatch modal — lists aircraft within 5 nm below 3,000 ft.
Local ALPR + body-type classification. HSV K-means for color. All open-source.
Cloud make/model enrichment on high-confidence reads. Quota-conserved.
Native Kotlin. Mavic 3, Mini 4 Pro, Air 3, Avata. Part 107 gated. iOS pending.
Bare workflow. EAS Build cloud — no Mac required for iOS. OTA via expo-updates.
Coordinator alerts. Embed: plate + confidence + golden frame + vehicle match verdict.
HIGH_CONFIDENCE escalation path. Silently skips if env vars absent. Configured via TWILIO_* vars.
Async SQLAlchemy. JWT auth. slowapi rate limit. PM2 supervised.
Mapbox GL dark. 7 toggleable map layers. Mission dispatch + swarm panel.
Single Droplet, self-hostable. nginx reverse-proxy. Let's Encrypt TLS. PM2 supervised.
Non-matching frames are never written to disk. They pass through RAM for inference and are deleted immediately — no accumulation of plate sightings, no non-alert vehicle data. The platform is event-triggered; between alerts, the inference pipeline is fully idle.
When a HIGH_CONFIDENCE watchlist match fires, one golden evidence frame is saved — attached to the coordinator Discord alert inline so they can see exactly what the drone saw. That frame is stored with the detection record. Telemetry purges after 90 days. Detection records and golden frames purge after 1 year. The watchlist holds only plates from active government alerts, never our own sightings database.
Demo AMBER Alert injected. Two volunteers — one phone, one drone — both hit 99% peak confidence on plate YVJ024.