Activity heatmaps, wildlife journal with weather correlation, and package detection with sunset-aware reminders. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
11 KiB
Detection Intelligence — Design Spec
Overview
Three features that surface richer insights from detection data already flowing through the system: per-camera activity heatmaps, a full-featured wildlife sighting journal with weather correlation, and package delivery detection with sunset-aware reminders.
Q2: Activity Heatmap
What It Does
Generates a static PNG image per camera showing where detections cluster over 7 or 30 days. Helps evaluate camera placement, identify blind spots, and visualize traffic patterns.
Implementation
New file: vigilar/detection/heatmap.py
generate_heatmap(engine, camera_id, days, frame) -> bytes:
- Query all detection events for the camera within the time window. Events must include bounding box data — use
pet_sightings,wildlife_sightings, andeventstable (for person/vehicle detections). - Accumulate bounding box centers into a 2D grid (64x36 bins, mapping to the camera's aspect ratio).
- Apply Gaussian blur (sigma=2) for smooth heat distribution.
- Normalize to 0-255, apply a colormap (blue → yellow → red).
- Alpha-composite the heatmap onto the camera's most recent frame (or a representative still).
- Return as PNG bytes.
Bounding box storage prerequisite: Detection events must include normalized bounding box coordinates in the event payload. The camera worker already sends confidence and species in payloads — add bbox (as [x, y, w, h] normalized to 0.0-1.0 of frame dimensions) to pet, wildlife, and person/vehicle detection payloads.
Caching: Generated heatmaps are stored in /var/vigilar/cache/heatmaps/{camera_id}_{days}d.png. Regenerated on request only if the cached file is older than 1 hour.
Web Route
GET /cameras/<id>/heatmap?days=7 — returns PNG image. days defaults to 7, accepts 7 or 30.
UI
Add a "Heatmap" tab on each camera's detail page. Two toggle buttons (7d / 30d) that swap the <img> src. Simple Bootstrap tab, no JavaScript framework needed.
Q3: Wildlife Journal
What It Does
A dedicated wildlife monitoring page with chronological sighting log, seasonal tracking, per-species photo galleries, weather correlation, and CSV export. Transforms wildlife detections into a naturalist's field journal.
Location Config
Add to vigilar.toml:
[location]
latitude = 45.0
longitude = -85.0
Config model:
class LocationConfig(BaseModel):
latitude: float = 0.0
longitude: float = 0.0
Add to VigilarConfig as location: LocationConfig. Used by both wildlife weather correlation and package detection sunset calculation.
Weather Data
New file: vigilar/detection/weather.py
WeatherFetcher class:
- Fetches current conditions from Open-Meteo API (
https://api.open-meteo.com/v1/forecast). Free, no API key, no account. - Parameters: latitude, longitude, hourly temperature + weather code.
- Caches the response for 1 hour (in-memory dict keyed by hour).
- Graceful offline degradation: if the fetch fails, returns
Nonefor temperature/conditions. Never blocks the detection pipeline. - Called asynchronously after a wildlife sighting is inserted, not in the detection hot path.
get_conditions(lat, lon) -> dict | None:
Returns {"temperature_c": 12.5, "conditions": "Partly cloudy"} or None.
Database Changes
Add columns to wildlife_sightings table:
temperature_c(Float, nullable) — temperature at time of sightingconditions(String, nullable) — weather descriptionbbox(String, nullable) — normalized bounding box as JSON[x, y, w, h]
Web Blueprint
New file: vigilar/web/blueprints/wildlife.py
Routes:
| Route | Method | Purpose |
|---|---|---|
/wildlife/ |
GET | Wildlife journal page |
/wildlife/api/sightings |
GET | Paginated sighting list (filterable by species, threat, camera, date range) |
/wildlife/api/stats |
GET | Aggregate stats: counts per species, firsts/lasts per year, days since last predator |
/wildlife/api/frequency |
GET | Species frequency by time-of-day (6 four-hour buckets: 0-4, 4-8, 8-12, 12-16, 16-20, 20-24) |
/wildlife/api/gallery/<species> |
GET | Detection crop thumbnails for a species |
/wildlife/api/export |
GET | CSV export of all sightings with weather data |
Template
New file: vigilar/web/templates/wildlife/journal.html
Bootstrap 5 dark theme. Sections:
- Summary cards — total sightings this month, species count, days since last predator, most active species
- Seasonal firsts — "First bear: April 3", "First deer: March 12", etc. Grouped by year.
- Sighting log — paginated table with thumbnail, species, threat badge, camera, time, temperature, conditions. Filterable.
- Time-of-day chart — horizontal stacked bar chart per species showing when they appear (rendered with simple CSS bars or a
<canvas>, no charting library dependency). - Species gallery — clickable species cards that expand to show a grid of detection crop thumbnails.
- Export button — downloads CSV.
All data loaded via fetch() to the API endpoints. Auto-refresh every 60 seconds for the summary cards.
Weather Integration Flow
- Wildlife sighting detected → inserted into
wildlife_sightings(existing flow) - Event processor publishes
WILDLIFE_*event to MQTT (existing flow) - A new MQTT subscriber (or inline in the event processor) calls
WeatherFetcher.get_conditions()and updates the sighting row with temperature/conditions - If offline, temperature/conditions remain NULL — no data loss, just missing weather context
Q5: Package Detection
What It Does
Detects packages left at the front door after a delivery person leaves. Sends an alert on delivery, one reminder at the earlier of sunset or 3 hours later, and logs when the package is collected.
Detection Logic
New file: vigilar/detection/package.py
PackageTracker class, one instance per exterior camera:
State machine per tracked package:
IDLE → person detected on exterior camera
→ person leaves (no person detection for 30s)
→ check for stationary object in the detection zone
→ if stationary object found: PRESENT
→ schedule reminder at min(sunset, now + 3h)
PRESENT → REMINDED (when reminder fires)
PRESENT or REMINDED → COLLECTED (when object disappears after person returns)
Stationary object detection:
- After person departs, run YOLO on the current frame
- Look for COCO classes that could be packages:
suitcase(28),handbag(26),backpack(24) - If any are detected in approximately the same position for >30 seconds (compare bbox across 2+ frames), classify as a package
- Store the bbox and a crop image
Sunset calculation:
New file: vigilar/detection/solar.py
get_sunset(latitude, longitude, date) -> datetime.time:
- Simple solar position calculation using the NOAA solar equations
- Stdlib only (
math,datetime), no dependencies - Accuracy within ~2 minutes, sufficient for "roughly sunset" timing
- Uses
LocationConfigfrom the config
Database
New table: package_events
| Column | Type | Notes |
|---|---|---|
| id | Integer PK | Autoincrement |
| camera_id | String NOT NULL | Which camera |
| detected_at | Float NOT NULL | When package first seen |
| reminded_at | Float | When reminder was sent (NULL if not yet) |
| collected_at | Float | When package was picked up (NULL if still there) |
| status | String NOT NULL | PRESENT, REMINDED, COLLECTED |
| crop_path | String | Photo of the package |
| event_id | Integer | Linked event |
Index: idx_package_camera_status on (camera_id, status)
New Event Types
Add to EventType enum:
PACKAGE_DELIVERED— package detected after person leavesPACKAGE_REMINDER— sunset/3-hour reminder firedPACKAGE_COLLECTED— package no longer detected
Alert Integration
PACKAGE_DELIVERED: severity INFO, push notification "Package delivered — Front Entrance"PACKAGE_REMINDER: severity WARNING, push notification "Package still on porch — Front Entrance (3h / sunset)"PACKAGE_COLLECTED: severity INFO, logged only (no push)
These plug into the existing alert profile system as new detection_type values: package_delivered, package_reminder.
Reminder Scheduling
The PackageTracker calculates the reminder time at detection:
reminder_at = min(sunset_time, detected_at + 3 * 3600)
A background check runs every 60 seconds in the event processor (or a dedicated timer thread) that scans package_events where status=PRESENT and reminded_at IS NULL and current_time >= reminder_at. When triggered, fires the PACKAGE_REMINDER event and updates the row.
Camera Worker Integration
The PackageTracker is instantiated per exterior camera in the camera worker, alongside the YOLO detector. It receives detection results from the YOLO pass and manages its own state. It publishes package events to MQTT topics:
vigilar/camera/{id}/package/delivered
vigilar/camera/{id}/package/reminder
vigilar/camera/{id}/package/collected
UI
Small "Packages" widget on the main dashboard:
- Shows active packages (status PRESENT or REMINDED) with camera name, time on porch, and crop thumbnail
- Empty state: "No packages detected" (hidden when empty to avoid clutter)
- Historical package log accessible from a "Package History" link
Dependencies
New Packages
None — all features use existing dependencies and stdlib.
New Files
| File | Purpose |
|---|---|
vigilar/detection/heatmap.py |
Heatmap generation (numpy + Pillow) |
vigilar/detection/weather.py |
Open-Meteo weather fetcher with caching |
vigilar/detection/package.py |
Package detection state machine |
vigilar/detection/solar.py |
Sunset calculation from lat/lon |
vigilar/web/blueprints/wildlife.py |
Wildlife journal blueprint |
vigilar/web/templates/wildlife/journal.html |
Wildlife journal template |
Modified Files
| File | Changes |
|---|---|
vigilar/constants.py |
Add PACKAGE_DELIVERED, PACKAGE_REMINDER, PACKAGE_COLLECTED event types |
vigilar/config.py |
Add LocationConfig model |
vigilar/storage/schema.py |
Add package_events table; add columns to wildlife_sightings |
vigilar/storage/queries.py |
Add package and wildlife stats query functions |
vigilar/camera/worker.py |
Add bbox to detection payloads; instantiate PackageTracker |
vigilar/events/processor.py |
Handle package events; weather enrichment for wildlife |
vigilar/web/blueprints/cameras.py |
Add heatmap route |
vigilar/web/app.py |
Register wildlife blueprint |
Out of Scope
- Interactive/live heatmap overlay (future — start with static PNG)
- SpeciesNet integration (separate feature, would replace YOLO wildlife classes)
- Package detection on interior cameras (only exterior)
- Multi-package tracking on same camera (track one active package per camera for simplicity)
- Charting library (use CSS bars and canvas for time-of-day chart)