kovid 0.6.4 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/kovid/cache.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'typhoeus'
4
-
5
4
  module Kovid
6
5
  class Cache
7
6
  def initialize
data/lib/kovid/cli.rb CHANGED
@@ -9,20 +9,26 @@ module Kovid
9
9
  true
10
10
  end
11
11
 
12
- desc 'province PROVINCE or province "PROVINCE NAME"', 'Returns reported data on provided province. eg "kovid check "new brunswick".'
12
+ desc 'province PROVINCE or province "PROVINCE NAME"',
13
+ 'Returns reported data on provided province. ' \
14
+ 'eg "kovid check "new brunswick".'
13
15
  method_option :full, aliases: '-p'
14
16
  def province(name)
15
17
  puts Kovid.province(name)
16
18
  data_source
17
19
  end
18
20
 
19
- desc 'provinces PROVINCE PROVINCE', 'Returns full comparison table for the given provinces. Accepts multiple provinces.'
21
+ desc 'provinces PROVINCE PROVINCE',
22
+ 'Returns full comparison table for the given provinces. ' \
23
+ 'Accepts multiple provinces.'
20
24
  def provinces(*names)
21
25
  puts Kovid.provinces(names)
22
26
  data_source
23
27
  end
24
28
 
25
- desc 'check COUNTRY or check "COUNTRY NAME"', 'Returns reported data on provided country. eg: "kovid check "hong kong".'
29
+ desc 'check COUNTRY or check "COUNTRY NAME"',
30
+ 'Returns reported data on provided country. ' \
31
+ 'eg: "kovid check "hong kong".'
26
32
  method_option :full, aliases: '-f'
27
33
  def check(*name)
28
34
  if name.size == 1
@@ -48,18 +54,18 @@ module Kovid
48
54
  data_source
49
55
  end
50
56
 
51
- desc 'states STATE STATE', 'Returns full comparison table for the given states. Accepts multiple states.'
57
+ desc 'states STATE STATE or states --all',
58
+ 'Returns full comparison table for the given states. ' \
59
+ 'Accepts multiple states.'
60
+ method_option :all, aliases: '-a'
52
61
  def states(*states)
53
- downcased_states = states.map(&:downcase)
54
-
55
- puts Kovid.states(downcased_states)
56
- data_source
57
- end
62
+ if options[:all]
63
+ puts Kovid.all_us_states
64
+ else
65
+ downcased_states = states.map(&:downcase)
66
+ puts Kovid.states(downcased_states)
67
+ end
58
68
 
59
- desc 'states -a', 'Returns full comparison table for all US states'
60
- method_option :all, aliases: '-a'
61
- def states
62
- puts Kovid.all_us_states
63
69
  data_source
64
70
  end
65
71
 
@@ -69,12 +75,15 @@ module Kovid
69
75
  data_source
70
76
  end
71
77
 
72
- desc 'history COUNTRY or history COUNTRY N', 'Return history of incidents of COUNTRY (in the last N days)'
73
- def history(*params)
74
- if params.size == 2
75
- puts Kovid.history(params.first, params.last)
78
+ desc 'history COUNTRY or history COUNTRY N or' \
79
+ 'history STATE --usa',
80
+ 'Return history of incidents of COUNTRY|STATE (in the last N days)'
81
+ option :usa, :type => :boolean
82
+ def history(location, days=30)
83
+ if options[:usa]
84
+ puts Kovid.history_us_state(location, days)
76
85
  else
77
- puts Kovid.history(params.first, nil)
86
+ puts Kovid.history(location, days)
78
87
  end
79
88
  data_source
80
89
  end
@@ -138,8 +147,8 @@ module Kovid
138
147
  source = <<~TEXT
139
148
  #{Time.now}
140
149
  Sources:
141
- * JHU CSSE GISand Data
142
- * https://www.worldometers.info/coronavirus/
150
+ * Johns Hopkins University
151
+ * https://www.worldometers.info/coronavirus
143
152
  TEXT
144
153
  puts source
145
154
  end
@@ -23,6 +23,12 @@ module Kovid
23
23
  'Recovered'.paint_green
24
24
  ].freeze
25
25
 
26
+ DATE_CASES_DEATHS = [
27
+ 'Date'.paint_white,
28
+ 'Cases'.paint_white,
29
+ 'Deaths'.paint_red
30
+ ].freeze
31
+
26
32
  CONTINENTAL_AGGREGATE_HEADINGS = [
27
33
  'Cases'.paint_white,
28
34
  'Cases Today'.paint_white,
@@ -63,6 +69,20 @@ module Kovid
63
69
  'Cases/Million'.paint_white
64
70
  ].freeze
65
71
 
72
+ FULL_PROVINCE_TABLE_HEADINGS = [
73
+ 'Confirmed'.paint_white,
74
+ 'Deaths'.paint_red,
75
+ 'Recovered'.paint_green
76
+ ].freeze
77
+
78
+ FULL_STATE_TABLE_HEADINGS = [
79
+ 'Cases'.paint_white,
80
+ 'Cases Today'.paint_white,
81
+ 'Deaths'.paint_red,
82
+ 'Deaths Today'.paint_red,
83
+ 'Active'.paint_yellow
84
+ ].freeze
85
+
66
86
  COMPARE_STATES_HEADINGS = [
67
87
  'State'.paint_white,
68
88
  'Cases'.paint_white,
@@ -79,7 +99,19 @@ module Kovid
79
99
  'Recovered'.paint_green
80
100
  ].freeze
81
101
 
82
- FOOTER_LINE = ['------------', '------------', '------------', '------------'].freeze
102
+ FOOTER_LINE_THREE_COLUMNS = [
103
+ '------------',
104
+ '------------',
105
+ '------------'
106
+ ].freeze
107
+
108
+ FOOTER_LINE_FOUR_COLUMNS = [
109
+ '------------',
110
+ '------------',
111
+ '------------',
112
+ '------------'
113
+ ].freeze
114
+
83
115
  COUNTRY_LETTERS = 'A'.upto('Z').with_index(127_462).to_h.freeze
84
116
 
85
117
  RIGHT_ALIGN_COLUMNS = {
data/lib/kovid/helpers.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'terminal-table'
4
+
4
5
  module Kovid
5
6
  module_function
6
7
 
@@ -14,4 +15,25 @@ module Kovid
14
15
  date_to_parse = Date.strptime(date, '%m/%d/%y').to_s
15
16
  Date.parse(date_to_parse).strftime('%d %b, %y')
16
17
  end
18
+
19
+ def comma_delimit(number)
20
+ number.to_s.chars.to_a.reverse.each_slice(3).map(&:join).join(',').reverse
21
+ end
22
+
23
+ # Insert + sign to format positive numbers
24
+ def add_plus_sign(num)
25
+ num.to_i.positive? ? "+#{comma_delimit(num)}" : comma_delimit(num).to_s
26
+ end
27
+
28
+ def format_country_history_numbers(load)
29
+ load['timeline'].values.map(&:values).transpose.each do |data|
30
+ data.map! { |number| Kovid.comma_delimit(number) }
31
+ end
32
+ end
33
+
34
+ def lookup_us_state(state)
35
+ us = Carmen::Country.coded('USA')
36
+ lookup = us.subregions.coded(state) || us.subregions.named(state)
37
+ lookup ? lookup.name : state
38
+ end
17
39
  end
@@ -3,25 +3,15 @@
3
3
  module Kovid
4
4
  module Historians
5
5
  include Constants
6
+ include AsciiCharts
6
7
 
7
- def history(country, last)
8
- # Write checks for when country is spelt wrong.
9
- headings = DATE_CASES_DEATHS_RECOVERED
8
+ def history(location, days)
10
9
  rows = []
11
10
 
12
- stats = if last
13
- transpose(country).last(last.to_i)
14
- else
15
- transpose(country)
16
- end
11
+ stats = Kovid.format_country_history_numbers(location).last(days.to_i)
12
+ dates = location['timeline']['cases'].keys.last(days.to_i)
17
13
 
18
- dates = if last
19
- country['timeline']['cases'].keys.last(last.to_i)
20
- else
21
- country['timeline']['cases'].keys
22
- end
23
-
24
- unless last
14
+ if days.to_i >= 30
25
15
  stats = stats.reject { |stat| stat[0].to_i.zero? && stat[1].to_i.zero? }
26
16
  dates = dates.last(stats.count)
27
17
  end
@@ -32,14 +22,25 @@ module Kovid
32
22
  rows << row
33
23
  end
34
24
 
25
+ # Title and Column format if Country or US State
26
+ if location['country']
27
+ title = location['country'].try(:upcase)
28
+ col_format = DATE_CASES_DEATHS_RECOVERED
29
+ footer = FOOTER_LINE_FOUR_COLUMNS
30
+ else
31
+ title = location['state'].try(:upcase)
32
+ col_format = DATE_CASES_DEATHS
33
+ footer = FOOTER_LINE_THREE_COLUMNS
34
+ end
35
+
35
36
  if stats.size > 10
36
- rows << FOOTER_LINE
37
- rows << DATE_CASES_DEATHS_RECOVERED
37
+ rows << footer
38
+ rows << col_format
38
39
  end
39
40
 
40
41
  Terminal::Table.new(
41
- title: country['country'].upcase,
42
- headings: headings,
42
+ title: title,
43
+ headings: col_format,
43
44
  rows: rows
44
45
  )
45
46
  end
@@ -53,8 +54,9 @@ module Kovid
53
54
  end
54
55
 
55
56
  # From dates where number of !cases.zero?
56
- positive_cases_figures = country['timeline']['cases'].values.reject(&:zero?)
57
- dates = country['timeline']['cases'].reject { |_k, v| v.zero? }.keys
57
+ country_cases = country['timeline']['cases']
58
+ positive_cases_figures = country_cases.values.reject(&:zero?)
59
+ dates = country_cases.reject { |_k, v| v.zero? }.keys
58
60
  data = []
59
61
 
60
62
  # TODO: Refactor
@@ -88,12 +90,16 @@ module Kovid
88
90
  dates.each_with_index do |val, index|
89
91
  data << [val, positive_cases_figures[index]]
90
92
  end
91
- y_range = AsciiCharts::Cartesian.new(data, bar: true, hide_zero: true).y_range
93
+ y_range = Kovid::AsciiCharts::Cartesian.new(
94
+ data, bar: true, hide_zero: true
95
+ ).y_range
92
96
 
93
97
  last_two_y = y_range.last 2
94
98
  y_interval = last_two_y.last - last_two_y.first
95
99
 
96
- scale("Scale on Y: #{y_interval}:#{(y_interval / last_two_y.last.to_f * positive_cases_figures.last).round(2) / y_interval}")
100
+ scale("Scale on Y: #{y_interval}:#{(
101
+ y_interval / last_two_y.last.to_f * positive_cases_figures.last
102
+ ).round(2) / y_interval}")
97
103
 
98
104
  puts 'Experimental feature, please report issues.'
99
105
 
data/lib/kovid/painter.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rainbow'
4
-
5
4
  class String
6
5
  def paint_white
7
6
  Rainbow(self).white.bg(:black).bold
data/lib/kovid/request.rb CHANGED
@@ -1,30 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require 'carmen'
4
5
  require_relative 'tablelize'
5
6
  require_relative 'cache'
6
7
  require_relative 'uri_builder'
7
8
 
8
9
  module Kovid
9
10
  class Request
10
- COUNTRIES_PATH = UriBuilder.new('/countries').url
11
- STATES_URL = UriBuilder.new('/states').url
12
- JHUCSSE_URL = UriBuilder.new('/v2/jhucsse').url
11
+ COUNTRIES_PATH = UriBuilder.new('/v2/countries').url
12
+ STATES_URL = UriBuilder.new('/v2/states').url
13
+ JHUCSSE_URL = UriBuilder.new('/v2/jhucsse').url
14
+ HISTORICAL_URL = UriBuilder.new('/v2/historical').url
15
+ HISTORICAL_US_URL = UriBuilder.new('/v2/historical/usacounties').url
13
16
 
14
17
  SERVER_DOWN = 'Server overwhelmed. Please try again in a moment.'
15
18
 
16
19
  EU_ISOS = %w[AT BE BG CY CZ DE DK EE ES FI FR GR HR HU IE IT LT \
17
20
  LU LV MT NL PL PT RO SE SI SK].freeze
18
- EUROPE_ISOS = EU_ISOS + %w[GB IS NO CH MC AD SM VA BA RS ME MK AL\
19
- BY UA RU MD]
21
+ EUROPE_ISOS = EU_ISOS + %w[GB IS NO CH MC AD SM VA BA RS ME MK AL \
22
+ BY UA RU MD].freeze
20
23
  AFRICA_ISOS = %w[DZ AO BJ BW BF BI CM CV CF TD KM CD CG CI DJ EG \
21
- GQ ER SZ ET GA GM GH GN GW KE LS LR LY MG MW ML MR MU MA MZ NA NE \
22
- NG RW ST SN SC SL SO ZA SS SD TZ TG TN UG ZM ZW EH].freeze
23
- SOUTH_AMERICA_ISOS = ['AR' 'BO', 'BV', 'BR', 'CL', 'CO', 'EC', \
24
- 'FK', 'GF', 'GY', 'PY', 'PE', 'GS', 'SR', 'UY', 'VE'].freeze
24
+ GQ ER SZ ET GA GM GH GN GW KE LS LR LY MG MW ML \
25
+ MR MU MA MZ NA NE NG RW ST SN SC SL SO ZA SS SD \
26
+ TZ TG TN UG ZM ZW EH].freeze
27
+ SOUTH_AMERICA_ISOS = %w[AR BO BV BR CL CO EC FK GF GY PY PE GS SR \
28
+ UY VE].freeze
25
29
  ASIA_ISOS = %w[AE AF AM AZ BD BH BN BT CC CN CX GE HK ID IL IN \
26
- IQ IR JO JP KG KH KP KR KW KZ LA LB LK MM MN MO MY NP OM PH PK \
27
- PS QA SA SG SY TH TJ TL TM TR TW UZ VN YE].freeze
30
+ IQ IR JO JP KG KH KP KR KW KZ LA LB LK MM MN MO \
31
+ MY NP OM PH PK PS QA SA SG SY TH TJ TL TM TR TW \
32
+ UZ VN YE].freeze
28
33
 
29
34
  class << self
30
35
  def eu_aggregate
@@ -107,7 +112,7 @@ module Kovid
107
112
  end
108
113
 
109
114
  def state(state)
110
- response = fetch_state(state)
115
+ response = fetch_state(Kovid.lookup_us_state(state))
111
116
  if response.nil?
112
117
  not_found(state)
113
118
  else
@@ -119,7 +124,6 @@ module Kovid
119
124
 
120
125
  def states(states)
121
126
  compared_states = fetch_compared_states(states)
122
-
123
127
  Kovid::Tablelize.compare_us_states(compared_states)
124
128
  rescue JSON::ParserError
125
129
  puts SERVER_DOWN
@@ -147,25 +151,73 @@ module Kovid
147
151
  end
148
152
 
149
153
  def cases
150
- response = JSON.parse(Typhoeus.get(UriBuilder.new('/all').url, cache_ttl: 900).response_body)
154
+ response = JSON.parse(
155
+ Typhoeus.get(UriBuilder.new('/v2/all').url, cache_ttl: 900).response_body
156
+ )
151
157
 
152
158
  Kovid::Tablelize.cases(response)
153
159
  rescue JSON::ParserError
154
160
  puts SERVER_DOWN
155
161
  end
156
162
 
157
- def history(country, last)
163
+ def history(country, days)
158
164
  history_path = UriBuilder.new('/v2/historical').url
159
- response = JSON.parse(Typhoeus.get(history_path + "/#{country}", cache_ttl: 900).response_body)
160
165
 
161
- Kovid::Tablelize.history(response, last)
166
+ response = JSON.parse(
167
+ Typhoeus.get(
168
+ history_path + "/#{country}", cache_ttl: 900
169
+ ).response_body
170
+ )
171
+
172
+ if response.key?('message')
173
+ not_found(country) do |c|
174
+ "Could not find cases for #{c}.\nIf searching United States, add --usa option"
175
+ end
176
+ else
177
+ Kovid::Tablelize.history(response, days)
178
+ end
162
179
  rescue JSON::ParserError
163
180
  puts SERVER_DOWN
164
181
  end
165
182
 
183
+ def history_us_state(state, days)
184
+ history_path = UriBuilder.new('/v2/historical/usacounties').url
185
+ state = Kovid.lookup_us_state(state).downcase()
186
+
187
+ response = JSON.parse(
188
+ Typhoeus.get(
189
+ history_path + "/#{state}", cache_ttl: 900
190
+ ).response_body
191
+ )
192
+
193
+ if response.respond_to?(:key?) and response.key?('message')
194
+ return not_found(state)
195
+ end
196
+
197
+ # API Endpoint returns list of counties for given state, so
198
+ # we aggreage cases for all counties
199
+ # Note: no data for 'Recovered'
200
+ cases = usacounties_aggregator(response, 'cases')
201
+ deaths = usacounties_aggregator(response, 'deaths')
202
+
203
+ # normalize data so we can call Kovid::Tablelize.history on US State data
204
+ response = {
205
+ "state" => state,
206
+ "timeline" => { "cases" => cases, "deaths" => deaths }
207
+ }
208
+
209
+ Kovid::Tablelize.history(response, days)
210
+ rescue JSON::ParserError
211
+ puts SERVER_DOWN
212
+ end
213
+
166
214
  def histogram(country, date)
167
215
  history_path = UriBuilder.new('/v2/historical').url
168
- response = JSON.parse(Typhoeus.get(history_path + "/#{country}", cache_ttl: 900).response_body)
216
+ response = JSON.parse(
217
+ Typhoeus.get(
218
+ history_path + "/#{country}", cache_ttl: 900
219
+ ).response_body
220
+ )
169
221
 
170
222
  Kovid::Tablelize.histogram(response, date)
171
223
  end
@@ -176,21 +228,35 @@ module Kovid
176
228
 
177
229
  private
178
230
 
179
- def not_found(country)
180
- rows = [["Wrong spelling/No reported cases on #{country.upcase}."]]
181
- Terminal::Table.new title: "You checked: #{country.upcase}", rows: rows
231
+ def not_found(location)
232
+ rows = []
233
+ default_warning = "Wrong spelling/No reported cases on #{location.upcase}."
234
+
235
+ if block_given?
236
+ rows << [ yield(location) ]
237
+ else
238
+ rows << [ default_warning ]
239
+ end
240
+
241
+ Terminal::Table.new title: "You checked: #{location.upcase}", rows: rows
182
242
  end
183
243
 
184
244
  def fetch_countries(list)
185
245
  list.map do |country|
186
- JSON.parse(Typhoeus.get(COUNTRIES_PATH + "/#{country}", cache_ttl: 900).response_body)
246
+ JSON.parse(
247
+ Typhoeus.get(
248
+ COUNTRIES_PATH + "/#{country}", cache_ttl: 900
249
+ ).response_body
250
+ )
187
251
  end.sort_by { |json| -json['cases'] }
188
252
  end
189
253
 
190
254
  def fetch_compared_states(submitted_states)
191
- state_data = fetch_state_data
255
+ submitted_states.map! { |s| Kovid.lookup_us_state(s) }
192
256
 
193
- state_data.select { |state| submitted_states.include?(state['state'].downcase) }
257
+ fetch_state_data.select do |state|
258
+ submitted_states.include?(state['state'])
259
+ end
194
260
  end
195
261
 
196
262
  def fetch_state_data
@@ -198,6 +264,8 @@ module Kovid
198
264
  end
199
265
 
200
266
  def fetch_country(country_name)
267
+ # TODO: Match ISOs to full country names
268
+ country_name = 'nl' if country_name == 'netherlands'
201
269
  country_url = COUNTRIES_PATH + "/#{country_name}"
202
270
 
203
271
  JSON.parse(Typhoeus.get(country_url, cache_ttl: 900).response_body)
@@ -209,7 +277,9 @@ module Kovid
209
277
 
210
278
  def fetch_province(province)
211
279
  response = fetch_jhucsse
212
- response.select { |datum| datum['province'] == capitalize_words(province) }.first
280
+ response.select do |datum|
281
+ datum['province'] == capitalize_words(province)
282
+ end.first
213
283
  end
214
284
 
215
285
  def fetch_provinces(provinces)
@@ -219,19 +289,35 @@ module Kovid
219
289
  end
220
290
 
221
291
  def fetch_state(state)
222
- states_array = JSON.parse(Typhoeus.get(STATES_URL, cache_ttl: 900).response_body)
292
+ states_array = JSON.parse(
293
+ Typhoeus.get(STATES_URL, cache_ttl: 900).response_body
294
+ )
223
295
 
224
- states_array.select { |state_name| state_name['state'] == capitalize_words(state) }.first
296
+ states_array.select do |state_name|
297
+ state_name['state'] == capitalize_words(state)
298
+ end.first
225
299
  end
226
300
 
227
301
  def aggregator(isos, meth)
228
- countries_array = JSON.parse(Typhoeus.get(UriBuilder.new('/countries').url, cache_ttl: 900).response_body)
229
-
302
+ countries_array = JSON.parse(countries_request)
230
303
  country_array = countries_array.select do |hash|
231
304
  isos.include?(hash['countryInfo']['iso2'])
232
305
  end
306
+ data = countries_aggregator(country_array)
307
+
308
+ meth === data
309
+ rescue JSON::ParserError
310
+ puts SERVER_DOWN
311
+ end
233
312
 
234
- data = country_array.inject do |base, other|
313
+ def countries_request
314
+ Typhoeus.get(
315
+ UriBuilder.new('/v2/countries').url, cache_ttl: 900
316
+ ).response_body
317
+ end
318
+
319
+ def countries_aggregator(country_array)
320
+ country_array.inject do |base, other|
235
321
  base.merge(other) do |key, left, right|
236
322
  left ||= 0
237
323
  right ||= 0
@@ -239,10 +325,16 @@ module Kovid
239
325
  left + right unless %w[country countryInfo].include?(key)
240
326
  end
241
327
  end.compact
328
+ end
242
329
 
243
- meth === data
244
- rescue JSON::ParserError
245
- puts SERVER_DOWN
330
+ def usacounties_aggregator(data, key=nil)
331
+ data.inject({}) do |base,other|
332
+ base.merge(other['timeline'][key]) do |k,l,r|
333
+ l ||= 0
334
+ l ||= 0
335
+ l + r
336
+ end
337
+ end.compact
246
338
  end
247
339
  end
248
340
  end