22
33# standard library
44import unittest
5- from unittest .mock import MagicMock
6- from unittest .mock import sentinel
5+ from unittest .mock import (MagicMock , sentinel , patch )
76
8- from delphi .epidata .acquisition .flusurv .flusurv import fetch_json
7+ import delphi .epidata .acquisition .flusurv .flusurv as flusurv
98
109# py3tester coverage target
1110__test_target__ = "delphi.epidata.acquisition.flusurv.flusurv"
1211
1312
13+ # Example location-specific return JSON from CDC GRASP API. Contains
14+ # partial data for "network_all" location and season 49.
15+ network_all_example_data = {
16+ 'default_data' : [
17+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 0 , 'sexid' : 0 , 'raceid' : 1 , 'rate' : 20.7 , 'weeklyrate' : 0.0 , 'mmwrid' : 2519 },
18+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 0 , 'sexid' : 0 , 'raceid' : 2 , 'rate' : 41.3 , 'weeklyrate' : 0.1 , 'mmwrid' : 2519 },
19+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 1 , 'sexid' : 0 , 'raceid' : 0 , 'rate' : 42 , 'weeklyrate' : 0.5 , 'mmwrid' : 2519 },
20+
21+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 0 , 'sexid' : 0 , 'raceid' : 1 , 'rate' : 4.3 , 'weeklyrate' : 1.7 , 'mmwrid' : 2493 },
22+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 0 , 'sexid' : 0 , 'raceid' : 2 , 'rate' : 11.6 , 'weeklyrate' : 3.6 , 'mmwrid' : 2493 },
23+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 0 , 'sexid' : 0 , 'raceid' : 3 , 'rate' : 12.8 , 'weeklyrate' : 4.8 , 'mmwrid' : 2493 },
24+
25+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 0 , 'sexid' : 0 , 'raceid' : 1 , 'rate' : 20.6 , 'weeklyrate' : 0.1 , 'mmwrid' : 2516 },
26+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 0 , 'sexid' : 0 , 'raceid' : 2 , 'rate' : 40.7 , 'weeklyrate' : 0.5 , 'mmwrid' : 2516 },
27+
28+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 0 , 'sexid' : 0 , 'raceid' : 1 , 'rate' : 20.3 , 'weeklyrate' : 0.1 , 'mmwrid' : 2513 },
29+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 0 , 'sexid' : 0 , 'raceid' : 2 , 'rate' : 39.6 , 'weeklyrate' : 0.3 , 'mmwrid' : 2513 },
30+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 0 , 'sexid' : 0 , 'raceid' : 3 , 'rate' : 36.0 , 'weeklyrate' : 0.1 , 'mmwrid' : 2513 },
31+ ]
32+ }
33+
34+ # Example metadata response containing "master_lookup" element only, used
35+ # for mapping between valueids and strata descriptions
36+ master_lookup_metadata = {
37+ 'master_lookup' : [
38+ {'Variable' : 'Age' , 'valueid' : 1 , 'parentid' : 97 , 'Label' : '0-4 yr' , 'Color_HexValue' : '#d19833' , 'Enabled' : True },
39+ {'Variable' : 'Age' , 'valueid' : 2 , 'parentid' : 97 , 'Label' : '5-17 yr' , 'Color_HexValue' : '#707070' , 'Enabled' : True },
40+ {'Variable' : 'Age' , 'valueid' : 3 , 'parentid' : 98 , 'Label' : '18-49 yr' , 'Color_HexValue' : '#44b3c6' , 'Enabled' : True },
41+ {'Variable' : 'Age' , 'valueid' : 4 , 'parentid' : 98 , 'Label' : '50-64 yr' , 'Color_HexValue' : '#516889' , 'Enabled' : True },
42+ {'Variable' : 'Age' , 'valueid' : 5 , 'parentid' : 98 , 'Label' : '65+ yr' , 'Color_HexValue' : '#cc5e56' , 'Enabled' : True },
43+ {'Variable' : 'Age' , 'valueid' : 7 , 'parentid' : 5 , 'Label' : '65-74 yr' , 'Color_HexValue' : '#cc5e56' , 'Enabled' : True },
44+ {'Variable' : 'Age' , 'valueid' : 8 , 'parentid' : 5 , 'Label' : '75-84 yr' , 'Color_HexValue' : '#cc5e56' , 'Enabled' : True },
45+ {'Variable' : 'Age' , 'valueid' : 9 , 'parentid' : 5 , 'Label' : '85+' , 'Color_HexValue' : '#cc5e56' , 'Enabled' : True },
46+ {'Variable' : 'Age' , 'valueid' : 10 , 'parentid' : 3 , 'Label' : '18-29 yr' , 'Color_HexValue' : '#44b3c6' , 'Enabled' : True },
47+ {'Variable' : 'Age' , 'valueid' : 11 , 'parentid' : 3 , 'Label' : '30-39 yr' , 'Color_HexValue' : '#44b3c6' , 'Enabled' : True },
48+ {'Variable' : 'Age' , 'valueid' : 12 , 'parentid' : 3 , 'Label' : '40-49 yr' , 'Color_HexValue' : '#44b3c6' , 'Enabled' : True },
49+ {'Variable' : 'Age' , 'valueid' : 21 , 'parentid' : 2 , 'Label' : '5-11 yr' , 'Color_HexValue' : '#707070' , 'Enabled' : True },
50+ {'Variable' : 'Age' , 'valueid' : 22 , 'parentid' : 2 , 'Label' : '12-17 yr' , 'Color_HexValue' : '#707070' , 'Enabled' : True },
51+ {'Variable' : 'Age' , 'valueid' : 97 , 'parentid' : 0 , 'Label' : '< 18' , 'Color_HexValue' : '#000000' , 'Enabled' : True },
52+ {'Variable' : 'Age' , 'valueid' : 98 , 'parentid' : 0 , 'Label' : '>= 18' , 'Color_HexValue' : '#000000' , 'Enabled' : True },
53+
54+ {'Variable' : 'Race' , 'valueid' : 1 , 'parentid' : None , 'Label' : 'White' , 'Color_HexValue' : '#516889' , 'Enabled' : True },
55+ {'Variable' : 'Race' , 'valueid' : 2 , 'parentid' : None , 'Label' : 'Black' , 'Color_HexValue' : '#44b3c6' , 'Enabled' : True },
56+ {'Variable' : 'Race' , 'valueid' : 3 , 'parentid' : None , 'Label' : 'Hispanic/Latino' , 'Color_HexValue' : '#d19833' , 'Enabled' : True },
57+ {'Variable' : 'Race' , 'valueid' : 4 , 'parentid' : None , 'Label' : 'Asian/Pacific Islander' , 'Color_HexValue' : '#cc5e56' , 'Enabled' : True },
58+ {'Variable' : 'Race' , 'valueid' : 5 , 'parentid' : None , 'Label' : 'American Indian/Alaska Native' , 'Color_HexValue' : '#007d8e' , 'Enabled' : True },
59+
60+ {'Variable' : 'Sex' , 'valueid' : 1 , 'parentid' : None , 'Label' : 'Male' , 'Color_HexValue' : '#44b3c6' , 'Enabled' : True },
61+ {'Variable' : 'Sex' , 'valueid' : 2 , 'parentid' : None , 'Label' : 'Female' , 'Color_HexValue' : '#F2775F' , 'Enabled' : True },
62+
63+ {'Variable' : None , 'valueid' : 0 , 'parentid' : 0 , 'Label' : 'Overall' , 'Color_HexValue' : '#000000' , 'Enabled' : True },
64+ ],
65+ }
66+
67+ # Map derived from "master_lookup" dictionary above mapping between valueids
68+ # by type and cleaned-up descriptions (no spaces or capital letters, etc)
69+ id_label_map = {
70+ "Age" : {
71+ 1 : "0t4" ,
72+ 2 : "5t17" ,
73+ 3 : "18t49" ,
74+ 4 : "50t64" ,
75+ 5 : "65+" ,
76+ 7 : "65t74" ,
77+ 8 : "75t84" ,
78+ 9 : "85+" ,
79+ 10 : "18t29" ,
80+ 11 : "30t39" ,
81+ 12 : "40t49" ,
82+ 21 : "5t11" ,
83+ 22 : "12t17" ,
84+ 97 : "<18" ,
85+ 98 : ">=18" ,
86+ },
87+ "Race" : {
88+ 1 : "white" ,
89+ 2 : "black" ,
90+ 3 : "hispaniclatino" ,
91+ 4 : "asianpacificislander" ,
92+ 5 : "americanindianalaskanative" ,
93+ },
94+ "Sex" : {
95+ 1 : "male" ,
96+ 2 : "female" ,
97+ },
98+ }
99+
100+
14101class FunctionTests (unittest .TestCase ):
15102 """Tests each function individually."""
16103
@@ -28,6 +115,98 @@ def test_fetch_json(self):
28115 requests_impl = MagicMock ()
29116 requests_impl .get .return_value = response_object
30117
31- actual = fetch_json (path , payload , requests_impl = requests_impl )
118+ actual = flusurv . fetch_json (path , payload , requests_impl = requests_impl )
32119
33120 self .assertEqual (actual , sentinel .expected )
121+
122+ def test_mmwrid_to_epiweek (self ):
123+ # Test epoch
124+ self .assertEqual (flusurv .mmwrid_to_epiweek (2179 ), 200340 )
125+
126+ metadata = flusurv .fetch_flusurv_metadata ()
127+ for mmwr in metadata ["mmwr" ]:
128+ self .assertEqual (flusurv .mmwrid_to_epiweek (mmwr ["mmwrid" ]), mmwr ["yearweek" ])
129+
130+ @patch (__test_target__ + ".fetch_flusurv_location" )
131+ def test_get_data (self , MockFlusurvLocation ):
132+ MockFlusurvLocation .return_value = network_all_example_data
133+
134+ self .assertEqual (flusurv .get_data ("network_all" , [30 , 49 ]), {
135+ 201014 : {"rate_race_white" : 0.0 , "rate_race_black" : 0.1 , "rate_age_0" : 0.5 },
136+ 200940 : {"rate_race_white" : 1.7 , "rate_race_black" : 3.6 , "rate_race_hispaniclatino" : 4.8 },
137+ 201011 : {"rate_race_white" : 0.1 , "rate_race_black" : 0.5 },
138+ 201008 : {"rate_race_white" : 0.1 , "rate_race_black" : 0.3 , "rate_race_hispaniclatino" : 0.1 },
139+ }
140+ )
141+
142+ @patch (__test_target__ + ".fetch_flusurv_metadata" )
143+ def test_group_by_epiweek (self , MockFlusurvMetadata ):
144+ # Flusurv metadata is fetched by `make_id_label_map()`.
145+ MockFlusurvMetadata .return_value = master_lookup_metadata
146+
147+ input_data = network_all_example_data
148+ self .assertEqual (flusurv .group_by_epiweek (input_data ), {
149+ 201014 : {"rate_race_white" : 0.0 , "rate_race_black" : 0.1 , "rate_age_0" : 0.5 },
150+ 200940 : {"rate_race_white" : 1.7 , "rate_race_black" : 3.6 , "rate_race_hispaniclatino" : 4.8 },
151+ 201011 : {"rate_race_white" : 0.1 , "rate_race_black" : 0.5 },
152+ 201008 : {"rate_race_white" : 0.1 , "rate_race_black" : 0.3 , "rate_race_hispaniclatino" : 0.1 },
153+ }
154+ )
155+
156+ duplicate_input_data = {
157+ 'default_data' : [
158+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 1 , 'sexid' : 0 , 'raceid' : 0 , 'rate' : 42 , 'weeklyrate' : 0.5 , 'mmwrid' : 2519 },
159+ {'networkid' : 1 , 'catchmentid' : 22 , 'seasonid' : 49 , 'ageid' : 1 , 'sexid' : 0 , 'raceid' : 0 , 'rate' : 42 , 'weeklyrate' : 54 , 'mmwrid' : 2519 },
160+ ]
161+ }
162+
163+ with self .assertWarnsRegex (Warning , "warning: Multiple rates seen for 201014" ):
164+ flusurv .group_by_epiweek (duplicate_input_data )
165+
166+ with self .assertRaisesRegex (Exception , "no data found" ):
167+ flusurv .group_by_epiweek ({"default_data" : []})
168+
169+ @patch ('builtins.print' )
170+ def test_group_by_epiweek_print_msgs (self , mock_print ):
171+ input_data = network_all_example_data
172+ flusurv .group_by_epiweek (input_data )
173+ mock_print .assert_called_with ("found data for 4 epiweeks" )
174+
175+ def test_get_current_issue (self ):
176+ input_data = {
177+ 'loaddatetime' : 'Sep 12, 2023'
178+ }
179+ self .assertEqual (flusurv .get_current_issue (input_data ), 202337 )
180+
181+ @patch (__test_target__ + ".fetch_flusurv_metadata" )
182+ def test_make_id_label_map (self , MockFlusurvMetadata ):
183+ MockFlusurvMetadata .return_value = master_lookup_metadata
184+ self .assertEqual (flusurv .make_id_label_map (), id_label_map )
185+
186+ def test_groupids_to_name (self ):
187+ ids = (
188+ (1 , 0 , 0 ),
189+ (9 , 0 , 0 ),
190+ (0 , 2 , 0 ),
191+ (0 , 0 , 3 ),
192+ (0 , 0 , 5 ),
193+ (0 , 0 , 0 ),
194+ )
195+ expected_list = [
196+ "rate_age_0" ,
197+ "rate_age_7" ,
198+ "rate_sex_female" ,
199+ "rate_race_hispaniclatino" ,
200+ "rate_race_americanindianalaskanative" ,
201+ "rate_overall" ,
202+ ]
203+
204+ for (ageid , sexid , raceid ), expected in zip (ids , expected_list ):
205+ self .assertEqual (flusurv .groupids_to_name (ageid , sexid , raceid , id_label_map ), expected )
206+
207+ with self .assertRaisesRegex (ValueError , "Ageid cannot be 6" ):
208+ flusurv .groupids_to_name (6 , 0 , 0 , id_label_map )
209+ with self .assertRaisesRegex (AssertionError , "At most one groupid can be non-zero" ):
210+ flusurv .groupids_to_name (1 , 1 , 0 , id_label_map )
211+ flusurv .groupids_to_name (0 , 1 , 1 , id_label_map )
212+ flusurv .groupids_to_name (1 , 1 , 1 , id_label_map )
0 commit comments