5
5
from flask .json import loads , jsonify
6
6
from bisect import bisect_right
7
7
from sqlalchemy import text
8
- from pandas import read_csv
8
+ from pandas import read_csv , to_datetime
9
9
10
10
from .._common import is_compatibility_mode , db
11
11
from .._exceptions import ValidationFailedException , DatabaseErrorException
16
16
parse_geo_arg ,
17
17
parse_source_signal_arg ,
18
18
parse_time_arg ,
19
- parse_day_arg ,
19
+ parse_day_or_week_arg ,
20
20
parse_day_range_arg ,
21
+ parse_day_or_week_range_arg ,
21
22
parse_single_source_signal_arg ,
22
23
parse_single_time_arg ,
23
24
parse_single_geo_arg ,
34
35
)
35
36
from .._pandas import as_pandas , print_pandas
36
37
from .covidcast_utils import compute_trend , compute_trends , compute_correlations , compute_trend_value , CovidcastMetaEntry
37
- from ..utils import shift_time_value , date_to_time_value , time_value_to_iso , time_value_to_date
38
+ from ..utils import shift_time_value , date_to_time_value , time_value_to_iso , time_value_to_date , shift_week_value , week_value_to_week
38
39
from .covidcast_utils .model import TimeType , data_sources , create_source_signal_alias_mapper
39
40
40
41
# first argument is the endpoint name
@@ -172,15 +173,18 @@ def transform_row(row, proxy):
172
173
173
174
@bp .route ("/trend" , methods = ("GET" , "POST" ))
174
175
def handle_trend ():
175
- require_all ("date " , "window " )
176
+ require_all ("window " , "date " )
176
177
source_signal_pairs = parse_source_signal_pairs ()
177
178
source_signal_pairs , alias_mapper = create_source_signal_alias_mapper (source_signal_pairs )
178
- # TODO alias
179
179
geo_pairs = parse_geo_pairs ()
180
180
181
- time_value = parse_day_arg ("date" )
182
- time_window = parse_day_range_arg ("window" )
183
- basis_time_value = extract_date ("basis" ) or shift_time_value (time_value , - 7 )
181
+ time_window , is_day = parse_day_or_week_range_arg ("window" )
182
+ time_value , is_also_day = parse_day_or_week_arg ("date" )
183
+ if is_day != is_also_day :
184
+ raise ValidationFailedException ('mixing weeks with day arguments' )
185
+ basis_time_value = extract_date ("basis" )
186
+ if basis_time_value is None :
187
+ basis_time_value = (shift_time_value (time_value , - 7 ) if is_day else shift_week_value (time_value , - 7 ))
184
188
185
189
# build query
186
190
q = QueryBuilder ("covidcast" , "t" )
@@ -193,7 +197,7 @@ def handle_trend():
193
197
194
198
q .where_source_signal_pairs ("source" , "signal" , source_signal_pairs )
195
199
q .where_geo_pairs ("geo_type" , "geo_value" , geo_pairs )
196
- q .where_time_pairs ("time_type" , "time_value" , [TimePair ("day" , [time_window ])])
200
+ q .where_time_pairs ("time_type" , "time_value" , [TimePair ("day" if is_day else "week" , [time_window ])])
197
201
198
202
# fetch most recent issue fast
199
203
_handle_lag_issues_as_of (q , None , None , None )
@@ -225,7 +229,7 @@ def handle_trendseries():
225
229
source_signal_pairs , alias_mapper = create_source_signal_alias_mapper (source_signal_pairs )
226
230
geo_pairs = parse_geo_pairs ()
227
231
228
- time_window = parse_day_range_arg ("window" )
232
+ time_window , is_day = parse_day_or_week_range_arg ("window" )
229
233
basis_shift = extract_integer ("basis" )
230
234
if basis_shift is None :
231
235
basis_shift = 7
@@ -241,14 +245,16 @@ def handle_trendseries():
241
245
242
246
q .where_source_signal_pairs ("source" , "signal" , source_signal_pairs )
243
247
q .where_geo_pairs ("geo_type" , "geo_value" , geo_pairs )
244
- q .where_time_pairs ("time_type" , "time_value" , [TimePair ("day" , [time_window ])])
248
+ q .where_time_pairs ("time_type" , "time_value" , [TimePair ("day" if is_day else 'week' , [time_window ])])
245
249
246
250
# fetch most recent issue fast
247
251
_handle_lag_issues_as_of (q , None , None , None )
248
252
249
253
p = create_printer ()
250
254
251
255
shifter = lambda x : shift_time_value (x , - basis_shift )
256
+ if not is_day :
257
+ shifter = lambda x : shift_week_value (x , - basis_shift )
252
258
253
259
def gen (rows ):
254
260
for key , group in groupby ((parse_row (row , fields_string , fields_int , fields_float ) for row in rows ), lambda row : (row ["geo_type" ], row ["geo_value" ], row ["source" ], row ["signal" ])):
@@ -276,7 +282,8 @@ def handle_correlation():
276
282
other_pairs = parse_source_signal_arg ("others" )
277
283
source_signal_pairs , alias_mapper = create_source_signal_alias_mapper (other_pairs + [reference ])
278
284
geo_pairs = parse_geo_arg ()
279
- time_window = parse_day_range_arg ("window" )
285
+ time_window , is_day = parse_day_or_week_range_arg ("window" )
286
+
280
287
lag = extract_integer ("lag" )
281
288
if lag is None :
282
289
lag = 28
@@ -296,12 +303,17 @@ def handle_correlation():
296
303
source_signal_pairs ,
297
304
)
298
305
q .where_geo_pairs ("geo_type" , "geo_value" , geo_pairs )
299
- q .where_time_pairs ("time_type" , "time_value" , [TimePair ("day" , [time_window ])])
306
+ q .where_time_pairs ("time_type" , "time_value" , [TimePair ("day" if is_day else "week" , [time_window ])])
300
307
301
308
# fetch most recent issue fast
302
309
q .conditions .append (f"({ q .alias } .is_latest_issue IS TRUE)" )
303
310
304
- df = as_pandas (str (q ), q .params , parse_dates = {"time_value" : "%Y%m%d" })
311
+ df = as_pandas (str (q ), q .params )
312
+ if is_day :
313
+ df ['time_value' ] = to_datetime (df ['time_value' ], format = "%Y%m%d" )
314
+ else :
315
+ # week but convert to date for simpler shifting
316
+ df ['time_value' ] = to_datetime (df ['time_value' ].apply (lambda v : week_value_to_week (v ).startdate ()))
305
317
306
318
p = create_printer ()
307
319
@@ -329,7 +341,7 @@ def gen():
329
341
for (source , signal ), other_group in other_groups :
330
342
if alias_mapper :
331
343
source = alias_mapper (source , signal )
332
- for cor in compute_correlations (geo_type , geo_value , source , signal , lag , reference_group , other_group ):
344
+ for cor in compute_correlations (geo_type , geo_value , source , signal , lag , reference_group , other_group , is_day ):
333
345
yield cor .asdict ()
334
346
335
347
# now use a generator for sending the rows and execute all the other queries
@@ -345,6 +357,8 @@ def handle_export():
345
357
geo_type = request .args .get ("geo_type" , "county" )
346
358
geo_values = request .args .get ("geo_values" , "*" )
347
359
360
+ # TODO weekly signals
361
+
348
362
if geo_values != "*" :
349
363
geo_values = geo_values .split ("," )
350
364
@@ -424,8 +438,9 @@ def handle_backfill():
424
438
# don't need the alias mapper since we don't return the source
425
439
426
440
time_pair = parse_single_time_arg ("time" )
441
+ is_day = time_pair .is_day
427
442
geo_pair = parse_single_geo_arg ("geo" )
428
- reference_anchor_lag = extract_integer ("anchor_lag" ) # in days
443
+ reference_anchor_lag = extract_integer ("anchor_lag" ) # in days or weeks
429
444
if reference_anchor_lag is None :
430
445
reference_anchor_lag = 60
431
446
@@ -461,7 +476,8 @@ def gen(rows):
461
476
for time_value , group in groupby ((parse_row (row , fields_string , fields_int , fields_float ) for row in rows ), lambda row : row ["time_value" ]):
462
477
# compute data per time value
463
478
issues : List [Dict [str , Any ]] = [r for r in group ]
464
- anchor_row = find_anchor_row (issues , shift_time_value (time_value , reference_anchor_lag ))
479
+ shifted_time_value = shift_time_value (time_value , reference_anchor_lag ) if is_day else shift_week_value (time_value , reference_anchor_lag )
480
+ anchor_row = find_anchor_row (issues , shifted_time_value )
465
481
466
482
for i , row in enumerate (issues ):
467
483
if i > 0 :
@@ -576,8 +592,9 @@ def handle_coverage():
576
592
source_signal_pairs , alias_mapper = create_source_signal_alias_mapper (source_signal_pairs )
577
593
geo_type = request .args .get ("geo_type" , "county" )
578
594
if "window" in request .values :
579
- time_window = parse_day_range_arg ("window" )
595
+ time_window , is_day = parse_day_or_week_range_arg ("window" )
580
596
else :
597
+ is_day = False # TODO
581
598
now_time = extract_date ("latest" )
582
599
now = date .today () if now_time is None else time_value_to_date (now_time )
583
600
last = extract_integer ("days" )
@@ -601,7 +618,7 @@ def handle_coverage():
601
618
else :
602
619
q .where (geo_type = geo_type )
603
620
q .where_source_signal_pairs ("source" , "signal" , source_signal_pairs )
604
- q .where_time_pairs ("time_type" , "time_value" , [TimePair ("day" , [time_window ])])
621
+ q .where_time_pairs ("time_type" , "time_value" , [TimePair ("day" if is_day else 'week' , [time_window ])])
605
622
q .group_by = "c.source, c.signal, c.time_value"
606
623
q .set_order ("source" , "signal" , "time_value" )
607
624
0 commit comments