Adjust threat level interaction to be less strict

This commit is contained in:
Nicolas Mowen 2025-11-26 10:54:49 -07:00
parent 890ae6ae50
commit 376e5b5c25
6 changed files with 76 additions and 23 deletions

View File

@ -375,7 +375,19 @@ class WebPushClient(Communicator):
ended = state == "end" or state == "genai" ended = state == "end" or state == "genai"
if state == "genai" and payload["after"]["data"]["metadata"]: if state == "genai" and payload["after"]["data"]["metadata"]:
title = payload["after"]["data"]["metadata"]["title"] base_title = payload["after"]["data"]["metadata"]["title"]
threat_level = payload["after"]["data"]["metadata"].get(
"potential_threat_level", 0
)
# Add prefix for threat levels 1 and 2
if threat_level == 1:
title = f"Needs Review: {base_title}"
elif threat_level == 2:
title = f"Security Concern: {base_title}"
else:
title = base_title
message = payload["after"]["data"]["metadata"]["scene"] message = payload["after"]["data"]["metadata"]["scene"]
else: else:
title = f"{titlecase(', '.join(sorted_objects).replace('_', ' '))}{' was' if state == 'end' else ''} detected in {titlecase(', '.join(payload['after']['data']['zones']).replace('_', ' '))}" title = f"{titlecase(', '.join(sorted_objects).replace('_', ' '))}{' was' if state == 'end' else ''} detected in {titlecase(', '.join(payload['after']['data']['zones']).replace('_', ' '))}"

View File

@ -205,14 +205,20 @@ Rules for the report:
- Group bullets under subheadings when multiple events fall into the same category (e.g., Vehicle Activity, Porch Activity, Unusual Behavior). - Group bullets under subheadings when multiple events fall into the same category (e.g., Vehicle Activity, Porch Activity, Unusual Behavior).
- Threat levels - Threat levels
- Always show (threat level: X) for each event. - Always show the threat level for each event using these labels:
- Threat level 0: "Normal"
- Threat level 1: "Needs review"
- Threat level 2: "Security concern"
- Format as (threat level: Normal), (threat level: Needs review), or (threat level: Security concern).
- If multiple events at the same time share the same threat level, only state it once. - If multiple events at the same time share the same threat level, only state it once.
- Final assessment - Final assessment
- End with a Final Assessment section. - End with a Final Assessment section.
- If all events are threat level 1 with no escalation: - If all events are threat level 0:
Final assessment: Only normal residential activity observed during this period. Final assessment: Only normal residential activity observed during this period.
- If threat level 2+ events are present, clearly summarize them as Potential concerns requiring review. - If threat level 1 events are present:
Final assessment: Some activity requires review but no security concerns identified.
- If threat level 2 events are present, clearly summarize them as Security concerns requiring immediate attention.
- Conciseness - Conciseness
- Do not repeat benign clothing/appearance details unless they distinguish individuals. - Do not repeat benign clothing/appearance details unless they distinguish individuals.

View File

@ -54,6 +54,7 @@
"selected_other": "{{count}} selected", "selected_other": "{{count}} selected",
"camera": "Camera", "camera": "Camera",
"detected": "detected", "detected": "detected",
"suspiciousActivity": "Suspicious Activity", "normalActivity": "Normal",
"threateningActivity": "Threatening Activity" "needsReview": "Needs review",
"securityConcern": "Security concern"
} }

View File

@ -1,7 +1,11 @@
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer"; import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ReviewSegment, ThreatLevel } from "@/types/review"; import {
ReviewSegment,
ThreatLevel,
THREAT_LEVEL_LABELS,
} from "@/types/review";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { isDesktop } from "react-device-detect"; import { isDesktop } from "react-device-detect";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -55,13 +59,22 @@ export function GenAISummaryDialog({
} }
let concerns = ""; let concerns = "";
switch (aiAnalysis.potential_threat_level) { const threatLevel = aiAnalysis.potential_threat_level ?? 0;
case ThreatLevel.SUSPICIOUS:
concerns = `${t("suspiciousActivity", { ns: "views/events" })}\n`; if (threatLevel > 0) {
let label = "";
switch (threatLevel) {
case ThreatLevel.NEEDS_REVIEW:
label = t("needsReview", { ns: "views/events" });
break; break;
case ThreatLevel.DANGER: case ThreatLevel.SECURITY_CONCERN:
concerns = `${t("threateningActivity", { ns: "views/events" })}\n`; label = t("securityConcern", { ns: "views/events" });
break; break;
default:
label = THREAT_LEVEL_LABELS[threatLevel as ThreatLevel] || "Unknown";
}
concerns = `${label}\n`;
} }
(aiAnalysis.other_concerns ?? []).forEach((c) => { (aiAnalysis.other_concerns ?? []).forEach((c) => {

View File

@ -1,7 +1,11 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useApiHost } from "@/api"; import { useApiHost } from "@/api";
import { isCurrentHour } from "@/utils/dateUtil"; import { isCurrentHour } from "@/utils/dateUtil";
import { ReviewSegment } from "@/types/review"; import {
ReviewSegment,
ThreatLevel,
THREAT_LEVEL_LABELS,
} from "@/types/review";
import { getIconForLabel } from "@/utils/iconUtil"; import { getIconForLabel } from "@/utils/iconUtil";
import TimeAgo from "../dynamic/TimeAgo"; import TimeAgo from "../dynamic/TimeAgo";
import useSWR from "swr"; import useSWR from "swr";
@ -44,7 +48,7 @@ export default function PreviewThumbnailPlayer({
onClick, onClick,
onTimeUpdate, onTimeUpdate,
}: PreviewPlayerProps) { }: PreviewPlayerProps) {
const { t } = useTranslation(["components/player"]); const { t } = useTranslation(["components/player", "views/events"]);
const apiHost = useApiHost(); const apiHost = useApiHost();
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const [imgRef, imgLoaded, onImgLoad] = useImageLoaded(); const [imgRef, imgLoaded, onImgLoad] = useImageLoaded();
@ -319,11 +323,21 @@ export default function PreviewThumbnailPlayer({
</TooltipTrigger> </TooltipTrigger>
</div> </div>
<TooltipContent className="smart-capitalize"> <TooltipContent className="smart-capitalize">
{review.data.metadata.potential_threat_level == 1 ? ( {(() => {
<>{t("suspiciousActivity", { ns: "views/events" })}</> const threatLevel =
) : ( review.data.metadata.potential_threat_level ?? 0;
<>{t("threateningActivity", { ns: "views/events" })}</> switch (threatLevel) {
)} case ThreatLevel.NEEDS_REVIEW:
return t("needsReview", { ns: "views/events" });
case ThreatLevel.SECURITY_CONCERN:
return t("securityConcern", { ns: "views/events" });
default:
return (
THREAT_LEVEL_LABELS[threatLevel as ThreatLevel] ||
"Unknown"
);
}
})()}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
)} )}

View File

@ -87,6 +87,13 @@ export type ZoomLevel = {
}; };
export enum ThreatLevel { export enum ThreatLevel {
SUSPICIOUS = 1, NORMAL = 0,
DANGER = 2, NEEDS_REVIEW = 1,
SECURITY_CONCERN = 2,
} }
export const THREAT_LEVEL_LABELS: Record<ThreatLevel, string> = {
[ThreatLevel.NORMAL]: "Normal",
[ThreatLevel.NEEDS_REVIEW]: "Needs review",
[ThreatLevel.SECURITY_CONCERN]: "Security concern",
};