diff --git a/vigilar/config.py b/vigilar/config.py index 1739560..7da3cd1 100644 --- a/vigilar/config.py +++ b/vigilar/config.py @@ -20,6 +20,7 @@ from vigilar.constants import ( DEFAULT_RETENTION_DAYS, DEFAULT_UPS_POLL_INTERVAL_S, DEFAULT_WEB_PORT, + CameraLocation, ) # --- Camera Config --- @@ -42,7 +43,7 @@ class CameraConfig(BaseModel): resolution_capture: list[int] = Field(default_factory=lambda: [1920, 1080]) resolution_motion: list[int] = Field(default_factory=lambda: [640, 360]) zones: list["CameraZone"] = Field(default_factory=list) - location: str = "INTERIOR" # EXTERIOR | INTERIOR | TRANSITION + location: CameraLocation = CameraLocation.INTERIOR # --- Sensor Config --- diff --git a/vigilar/detection/trainer.py b/vigilar/detection/trainer.py index 51f32a0..28a8584 100644 --- a/vigilar/detection/trainer.py +++ b/vigilar/detection/trainer.py @@ -122,7 +122,9 @@ class PetTrainer: epoch + 1, epochs, running_loss / len(loader), accuracy) if self._model_output_path.exists(): - backup_path = self._model_output_path.with_suffix(".backup.pt") + backup_path = self._model_output_path.with_name( + self._model_output_path.stem + "_backup.pt" + ) shutil.copy2(self._model_output_path, backup_path) log.info("Backed up previous model to %s", backup_path) diff --git a/vigilar/detection/wildlife.py b/vigilar/detection/wildlife.py index 85fb175..f423721 100644 --- a/vigilar/detection/wildlife.py +++ b/vigilar/detection/wildlife.py @@ -1,6 +1,7 @@ """Wildlife threat level classification.""" from vigilar.config import WildlifeConfig +from vigilar.constants import ThreatLevel from vigilar.detection.person import Detection @@ -8,7 +9,7 @@ def classify_wildlife_threat( detection: Detection, config: WildlifeConfig, frame_area: int, -) -> tuple[str, str]: +) -> tuple[ThreatLevel, str]: """Classify a wildlife detection into threat level and species. Returns (threat_level, species_name). @@ -18,11 +19,11 @@ def classify_wildlife_threat( # Direct COCO class mapping first if species in threat_map.predator: - return "PREDATOR", species + return ThreatLevel.PREDATOR, species if species in threat_map.nuisance: - return "NUISANCE", species + return ThreatLevel.NUISANCE, species if species in threat_map.passive: - return "PASSIVE", species + return ThreatLevel.PASSIVE, species # Fallback to size heuristics for unknown species _, _, w, h = detection.bbox @@ -31,8 +32,8 @@ def classify_wildlife_threat( heuristics = config.size_heuristics if area_ratio < heuristics.small: - return "NUISANCE", species + return ThreatLevel.NUISANCE, species elif area_ratio < heuristics.medium: - return "PREDATOR", species + return ThreatLevel.PREDATOR, species else: - return "PASSIVE", species + return ThreatLevel.PASSIVE, species diff --git a/vigilar/health/digest.py b/vigilar/health/digest.py index 38ed016..034c486 100644 --- a/vigilar/health/digest.py +++ b/vigilar/health/digest.py @@ -36,6 +36,8 @@ def build_digest(engine: Engine, data_dir: str, since_hours: int = 12) -> dict: .where(recordings.c.started_at >= since_ts // 1000) ).scalar() or 0 + # pet_sightings/wildlife_sightings use float seconds (time.time()), + # while events table uses integer milliseconds (time.time() * 1000) pet_count = conn.execute( select(func.count()).select_from(pet_sightings) .where(pet_sightings.c.ts >= since_ts / 1000) diff --git a/vigilar/web/blueprints/pets.py b/vigilar/web/blueprints/pets.py index 6daf0ac..3d94db1 100644 --- a/vigilar/web/blueprints/pets.py +++ b/vigilar/web/blueprints/pets.py @@ -58,13 +58,20 @@ def pet_status(): from vigilar.storage.queries import get_all_pets, get_pet_last_location pets_list = get_all_pets(engine) + now = time.time() result = [] for pet in pets_list: last = get_pet_last_location(engine, pet["id"]) + if last: + last_seen_ago = now - last["ts"] + status = "safe" if last_seen_ago < 300 else "unknown" # seen in last 5 min + else: + status = "unknown" result.append({ **pet, "last_seen_ts": last["ts"] if last else None, "last_camera": last["camera_id"] if last else None, + "status": status, }) return jsonify({"pets": result}) diff --git a/vigilar/web/templates/pets/dashboard.html b/vigilar/web/templates/pets/dashboard.html index ef89123..7b24bdb 100644 --- a/vigilar/web/templates/pets/dashboard.html +++ b/vigilar/web/templates/pets/dashboard.html @@ -392,10 +392,10 @@ function submitLabel(petId) { if (!pendingLabelCropId) return; - fetch('/pets/api/label', { + fetch(`/pets/${pendingLabelCropId}/label`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ crop_id: pendingLabelCropId, pet_id: petId }), + body: JSON.stringify({ sighting_id: pendingLabelCropId, pet_id: petId }), }).finally(() => { bootstrap.Modal.getOrCreateInstance(document.getElementById('labelModal')).hide(); loadUnlabeled();