Improve usefulness

This commit is contained in:
Nicolas Mowen 2026-03-31 16:04:28 -06:00
parent f25115da69
commit 62ee06feef

View File

@ -317,10 +317,11 @@ def get_tool_definitions() -> List[Dict[str, Any]]:
"function": { "function": {
"name": "get_recap", "name": "get_recap",
"description": ( "description": (
"Get a recap of review items (alerts and detections) for a given time period. " "Get a recap of all activity (alerts and detections) for a given time period. "
"Use this after calling get_profile_status to retrieve what happened during " "Use this after calling get_profile_status to retrieve what happened during "
"a specific window — e.g. 'what happened while I was away?'. Returns review " "a specific window — e.g. 'what happened while I was away?'. Returns a "
"segments grouped by camera with object labels, severity, and timestamps." "chronological list of activity with camera, objects, zones, and GenAI-generated "
"descriptions when available. Summarize the results for the user."
), ),
"parameters": { "parameters": {
"type": "object", "type": "object",
@ -778,21 +779,19 @@ def _execute_get_profile_status(request: Request) -> Dict[str, Any]:
info = profile_manager.get_profile_info() info = profile_manager.get_profile_info()
# Add human-readable local times for last_activated timestamps # Convert timestamps to human-readable local times inline
last_activated = info.get("last_activated", {}) last_activated = {}
last_activated_local = {} for name, ts in info.get("last_activated", {}).items():
for name, ts in last_activated.items():
try: try:
dt = datetime.fromtimestamp(ts) dt = datetime.fromtimestamp(ts)
last_activated_local[name] = dt.strftime("%Y-%m-%d %I:%M:%S %p") last_activated[name] = dt.strftime("%Y-%m-%d %I:%M:%S %p")
except (TypeError, ValueError, OSError): except (TypeError, ValueError, OSError):
pass last_activated[name] = str(ts)
return { return {
"active_profile": info.get("active_profile"), "active_profile": info.get("active_profile"),
"profiles": info.get("profiles", []), "profiles": info.get("profiles", []),
"last_activated": last_activated, "last_activated": last_activated,
"last_activated_local": last_activated_local,
} }
@ -800,7 +799,7 @@ def _execute_get_recap(
arguments: Dict[str, Any], arguments: Dict[str, Any],
allowed_cameras: List[str], allowed_cameras: List[str],
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Fetch review segments for a time period and return a structured recap.""" """Fetch review segments with GenAI metadata for a time period."""
from functools import reduce from functools import reduce
from peewee import operator from peewee import operator
@ -830,7 +829,7 @@ def _execute_get_recap(
requested = set(cameras.split(",")) requested = set(cameras.split(","))
camera_list = list(requested.intersection(allowed_cameras)) camera_list = list(requested.intersection(allowed_cameras))
if not camera_list: if not camera_list:
return {"cameras": {}, "total_alerts": 0, "total_detections": 0} return {"events": [], "message": "No accessible cameras matched."}
else: else:
camera_list = allowed_cameras camera_list = allowed_cameras
@ -847,7 +846,6 @@ def _execute_get_recap(
try: try:
rows = ( rows = (
ReviewSegment.select( ReviewSegment.select(
ReviewSegment.id,
ReviewSegment.camera, ReviewSegment.camera,
ReviewSegment.start_time, ReviewSegment.start_time,
ReviewSegment.end_time, ReviewSegment.end_time,
@ -855,36 +853,15 @@ def _execute_get_recap(
ReviewSegment.data, ReviewSegment.data,
) )
.where(reduce(operator.and_, clauses)) .where(reduce(operator.and_, clauses))
.order_by(ReviewSegment.start_time.desc()) .order_by(ReviewSegment.start_time.asc())
.limit(100) .limit(100)
.dicts() .dicts()
.iterator() .iterator()
) )
# Group by camera and build summary events: List[Dict[str, Any]] = []
cameras_summary: Dict[str, Dict[str, Any]] = {}
total_alerts = 0
total_detections = 0
for row in rows: for row in rows:
cam = row["camera"]
if cam not in cameras_summary:
cameras_summary[cam] = {
"alerts": 0,
"detections": 0,
"objects": [],
"zones": [],
"items": [],
}
severity = row.get("severity", "detection")
if severity == "alert":
cameras_summary[cam]["alerts"] += 1
total_alerts += 1
else:
cameras_summary[cam]["detections"] += 1
total_detections += 1
data = row.get("data") or {} data = row.get("data") or {}
if isinstance(data, str): if isinstance(data, str):
try: try:
@ -892,53 +869,65 @@ def _execute_get_recap(
except json.JSONDecodeError: except json.JSONDecodeError:
data = {} data = {}
objects = data.get("objects", []) camera = row["camera"]
zones = data.get("zones", []) event: Dict[str, Any] = {
"camera": camera.replace("_", " ").title(),
for obj in objects: "severity": row.get("severity", "detection"),
if obj not in cameras_summary[cam]["objects"]:
cameras_summary[cam]["objects"].append(obj)
for zone in zones:
if zone not in cameras_summary[cam]["zones"]:
cameras_summary[cam]["zones"].append(zone)
# Build a concise item entry
item = {
"severity": severity,
"objects": objects,
} }
# Include GenAI metadata when available
metadata = data.get("metadata")
if metadata and isinstance(metadata, dict):
if metadata.get("title"):
event["title"] = metadata["title"]
if metadata.get("scene"):
event["description"] = metadata["scene"]
threat = metadata.get("potential_threat_level")
if threat is not None:
threat_labels = {
0: "normal",
1: "needs_review",
2: "security_concern",
}
event["threat_level"] = threat_labels.get(threat, str(threat))
# Only include objects/zones/audio when there's no GenAI description
# to keep the payload concise — the description already covers these
if "description" not in event:
objects = data.get("objects", [])
if objects:
event["objects"] = objects
zones = data.get("zones", [])
if zones:
event["zones"] = zones
audio = data.get("audio", [])
if audio:
event["audio"] = audio
start_ts = row.get("start_time") start_ts = row.get("start_time")
end_ts = row.get("end_time") end_ts = row.get("end_time")
if start_ts is not None: if start_ts is not None:
try: try:
item["start_time_local"] = datetime.fromtimestamp( event["time"] = datetime.fromtimestamp(start_ts).strftime(
start_ts "%I:%M %p"
).strftime("%Y-%m-%d %I:%M:%S %p")
except (TypeError, ValueError, OSError):
pass
if end_ts is not None:
try:
item["end_time_local"] = datetime.fromtimestamp(end_ts).strftime(
"%Y-%m-%d %I:%M:%S %p"
) )
except (TypeError, ValueError, OSError): except (TypeError, ValueError, OSError):
pass pass
if end_ts is not None and start_ts is not None:
try:
event["duration_seconds"] = round(end_ts - start_ts)
except (TypeError, ValueError):
pass
if zones: events.append(event)
item["zones"] = zones
audio = data.get("audio", []) if not events:
if audio: return {
item["audio"] = audio "events": [],
"message": "No activity was found during this time period.",
}
cameras_summary[cam]["items"].append(item) return {"events": events}
return {
"cameras": cameras_summary,
"total_alerts": total_alerts,
"total_detections": total_detections,
}
except Exception as e: except Exception as e:
logger.error("Error executing get_recap: %s", e, exc_info=True) logger.error("Error executing get_recap: %s", e, exc_info=True)
return {"error": "Failed to fetch recap data."} return {"error": "Failed to fetch recap data."}