summaryrefslogblamecommitdiff
path: root/lib/lsg_irc/coronavirus_plugin.ex
blob: 8038d14753ccce9ca073d8f37b1fcdb681fb92a8 (plain) (tree)
1
2
3

                                      
                                                          
















                                                                                                                                 


                                         









                                         

                                                                                                                               



                                                  



                                                                

                                

                                               

                                                                             

                                                                                                                       

                                 






                                                                


                                       
                                                        





                                                                                                                       




                                                                                                 
























                                                                                                                        
                                  
                                            

                                                                                                                            

                                                                                                                                                               











































                                                                                                                                     
             














                                                                  
defmodule LSG.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__, [])

  def init(_) do
    {:ok, _} = Registry.register(IRC.PubSub, "trigger:coronavirus", [])
    date = Date.add(Date.utc_today(), -2)
    {data, _} = fetch_data(%{}, date)
    {data, next} = fetch_data(data)
    :timer.send_after(next, :update)
    {:ok, %{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