Covid-19 vaccination data

A API to fetch Covid-19 vaccination data from RKI via JSON

CI

The API provides the current covid-19 vaccination data of the 16 German federal states as JSON. The data source is an Excel sheet provided by RKI. The data will be updated every working day by the RKI.

# API

Base-URL: https://rki-vaccination-data.vercel.app

# Show vaccination data

Returns json data with the numbers for germany and every state.

# Endpoint

  GET https://rki-vaccination-data.vercel.app/api

# Response

{
  "lastUpdate": "2021-01-18T00:00:00",
  "states": {
    "Baden-Württemberg": {
      "total": 11100394,
      "rs": "08",
      "vaccinated": 122057,
      "vaccinated_by_accine": {
        "biontech": 121737,
        "moderna": 320
      },
      "difference_to_the_previous_day": 7354,
      "vaccinations_per_1000_inhabitants": 11.0,
      "quote": 1.1,
      "2nd_vaccination": {
        "vaccinated": 4457,
        "difference_to_the_previous_day": 1953
      }
    },
    "Bayern": {
      "total": 13124737,
      "rs": "09",
      "vaccinated": 220318,
      "vaccinated_by_accine": {
        "biontech": 213624,
        "moderna": 6694
      },
      "difference_to_the_previous_day": 6481,
      "vaccinations_per_1000_inhabitants": 16.79,
      "quote": 1.68,
      "2nd_vaccination": {
        "vaccinated": 419,
        "difference_to_the_previous_day": 419
      }
    },
    "Berlin": {
      "total": 3669491,
      "rs": "11",
      "vaccinated": 55134,
      "vaccinated_by_accine": {
        "biontech": 54499,
        "moderna": 635
      },
      "difference_to_the_previous_day": 2512,
      "vaccinations_per_1000_inhabitants": 15.02,
      "quote": 1.5,
      "2nd_vaccination": {
        "vaccinated": 4138,
        "difference_to_the_previous_day": 2205
      }
    },
    "Brandenburg": {
      "total": 2521893,
      "rs": "12",
      "vaccinated": 41094,
      "vaccinated_by_accine": {
        "biontech": 41094,
        "moderna": 0
      },
      "difference_to_the_previous_day": 2620,
      "vaccinations_per_1000_inhabitants": 16.29,
      "quote": 1.63,
      "2nd_vaccination": {
        "vaccinated": 0,
        "difference_to_the_previous_day": 0
      }
    },
    "Bremen": {
      "total": 681202,
      "rs": "04",
      "vaccinated": 12866,
      "vaccinated_by_accine": {
        "biontech": 12433,
        "moderna": 433
      },
      "difference_to_the_previous_day": 614,
      "vaccinations_per_1000_inhabitants": 18.89,
      "quote": 1.89,
      "2nd_vaccination": {
        "vaccinated": 390,
        "difference_to_the_previous_day": 318
      }
    },
    "Hamburg": {
      "total": 1847253,
      "rs": "02",
      "vaccinated": 26517,
      "vaccinated_by_accine": {
        "biontech": 26362,
        "moderna": 155
      },
      "difference_to_the_previous_day": 1945,
      "vaccinations_per_1000_inhabitants": 14.35,
      "quote": 1.44,
      "2nd_vaccination": {
        "vaccinated": 598,
        "difference_to_the_previous_day": 174
      }
    },
    "Hessen": {
      "total": 6288080,
      "rs": "06",
      "vaccinated": 74071,
      "vaccinated_by_accine": {
        "biontech": 74071,
        "moderna": 0
      },
      "difference_to_the_previous_day": 1255,
      "vaccinations_per_1000_inhabitants": 11.78,
      "quote": 1.18,
      "2nd_vaccination": {
        "vaccinated": 3000,
        "difference_to_the_previous_day": 1264
      }
    },
    "Mecklenburg-Vorpommern": {
      "total": 1608138,
      "rs": "13",
      "vaccinated": 39116,
      "vaccinated_by_accine": {
        "biontech": 38745,
        "moderna": 371
      },
      "difference_to_the_previous_day": 1420,
      "vaccinations_per_1000_inhabitants": 24.32,
      "quote": 2.43,
      "2nd_vaccination": {
        "vaccinated": 1157,
        "difference_to_the_previous_day": 1157
      }
    },
    "Niedersachsen": {
      "total": 7993608,
      "rs": "03",
      "vaccinated": 94116,
      "vaccinated_by_accine": {
        "biontech": 92738,
        "moderna": 1378
      },
      "difference_to_the_previous_day": 3546,
      "vaccinations_per_1000_inhabitants": 11.77,
      "quote": 1.18,
      "2nd_vaccination": {
        "vaccinated": 837,
        "difference_to_the_previous_day": 473
      }
    },
    "Nordrhein-Westfalen": {
      "total": 17947221,
      "rs": "05",
      "vaccinated": 218699,
      "vaccinated_by_accine": {
        "biontech": 218699,
        "moderna": 0
      },
      "difference_to_the_previous_day": 2360,
      "vaccinations_per_1000_inhabitants": 12.19,
      "quote": 1.22,
      "2nd_vaccination": {
        "vaccinated": 4593,
        "difference_to_the_previous_day": 1722
      }
    },
    "Rheinland-Pfalz": {
      "total": 4093903,
      "rs": "07",
      "vaccinated": 91423,
      "vaccinated_by_accine": {
        "biontech": 89129,
        "moderna": 2294
      },
      "difference_to_the_previous_day": 7496,
      "vaccinations_per_1000_inhabitants": 22.33,
      "quote": 2.23,
      "2nd_vaccination": {
        "vaccinated": 1196,
        "difference_to_the_previous_day": null
      }
    },
    "Saarland": {
      "total": 986887,
      "rs": "10",
      "vaccinated": 16957,
      "vaccinated_by_accine": {
        "biontech": 16957,
        "moderna": 0
      },
      "difference_to_the_previous_day": 1246,
      "vaccinations_per_1000_inhabitants": 17.18,
      "quote": 1.72,
      "2nd_vaccination": {
        "vaccinated": 0,
        "difference_to_the_previous_day": 0
      }
    },
    "Sachsen": {
      "total": 4071971,
      "rs": "14",
      "vaccinated": 53663,
      "vaccinated_by_accine": {
        "biontech": 53663,
        "moderna": 0
      },
      "difference_to_the_previous_day": 3661,
      "vaccinations_per_1000_inhabitants": 13.18,
      "quote": 1.32,
      "2nd_vaccination": {
        "vaccinated": 368,
        "difference_to_the_previous_day": null
      }
    },
    "Sachsen-Anhalt": {
      "total": 2194782,
      "rs": "15",
      "vaccinated": 34411,
      "vaccinated_by_accine": {
        "biontech": 34411,
        "moderna": 0
      },
      "difference_to_the_previous_day": 1263,
      "vaccinations_per_1000_inhabitants": 15.68,
      "quote": 1.57,
      "2nd_vaccination": {
        "vaccinated": 2927,
        "difference_to_the_previous_day": 2248
      }
    },
    "Schleswig-Holstein": {
      "total": 2903773,
      "rs": "01",
      "vaccinated": 69126,
      "vaccinated_by_accine": {
        "biontech": 69076,
        "moderna": 50
      },
      "difference_to_the_previous_day": 3277,
      "vaccinations_per_1000_inhabitants": 23.81,
      "quote": 2.38,
      "2nd_vaccination": {
        "vaccinated": 640,
        "difference_to_the_previous_day": 640
      }
    },
    "Thüringen": {
      "total": 2133378,
      "rs": "16",
      "vaccinated": 25975,
      "vaccinated_by_accine": {
        "biontech": 25975,
        "moderna": 0
      },
      "difference_to_the_previous_day": 2239,
      "vaccinations_per_1000_inhabitants": 12.18,
      "quote": 1.22,
      "2nd_vaccination": {
        "vaccinated": 21,
        "difference_to_the_previous_day": 21
      }
    }
  },
  "vaccinated": 1195543,
  "2nd_vaccination": {
    "vaccinated": 24741,
    "difference_to_the_previous_day": 12594
  },
  "sum_vaccine_doses": 1220284,
  "difference_to_the_previous_day": 49289,
  "vaccinations_per_1000_inhabitants": 14.38,
  "total": 83166711,
  "quote": 1.44
}

# Sample Call

cURL:

  curl -X GET 'https://rki-vaccination-data.vercel.app/api'

jQuery:

  var settings = {
    "url": "https://rki-vaccination-data.vercel.app/api",
    "method": "GET"
  };

  $.ajax(settings).done(function (response) {
    console.log(response);
  });

Scriptable App:

  const req = new Request('https://rki-vaccination-data.vercel.app/api')
  const res = await req.loadJSON()
  console.log(res)

# Code

The API Code is Open-Source

# API Interface

""" API Endpont """
from http.server import BaseHTTPRequestHandler
import json
from datetime import datetime
import pytz
# pylint: disable=import-error
from api._utils import scrap_data

try:
  data = scrap_data.get_data()
  res = {
    'lastUpdate': data['lastUpdate'].isoformat(),
    'states': data['states'],
    'vaccinated': data['sumStates'],
    '2nd_vaccination': {
      'vaccinated': data['sumStates2nd'],
      'difference_to_the_previous_day': data['sumDiffStates2nd']
    },
    'sum_vaccine_doses': data['sumStates'] + data['sumStates2nd'],
    'difference_to_the_previous_day': data['sumDiffStates'],
    'vaccinations_per_1000_inhabitants': round(data['sumStates'] / data['totalGermany'] * 1000, 2),
    'total': data['totalGermany'],
    'quote': round(data['sumStates'] / data['totalGermany'] * 100, 2)
  }
  HTTPCODE = 200
except TypeError:
  res = {
    'message': 'scrapping data from RKI excel failed'
  }
  HTTPCODE = 500

class Handler(BaseHTTPRequestHandler):
  """ HTTP Handler """
  # pylint: disable=invalid-name
  def do_GET(self):
    """ GET Method """
    self.send_response(HTTPCODE)
    self.send_header('Content-Type', 'application/json')
    self.send_header('X-Cache-Timestamp',
      pytz.timezone('Europe/Berlin').localize(datetime.now()).isoformat())
    self.send_header('Access-Control-Allow-Origin', '*')
    self.end_headers()
    self.wfile.write(json.dumps(res).encode())

# Scrapping Data

""" Scrap Data from Excel sheet """
import re
import datetime
from io import BytesIO
import urllib.request
import openpyxl
from .statics import inhabitants

def get_file():
  """ Get remote file content """
  # Request to load excel sheet
  url = 'https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Daten' \
    '/Impfquotenmonitoring.xlsx?__blob=publicationFile'
  hdr = {
      'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11' \
        ' (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11'}

  req = urllib.request.Request(url, headers=hdr)
  with urllib.request.urlopen(req) as response:
    return response.read()

def get_data():
  """ Get Data for API """
  states = inhabitants.STATES

  file = get_file()

  # Read excel sheet
  work_book = openpyxl.load_workbook(filename = BytesIO(file))
  sheet = work_book[work_book.sheetnames[2]]

  # Load update time
  last_update_raw_string = work_book.sheetnames[1]
  relast_update_match = re.search(r"[\d]{2}\.[\d]{2}\.[\d]{2}", last_update_raw_string)
  last_update = datetime.datetime.strptime(relast_update_match.group(), '%d.%m.%y')

  # Load data from rows
  sum_states = 0
  sum_states2nd = 0
  sum_diff_states = 0
  sum_diff_states2nd = 0
  for row in sheet.iter_rows(max_row=21):
    if row[1].value is None:
      continue
    state = row[1].value.replace("*", "").strip()

    if state in states:
      states[state]['rs'] = str(row[0].value)

      # First vaccination
      states[state]['vaccinated'] = row[2].value + row[13].value
      states[state]['vaccinated_by_accine'] = {}
      states[state]['vaccinated_by_accine']['biontech'] = row[3].value + row[14].value
      states[state]['vaccinated_by_accine']['moderna'] = row[4].value + row[15].value
      states[state]['vaccinated_by_accine']['astrazeneca'] = row[5].value + row[16].value
      states[state]['difference_to_the_previous_day'] = row[6].value + row[17].value
      states[state]['vaccinations_per_1000_inhabitants'] = round(states[state]['vaccinated']
        / states[state]['total'] * 1000, 2)
      states[state]['quote'] = round(states[state]['vaccinated'] / states[state]['total'] * 100, 2)

      sum_states += states[state]['vaccinated']
      sum_diff_states += states[state]['difference_to_the_previous_day']


      # Second vaccination
      states[state]['2nd_vaccination'] = {}
      states[state]['2nd_vaccination']['vaccinated'] = row[7].value + row[18].value
      states[state]['2nd_vaccination']['vaccinated_by_accine'] = {}
      states[state]['2nd_vaccination']['vaccinated_by_accine']['biontech'] = (row[8].value
      + row[19].value)
      states[state]['2nd_vaccination']['vaccinated_by_accine']['moderna'] = (row[9].value
      + row[20].value)
      states[state]['2nd_vaccination']['vaccinated_by_accine']['astrazeneca'] = (row[10].value
      + row[21].value)
      states[state]['2nd_vaccination']['vaccinated_by_accine']['janssen'] = (row[11].value
      + row[22].value)
      states[state]['2nd_vaccination']['difference_to_the_previous_day'] = (row[12].value
      + row[23].value)
      states[state]['2nd_vaccination']['quote'] = round(
          states[state]['2nd_vaccination']['vaccinated'] / states[state]['total'] * 100, 2)

      sum_states2nd += states[state]['2nd_vaccination']['vaccinated']

      if states[state]['2nd_vaccination']['difference_to_the_previous_day'] is not None:
        sum_diff_states2nd += states[state]['2nd_vaccination']['difference_to_the_previous_day']

    elif state == 'Impfzentren Bund':
      sum_states += row[2].value
      sum_diff_states += row[6].value
      sum_states2nd += row[7].value
      sum_diff_states2nd += row[12].value

  return {
    "lastUpdate": last_update,
    "states": states,
    "sumStates": sum_states,
    "sumStates2nd": sum_states2nd,
    "sumDiffStates": sum_diff_states,
    "sumDiffStates2nd": sum_diff_states2nd,
    "totalGermany": inhabitants.TOTAL
  }

# Data-Sources

Licence: Robert Koch-Institut (RKI), dl-de/by-2-0