1
1
import sqlite3
2
+ from datetime import datetime , timedelta , timezone
2
3
3
4
import pandas as pd
4
5
import plotly .express as px
@@ -43,13 +44,27 @@ def df():
43
44
params = [150 ],
44
45
)
45
46
# 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" )
47
49
# Reverse order of rows
48
50
tbl = tbl .iloc [::- 1 ]
49
51
50
52
return tbl
51
53
52
54
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
+
53
68
model_colors = {
54
69
"model_1" : "#7fc97f" ,
55
70
"model_2" : "#beaed4" ,
@@ -60,37 +75,79 @@ def df():
60
75
model_names = list (model_colors .keys ())
61
76
62
77
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
+ ),
75
119
),
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;" ,
82
128
),
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
+ )
89
131
90
132
91
133
def 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
+
92
149
@reactive .Calc
93
- def throttled_df ():
150
+ def recent_df ():
94
151
refresh = int (input .refresh ())
95
152
if refresh == 0 :
96
153
return df ()
@@ -99,12 +156,22 @@ def throttled_df():
99
156
with reactive .isolate ():
100
157
return df ()
101
158
159
+ @reactive .Calc
160
+ def timeframe_df ():
161
+ start , end = input .timerange ()
162
+ return read_time_period (start , end )
163
+
102
164
@reactive .Calc
103
165
def filtered_df ():
104
- data = throttled_df ()
166
+ data = recent_df () if input .timeframe () == "Latest" else timeframe_df ()
167
+
105
168
# Filter the rows so we only include the desired models
106
169
return data [data ["model" ].isin (input .models ())]
107
170
171
+ @reactive .Calc
172
+ def filtered_model_names ():
173
+ return filtered_df ()["model" ].unique ()
174
+
108
175
@output
109
176
@render .ui
110
177
def value_boxes ():
@@ -134,11 +201,11 @@ def value_boxes():
134
201
)
135
202
136
203
@output
137
- @render_plotly_streaming (recreate_when = input . models )
204
+ @render_plotly_streaming (recreate_key = filtered_model_names )
138
205
def plot_timeseries ():
139
206
fig = px .line (
140
207
filtered_df (),
141
- x = "timestamp " ,
208
+ x = "time " ,
142
209
y = "score" ,
143
210
labels = dict (score = "auc" ),
144
211
color = "model" ,
@@ -162,7 +229,7 @@ def plot_timeseries():
162
229
return fig
163
230
164
231
@output
165
- @render_plotly_streaming (recreate_when = input . models )
232
+ @render_plotly_streaming (recreate_key = filtered_model_names )
166
233
def plot_dist ():
167
234
fig = px .histogram (
168
235
filtered_df (),
@@ -188,6 +255,7 @@ def plot_dist():
188
255
# From https://plotly.com/python/facet-plots/#customizing-subplot-figure-titles
189
256
fig .for_each_annotation (lambda a : a .update (text = a .text .split ("=" )[- 1 ]))
190
257
258
+ fig .update_yaxes (matches = None )
191
259
fig .update_xaxes (range = [0 , 1 ], fixedrange = True )
192
260
fig .layout .height = 500
193
261
0 commit comments