@@ -947,17 +947,53 @@ async def _handle_detection(self, event_data: dict) -> None:
947947 ai_description : str | None = await ai_prep_task
948948 _escalation_description = ai_description
949949
950- s_esc_description , s_esc_push_ok , s_esc_message = await self ._run_escalation (
951- event_id , camera_name , camera_stream , mode_name , ai_description ,
952- _active_mode_obj .voice ,
953- )
954- # Use the more detailed description if the escalation stage got one
955- if s_esc_description :
956- _escalation_description = s_esc_description
957- _escalation_message = s_esc_message
958- _escalation_audio_success = s_esc_push_ok
959- if s_esc_push_ok :
960- record_audio_push (self ._camera_stats , camera_name , s_esc_push_ok )
950+ # AI validation: if the AI couldn't identify anyone (all fields
951+ # are "unknown"), this is likely a Frigate false positive. Skip
952+ # the escalation to avoid playing a dispatch call for nobody.
953+ _skip_escalation = False
954+ if ai_description and '"unknown"' in ai_description :
955+ import json as _json
956+ try :
957+ _cleaned = ai_description .strip ()
958+ if _cleaned .startswith ("```" ):
959+ _lines = _cleaned .splitlines ()
960+ _inner = _lines [1 :]
961+ if _inner and _inner [- 1 ].strip ().startswith ("```" ):
962+ _inner = _inner [:- 1 ]
963+ _cleaned = "\n " .join (_inner ).strip ()
964+ _parsed = _json .loads (_cleaned )
965+ if isinstance (_parsed , dict ):
966+ _desc = _parsed .get ("description" , "" ).strip ().lower ()
967+ _loc = _parsed .get ("location" , "" ).strip ().lower ()
968+ if _desc in ("unknown" , "" ) and _loc in ("unknown" , "" ):
969+ logger .info (
970+ "Escalation: AI returned all-unknown description — "
971+ "likely false positive from Frigate. Skipping escalation."
972+ )
973+ _skip_escalation = True
974+ _escalation_ran = False
975+ if self ._publisher :
976+ self ._publisher .publish_error (
977+ camera = camera_name ,
978+ error_type = "false_positive" ,
979+ error_message = "AI could not identify anyone in snapshots — likely Frigate false positive" ,
980+ fallback_used = False ,
981+ )
982+ except Exception :
983+ pass # JSON parse failed — proceed with escalation anyway
984+
985+ if not _skip_escalation :
986+ s_esc_description , s_esc_push_ok , s_esc_message = await self ._run_escalation (
987+ event_id , camera_name , camera_stream , mode_name , ai_description ,
988+ _active_mode_obj .voice ,
989+ )
990+ # Use the more detailed description if the escalation stage got one
991+ if s_esc_description :
992+ _escalation_description = s_esc_description
993+ _escalation_message = s_esc_message
994+ _escalation_audio_success = s_esc_push_ok
995+ if s_esc_push_ok :
996+ record_audio_push (self ._camera_stats , camera_name , s_esc_push_ok )
961997
962998 # ── MQTT: publish stage 2 ──────────────────────────────────────
963999 if self ._publisher and _escalation_ran :
0 commit comments