Skip to content

Commit 814d107

Browse files
committed
added county api
1 parent 48d4ac7 commit 814d107

File tree

6 files changed

+204
-16
lines changed

6 files changed

+204
-16
lines changed

app/location.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,35 @@ def serialize(self, timelines = False):
6161
}})
6262

6363
# Return the serialized location.
64+
return serialized
65+
66+
class LocationCounty:
67+
"""
68+
A county in the United States affected by coronavirus
69+
"""
70+
def __init__(self, county, coordinates, confirmed, new, death):
71+
self.county = county
72+
self.coordinates = coordinates
73+
self.confirmed = confirmed
74+
self.new = new
75+
self.death = death
76+
77+
def serialize(self):
78+
"""
79+
Serializes the LocationCounty into a dict
80+
81+
:returns: The serialized LocationCounty
82+
:rtype: dict
83+
"""
84+
serialized = {
85+
'county': self.county,
86+
87+
'coordinates': self.coordinates.serialize(),
88+
89+
'latest': {
90+
'confirmed': self.confirmed,
91+
'new': self.new,
92+
'death': self.death
93+
}
94+
}
6495
return serialized

app/routes/v2/locations.py

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,55 @@
11
from flask import jsonify, request
22
from distutils.util import strtobool
33
from ...routes import api_v2 as api
4-
from ...services import jhu
4+
from ...services import jhu, csbs
55

66
@api.route('/locations')
77
def locations():
8-
# Query parameters.
9-
timelines = strtobool(request.args.get('timelines', default='0'))
10-
country_code = request.args.get('country_code', type=str)
8+
# Query parameter -> data source.
9+
source = request.args.get('source', default='jhu')
1110

12-
# Retrieve all the locations.
13-
locations = jhu.get_all()
11+
if source == 'csbs':
12+
# Query parameters
13+
state = request.args.get('state', type=str, default='')
14+
15+
# Retrieve all locations.
16+
data = csbs.get_all()
17+
locations = data.copy()
1418

15-
# Filtering my country code if provided.
16-
if not country_code is None:
17-
locations = list(filter(lambda location: location.country_code == country_code.upper(), locations))
19+
# Filter by state if applicable
20+
if state in locations:
21+
locations = locations[state]
22+
return jsonify({
23+
state: [
24+
location.serialize() for location in locations
25+
]
26+
})
27+
28+
# serialize everything
29+
for state in locations:
30+
locations[state] = [county.serialize() for county in locations[state]]
31+
32+
return jsonify(locations)
33+
else:
34+
# Query parameters
35+
timelines = strtobool(request.args.get('timelines', default='0'))
36+
country_code = request.args.get('country_code', type=str)
37+
38+
# Retrieve all the locations.
39+
locations = jhu.get_all()
1840

19-
# Serialize each location and return.
20-
return jsonify({
21-
'locations': [
22-
location.serialize(timelines) for location in locations
23-
]
24-
})
41+
# Filtering my country code if provided.
42+
if not country_code is None:
43+
locations = list(filter(lambda location: location.country_code == country_code.upper(), locations))
44+
45+
# Serialize each location and return.
46+
return jsonify({
47+
'locations': [
48+
location.serialize(timelines) for location in locations
49+
]
50+
})
51+
52+
2553

2654
@api.route('/locations/<int:id>')
2755
def location(id):

app/services/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from .location.jhu import JhuLocationService
2+
from .location.csbs import CSBSLocationService
23

34
# Instances of the services.
4-
jhu = JhuLocationService()
5+
jhu = JhuLocationService()
6+
csbs = CSBSLocationService()

app/services/location/csbs.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from . import LocationService
2+
from ...location import LocationCounty
3+
from ...coordinates import Coordinates
4+
5+
class CSBSLocationService(LocationService):
6+
"""
7+
Servive for retrieving locations from csbs
8+
"""
9+
10+
def get_all(self):
11+
# Get the locations
12+
return get_locations()
13+
14+
def get(self, state):
15+
return self.get_all()[state]
16+
17+
import requests
18+
import csv
19+
from datetime import datetime
20+
from cachetools import cached, TTLCache
21+
22+
# Base URL for fetching data
23+
base_url = 'https://facts.csbs.org/covid-19/covid19_county.csv'
24+
25+
@cached(cache=TTLCache(maxsize=1, ttl=3600))
26+
def get_locations():
27+
"""
28+
Retrieves county locations; locations are cached for 1 hour
29+
30+
:returns: The locations.
31+
:rtype: dict
32+
"""
33+
request = requests.get(base_url)
34+
text = request.text
35+
36+
data = list(csv.DictReader(text.splitlines()))
37+
38+
locations = {}
39+
40+
for item in data:
41+
state = item['State Name']
42+
if state not in locations:
43+
locations[state] = []
44+
45+
county = item['County Name']
46+
if county == "Unassigned" or county == "Unknown":
47+
continue
48+
49+
confirmed = int(item['Confirmed'] or 0)
50+
new = int(item['New'] or 0)
51+
death = int(item['Death'] or 0)
52+
coordinates = Coordinates(float(item['Latitude']), float(item['Longitude']))
53+
location_county = LocationCounty(county, coordinates, confirmed, new, death)
54+
locations[state].append(location_county)
55+
56+
return locations
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
County Name,State Name,Confirmed,New,Death,Fatality Rate,Latitude,Longitude,Last Update
2+
New York,New York,4408,454,26,0.6%,40.71455,-74.00714,2020-03-20 13:58 EDT
3+
Westchester,New York,1091,293,0,0%,41.16319759,-73.7560629,2020-03-20 13:58 EDT
4+
Nassau,New York,754,382,4,0.5%,40.74165225,-73.58899619,2020-03-20 13:58 EDT
5+
Yakima,Washington,7,0,0,0%,46.60448,-120.50721,2020-03-20 13:58 EDT
6+
Thurston,Washington,6,0,0,0%,46.91980578,-122.8298691,2020-03-20 13:58 EDT
7+
Jefferson,Washington,4,0,0,0%,47.74810608,-123.6000095,2020-03-20 13:58 EDT
8+
Douglas,Kansas,1,0,0,0%,38.88462907,-95.29255463,2020-03-20 13:58 EDT
9+
Cherokee,Kansas,1,0,0,0%,37.16926692,-94.8462675759999,2020-03-20 13:58 EDT
10+
Jackson,Kansas,1,0,0,0%,39.4168027220001,-95.793674403,2020-03-20 13:58 EDT
11+
Twin Falls,Idaho,1,0,0,0%,42.55619,-114.4696,2020-03-20 13:58 EDT
12+
Kootenai,Idaho,1,0,0,0%,47.6775872760001,-116.697131928,2020-03-20 13:58 EDT
13+
Chittenden,Vermont,4,0,1,25%,44.45799511,-73.05404973,2020-03-20 13:58 EDT
14+
Bennington,Vermont,3,0,0,0%,42.87672,-73.19818,2020-03-20 13:58 EDT
15+
Windsor,Vermont,3,0,1,33.3%,43.48115,-72.38581,2020-03-20 13:58 EDT
16+
Washington,Vermont,1,0,0,0%,44.27344561,-72.61485925,2020-03-20 13:58 EDT
17+
Orange,Vermont,1,0,0,0%,44.14854,-72.40233,2020-03-20 13:58 EDT
18+
Addison,Vermont,1,0,0,0%,44.0280736,-73.13152876,2020-03-20 13:58 EDT
19+
Burleigh,North Dakota,11,0,0,0%,46.97801044,-100.4669442,2020-03-20 13:58 EDT
20+
Tucker,West Virginia,2,0,0,0%,39.1135508250001,-79.56492129,2020-03-20 13:58 EDT
21+
Mercer,West Virginia,1,0,0,0%,37.40556515,-81.11143231,2020-03-20 13:58 EDT
22+
Monongalia,West Virginia,1,0,0,0%,39.630233859,-80.0465546289999,2020-03-20 13:58 EDT
23+
Unassigned,New York,166,149,4,2.4%,42.165726,-74.948051,2020-03-20 13:58 EDT
24+
Unassigned,Washington,151,0,0,0%,47.400902,-121.490494,2020-03-20 13:58 EDT
25+
Unassigned,Colorado,57,0,0,0%,39.059811,-105.311104,2020-03-20 13:58 EDT
26+
Unknown,Pennsylvania,55,55,0,0%,40.590752,-77.209755,2020-03-20 13:58 EDT
27+
Unassigned,Pennsylvania,0,0,0,NaN%,40.590752,-77.209755,2020-03-20 13:58 EDT
28+
Franklin,Pennsylvania,1,1,0,0%,39.927495836,-77.721161869,2020-03-20 13:58 EDT
29+
Franklin,North Carolina,4,4,0,0%,36.0827448150001,-78.285600305,2020-03-20 13:58 EDT
30+
Lee,North Carolina,1,1,0,0%,35.475059921,-79.17154054,2020-03-20 13:58 EDT
31+
Clay,Minnesota,1,1,0,0%,46.892347886,-96.490737839,2020-03-20 13:58 EDT
32+
Yuma,Arizona,1,1,0,0%,32.768956524,-113.905830295,2020-03-20 13:58 EDT
33+
Dunklin,Missouri,1,1,0,0%,36.105848973,-90.16563,2020-03-20 13:58 EDT

tests/test_csbs.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import app
2+
import datetime
3+
import pytest
4+
from unittest import mock
5+
from app.services.location import csbs
6+
7+
def mocked_csbs_requests_get(*args, **kwargs):
8+
class FakeRequestsGetResponse:
9+
"""
10+
Returns instance of `FakeRequestsGetResponse`
11+
when calling `app.services.location.csbs.requests.get()
12+
"""
13+
def __init__(self):
14+
self.text = self.read_file()
15+
16+
def read_file(self):
17+
"""
18+
Mock HTTP GET-method and return text from file
19+
"""
20+
filepath = "tests/example_data/sample_covid19_county.csv"
21+
print("Try to read {}".format(filepath))
22+
with open(filepath, "r") as file:
23+
return file.read()
24+
25+
return FakeRequestsGetResponse()
26+
27+
@mock.patch('app.services.location.csbs.requests.get', side_effect=mocked_csbs_requests_get)
28+
def test_get_locations(mock_request_get):
29+
data = csbs.get_locations()
30+
assert isinstance(data, dict)
31+
assert isinstance(data["Washington"], list)
32+
assert data.get("Wisconsin") == None
33+
34+
# check to see that Unknown/Unassigned has been filtered
35+
for state in data:
36+
for county in data[state]:
37+
assert county.county != "Unknown"
38+
assert county.county != "Unassigned"

0 commit comments

Comments
 (0)