Skip to content

Commit bb0f92d

Browse files
authored
Merge pull request ExpDev07#114 from JKSenthil/master
Adding county information to API
2 parents 54e5f94 + 9c20a9f commit bb0f92d

File tree

7 files changed

+217
-6
lines changed

7 files changed

+217
-6
lines changed

README.md

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ We provide multiple data-sources you can pick from, simply add the query paramat
2323

2424
* **jhu** - https://github.com/CSSEGISandData/COVID-19 - Data repository operated by the Johns Hopkins University Center for Systems Science and Engineering (JHU CSSE).
2525

26+
* **csbs** - https://www.csbs.org/information-covid-19-coronavirus - US County data comes from Conference of State Bank Supervisors
27+
2628
* **... more to come later**.
2729

2830
### Getting latest amount of total confirmed cases, deaths, and recoveries.
@@ -126,11 +128,53 @@ Exclude timelines.
126128
GET /v2/locations?timelines=0
127129
```
128130

129-
## Data
130-
131-
The data comes from the [2019 Novel Coronavirus (nCoV) Data Repository, provided
132-
by JHU CCSE](https://github.com/CSSEGISandData/2019-nCoV). It is
133-
programmatically retrieved, re-formatted and stored in the cache for one hour.
131+
### Getting US per county information.
132+
```http
133+
GET /v2/locations?source=csbs
134+
```
135+
```json
136+
{
137+
"locations": [
138+
{
139+
"coordinates": {
140+
"latitude": 40.71455,
141+
"longitude": -74.00714
142+
},
143+
"country": "US",
144+
"country_code": "US",
145+
"county": "New York",
146+
"id": 0,
147+
"last_updated": "2020-03-21T14:00:00Z",
148+
"latest": {
149+
"confirmed": 6211,
150+
"deaths": 43,
151+
"recovered": 0
152+
},
153+
"province": "New York",
154+
"state": "New York"
155+
},
156+
{
157+
"coordinates": {
158+
"latitude": 41.16319759,
159+
"longitude": -73.7560629
160+
},
161+
"country": "US",
162+
"country_code": "US",
163+
"county": "Westchester",
164+
"id": 1,
165+
"last_updated": "2020-03-21T14:00:00Z",
166+
"latest": {
167+
"confirmed": 1385,
168+
"deaths": 0,
169+
"recovered": 0
170+
},
171+
"province": "Westchester",
172+
"state": "New York"
173+
},
174+
...
175+
]
176+
}
177+
```
134178

135179
## Wrappers
136180

app/data/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from ..services.location.jhu import JhuLocationService
2+
from ..services.location.csbs import CSBSLocationService
23

34
# Mapping of services to data-sources.
45
data_sources = {
56
'jhu': JhuLocationService(),
7+
'csbs': CSBSLocationService()
68
}
79

810
def data_source(source):

app/location/__init__.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,35 @@ def serialize(self, timelines = False):
9292
key: value.serialize() for (key, value) in self.timelines.items()
9393
}})
9494

95+
# Return the serialized location.
96+
return serialized
97+
98+
class CSBSLocation(Location):
99+
"""
100+
A CSBS (county) location.
101+
"""
102+
def __init__(self, id, state, county, coordinates, last_updated, confirmed, deaths):
103+
super().__init__(
104+
id, 'US', county, coordinates, last_updated, confirmed, deaths, recovered=0
105+
)
106+
107+
self.state = state
108+
self.county = county
109+
110+
def serialize(self, timelines=False):
111+
"""
112+
Serializes the location into a dict.
113+
114+
:returns: The serialized location.
115+
:rtype: dict
116+
"""
117+
serialized = super().serialize()
118+
119+
# Update with new fields.
120+
serialized.update({
121+
'state': self.state,
122+
'county': self.county,
123+
})
124+
95125
# Return the serialized location.
96126
return serialized

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: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from . import LocationService
2+
from ...location import CSBSLocation
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, id):
15+
return self.get_all()[id]
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 i, item in enumerate(data):
41+
state = item['State Name']
42+
county = item['County Name']
43+
if county == "Unassigned" or county == "Unknown":
44+
continue
45+
46+
confirmed = int(item['Confirmed'] or 0)
47+
death = int(item['Death'] or 0)
48+
coordinates = Coordinates(float(item['Latitude']), float(item['Longitude']))
49+
50+
# Parse time to ISO format
51+
last_update = item['Last Update']
52+
date = last_update.split("-")
53+
year = int(date[0])
54+
month = int(date[1])
55+
date = date[2].split(" ")
56+
day = int(date[0])
57+
time = date[1].split(":")
58+
hour = int(time[0])
59+
minute = int(time[1])
60+
d = datetime(year=year, month=month, day=day, hour=hour, minute=minute)
61+
last_update = d.isoformat() + 'Z'
62+
63+
locations.append(CSBSLocation(i, state, county, coordinates, last_update, confirmed, death))
64+
65+
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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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, list)
31+
32+
# check to see that Unknown/Unassigned has been filtered
33+
for d in data:
34+
assert d.county != "Unknown"
35+
assert d.county != "Unassigned"

0 commit comments

Comments
 (0)