summaryrefslogtreecommitdiff
path: root/lib/nola_plugins/coronavirus_plugin.ex
diff options
context:
space:
mode:
Diffstat (limited to 'lib/nola_plugins/coronavirus_plugin.ex')
-rw-r--r--lib/nola_plugins/coronavirus_plugin.ex172
1 files changed, 172 insertions, 0 deletions
diff --git a/lib/nola_plugins/coronavirus_plugin.ex b/lib/nola_plugins/coronavirus_plugin.ex
new file mode 100644
index 0000000..d04d8f9
--- /dev/null
+++ b/lib/nola_plugins/coronavirus_plugin.ex
@@ -0,0 +1,172 @@
+defmodule Nola.IRC.CoronavirusPlugin do
+ require Logger
+ NimbleCSV.define(CovidCsv, separator: ",", escape: "\"")
+ @moduledoc """
+ # Corona Virus
+
+ Données de [Johns Hopkins University](https://github.com/CSSEGISandData/COVID-19) et mises à jour a peu près tous les jours.
+
+ * `!coronavirus [France | Country]`: :-)
+ * `!coronavirus`: top 10 confirmés et non guéris
+ * `!coronavirus confirmés`: top 10 confirmés
+ * `!coronavirus morts`: top 10 morts
+ * `!coronavirus soignés`: top 10 soignés
+ """
+ def irc_doc, do: @moduledoc
+
+ def start_link() do
+ GenServer.start_link(__MODULE__, [], name: __MODULE__)
+ end
+
+ def init(_) do
+ {:ok, _} = Registry.register(IRC.PubSub, "trigger:coronavirus", [plugin: __MODULE__])
+ {:ok, nil, {:continue, :init}}
+ :ignore
+ end
+
+ def handle_continue(:init, _) do
+ date = Date.add(Date.utc_today(), -2)
+ {data, _} = fetch_data(%{}, date)
+ {data, next} = fetch_data(data)
+ :timer.send_after(next, :update)
+ {:noreply, %{data: data}}
+ end
+
+ def handle_info(:update, state) do
+ {data, next} = fetch_data(state.data)
+ :timer.send_after(next, :update)
+ {:noreply, %{data: data}}
+ end
+
+ def handle_info({:irc, :trigger, "coronavirus", m = %IRC.Message{trigger: %{type: :bang, args: args}}}, state) when args in [
+ [], ["morts"], ["confirmés"], ["soignés"], ["malades"], ["n"], ["nmorts"], ["nsoignés"], ["nconfirmés"]] do
+ {field, name} = case args do
+ ["confirmés"] -> {:confirmed, "confirmés"}
+ ["morts"] -> {:deaths, "morts"}
+ ["soignés"] -> {:recovered, "soignés"}
+ ["nmorts"] -> {:new_deaths, "nouveaux morts"}
+ ["nconfirmés"] -> {:new_confirmed, "nouveaux confirmés"}
+ ["n"] -> {:new_current, "nouveaux malades"}
+ ["nsoignés"] -> {:new_recovered, "nouveaux soignés"}
+ _ -> {:current, "malades"}
+ end
+ IO.puts("FIELD #{inspect field}")
+ field_evol = String.to_atom("new_#{field}")
+ sorted = state.data
+ |> Enum.filter(fn({_, %{region: region}}) -> region == true end)
+ |> Enum.map(fn({location, data}) -> {location, Map.get(data, field, 0), Map.get(data, field_evol, 0)} end)
+ |> Enum.sort_by(fn({_,count,_}) -> count end, &>=/2)
+ |> Enum.take(10)
+ |> Enum.with_index()
+ |> Enum.map(fn({{location, count, evol}, index}) ->
+ ev = if String.starts_with?(name, "nouveaux") do
+ ""
+ else
+ " (#{Util.plusminus(evol)})"
+ end
+ "##{index+1}: #{location} #{count}#{ev}"
+ end)
+ |> Enum.intersperse(" - ")
+ |> Enum.join()
+ m.replyfun.("CORONAVIRUS TOP10 #{name}: " <> sorted)
+ {:noreply, state}
+ end
+
+ def handle_info({:irc, :trigger, "coronavirus", m = %IRC.Message{trigger: %{type: :bang, args: location}}}, state) do
+ location = Enum.join(location, " ") |> String.downcase()
+ if data = Map.get(state.data, location) do
+ m.replyfun.("coronavirus: #{location}: "
+ <> "#{data.current} malades (#{Util.plusminus(data.new_current)}), "
+ <> "#{data.confirmed} confirmés (#{Util.plusminus(data.new_confirmed)}), "
+ <> "#{data.deaths} morts (#{Util.plusminus(data.new_deaths)}), "
+ <> "#{data.recovered} soignés (#{Util.plusminus(data.new_recovered)}) (@ #{data.update})")
+ end
+ {:noreply, state}
+ end
+
+ def handle_info({:irc, :trigger, "coronavirus", m = %IRC.Message{trigger: %{type: :query, args: location}}}, state) do
+ m.replyfun.("https://github.com/CSSEGISandData/COVID-19")
+ {:noreply, state}
+ end
+
+ # 1. Try to fetch data for today
+ # 2. Fetch yesterday if no results
+ defp fetch_data(current_data, date \\ nil) do
+ now = Date.utc_today()
+ url = fn(date) ->
+ "https://github.com/CSSEGISandData/COVID-19/raw/master/csse_covid_19_data/csse_covid_19_daily_reports/#{date}.csv"
+ end
+ request_date = date || now
+ Logger.debug("Coronavirus check date: #{inspect request_date}")
+ {:ok, date_s} = Timex.format({request_date.year, request_date.month, request_date.day}, "%m-%d-%Y", :strftime)
+ cur_url = url.(date_s)
+ Logger.debug "Fetching URL #{cur_url}"
+ case HTTPoison.get(cur_url, [], follow_redirect: true) do
+ {:ok, %HTTPoison.Response{status_code: 200, body: csv}} ->
+ # Parse CSV update data
+ data = csv
+ |> CovidCsv.parse_string()
+ |> Enum.reduce(%{}, fn(line, acc) ->
+ case line do
+ # FIPS,Admin2,Province_State,Country_Region,Last_Update,Lat,Long_,Confirmed,Deaths,Recovered,Active,Combined_Key
+ #0FIPS,Admin2,Province_State,Country_Region,Last_Update,Lat,Long_,Confirmed,Deaths,Recovered,Active,Combined_Key,Incidence_Rate,Case-Fatality_Ratio
+ [_, _, state, region, update, _lat, _lng, confirmed, deaths, recovered, _active, _combined_key, _incidence_rate, _fatality_ratio] ->
+ state = String.downcase(state)
+ region = String.downcase(region)
+ confirmed = String.to_integer(confirmed)
+ deaths = String.to_integer(deaths)
+ recovered = String.to_integer(recovered)
+
+ current = (confirmed - recovered) - deaths
+
+ entry = %{update: update, confirmed: confirmed, deaths: deaths, recovered: recovered, current: current, region: region}
+
+ region_entry = Map.get(acc, region, %{update: nil, confirmed: 0, deaths: 0, recovered: 0, current: 0})
+ region_entry = %{
+ update: region_entry.update || update,
+ confirmed: region_entry.confirmed + confirmed,
+ deaths: region_entry.deaths + deaths,
+ current: region_entry.current + current,
+ recovered: region_entry.recovered + recovered,
+ region: true
+ }
+
+ changes = if old = Map.get(current_data, region) do
+ %{
+ new_confirmed: region_entry.confirmed - old.confirmed,
+ new_current: region_entry.current - old.current,
+ new_deaths: region_entry.deaths - old.deaths,
+ new_recovered: region_entry.recovered - old.recovered,
+ }
+ else
+ %{new_confirmed: 0, new_current: 0, new_deaths: 0, new_recovered: 0}
+ end
+
+ region_entry = Map.merge(region_entry, changes)
+
+ acc = Map.put(acc, region, region_entry)
+
+ acc = if state && state != "" do
+ Map.put(acc, state, entry)
+ else
+ acc
+ end
+
+ other ->
+ Logger.info("Coronavirus line failed: #{inspect line}")
+ acc
+ end
+ end)
+ Logger.info "Updated coronavirus database"
+ {data, :timer.minutes(60)}
+ {:ok, %HTTPoison.Response{status_code: 404}} ->
+ Logger.debug "Corona 404 #{cur_url}"
+ date = Date.add(date || now, -1)
+ fetch_data(current_data, date)
+ other ->
+ Logger.error "Coronavirus: Update failed #{inspect other}"
+ {current_data, :timer.minutes(5)}
+ end
+ end
+
+end