From 17f3bc2ac716bc0742f782db5103a5788a21d3e1 Mon Sep 17 00:00:00 2001 From: Dmytro Trotsko Date: Mon, 7 Jul 2025 20:25:08 +0300 Subject: [PATCH 1/6] Added new endpoint which accepts source:signals and returns list of geo_type:geo_value pairs --- src/server/endpoints/covidcast.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/server/endpoints/covidcast.py b/src/server/endpoints/covidcast.py index f4875fb6d..965f36481 100644 --- a/src/server/endpoints/covidcast.py +++ b/src/server/endpoints/covidcast.py @@ -561,6 +561,26 @@ def handle_geo_coverage(): return execute_query(q.query, q.params, fields_string, [], []) +@bp.route("/indicator_geo_coverage", methods=("GET", "POST")) +def handle_indicator_geo_coverage(): + source_signal_sets = parse_source_signal_sets() + source_signal_sets = restrict_by_roles(source_signal_sets) + source_signal_sets, alias_mapper = create_source_signal_alias_mapper(source_signal_sets) + + q = QueryBuilder("coverage_crossref_v", "c") + fields_string = ["geo_type", "geo_value"] + + q.set_fields(fields_string) + + q.apply_source_signal_filters("source", "signal", source_signal_sets) + q.set_sort_order("geo_type", "geo_value") + q.group_by = ["c." + field for field in fields_string] # this condenses duplicate results, similar to `SELECT DISTINCT` + + def transform_row(row, proxy): + return f"{row['geo_type']}:{row['geo_value']}" + + return execute_query(q.query, q.params, fields_string, [], [], transform=transform_row) + @bp.route("/anomalies", methods=("GET", "POST")) def handle_anomalies(): """ From debf127308db8c69037315fe38c953fcdc0253d6 Mon Sep 17 00:00:00 2001 From: Dmytro Trotsko Date: Mon, 7 Jul 2025 20:25:43 +0300 Subject: [PATCH 2/6] Added tests for the /indicator_geo_coverage endpoint --- .../server/test_covidcast_endpoints.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/integrations/server/test_covidcast_endpoints.py b/integrations/server/test_covidcast_endpoints.py index 206ed3c54..c04dc318a 100644 --- a/integrations/server/test_covidcast_endpoints.py +++ b/integrations/server/test_covidcast_endpoints.py @@ -11,6 +11,8 @@ from delphi.epidata.maintenance.covidcast_meta_cache_updater import main as update_cache from delphi.epidata.acquisition.covidcast.test_utils import CovidcastBase, CovidcastTestRow +from delphi.epidata.maintenance.coverage_crossref_updater import main as update_crossref + # use the local instance of the Epidata API BASE_URL = "http://delphi_web_epidata/epidata/covidcast" @@ -385,3 +387,25 @@ def test_coverage(self): with self.subTest("invalid geo_type"): out = self._fetch("/coverage", signal=first.signal_pair(), geo_type="doesnt_exist", format="json") self.assertEqual(len(out), 0) + + def test_indicator_geo_coverage(self): + """Request a geo_type:geo_value from the /indicator_geo_coverage endpoint.""" + + self._insert_rows([ + CovidcastTestRow.make_default_row(geo_type="state", geo_value="pa"), + CovidcastTestRow.make_default_row(geo_type="state", geo_value="ny"), + CovidcastTestRow.make_default_row(geo_type="state", geo_value="ny", signal="sig2"), + ]) + + update_crossref() + + response = requests.get( + f"{BASE_URL}/indicator_geo_coverage", + params=dict(data_source="src", signals="sig"), + ) + response.raise_for_status() + out = response.json() + + self.assertEqual(len(out["epidata"]), 2) + self.assertEqual(out["epidata"], ['state:ny', 'state:pa']) + From 981ed24d03b89a6398cf09e69706e571a42a9c7c Mon Sep 17 00:00:00 2001 From: Dmytro Trotsko Date: Fri, 18 Jul 2025 18:16:53 +0300 Subject: [PATCH 3/6] Update src/server/endpoints/covidcast.py Co-authored-by: george --- src/server/endpoints/covidcast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/endpoints/covidcast.py b/src/server/endpoints/covidcast.py index 965f36481..fe47f0fd7 100644 --- a/src/server/endpoints/covidcast.py +++ b/src/server/endpoints/covidcast.py @@ -565,7 +565,7 @@ def handle_geo_coverage(): def handle_indicator_geo_coverage(): source_signal_sets = parse_source_signal_sets() source_signal_sets = restrict_by_roles(source_signal_sets) - source_signal_sets, alias_mapper = create_source_signal_alias_mapper(source_signal_sets) + source_signal_sets, _ = create_source_signal_alias_mapper(source_signal_sets) q = QueryBuilder("coverage_crossref_v", "c") fields_string = ["geo_type", "geo_value"] From ad5ae6625541865015f890ffb0defbe75cf98b78 Mon Sep 17 00:00:00 2001 From: Dmytro Trotsko Date: Mon, 21 Jul 2025 19:32:11 +0300 Subject: [PATCH 4/6] Updated endpoint name --- integrations/server/test_covidcast_endpoints.py | 6 +++--- src/server/endpoints/covidcast.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integrations/server/test_covidcast_endpoints.py b/integrations/server/test_covidcast_endpoints.py index c04dc318a..45dbc9bce 100644 --- a/integrations/server/test_covidcast_endpoints.py +++ b/integrations/server/test_covidcast_endpoints.py @@ -388,8 +388,8 @@ def test_coverage(self): out = self._fetch("/coverage", signal=first.signal_pair(), geo_type="doesnt_exist", format="json") self.assertEqual(len(out), 0) - def test_indicator_geo_coverage(self): - """Request a geo_type:geo_value from the /indicator_geo_coverage endpoint.""" + def test_geo_indicator_coverage(self): + """Request a geo_type:geo_value from the /geo_indicator_coverage endpoint.""" self._insert_rows([ CovidcastTestRow.make_default_row(geo_type="state", geo_value="pa"), @@ -400,7 +400,7 @@ def test_indicator_geo_coverage(self): update_crossref() response = requests.get( - f"{BASE_URL}/indicator_geo_coverage", + f"{BASE_URL}/geo_indicator_coverage", params=dict(data_source="src", signals="sig"), ) response.raise_for_status() diff --git a/src/server/endpoints/covidcast.py b/src/server/endpoints/covidcast.py index fe47f0fd7..4c6b0c734 100644 --- a/src/server/endpoints/covidcast.py +++ b/src/server/endpoints/covidcast.py @@ -561,8 +561,8 @@ def handle_geo_coverage(): return execute_query(q.query, q.params, fields_string, [], []) -@bp.route("/indicator_geo_coverage", methods=("GET", "POST")) -def handle_indicator_geo_coverage(): +@bp.route("/geo_indicator_coverage", methods=("GET", "POST")) +def handle_geo_indicator_coverage(): source_signal_sets = parse_source_signal_sets() source_signal_sets = restrict_by_roles(source_signal_sets) source_signal_sets, _ = create_source_signal_alias_mapper(source_signal_sets) From 99d648b9abdbe9adf20eacc5c48fbe0d3b66984a Mon Sep 17 00:00:00 2001 From: george Date: Fri, 18 Jul 2025 13:52:38 -0400 Subject: [PATCH 5/6] Update python docker image to current debian release (#1660) --- dev/docker/python/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/docker/python/Dockerfile b/dev/docker/python/Dockerfile index 1836e4a5b..74e383d97 100644 --- a/dev/docker/python/Dockerfile +++ b/dev/docker/python/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8-buster +FROM python:3.8-bookworm RUN apt-get update && apt-get install -y r-base && Rscript -e "install.packages(c('httr','xml2'))" From f87a7680ee52ad9300c4b72920868127578168b0 Mon Sep 17 00:00:00 2001 From: Dmytro Trotsko Date: Mon, 21 Jul 2025 23:44:39 +0300 Subject: [PATCH 6/6] Fixed test --- integrations/server/test_covidcast_endpoints.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/integrations/server/test_covidcast_endpoints.py b/integrations/server/test_covidcast_endpoints.py index 45dbc9bce..09f195f7c 100644 --- a/integrations/server/test_covidcast_endpoints.py +++ b/integrations/server/test_covidcast_endpoints.py @@ -399,13 +399,7 @@ def test_geo_indicator_coverage(self): update_crossref() - response = requests.get( - f"{BASE_URL}/geo_indicator_coverage", - params=dict(data_source="src", signals="sig"), - ) - response.raise_for_status() - out = response.json() - + out = self._fetch("/geo_indicator_coverage", data_source="src", signals="sig") self.assertEqual(len(out["epidata"]), 2) self.assertEqual(out["epidata"], ['state:ny', 'state:pa'])