22
33import base64
44import datetime
5+ import json
56import logging
67import os
78import random
5859from frigate .models import Event , ReviewSegment , Timeline , Trigger
5960from frigate .track .object_processing import TrackedObject
6061from frigate .util .path import get_event_thumbnail_bytes
61- from frigate .util .time import get_tz_modifiers
62+ from frigate .util .time import get_dst_transitions , get_tz_modifiers
6263
6364logger = logging .getLogger (__name__ )
6465
@@ -813,7 +814,6 @@ def events_summary(
813814 allowed_cameras : List [str ] = Depends (get_allowed_cameras_for_filter ),
814815):
815816 tz_name = params .timezone
816- hour_modifier , minute_modifier , seconds_offset = get_tz_modifiers (tz_name )
817817 has_clip = params .has_clip
818818 has_snapshot = params .has_snapshot
819819
@@ -828,33 +828,91 @@ def events_summary(
828828 if len (clauses ) == 0 :
829829 clauses .append ((True ))
830830
831- groups = (
831+ time_range_query = (
832832 Event .select (
833- Event .camera ,
834- Event .label ,
835- Event .sub_label ,
836- Event .data ,
837- fn .strftime (
838- "%Y-%m-%d" ,
839- fn .datetime (
840- Event .start_time , "unixepoch" , hour_modifier , minute_modifier
841- ),
842- ).alias ("day" ),
843- Event .zones ,
844- fn .COUNT (Event .id ).alias ("count" ),
833+ fn .MIN (Event .start_time ).alias ("min_time" ),
834+ fn .MAX (Event .start_time ).alias ("max_time" ),
845835 )
846836 .where (reduce (operator .and_ , clauses ) & (Event .camera << allowed_cameras ))
847- .group_by (
848- Event .camera ,
849- Event .label ,
850- Event .sub_label ,
851- Event .data ,
852- (Event .start_time + seconds_offset ).cast ("int" ) / (3600 * 24 ),
853- Event .zones ,
854- )
837+ .dicts ()
838+ .get ()
855839 )
856840
857- return JSONResponse (content = [e for e in groups .dicts ()])
841+ min_time = time_range_query .get ("min_time" )
842+ max_time = time_range_query .get ("max_time" )
843+
844+ if min_time is None or max_time is None :
845+ return JSONResponse (content = [])
846+
847+ dst_periods = get_dst_transitions (tz_name , min_time , max_time )
848+
849+ grouped : dict [tuple , dict ] = {}
850+
851+ for period_start , period_end , period_offset in dst_periods :
852+ hours_offset = int (period_offset / 60 / 60 )
853+ minutes_offset = int (period_offset / 60 - hours_offset * 60 )
854+ period_hour_modifier = f"{ hours_offset } hour"
855+ period_minute_modifier = f"{ minutes_offset } minute"
856+
857+ period_groups = (
858+ Event .select (
859+ Event .camera ,
860+ Event .label ,
861+ Event .sub_label ,
862+ Event .data ,
863+ fn .strftime (
864+ "%Y-%m-%d" ,
865+ fn .datetime (
866+ Event .start_time ,
867+ "unixepoch" ,
868+ period_hour_modifier ,
869+ period_minute_modifier ,
870+ ),
871+ ).alias ("day" ),
872+ Event .zones ,
873+ fn .COUNT (Event .id ).alias ("count" ),
874+ )
875+ .where (
876+ reduce (operator .and_ , clauses )
877+ & (Event .camera << allowed_cameras )
878+ & (Event .start_time >= period_start )
879+ & (Event .start_time <= period_end )
880+ )
881+ .group_by (
882+ Event .camera ,
883+ Event .label ,
884+ Event .sub_label ,
885+ Event .data ,
886+ (Event .start_time + period_offset ).cast ("int" ) / (3600 * 24 ),
887+ Event .zones ,
888+ )
889+ .namedtuples ()
890+ )
891+
892+ for g in period_groups :
893+ key = (
894+ g .camera ,
895+ g .label ,
896+ g .sub_label ,
897+ json .dumps (g .data , sort_keys = True ) if g .data is not None else None ,
898+ g .day ,
899+ json .dumps (g .zones , sort_keys = True ) if g .zones is not None else None ,
900+ )
901+
902+ if key in grouped :
903+ grouped [key ]["count" ] += int (g .count or 0 )
904+ else :
905+ grouped [key ] = {
906+ "camera" : g .camera ,
907+ "label" : g .label ,
908+ "sub_label" : g .sub_label ,
909+ "data" : g .data ,
910+ "day" : g .day ,
911+ "zones" : g .zones ,
912+ "count" : int (g .count or 0 ),
913+ }
914+
915+ return JSONResponse (content = list (grouped .values ()))
858916
859917
860918@router .get (
0 commit comments