Skip to content

Commit 4bd0706

Browse files
committed
new endpoint: covid_hosp_facility_lookup
- the hhs covid hosp dataset is keyed by `hospital_pk`, which is a unique identifier for healthcare facilities. - the key is not generally well-known, but querying the `covid_hosp_facility` dataset requires key(s) - the new endpoint returns facility information (including key) for given regions (e.g. city, state, zip, ccn, fips) - users would be expected to lookup hospitals of interest e.g. by state or city. then query `covid_hosp_facility` with the key for those hospitals. - server and clients updates - all unit and integrations pass note that new indexes are added to the database to facilitate fast lookups. otherwise it would be a full table scan each time, which does not scale well. indexes are expected to be very small.
1 parent 83ba6a5 commit 4bd0706

File tree

7 files changed

+248
-3
lines changed

7 files changed

+248
-3
lines changed

integrations/acquisition/covid_hosp/facility/test_scenarios.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,79 @@ def test_acquire_dataset(self):
8585
'450822', Epidata.range(20200101, 20210101))
8686
self.assertEqual(response['result'], 1)
8787
self.assertEqual(len(response['epidata']), 1)
88+
89+
def test_facility_lookup(self):
90+
"""Lookup facilities using various filters."""
91+
92+
# only mock out network calls to external hosts
93+
mock_network = MagicMock()
94+
mock_network.fetch_metadata.return_value = \
95+
self.test_utils.load_sample_metadata()
96+
mock_network.fetch_dataset.return_value = \
97+
self.test_utils.load_sample_dataset()
98+
99+
# acquire sample data into local database
100+
with self.subTest(name='first acquisition'):
101+
acquired = Update.run(network=mock_network)
102+
self.assertTrue(acquired)
103+
104+
# texas ground truth, sorted by `hospital_pk`
105+
# see sample data at testdata/acquisition/covid_hosp/facility/dataset.csv
106+
texas_hospitals = [{
107+
'hospital_pk': '450771',
108+
'state': 'TX',
109+
'ccn': '450771',
110+
'hospital_name': 'TEXAS HEALTH PRESBYTERIAN HOSPITAL PLANO',
111+
'address': '6200 W PARKER RD',
112+
'city': 'PLANO',
113+
'zip': '75093',
114+
'hospital_subtype': 'Short Term',
115+
'fips_code': '48085',
116+
'is_metro_micro': 1,
117+
}, {
118+
'hospital_pk': '450822',
119+
'state': 'TX',
120+
'ccn': '450822',
121+
'hospital_name': 'MEDICAL CITY LAS COLINAS',
122+
'address': '6800 N MACARTHUR BLVD',
123+
'city': 'IRVING',
124+
'zip': '75039',
125+
'hospital_subtype': 'Short Term',
126+
'fips_code': '48113',
127+
'is_metro_micro': 1,
128+
}, {
129+
'hospital_pk': '451329',
130+
'state': 'TX',
131+
'ccn': '451329',
132+
'hospital_name': 'RANKIN HOSPITAL MEDICAL CLINIC',
133+
'address': '1611 SPUR 576',
134+
'city': 'RANKIN',
135+
'zip': '79778',
136+
'hospital_subtype': 'Critical Access Hospitals',
137+
'fips_code': '48461',
138+
'is_metro_micro': 0,
139+
}]
140+
141+
with self.subTest(name='by state'):
142+
response = Epidata.covid_hosp_facility_lookup(state='tx')
143+
self.assertEqual(response['epidata'], texas_hospitals)
144+
145+
with self.subTest(name='by ccn'):
146+
response = Epidata.covid_hosp_facility_lookup(ccn='450771')
147+
self.assertEqual(response['epidata'], texas_hospitals[0:1])
148+
149+
with self.subTest(name='by city'):
150+
response = Epidata.covid_hosp_facility_lookup(city='irving')
151+
self.assertEqual(response['epidata'], texas_hospitals[1:2])
152+
153+
with self.subTest(name='by zip'):
154+
response = Epidata.covid_hosp_facility_lookup(zip='79778')
155+
self.assertEqual(response['epidata'], texas_hospitals[2:3])
156+
157+
with self.subTest(name='by fips_code'):
158+
response = Epidata.covid_hosp_facility_lookup(fips_code='48085')
159+
self.assertEqual(response['epidata'], texas_hospitals[0:1])
160+
161+
with self.subTest(name='no results'):
162+
response = Epidata.covid_hosp_facility_lookup(state='not a state')
163+
self.assertEqual(response['result'], -2)

src/client/delphi_epidata.R

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,27 @@ Epidata <- (function() {
585585
return(.request(params))
586586
}
587587

588+
# Lookup COVID hospitalization facility identifiers
589+
covid_hosp_facility_lookup <- function(state, ccn, city, zip, fips_code) {
590+
# Set up request
591+
params <- list(source = 'covid_hosp_facility_lookup')
592+
if(!missing(state)) {
593+
params$state <- state
594+
} else if(!missing(ccn)) {
595+
params$ccn <- ccn
596+
} else if(!missing(city)) {
597+
params$city <- city
598+
} else if(!missing(zip)) {
599+
params$zip <- zip
600+
} else if(!missing(fips_code)) {
601+
params$fips_code <- fips_code
602+
} else {
603+
stop('one of `state`, `ccn`, `city`, `zip`, or `fips_code` is required')
604+
}
605+
# Make the API call
606+
return(.request(params))
607+
}
608+
588609
# Export the public methods
589610
return(list(
590611
range = range,
@@ -613,6 +634,7 @@ Epidata <- (function() {
613634
covidcast = covidcast,
614635
covidcast_meta = covidcast_meta,
615636
covid_hosp = covid_hosp,
616-
covid_hosp_facility = covid_hosp_facility
637+
covid_hosp_facility = covid_hosp_facility,
638+
covid_hosp_facility_lookup = covid_hosp_facility_lookup
617639
))
618640
})()

src/client/delphi_epidata.coffee

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,5 +419,25 @@ class Epidata
419419
# Make the API call
420420
_request(callback, params)
421421

422+
# Lookup COVID hospitalization facility identifiers
423+
@covid_hosp_facility_lookup: (state, ccn, city, zip, fips_code) ->
424+
# Set up request
425+
params =
426+
'source': 'covid_hosp_facility'
427+
if state?
428+
params.state = state
429+
else if ccn?
430+
params.ccn = ccn
431+
else if city?
432+
params.city = city
433+
else if zip?
434+
params.zip = zip
435+
else if fips_code?
436+
params.fips_code = fips_code
437+
else
438+
throw { msg: 'one of `state`, `ccn`, `city`, `zip`, or `fips_code` is required' }
439+
# Make the API call
440+
_request(callback, params)
441+
422442
# Export the API to the global environment
423443
(exports ? window).Epidata = Epidata

src/client/delphi_epidata.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,35 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
661661
} // Make the API call
662662

663663

664+
return _request(callback, params);
665+
} // Lookup COVID hospitalization facility identifiers
666+
667+
}, {
668+
key: "covid_hosp_facility_lookup",
669+
value: function covid_hosp_facility_lookup(state, ccn, city, zip, fips_code) {
670+
var params; // Set up request
671+
672+
params = {
673+
'source': 'covid_hosp_facility'
674+
};
675+
676+
if (state != null) {
677+
params.state = state;
678+
} else if (ccn != null) {
679+
params.ccn = ccn;
680+
} else if (city != null) {
681+
params.city = city;
682+
} else if (zip != null) {
683+
params.zip = zip;
684+
} else if (fips_code != null) {
685+
params.fips_code = fips_code;
686+
} else {
687+
throw {
688+
msg: 'one of `state`, `ccn`, `city`, `zip`, or `fips_code` is required'
689+
};
690+
} // Make the API call
691+
692+
664693
return _request(callback, params);
665694
}
666695
}]);

src/client/delphi_epidata.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,3 +643,25 @@ def covid_hosp_facility(
643643
params['publication_dates'] = Epidata._list(publication_dates)
644644
# Make the API call
645645
return Epidata._request(params)
646+
647+
# Lookup COVID hospitalization facility identifiers
648+
@staticmethod
649+
def covid_hosp_facility_lookup(
650+
state=None, ccn=None, city=None, zip=None, fips_code=None):
651+
"""Lookup COVID hospitalization facility identifiers."""
652+
# Set up request
653+
params = {'source': 'covid_hosp_facility_lookup'}
654+
if state is not None:
655+
params['state'] = state
656+
elif ccn is not None:
657+
params['ccn'] = ccn
658+
elif city is not None:
659+
params['city'] = city
660+
elif zip is not None:
661+
params['zip'] = zip
662+
elif fips_code is not None:
663+
params['fips_code'] = fips_code
664+
else:
665+
raise Exception('one of `state`, `ccn`, `city`, `zip`, or `fips_code` is required')
666+
# Make the API call
667+
return Epidata._request(params)

src/ddl/covid_hosp.sql

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,10 @@ CREATE TABLE `covid_hosp_facility` (
10431043
UNIQUE KEY (`hospital_pk`, `collection_week`, `publication_date`),
10441044
-- for fast lookup of a time-series for a given hospital and publication date
10451045
KEY (`publication_date`, `hospital_pk`, `collection_week`),
1046-
-- for fast lookup of all hospital identifiers in a given state
1047-
KEY (`state`, `hospital_pk`)
1046+
-- for fast lookup of hospitals in a given location
1047+
KEY (`state`, `hospital_pk`),
1048+
KEY (`ccn`, `hospital_pk`),
1049+
KEY (`city`, `hospital_pk`),
1050+
KEY (`zip`, `hospital_pk`),
1051+
KEY (`fips_code`, `hospital_pk`)
10481052
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

src/server/api.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,67 @@ function get_covid_hosp_facility($hospital_pks, $collection_weeks, $publication_
14461446
return count($epidata) === 0 ? null : $epidata;
14471447
}
14481448

1449+
// queries the `covid_hosp_facility` table for hospital discovery
1450+
// $state (optional): 2-letter state abbreviation
1451+
// $ccn (optional): cms certification number (ccn) of the given facility
1452+
// $city (optional): name of
1453+
// $zip (optional): 2-letter state abbreviation
1454+
// $fips_code (optional): 2-letter state abbreviation
1455+
// note: exactly one of the above parameters should be non-null. if more than
1456+
// one is non-null, then only the first filter will be used.
1457+
function get_covid_hosp_facility_lookup($state, $ccn, $city, $zip, $fips_code) {
1458+
$epidata = array();
1459+
$table = '`covid_hosp_facility` c';
1460+
$fields = implode(', ', array(
1461+
'c.`hospital_pk`',
1462+
'MAX(c.`state`) `state`',
1463+
'MAX(c.`ccn`) `ccn`',
1464+
'MAX(c.`hospital_name`) `hospital_name`',
1465+
'MAX(c.`address`) `address`',
1466+
'MAX(c.`city`) `city`',
1467+
'MAX(c.`zip`) `zip`',
1468+
'MAX(c.`hospital_subtype`) `hospital_subtype`',
1469+
'MAX(c.`fips_code`) `fips_code`',
1470+
'MAX(c.`is_metro_micro`) `is_metro_micro`',
1471+
));
1472+
// basic query info
1473+
$group = 'c.`hospital_pk`';
1474+
$order = "c.`hospital_pk` ASC";
1475+
// build the filter
1476+
// these are all fast because the table has indexes on each of these fields
1477+
$condition = 'FALSE';
1478+
if ($state !== null) {
1479+
$condition = filter_strings('c.`state`', $state);
1480+
} else if ($ccn !== null) {
1481+
$condition = filter_strings('c.`ccn`', $ccn);
1482+
} else if ($city !== null) {
1483+
$condition = filter_strings('c.`city`', $city);
1484+
} else if ($zip !== null) {
1485+
$condition = filter_strings('c.`zip`', $zip);
1486+
} else if ($fips_code !== null) {
1487+
$condition = filter_strings('c.`fips_code`', $fips_code);
1488+
}
1489+
// final query using specific issues
1490+
$query = "SELECT {$fields} FROM {$table} WHERE ({$condition}) GROUP BY {$group} ORDER BY {$order}";
1491+
// get the data from the database
1492+
$fields_string = array(
1493+
'hospital_pk',
1494+
'state',
1495+
'ccn',
1496+
'hospital_name',
1497+
'address',
1498+
'city',
1499+
'zip',
1500+
'hospital_subtype',
1501+
'fips_code',
1502+
);
1503+
$fields_int = array('is_metro_micro');
1504+
$fields_float = null;
1505+
execute_query($query, $epidata, $fields_string, $fields_int, $fields_float);
1506+
// return the data
1507+
return count($epidata) === 0 ? null : $epidata;
1508+
}
1509+
14491510
// queries a bunch of epidata tables
14501511
function get_meta() {
14511512
// query and return metadata
@@ -1956,6 +2017,17 @@ function meta_delphi() {
19562017
$epidata = get_covid_hosp_facility($hospital_pks, $collection_weeks, $publication_dates);
19572018
store_result($data, $epidata);
19582019
}
2020+
} else if($source === 'covid_hosp_facility_lookup') {
2021+
if(require_any($data, array('state', 'ccn', 'city', 'zip', 'fips_code'))) {
2022+
$state = isset($_REQUEST['state']) ? extract_values($_REQUEST['state'], 'str') : null;
2023+
$ccn = isset($_REQUEST['ccn']) ? extract_values($_REQUEST['ccn'], 'str') : null;
2024+
$city = isset($_REQUEST['city']) ? extract_values($_REQUEST['city'], 'str') : null;
2025+
$zip = isset($_REQUEST['zip']) ? extract_values($_REQUEST['zip'], 'str') : null;
2026+
$fips_code = isset($_REQUEST['fips_code']) ? extract_values($_REQUEST['fips_code'], 'str') : null;
2027+
// get the data
2028+
$epidata = get_covid_hosp_facility_lookup($state, $ccn, $city, $zip, $fips_code);
2029+
store_result($data, $epidata);
2030+
}
19592031
} else {
19602032
$data['message'] = 'no data source specified';
19612033
}

0 commit comments

Comments
 (0)