Skip to content

Commit 59e35d6

Browse files
authored
Merge pull request #572 from cmu-delphi/sgratzl/coverage_endpoint
add a simple coverage endpoint
2 parents a0d6dfe + 4b3f439 commit 59e35d6

File tree

2 files changed

+70
-4
lines changed

2 files changed

+70
-4
lines changed

integrations/server/test_covidcast_endpoints.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def from_json(json: Dict[str, Any]) -> "CovidcastRow":
8484
sample_size=json["sample_size"],
8585
missing_value=json["missing_value"],
8686
missing_stderr=json["missing_stderr"],
87-
missing_sample_size=json["missing_sample_size"]
87+
missing_sample_size=json["missing_sample_size"],
8888
)
8989

9090
@property
@@ -361,3 +361,28 @@ def test_meta(self):
361361
self.assertEqual(out[0]["source"], first.source)
362362
out = self._fetch("/meta", signal=f"{first.source}:X")
363363
self.assertEqual(len(out), 0)
364+
365+
def test_coverage(self):
366+
"""Request a signal the /coverage endpoint."""
367+
368+
num_geos_per_date = [10, 20, 30, 40, 44]
369+
dates = [20200401 + i for i in range(len(num_geos_per_date))]
370+
rows = [CovidcastRow(time_value=dates[i], value=i, geo_value=str(geo_value)) for i, num_geo in enumerate(num_geos_per_date) for geo_value in range(num_geo)]
371+
self._insert_rows(rows)
372+
first = rows[0]
373+
374+
with self.subTest("default"):
375+
out = self._fetch("/coverage", signal=first.signal_pair, latest=dates[-1], format="json")
376+
self.assertEqual(len(out), len(num_geos_per_date))
377+
self.assertEqual([o["time_value"] for o in out], dates)
378+
self.assertEqual([o["count"] for o in out], num_geos_per_date)
379+
380+
with self.subTest("specify window"):
381+
out = self._fetch("/coverage", signal=first.signal_pair, window=f"{dates[0]}-{dates[1]}", format="json")
382+
self.assertEqual(len(out), 2)
383+
self.assertEqual([o["time_value"] for o in out], dates[:2])
384+
self.assertEqual([o["count"] for o in out], num_geos_per_date[:2])
385+
386+
with self.subTest("invalid geo_type"):
387+
out = self._fetch("/coverage", signal=first.signal_pair, geo_type="state", format="json")
388+
self.assertEqual(len(out), 0)

src/server/endpoints/covidcast.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import List, Optional, Union, Tuple, Dict, Any, Set
22
from itertools import groupby
3-
from datetime import date, datetime
3+
from datetime import date, datetime, timedelta
44
from flask import Blueprint, request
55
from flask.json import loads, jsonify
66
from bisect import bisect_right
@@ -34,7 +34,7 @@
3434
from .._db import sql_table_has_columns
3535
from .._pandas import as_pandas
3636
from .covidcast_utils import compute_trend, compute_trends, compute_correlations, compute_trend_value, CovidcastMetaEntry, AllSignalsMap
37-
from ..utils import shift_time_value, date_to_time_value, time_value_to_iso
37+
from ..utils import shift_time_value, date_to_time_value, time_value_to_iso, time_value_to_date
3838

3939
# first argument is the endpoint name
4040
bp = Blueprint("covidcast", __name__)
@@ -150,7 +150,6 @@ def handle():
150150
q.set_order("source", "signal", "time_type", "time_value", "geo_type", "geo_value", "issue")
151151
q.set_fields(fields_string, fields_int, fields_float)
152152

153-
154153
# basic query info
155154
# data type of each field
156155
# build the source, signal, time, and location (type and id) filters
@@ -493,3 +492,45 @@ def handle_meta():
493492
entry.intergrate(row)
494493

495494
return jsonify([r.asdict() for r in out.values()])
495+
496+
497+
@bp.route("/coverage", methods=("GET", "POST"))
498+
def handle_coverage():
499+
"""
500+
similar to /signal_dashboard_coverage for a specific signal returns the coverage (number of locations for a given geo_type)
501+
"""
502+
503+
signal = parse_source_signal_arg("signal")
504+
geo_type = request.args.get("geo_type", "county")
505+
if "window" in request.values:
506+
time_window = parse_day_range_arg("window")
507+
else:
508+
now_time = extract_date("latest")
509+
now = date.today() if now_time is None else time_value_to_date(now_time)
510+
last = extract_integer("days")
511+
if last is None:
512+
last = 30
513+
time_window = (date_to_time_value(now - timedelta(days=last)), date_to_time_value(now))
514+
515+
q = QueryBuilder("covidcast", "c")
516+
fields_string = ["source", "signal"]
517+
fields_int = ["time_value"]
518+
519+
q.set_fields(fields_string, fields_int)
520+
521+
# manually append the count column because of grouping
522+
fields_int.append("count")
523+
q.fields.append(f"count({q.alias}.geo_value) as count")
524+
525+
if geo_type == "only-county":
526+
q.where(geo_type="county")
527+
q.conditions.append('geo_value not like "%000"')
528+
else:
529+
q.where(geo_type=geo_type)
530+
q.where_source_signal_pairs("source", "signal", signal)
531+
q.where_time_pairs("time_type", "time_value", [TimePair("day", [time_window])])
532+
q.group_by = "c.source, c.signal, c.time_value"
533+
534+
_handle_lag_issues_as_of(q, None, None, None)
535+
536+
return execute_query(q.query, q.params, fields_string, fields_int, [])

0 commit comments

Comments
 (0)