Add pet and wildlife counts to daily digest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
547193fd79
commit
2b3a4ba853
@ -23,7 +23,7 @@ def _create_test_engine(db_path: Path):
|
|||||||
def tmp_data_dir(tmp_path):
|
def tmp_data_dir(tmp_path):
|
||||||
"""Temporary data directory for tests."""
|
"""Temporary data directory for tests."""
|
||||||
data_dir = tmp_path / "data"
|
data_dir = tmp_path / "data"
|
||||||
data_dir.mkdir()
|
data_dir.mkdir(exist_ok=True)
|
||||||
return data_dir
|
return data_dir
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,10 @@ import tempfile
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from vigilar.health.digest import build_digest, format_digest
|
||||||
from vigilar.health.pruner import find_prunable_recordings, calculate_disk_usage_pct
|
from vigilar.health.pruner import find_prunable_recordings, calculate_disk_usage_pct
|
||||||
from vigilar.health.monitor import HealthCheck, HealthStatus, check_disk, check_mqtt_port
|
from vigilar.health.monitor import HealthCheck, HealthStatus, check_disk, check_mqtt_port
|
||||||
|
from vigilar.storage.schema import pet_sightings, wildlife_sightings
|
||||||
|
|
||||||
|
|
||||||
class TestDiskCheck:
|
class TestDiskCheck:
|
||||||
@ -40,6 +42,48 @@ class TestPruner:
|
|||||||
assert result == []
|
assert result == []
|
||||||
|
|
||||||
|
|
||||||
|
class TestPetDigest:
|
||||||
|
def test_digest_includes_pet_sightings(self, test_db, tmp_data_dir):
|
||||||
|
import time
|
||||||
|
with test_db.begin() as conn:
|
||||||
|
conn.execute(pet_sightings.insert().values(
|
||||||
|
ts=time.time(), pet_id="p1", species="cat",
|
||||||
|
camera_id="kitchen", confidence=0.9, labeled=1,
|
||||||
|
))
|
||||||
|
conn.execute(pet_sightings.insert().values(
|
||||||
|
ts=time.time(), pet_id="p2", species="dog",
|
||||||
|
camera_id="kitchen", confidence=0.85, labeled=1,
|
||||||
|
))
|
||||||
|
data = build_digest(test_db, str(tmp_data_dir), since_hours=1)
|
||||||
|
assert data["pet_sightings"] == 2
|
||||||
|
|
||||||
|
def test_digest_includes_wildlife(self, test_db, tmp_data_dir):
|
||||||
|
import time
|
||||||
|
with test_db.begin() as conn:
|
||||||
|
conn.execute(wildlife_sightings.insert().values(
|
||||||
|
ts=time.time(), species="bear", threat_level="PREDATOR",
|
||||||
|
camera_id="front", confidence=0.9,
|
||||||
|
))
|
||||||
|
conn.execute(wildlife_sightings.insert().values(
|
||||||
|
ts=time.time(), species="deer", threat_level="PASSIVE",
|
||||||
|
camera_id="back", confidence=0.8,
|
||||||
|
))
|
||||||
|
data = build_digest(test_db, str(tmp_data_dir), since_hours=1)
|
||||||
|
assert data["wildlife_predators"] == 1
|
||||||
|
assert data["wildlife_passive"] == 1
|
||||||
|
|
||||||
|
def test_format_includes_pets(self):
|
||||||
|
data = {
|
||||||
|
"person_detections": 2, "unknown_vehicles": 0,
|
||||||
|
"recordings": 5, "disk_used_gb": 100.0, "disk_used_pct": 50,
|
||||||
|
"since_hours": 12, "pet_sightings": 15,
|
||||||
|
"wildlife_predators": 0, "wildlife_nuisance": 1, "wildlife_passive": 3,
|
||||||
|
}
|
||||||
|
text = format_digest(data)
|
||||||
|
assert "15 pet" in text
|
||||||
|
assert "wildlife" in text.lower() or "nuisance" in text.lower()
|
||||||
|
|
||||||
|
|
||||||
class TestMQTTCheck:
|
class TestMQTTCheck:
|
||||||
@patch("vigilar.health.monitor.socket.create_connection")
|
@patch("vigilar.health.monitor.socket.create_connection")
|
||||||
def test_mqtt_reachable(self, mock_conn):
|
def test_mqtt_reachable(self, mock_conn):
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import time
|
|||||||
from sqlalchemy import func, select
|
from sqlalchemy import func, select
|
||||||
from sqlalchemy.engine import Engine
|
from sqlalchemy.engine import Engine
|
||||||
|
|
||||||
from vigilar.storage.schema import events, recordings
|
from vigilar.storage.schema import events, pet_sightings, recordings, wildlife_sightings
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -36,6 +36,29 @@ def build_digest(engine: Engine, data_dir: str, since_hours: int = 12) -> dict:
|
|||||||
.where(recordings.c.started_at >= since_ts // 1000)
|
.where(recordings.c.started_at >= since_ts // 1000)
|
||||||
).scalar() or 0
|
).scalar() or 0
|
||||||
|
|
||||||
|
pet_count = conn.execute(
|
||||||
|
select(func.count()).select_from(pet_sightings)
|
||||||
|
.where(pet_sightings.c.ts >= since_ts / 1000)
|
||||||
|
).scalar() or 0
|
||||||
|
|
||||||
|
wildlife_predator_count = conn.execute(
|
||||||
|
select(func.count()).select_from(wildlife_sightings)
|
||||||
|
.where(wildlife_sightings.c.ts >= since_ts / 1000,
|
||||||
|
wildlife_sightings.c.threat_level == "PREDATOR")
|
||||||
|
).scalar() or 0
|
||||||
|
|
||||||
|
wildlife_nuisance_count = conn.execute(
|
||||||
|
select(func.count()).select_from(wildlife_sightings)
|
||||||
|
.where(wildlife_sightings.c.ts >= since_ts / 1000,
|
||||||
|
wildlife_sightings.c.threat_level == "NUISANCE")
|
||||||
|
).scalar() or 0
|
||||||
|
|
||||||
|
wildlife_passive_count = conn.execute(
|
||||||
|
select(func.count()).select_from(wildlife_sightings)
|
||||||
|
.where(wildlife_sightings.c.ts >= since_ts / 1000,
|
||||||
|
wildlife_sightings.c.threat_level == "PASSIVE")
|
||||||
|
).scalar() or 0
|
||||||
|
|
||||||
usage = shutil.disk_usage(data_dir)
|
usage = shutil.disk_usage(data_dir)
|
||||||
disk_pct = usage.used / usage.total * 100
|
disk_pct = usage.used / usage.total * 100
|
||||||
disk_gb = usage.used / (1024**3)
|
disk_gb = usage.used / (1024**3)
|
||||||
@ -47,15 +70,31 @@ def build_digest(engine: Engine, data_dir: str, since_hours: int = 12) -> dict:
|
|||||||
"disk_used_gb": round(disk_gb, 1),
|
"disk_used_gb": round(disk_gb, 1),
|
||||||
"disk_used_pct": round(disk_pct, 0),
|
"disk_used_pct": round(disk_pct, 0),
|
||||||
"since_hours": since_hours,
|
"since_hours": since_hours,
|
||||||
|
"pet_sightings": pet_count,
|
||||||
|
"wildlife_predators": wildlife_predator_count,
|
||||||
|
"wildlife_nuisance": wildlife_nuisance_count,
|
||||||
|
"wildlife_passive": wildlife_passive_count,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def format_digest(data: dict) -> str:
|
def format_digest(data: dict) -> str:
|
||||||
return (
|
lines = [
|
||||||
f"Vigilar Daily Summary\n"
|
"Vigilar Daily Summary",
|
||||||
f"Last {data['since_hours']}h: "
|
f"Last {data['since_hours']}h: "
|
||||||
f"{data['person_detections']} person detections, "
|
f"{data['person_detections']} person detections, "
|
||||||
f"{data['unknown_vehicles']} unknown vehicles, "
|
f"{data['unknown_vehicles']} unknown vehicles, "
|
||||||
f"{data['recordings']} recordings\n"
|
f"{data['recordings']} recordings",
|
||||||
f"Storage: {data['disk_used_gb']} GB ({data['disk_used_pct']:.0f}%)"
|
]
|
||||||
)
|
if data.get("pet_sightings", 0) > 0:
|
||||||
|
lines.append(f"Pets: {data['pet_sightings']} pet sightings")
|
||||||
|
wildlife_parts = []
|
||||||
|
if data.get("wildlife_predators", 0) > 0:
|
||||||
|
wildlife_parts.append(f"{data['wildlife_predators']} predator")
|
||||||
|
if data.get("wildlife_nuisance", 0) > 0:
|
||||||
|
wildlife_parts.append(f"{data['wildlife_nuisance']} nuisance")
|
||||||
|
if data.get("wildlife_passive", 0) > 0:
|
||||||
|
wildlife_parts.append(f"{data['wildlife_passive']} passive")
|
||||||
|
if wildlife_parts:
|
||||||
|
lines.append(f"Wildlife: {', '.join(wildlife_parts)}")
|
||||||
|
lines.append(f"Storage: {data['disk_used_gb']} GB ({data['disk_used_pct']:.0f}%)")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user