11import sqlite3
2+ from datetime import datetime , timedelta , timezone
23
34import pandas as pd
45import plotly .express as px
@@ -43,13 +44,27 @@ def df():
4344 params = [150 ],
4445 )
4546 # Treat timestamp as a continuous variable
46- tbl ["timestamp" ] = pd .to_datetime (tbl ["timestamp" ]).dt .strftime ("%H:%M:%S" )
47+ tbl ["timestamp" ] = pd .to_datetime (tbl ["timestamp" ], utc = True )
48+ tbl ["time" ] = tbl ["timestamp" ].dt .strftime ("%H:%M:%S" )
4749 # Reverse order of rows
4850 tbl = tbl .iloc [::- 1 ]
4951
5052 return tbl
5153
5254
55+ def read_time_period (from_time , to_time ):
56+ tbl = pd .read_sql (
57+ "select * from auc_scores where timestamp between ? and ? order by timestamp, model" ,
58+ con ,
59+ params = [from_time , to_time ],
60+ )
61+ # Treat timestamp as a continuous variable
62+ tbl ["timestamp" ] = pd .to_datetime (tbl ["timestamp" ], utc = True )
63+ tbl ["time" ] = tbl ["timestamp" ].dt .strftime ("%H:%M:%S" )
64+
65+ return tbl
66+
67+
5368model_colors = {
5469 "model_1" : "#7fc97f" ,
5570 "model_2" : "#beaed4" ,
@@ -60,37 +75,79 @@ def df():
6075model_names = list (model_colors .keys ())
6176
6277
63- app_ui = x .ui .page_sidebar (
64- x .ui .sidebar (
65- ui .input_selectize (
66- "refresh" ,
67- "Refresh interval" ,
68- {
69- 0 : "Realtime" ,
70- 5 : "5 seconds" ,
71- 30 : "30 seconds" ,
72- 60 * 5 : "5 minutes" ,
73- 60 * 15 : "15 minutes" ,
74- },
78+ def app_ui (req ):
79+ end_time = datetime .now (timezone .utc )
80+ start_time = end_time - timedelta (minutes = 1 )
81+
82+ return x .ui .page_sidebar (
83+ x .ui .sidebar (
84+ ui .input_checkbox_group (
85+ "models" , "Models" , model_names , selected = model_names
86+ ),
87+ ui .input_radio_buttons (
88+ "timeframe" ,
89+ "Timeframe" ,
90+ ["Latest" , "Specific timeframe" ],
91+ selected = "Latest" ,
92+ ),
93+ ui .panel_conditional (
94+ "input.timeframe === 'Latest'" ,
95+ ui .input_selectize (
96+ "refresh" ,
97+ "Refresh interval" ,
98+ {
99+ 0 : "Realtime" ,
100+ 5 : "5 seconds" ,
101+ 30 : "30 seconds" ,
102+ 60 * 5 : "5 minutes" ,
103+ 60 * 15 : "15 minutes" ,
104+ },
105+ ),
106+ ),
107+ ui .panel_conditional (
108+ "input.timeframe !== 'Latest'" ,
109+ ui .input_slider (
110+ "timerange" ,
111+ "Time range" ,
112+ min = start_time ,
113+ max = end_time ,
114+ value = [start_time , end_time ],
115+ step = timedelta (seconds = 1 ),
116+ time_format = "%H:%M:%S" ,
117+ ),
118+ ),
75119 ),
76- ui .input_checkbox_group ("models" , "Models" , model_names , selected = model_names ),
77- ),
78- ui .div (
79- ui .h1 ("Model monitoring dashboard" ),
80- ui .p (
81- x .ui .output_ui ("value_boxes" ),
120+ ui .div (
121+ ui .h1 ("Model monitoring dashboard" ),
122+ ui .p (
123+ x .ui .output_ui ("value_boxes" ),
124+ ),
125+ x .ui .card (output_widget ("plot_timeseries" )),
126+ x .ui .card (output_widget ("plot_dist" )),
127+ style = "max-width: 800px;" ,
82128 ),
83- x .ui .card (output_widget ("plot_timeseries" )),
84- x .ui .card (output_widget ("plot_dist" )),
85- style = "max-width: 800px;" ,
86- ),
87- fillable = False ,
88- )
129+ fillable = False ,
130+ )
89131
90132
91133def server (input : Inputs , output : Outputs , session : Session ):
134+ @reactive .Effect
135+ def update_time_range ():
136+ reactive .invalidate_later (5 )
137+ min_time , max_time = pd .to_datetime (
138+ con .execute (
139+ "select min(timestamp), max(timestamp) from auc_scores"
140+ ).fetchone (),
141+ utc = True ,
142+ )
143+ ui .update_slider (
144+ "timerange" ,
145+ min = min_time .replace (tzinfo = timezone .utc ),
146+ max = max_time .replace (tzinfo = timezone .utc ),
147+ )
148+
92149 @reactive .Calc
93- def throttled_df ():
150+ def recent_df ():
94151 refresh = int (input .refresh ())
95152 if refresh == 0 :
96153 return df ()
@@ -99,12 +156,22 @@ def throttled_df():
99156 with reactive .isolate ():
100157 return df ()
101158
159+ @reactive .Calc
160+ def timeframe_df ():
161+ start , end = input .timerange ()
162+ return read_time_period (start , end )
163+
102164 @reactive .Calc
103165 def filtered_df ():
104- data = throttled_df ()
166+ data = recent_df () if input .timeframe () == "Latest" else timeframe_df ()
167+
105168 # Filter the rows so we only include the desired models
106169 return data [data ["model" ].isin (input .models ())]
107170
171+ @reactive .Calc
172+ def filtered_model_names ():
173+ return filtered_df ()["model" ].unique ()
174+
108175 @output
109176 @render .ui
110177 def value_boxes ():
@@ -134,11 +201,11 @@ def value_boxes():
134201 )
135202
136203 @output
137- @render_plotly_streaming (recreate_when = input . models )
204+ @render_plotly_streaming (recreate_key = filtered_model_names )
138205 def plot_timeseries ():
139206 fig = px .line (
140207 filtered_df (),
141- x = "timestamp " ,
208+ x = "time " ,
142209 y = "score" ,
143210 labels = dict (score = "auc" ),
144211 color = "model" ,
@@ -162,7 +229,7 @@ def plot_timeseries():
162229 return fig
163230
164231 @output
165- @render_plotly_streaming (recreate_when = input . models )
232+ @render_plotly_streaming (recreate_key = filtered_model_names )
166233 def plot_dist ():
167234 fig = px .histogram (
168235 filtered_df (),
@@ -188,6 +255,7 @@ def plot_dist():
188255 # From https://plotly.com/python/facet-plots/#customizing-subplot-figure-titles
189256 fig .for_each_annotation (lambda a : a .update (text = a .text .split ("=" )[- 1 ]))
190257
258+ fig .update_yaxes (matches = None )
191259 fig .update_xaxes (range = [0 , 1 ], fixedrange = True )
192260 fig .layout .height = 500
193261
0 commit comments