Compare commits

..

1 Commits

Author SHA1 Message Date
Nicolas Mowen
96665deef5
Merge 130dc76a01 into 9ab78f496c 2025-12-04 04:00:04 +00:00
3 changed files with 40 additions and 123 deletions

View File

@ -209,22 +209,10 @@ class ReviewDescriptionProcessor(PostProcessorApi):
logger.debug( logger.debug(
f"Found GenAI Review Summary request for {start_ts} to {end_ts}" f"Found GenAI Review Summary request for {start_ts} to {end_ts}"
) )
items: list[dict[str, Any]] = [
# Query all review segments with camera and time information r["data"]["metadata"]
segments: list[dict[str, Any]] = [
{
"camera": r["camera"].replace("_", " ").title(),
"start_time": r["start_time"],
"end_time": r["end_time"],
"metadata": r["data"]["metadata"],
}
for r in ( for r in (
ReviewSegment.select( ReviewSegment.select(ReviewSegment.data)
ReviewSegment.camera,
ReviewSegment.start_time,
ReviewSegment.end_time,
ReviewSegment.data,
)
.where( .where(
(ReviewSegment.data["metadata"].is_null(False)) (ReviewSegment.data["metadata"].is_null(False))
& (ReviewSegment.start_time < end_ts) & (ReviewSegment.start_time < end_ts)
@ -236,66 +224,21 @@ class ReviewDescriptionProcessor(PostProcessorApi):
) )
] ]
if len(segments) == 0: if len(items) == 0:
logger.debug("No review items with metadata found during time period") logger.debug("No review items with metadata found during time period")
return "No activity was found during this time period." return "No activity was found during this time."
# Identify primary items (important items that need review) important_items = list(
primary_segments = [ filter(
seg lambda item: item.get("potential_threat_level", 0) > 0
for seg in segments or item.get("other_concerns"),
if seg["metadata"].get("potential_threat_level", 0) > 0 items,
or seg["metadata"].get("other_concerns") )
]
if not primary_segments:
return "No concerns were found during this time period."
# For each primary segment, find overlapping contextual items from other cameras
all_items_for_summary = []
for primary_seg in primary_segments:
# Add the primary item with marker
primary_item = copy.deepcopy(primary_seg["metadata"])
primary_item["_is_primary"] = True
primary_item["_camera"] = primary_seg["camera"]
all_items_for_summary.append(primary_item)
# Find overlapping contextual items from other cameras
primary_start = primary_seg["start_time"]
primary_end = primary_seg["end_time"]
primary_camera = primary_seg["camera"]
for seg in segments:
seg_camera = seg["camera"]
if seg_camera == primary_camera:
continue
if seg in primary_segments:
continue
seg_start = seg["start_time"]
seg_end = seg["end_time"]
if seg_start < primary_end and primary_start < seg_end:
contextual_item = copy.deepcopy(seg["metadata"])
contextual_item["_is_primary"] = False
contextual_item["_camera"] = seg_camera
contextual_item["_related_to_camera"] = primary_camera
if not any(
item.get("_camera") == seg_camera
and item.get("time") == contextual_item.get("time")
for item in all_items_for_summary
):
all_items_for_summary.append(contextual_item)
logger.debug(
f"Summary includes {len(primary_segments)} primary items and "
f"{len(all_items_for_summary) - len(primary_segments)} contextual items"
) )
if not important_items:
return "No concerns were found during this time period."
if self.config.review.genai.debug_save_thumbnails: if self.config.review.genai.debug_save_thumbnails:
Path( Path(
os.path.join(CLIPS_DIR, "genai-requests", f"{start_ts}-{end_ts}") os.path.join(CLIPS_DIR, "genai-requests", f"{start_ts}-{end_ts}")
@ -304,7 +247,7 @@ class ReviewDescriptionProcessor(PostProcessorApi):
return self.genai_client.generate_review_summary( return self.genai_client.generate_review_summary(
start_ts, start_ts,
end_ts, end_ts,
all_items_for_summary, important_items,
self.config.review.genai.debug_save_thumbnails, self.config.review.genai.debug_save_thumbnails,
) )
else: else:

View File

@ -185,66 +185,44 @@ Each line represents a detection state, not necessarily unique individuals. Pare
timeline_summary_prompt = f""" timeline_summary_prompt = f"""
You are a security officer. You are a security officer.
Time range: {time_range}. Time range: {time_range}.
Input: JSON list with "title", "scene", "confidence", "potential_threat_level" (0-2), "other_concerns", "_is_primary", "_camera". Input: JSON list with "title", "scene", "confidence", "potential_threat_level" (1-2), "other_concerns".
Task: Write a concise, human-presentable security report in markdown format. Task: Write a concise, human-presentable security report in markdown format.
CRITICAL - Understanding Primary vs Contextual Items:
- Items with "_is_primary": true are events that REQUIRE REVIEW and MUST be included in the report
- Items with "_is_primary": false are additional context from other camera perspectives that overlap in time
- **DO NOT create separate bullet points or sections for contextual items**
- **ONLY use contextual items to enrich and inform the description of primary items**
- The "_camera" field indicates which camera captured each event
- **When a contextual item provides relevant background, you MUST incorporate it directly into the primary event's bullet point**
- Contextual information often explains or de-escalates seemingly suspicious primary events
Rules for the report: Rules for the report:
- Title & overview - Title & overview
- Start with: - Start with:
# Security Summary - {time_range} # Security Summary - {time_range}
- Write a 1-2 sentence situational overview capturing the general pattern of the period. - Write a 1-2 sentence situational overview capturing the general pattern of the period.
- Keep the overview high-level; specific details will be in the event bullets below.
- Event details - Event details
- **ONLY create bullet points for PRIMARY items (_is_primary: true)** - Present events in chronological order as a bullet list.
- **Do NOT create sections or bullets for events that don't exist** - **If multiple events occur within the same minute or overlapping time range, COMBINE them into a single bullet.**
- Do NOT create separate bullets for contextual items - Summarize the distinct activities as sub-points under the shared timestamp.
- Present primary events in chronological order as a bullet list. - If no timestamp is given, preserve order but label as Time not specified.
- **CRITICAL: When contextual items overlap with a primary event, you MUST weave that information directly into the same bullet point**
- Format: **[Timestamp]** - [Description incorporating any contextual information]. [Camera info]. (threat level: X)
- If contextual information provides an explanation (e.g., delivery truck person is likely delivery driver), reflect this understanding in your description and potentially adjust the perceived threat level
- If multiple PRIMARY events occur within the same minute, combine them into a single bullet with sub-points.
- Use bold timestamps for clarity. - Use bold timestamps for clarity.
- Camera format: "Camera: [camera name]" or mention contextual cameras inline when relevant - Group bullets under subheadings when multiple events fall into the same category (e.g., Vehicle Activity, Porch Activity, Unusual Behavior).
- Group bullets under subheadings ONLY when you have actual PRIMARY events to list (e.g., Porch Activity, Unusual Behavior).
- Threat levels - Threat levels
- Show the threat level for PRIMARY events using these labels: - Always show the threat level for each event using these labels:
- Threat level 0: "Normal" - Threat level 0: "Normal"
- Threat level 1: "Needs review" - Threat level 1: "Needs review"
- Threat level 2: "Security concern" - Threat level 2: "Security concern"
- Format as (threat level: Normal), (threat level: Needs review), or (threat level: Security concern). - Format as (threat level: Normal), (threat level: Needs review), or (threat level: Security concern).
- **When contextual items clearly explain a primary event (e.g., delivery truck explains person at door), you should describe it as normal activity and note the explanation** - If multiple events at the same time share the same threat level, only state it once.
- **Your description and tone should reflect the fuller understanding provided by contextual information**
- Example: Primary event says "unidentified person with face covering" but context shows delivery truck describe as "delivery person (truck visible on Front Driveway Cam)" rather than emphasizing suspicious elements
- The stored threat level remains as originally classified, but your narrative should reflect the contextual understanding
- If multiple PRIMARY 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 primary events are threat level 0 or explained by contextual items: - 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 1 events are present: - If threat level 1 events are present:
Final assessment: Some activity requires review but no security concerns identified. 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. - If threat level 2 events are present, clearly summarize them as Security concerns requiring immediate attention.
- Keep this section brief - do not repeat details from the event descriptions above.
- Conciseness - Conciseness
- Do not repeat benign clothing/appearance details unless they distinguish individuals. - Do not repeat benign clothing/appearance details unless they distinguish individuals.
- Summarize similar routine events instead of restating full scene descriptions. - Summarize similar routine events instead of restating full scene descriptions.
- When incorporating contextual information, do so briefly and naturally within the primary event description.
- Avoid lengthy explanatory notes - integrate context seamlessly into the narrative.
""" """
for item in segments: for item in segments:

View File

@ -23,7 +23,6 @@ import { useTranslation } from "react-i18next";
import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay"; import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay";
import BlurredIconButton from "../button/BlurredIconButton"; import BlurredIconButton from "../button/BlurredIconButton";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
import { useIsAdmin } from "@/hooks/use-is-admin";
type ExportProps = { type ExportProps = {
className: string; className: string;
@ -41,7 +40,6 @@ export default function ExportCard({
onDelete, onDelete,
}: ExportProps) { }: ExportProps) {
const { t } = useTranslation(["views/exports"]); const { t } = useTranslation(["views/exports"]);
const isAdmin = useIsAdmin();
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const [loading, setLoading] = useState( const [loading, setLoading] = useState(
exportedRecording.thumb_path.length > 0, exportedRecording.thumb_path.length > 0,
@ -197,7 +195,7 @@ export default function ExportCard({
</Tooltip> </Tooltip>
</a> </a>
)} )}
{isAdmin && !exportedRecording.in_progress && ( {!exportedRecording.in_progress && (
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<BlurredIconButton <BlurredIconButton
@ -214,23 +212,21 @@ export default function ExportCard({
<TooltipContent>{t("tooltip.editName")}</TooltipContent> <TooltipContent>{t("tooltip.editName")}</TooltipContent>
</Tooltip> </Tooltip>
)} )}
{isAdmin && ( <Tooltip>
<Tooltip> <TooltipTrigger asChild>
<TooltipTrigger asChild> <BlurredIconButton
<BlurredIconButton onClick={() =>
onClick={() => onDelete({
onDelete({ file: exportedRecording.id,
file: exportedRecording.id, exportName: exportedRecording.name,
exportName: exportedRecording.name, })
}) }
} >
> <LuTrash className="size-4 fill-destructive text-destructive hover:text-white" />
<LuTrash className="size-4 fill-destructive text-destructive hover:text-white" /> </BlurredIconButton>
</BlurredIconButton> </TooltipTrigger>
</TooltipTrigger> <TooltipContent>{t("tooltip.deleteExport")}</TooltipContent>
<TooltipContent>{t("tooltip.deleteExport")}</TooltipContent> </Tooltip>
</Tooltip>
)}
</div> </div>
</div> </div>