From 48b142689151447c7ad42743eb9d80499ab1b2cc Mon Sep 17 00:00:00 2001 From: Greg <{ID}+{username}@users.noreply.github.com> Date: Fri, 8 May 2026 15:59:23 -0700 Subject: [PATCH 1/8] Add additional indicies on event and review tables. Every events or timeline endpoint filters on event start time and camera, this should speed things up by avoiding a range scan on the table. --- frigate/api/event.py | 20 +++++++++++++++- migrations/036_add_perf_indexes.py | 37 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 migrations/036_add_perf_indexes.py diff --git a/frigate/api/event.py b/frigate/api/event.py index fc7c58c375..ea99cab561 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -399,13 +399,31 @@ def events_explore( label_counts = {} + explore_columns = ( + Event.id, + Event.camera, + Event.label, + Event.sub_label, + Event.zones, + Event.start_time, + Event.end_time, + Event.has_clip, + Event.has_snapshot, + Event.plus_id, + Event.retain_indefinitely, + Event.top_score, + Event.false_positive, + Event.box, + Event.data, + ) + def event_generator(): for label_obj in distinct_labels.iterator(): label = label_obj.label # get most recent events for this label label_events = ( - Event.select() + Event.select(*explore_columns) .where((Event.label == label) & (Event.camera << allowed_cameras)) .order_by(Event.start_time.desc()) .limit(limit) diff --git a/migrations/036_add_perf_indexes.py b/migrations/036_add_perf_indexes.py new file mode 100644 index 0000000000..3796aa6acf --- /dev/null +++ b/migrations/036_add_perf_indexes.py @@ -0,0 +1,37 @@ +"""Peewee migrations -- 036_add_perf_indexes.py. + +Adds composite/single-column indexes to speed up the most common queries +issued by the web UI on initial page load: + +- event(camera, start_time DESC): /events list filtered by camera + time range +- reviewsegment(camera, start_time DESC): /api/review filtered by camera + time range +- reviewsegment(end_time): supports the end_time > after half of /api/review's range + +The existing event(label, start_time DESC) index from migration 027 already +covers /events/explore, so it is intentionally not duplicated here. +""" + +import peewee as pw + +SQL = pw.SQL + + +def migrate(migrator, database, fake=False, **kwargs): + migrator.sql( + 'CREATE INDEX IF NOT EXISTS "event_camera_start_time" ' + 'ON "event" ("camera", "start_time" DESC)' + ) + migrator.sql( + 'CREATE INDEX IF NOT EXISTS "reviewsegment_camera_start_time" ' + 'ON "reviewsegment" ("camera", "start_time" DESC)' + ) + migrator.sql( + 'CREATE INDEX IF NOT EXISTS "reviewsegment_end_time" ' + 'ON "reviewsegment" ("end_time")' + ) + + +def rollback(migrator, database, fake=False, **kwargs): + migrator.sql('DROP INDEX IF EXISTS "event_camera_start_time"') + migrator.sql('DROP INDEX IF EXISTS "reviewsegment_camera_start_time"') + migrator.sql('DROP INDEX IF EXISTS "reviewsegment_end_time"') From 311fb1bd19017a70ff2f349be901a7e6a6bd4933 Mon Sep 17 00:00:00 2001 From: Greg <{ID}+{username}@users.noreply.github.com> Date: Fri, 8 May 2026 16:18:37 -0700 Subject: [PATCH 2/8] Rewrite to use a CTE to leverage speedups by using sqllite internal optimization to do a single query instead of a starter query to get distinct labels and a subsequent loop of querys per distinct event labels. Frigate is currently shipping sqlite 3.46.1, which is above the minimum version 3.25 needed for CTEs. --- frigate/api/event.py | 151 +++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 91 deletions(-) diff --git a/frigate/api/event.py b/frigate/api/event.py index ea99cab561..c2f7e1eb30 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -389,100 +389,69 @@ def events_explore( limit: int = 10, allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter), ): - # get distinct labels for all events - distinct_labels = ( - Event.select(Event.label) - .where(Event.camera << allowed_cameras) - .distinct() - .order_by(Event.label) - ) + if not allowed_cameras: + return JSONResponse(content=[]) - label_counts = {} + # Single query: per-label COUNT and top-N ranking by start_time computed + # via window functions in a CTE, then filtered to rn <= limit. Replaces + # the previous loop that issued 2 queries per distinct label. + camera_placeholders = ",".join(["?"] * len(allowed_cameras)) + sql = f""" + WITH ranked AS ( + SELECT + id, camera, label, sub_label, zones, start_time, end_time, + has_clip, has_snapshot, plus_id, retain_indefinitely, + top_score, false_positive, box, data, + COUNT(*) OVER (PARTITION BY label) AS event_count, + ROW_NUMBER() OVER ( + PARTITION BY label ORDER BY start_time DESC + ) AS rn + FROM event + WHERE camera IN ({camera_placeholders}) + ) + SELECT * FROM ranked + WHERE rn <= ? + ORDER BY event_count DESC, start_time DESC + """ - explore_columns = ( - Event.id, - Event.camera, - Event.label, - Event.sub_label, - Event.zones, - Event.start_time, - Event.end_time, - Event.has_clip, - Event.has_snapshot, - Event.plus_id, - Event.retain_indefinitely, - Event.top_score, - Event.false_positive, - Event.box, - Event.data, - ) + allowed_data_keys = { + "type", + "score", + "top_score", + "description", + "sub_label_score", + "average_estimated_speed", + "velocity_angle", + "path_data", + "recognized_license_plate", + "recognized_license_plate_score", + } - def event_generator(): - for label_obj in distinct_labels.iterator(): - label = label_obj.label - - # get most recent events for this label - label_events = ( - Event.select(*explore_columns) - .where((Event.label == label) & (Event.camera << allowed_cameras)) - .order_by(Event.start_time.desc()) - .limit(limit) - .iterator() - ) - - # count total events for this label - label_counts[label] = ( - Event.select() - .where((Event.label == label) & (Event.camera << allowed_cameras)) - .count() - ) - - yield from label_events - - def process_events(): - for event in event_generator(): - processed_event = { - "id": event.id, - "camera": event.camera, - "label": event.label, - "zones": event.zones, - "start_time": event.start_time, - "end_time": event.end_time, - "has_clip": event.has_clip, - "has_snapshot": event.has_snapshot, - "plus_id": event.plus_id, - "retain_indefinitely": event.retain_indefinitely, - "sub_label": event.sub_label, - "top_score": event.top_score, - "false_positive": event.false_positive, - "box": event.box, - "data": { - k: v - for k, v in event.data.items() - if k - in [ - "type", - "score", - "top_score", - "description", - "sub_label_score", - "average_estimated_speed", - "velocity_angle", - "path_data", - "recognized_license_plate", - "recognized_license_plate_score", - ] - }, - "event_count": label_counts[event.label], - } - yield processed_event - - # convert iterator to list and sort - processed_events = sorted( - process_events(), - key=lambda x: (x["event_count"], x["start_time"]), - reverse=True, - ) + processed_events = [ + { + "id": event.id, + "camera": event.camera, + "label": event.label, + "zones": event.zones, + "start_time": event.start_time, + "end_time": event.end_time, + "has_clip": event.has_clip, + "has_snapshot": event.has_snapshot, + "plus_id": event.plus_id, + "retain_indefinitely": event.retain_indefinitely, + "sub_label": event.sub_label, + "top_score": event.top_score, + "false_positive": event.false_positive, + "box": event.box, + "data": { + k: v + for k, v in (event.data or {}).items() + if k in allowed_data_keys + }, + "event_count": event.event_count, + } + for event in Event.raw(sql, *allowed_cameras, limit) + ] return JSONResponse(content=processed_events) From 328a26b1692a0123b241154eacfb4874affc94d6 Mon Sep 17 00:00:00 2001 From: Greg <{ID}+{username}@users.noreply.github.com> Date: Mon, 11 May 2026 15:45:35 -0700 Subject: [PATCH 3/8] Collapse a few sequential queries into a single one. --- frigate/api/event.py | 16 +++---- frigate/api/review.py | 97 +++++++++++++++++++++++++++---------------- 2 files changed, 68 insertions(+), 45 deletions(-) diff --git a/frigate/api/event.py b/frigate/api/event.py index c2f7e1eb30..23e4cd9847 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -474,22 +474,18 @@ async def event_ids(ids: str, request: Request): status_code=400, ) - for event_id in ids: - try: - event = Event.get(Event.id == event_id) - await require_camera_access(event.camera, request=request) - except DoesNotExist: - # we should not fail the entire request if an event is not found - continue - try: - events = Event.select().where(Event.id << ids).dicts().iterator() - return JSONResponse(list(events)) + events = list(Event.select().where(Event.id << ids).dicts().iterator()) except Exception: return JSONResponse( content=({"success": False, "message": "Events not found"}), status_code=400 ) + for event in events: + await require_camera_access(event["camera"], request=request) + + return JSONResponse(events) + @router.get( "/events/search", diff --git a/frigate/api/review.py b/frigate/api/review.py index cb114db2a0..ae0f85f889 100644 --- a/frigate/api/review.py +++ b/frigate/api/review.py @@ -172,11 +172,17 @@ async def review_ids(request: Request, ids: str): status_code=400, ) + try: + reviews = list(ReviewSegment.select().where(ReviewSegment.id << ids).dicts().iterator()) + except Exception: + return JSONResponse( + content=({"success": False, "message": "Review segments not found"}), + status_code=400, + ) + + found_ids = {r["id"] for r in reviews} for review_id in ids: - try: - review = ReviewSegment.get(ReviewSegment.id == review_id) - await require_camera_access(review.camera, request=request) - except DoesNotExist: + if review_id not in found_ids: return JSONResponse( content=( {"success": False, "message": f"Review {review_id} not found"} @@ -184,16 +190,10 @@ async def review_ids(request: Request, ids: str): status_code=404, ) - try: - reviews = ( - ReviewSegment.select().where(ReviewSegment.id << ids).dicts().iterator() - ) - return JSONResponse(list(reviews)) - except Exception: - return JSONResponse( - content=({"success": False, "message": "Review segments not found"}), - status_code=400, - ) + for review in reviews: + await require_camera_access(review["camera"], request=request) + + return JSONResponse(reviews) @router.get( @@ -490,27 +490,54 @@ async def set_multiple_reviewed( user_id = current_user["username"] - for review_id in body.ids: - try: - review = ReviewSegment.get(ReviewSegment.id == review_id) - await require_camera_access(review.camera, request=request) - review_status = UserReviewStatus.get( - UserReviewStatus.user_id == user_id, - UserReviewStatus.review_segment == review_id, - ) - # Update based on the reviewed parameter - if review_status.has_been_reviewed != body.reviewed: - review_status.has_been_reviewed = body.reviewed - review_status.save() - except DoesNotExist: - try: - UserReviewStatus.create( - user_id=user_id, - review_segment=ReviewSegment.get(id=review_id), - has_been_reviewed=body.reviewed, - ) - except (DoesNotExist, IntegrityError): - pass + reviews = list(ReviewSegment.select(ReviewSegment.id, ReviewSegment.camera).where(ReviewSegment.id << body.ids)) + + for review in reviews: + await require_camera_access(review.camera, request=request) + + found_ids = [r.id for r in reviews] + + if not found_ids: + return JSONResponse( + content=( + { + "success": True, + "message": f"Marked multiple items as {'reviewed' if body.reviewed else 'unreviewed'}", + } + ), + status_code=200, + ) + + existing_statuses = list( + UserReviewStatus.select().where( + (UserReviewStatus.user_id == user_id) & + (UserReviewStatus.review_segment << found_ids) + ) + ) + + status_by_review = {s.review_segment_id: s for s in existing_statuses} + + to_update = [] + to_create = [] + + for review_id in found_ids: + if review_id in status_by_review: + status = status_by_review[review_id] + if status.has_been_reviewed != body.reviewed: + status.has_been_reviewed = body.reviewed + to_update.append(status) + else: + to_create.append({ + "user_id": user_id, + "review_segment_id": review_id, + "has_been_reviewed": body.reviewed, + }) + + if to_update: + UserReviewStatus.bulk_update(to_update, fields=[UserReviewStatus.has_been_reviewed], batch_size=100) + + if to_create: + UserReviewStatus.insert_many(to_create).execute() return JSONResponse( content=( From 39fba9b0a7c028342c604a71794d0d5b67204710 Mon Sep 17 00:00:00 2001 From: Greg <{ID}+{username}@users.noreply.github.com> Date: Mon, 11 May 2026 16:46:43 -0700 Subject: [PATCH 4/8] Use peewee instead of rw sql for the CTE query. --- frigate/api/event.py | 75 +++++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/frigate/api/event.py b/frigate/api/event.py index 23e4cd9847..72aac7d439 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -392,27 +392,66 @@ def events_explore( if not allowed_cameras: return JSONResponse(content=[]) + explore_columns = ( + Event.id, + Event.camera, + Event.label, + Event.sub_label, + Event.zones, + Event.start_time, + Event.end_time, + Event.has_clip, + Event.has_snapshot, + Event.plus_id, + Event.retain_indefinitely, + Event.top_score, + Event.false_positive, + Event.box, + Event.data, + ) + # Single query: per-label COUNT and top-N ranking by start_time computed # via window functions in a CTE, then filtered to rn <= limit. Replaces # the previous loop that issued 2 queries per distinct label. - camera_placeholders = ",".join(["?"] * len(allowed_cameras)) - sql = f""" - WITH ranked AS ( - SELECT - id, camera, label, sub_label, zones, start_time, end_time, - has_clip, has_snapshot, plus_id, retain_indefinitely, - top_score, false_positive, box, data, - COUNT(*) OVER (PARTITION BY label) AS event_count, - ROW_NUMBER() OVER ( - PARTITION BY label ORDER BY start_time DESC - ) AS rn - FROM event - WHERE camera IN ({camera_placeholders}) + event_count = fn.COUNT(Event.id).over(partition_by=[Event.label]).alias("event_count") + rn = fn.ROW_NUMBER().over( + partition_by=[Event.label], order_by=[Event.start_time.desc()] + ).alias("rn") + + base_query = ( + Event.select( + *explore_columns, + event_count, + rn, ) - SELECT * FROM ranked - WHERE rn <= ? - ORDER BY event_count DESC, start_time DESC - """ + .where(Event.camera << allowed_cameras) + ) + ranked = base_query.cte("ranked") + query = ( + Event.select( + ranked.c.id, + ranked.c.camera, + ranked.c.label, + ranked.c.sub_label, + ranked.c.zones, + ranked.c.start_time, + ranked.c.end_time, + ranked.c.has_clip, + ranked.c.has_snapshot, + ranked.c.plus_id, + ranked.c.retain_indefinitely, + ranked.c.top_score, + ranked.c.false_positive, + ranked.c.box, + ranked.c.data, + ranked.c.event_count, + ) + .from_(ranked) + .with_cte(ranked) + .where(ranked.c.rn <= limit) + .order_by(ranked.c.event_count.desc(), ranked.c.start_time.desc()) + .objects() + ) allowed_data_keys = { "type", @@ -450,7 +489,7 @@ def events_explore( }, "event_count": event.event_count, } - for event in Event.raw(sql, *allowed_cameras, limit) + for event in query ] return JSONResponse(content=processed_events) From 570e2e3f7672de52bc235edda0b9b98db0bfed6b Mon Sep 17 00:00:00 2001 From: Gdub <290704+gwmullin@users.noreply.github.com> Date: Mon, 18 May 2026 13:30:46 -0700 Subject: [PATCH 5/8] Slightly simplify review logic and avoid duplicating the json response for empty review IDs. --- frigate/api/review.py | 90 +++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/frigate/api/review.py b/frigate/api/review.py index ae0f85f889..f89af2cc94 100644 --- a/frigate/api/review.py +++ b/frigate/api/review.py @@ -10,7 +10,7 @@ import pandas as pd from fastapi import APIRouter, Request from fastapi.params import Depends from fastapi.responses import JSONResponse -from peewee import Case, DoesNotExist, IntegrityError, fn, operator +from peewee import Case, DoesNotExist, fn, operator from playhouse.shortcuts import model_to_dict from frigate.api.auth import ( @@ -173,7 +173,9 @@ async def review_ids(request: Request, ids: str): ) try: - reviews = list(ReviewSegment.select().where(ReviewSegment.id << ids).dicts().iterator()) + reviews = list( + ReviewSegment.select().where(ReviewSegment.id << ids).dicts().iterator() + ) except Exception: return JSONResponse( content=({"success": False, "message": "Review segments not found"}), @@ -490,54 +492,52 @@ async def set_multiple_reviewed( user_id = current_user["username"] - reviews = list(ReviewSegment.select(ReviewSegment.id, ReviewSegment.camera).where(ReviewSegment.id << body.ids)) - - for review in reviews: - await require_camera_access(review.camera, request=request) - - found_ids = [r.id for r in reviews] - - if not found_ids: - return JSONResponse( - content=( - { - "success": True, - "message": f"Marked multiple items as {'reviewed' if body.reviewed else 'unreviewed'}", - } - ), - status_code=200, - ) - - existing_statuses = list( - UserReviewStatus.select().where( - (UserReviewStatus.user_id == user_id) & - (UserReviewStatus.review_segment << found_ids) + reviews = list( + ReviewSegment.select(ReviewSegment.id, ReviewSegment.camera).where( + ReviewSegment.id << body.ids ) ) - - status_by_review = {s.review_segment_id: s for s in existing_statuses} - to_update = [] - to_create = [] + for review in reviews: + await require_camera_access(review.camera, request=request) - for review_id in found_ids: - if review_id in status_by_review: - status = status_by_review[review_id] - if status.has_been_reviewed != body.reviewed: - status.has_been_reviewed = body.reviewed - to_update.append(status) - else: - to_create.append({ - "user_id": user_id, - "review_segment_id": review_id, - "has_been_reviewed": body.reviewed, - }) + found_ids = [r.id for r in reviews] - if to_update: - UserReviewStatus.bulk_update(to_update, fields=[UserReviewStatus.has_been_reviewed], batch_size=100) - - if to_create: - UserReviewStatus.insert_many(to_create).execute() + if found_ids: + existing_statuses = list( + UserReviewStatus.select().where( + (UserReviewStatus.user_id == user_id) + & (UserReviewStatus.review_segment << found_ids) + ) + ) + + status_by_review = {s.review_segment_id: s for s in existing_statuses} + + to_update = [] + to_create = [] + + for review_id in found_ids: + if review_id in status_by_review: + status = status_by_review[review_id] + if status.has_been_reviewed != body.reviewed: + status.has_been_reviewed = body.reviewed + to_update.append(status) + else: + to_create.append( + { + "user_id": user_id, + "review_segment_id": review_id, + "has_been_reviewed": body.reviewed, + } + ) + + if to_update: + UserReviewStatus.bulk_update( + to_update, fields=[UserReviewStatus.has_been_reviewed], batch_size=100 + ) + + if to_create: + UserReviewStatus.insert_many(to_create).execute() return JSONResponse( content=( From 7b55c4b75811107241cd33d01a387821ca736044 Mon Sep 17 00:00:00 2001 From: Gdub <290704+gwmullin@users.noreply.github.com> Date: Mon, 18 May 2026 13:39:12 -0700 Subject: [PATCH 6/8] Rerun ruff formatting. --- frigate/api/event.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/frigate/api/event.py b/frigate/api/event.py index 72aac7d439..2268629c1c 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -413,19 +413,20 @@ def events_explore( # Single query: per-label COUNT and top-N ranking by start_time computed # via window functions in a CTE, then filtered to rn <= limit. Replaces # the previous loop that issued 2 queries per distinct label. - event_count = fn.COUNT(Event.id).over(partition_by=[Event.label]).alias("event_count") - rn = fn.ROW_NUMBER().over( - partition_by=[Event.label], order_by=[Event.start_time.desc()] - ).alias("rn") - - base_query = ( - Event.select( - *explore_columns, - event_count, - rn, - ) - .where(Event.camera << allowed_cameras) + event_count = ( + fn.COUNT(Event.id).over(partition_by=[Event.label]).alias("event_count") ) + rn = ( + fn.ROW_NUMBER() + .over(partition_by=[Event.label], order_by=[Event.start_time.desc()]) + .alias("rn") + ) + + base_query = Event.select( + *explore_columns, + event_count, + rn, + ).where(Event.camera << allowed_cameras) ranked = base_query.cte("ranked") query = ( Event.select( @@ -483,9 +484,7 @@ def events_explore( "false_positive": event.false_positive, "box": event.box, "data": { - k: v - for k, v in (event.data or {}).items() - if k in allowed_data_keys + k: v for k, v in (event.data or {}).items() if k in allowed_data_keys }, "event_count": event.event_count, } From 9fa345f19277086ac61cfffb65e062bee10d8d2e Mon Sep 17 00:00:00 2001 From: Greg <{ID}+{username}@users.noreply.github.com> Date: Mon, 18 May 2026 14:16:43 -0700 Subject: [PATCH 7/8] Remove 2x unnecessary index on reviewsegment, remove reference to prior code implementation in comment in event.py --- frigate/api/event.py | 32 +++++++++++++++--------------- migrations/036_add_perf_indexes.py | 10 ---------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/frigate/api/event.py b/frigate/api/event.py index 2268629c1c..80f3ab72dc 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -411,22 +411,20 @@ def events_explore( ) # Single query: per-label COUNT and top-N ranking by start_time computed - # via window functions in a CTE, then filtered to rn <= limit. Replaces - # the previous loop that issued 2 queries per distinct label. - event_count = ( - fn.COUNT(Event.id).over(partition_by=[Event.label]).alias("event_count") - ) - rn = ( - fn.ROW_NUMBER() - .over(partition_by=[Event.label], order_by=[Event.start_time.desc()]) - .alias("rn") - ) + # via window functions in a CTE, then filtered to rn <= limit + event_count = fn.COUNT(Event.id).over(partition_by=[Event.label]).alias("event_count") + rn = fn.ROW_NUMBER().over( + partition_by=[Event.label], order_by=[Event.start_time.desc()] + ).alias("rn") - base_query = Event.select( - *explore_columns, - event_count, - rn, - ).where(Event.camera << allowed_cameras) + base_query = ( + Event.select( + *explore_columns, + event_count, + rn, + ) + .where(Event.camera << allowed_cameras) + ) ranked = base_query.cte("ranked") query = ( Event.select( @@ -484,7 +482,9 @@ def events_explore( "false_positive": event.false_positive, "box": event.box, "data": { - k: v for k, v in (event.data or {}).items() if k in allowed_data_keys + k: v + for k, v in (event.data or {}).items() + if k in allowed_data_keys }, "event_count": event.event_count, } diff --git a/migrations/036_add_perf_indexes.py b/migrations/036_add_perf_indexes.py index 3796aa6acf..5354e5c09f 100644 --- a/migrations/036_add_perf_indexes.py +++ b/migrations/036_add_perf_indexes.py @@ -21,17 +21,7 @@ def migrate(migrator, database, fake=False, **kwargs): 'CREATE INDEX IF NOT EXISTS "event_camera_start_time" ' 'ON "event" ("camera", "start_time" DESC)' ) - migrator.sql( - 'CREATE INDEX IF NOT EXISTS "reviewsegment_camera_start_time" ' - 'ON "reviewsegment" ("camera", "start_time" DESC)' - ) - migrator.sql( - 'CREATE INDEX IF NOT EXISTS "reviewsegment_end_time" ' - 'ON "reviewsegment" ("end_time")' - ) def rollback(migrator, database, fake=False, **kwargs): migrator.sql('DROP INDEX IF EXISTS "event_camera_start_time"') - migrator.sql('DROP INDEX IF EXISTS "reviewsegment_camera_start_time"') - migrator.sql('DROP INDEX IF EXISTS "reviewsegment_end_time"') From c575fb223be830376ab96f3b40799e0272c40fdd Mon Sep 17 00:00:00 2001 From: Greg <{ID}+{username}@users.noreply.github.com> Date: Mon, 18 May 2026 14:18:17 -0700 Subject: [PATCH 8/8] Editor fail, re-ruff format. --- frigate/api/event.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/frigate/api/event.py b/frigate/api/event.py index 80f3ab72dc..605073a696 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -412,19 +412,20 @@ def events_explore( # Single query: per-label COUNT and top-N ranking by start_time computed # via window functions in a CTE, then filtered to rn <= limit - event_count = fn.COUNT(Event.id).over(partition_by=[Event.label]).alias("event_count") - rn = fn.ROW_NUMBER().over( - partition_by=[Event.label], order_by=[Event.start_time.desc()] - ).alias("rn") - - base_query = ( - Event.select( - *explore_columns, - event_count, - rn, - ) - .where(Event.camera << allowed_cameras) + event_count = ( + fn.COUNT(Event.id).over(partition_by=[Event.label]).alias("event_count") ) + rn = ( + fn.ROW_NUMBER() + .over(partition_by=[Event.label], order_by=[Event.start_time.desc()]) + .alias("rn") + ) + + base_query = Event.select( + *explore_columns, + event_count, + rn, + ).where(Event.camera << allowed_cameras) ranked = base_query.cte("ranked") query = ( Event.select( @@ -482,9 +483,7 @@ def events_explore( "false_positive": event.false_positive, "box": event.box, "data": { - k: v - for k, v in (event.data or {}).items() - if k in allowed_data_keys + k: v for k, v in (event.data or {}).items() if k in allowed_data_keys }, "event_count": event.event_count, }