From 37a3026132679fd5026ba070185da619dad553aa Mon Sep 17 00:00:00 2001
From: Benedikt Bastin <benedikt@benedikt-bastin.de>
Date: Sun, 17 Jan 2021 00:00:07 +0100
Subject: [PATCH 1/3] fix: Use correct filename when checking for previous
 plots

---
 plot.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/plot.py b/plot.py
index 2648758..9a8591d 100644
--- a/plot.py
+++ b/plot.py
@@ -85,7 +85,7 @@ def plot_extrapolation_portion(percentage):
 	plot_filename = '{}/{}_extrapolated_to_{}_percent'.format(plots_folder, filename_stand, print_percentage)
 	plot_filename_latest = '{}/latest_extrapolated_to_{}_percent'.format(plots_folder, print_percentage)
 
-	if os.path.isfile(plot_filename):
+	if os.path.isfile(plot_filename + 'pdf'):
 		print('Plot {} already exists'.format(plot_filename))
 		return
 

From 7cba369103eb3f6a0c4ed955121b5ba6497d0b31 Mon Sep 17 00:00:00 2001
From: Benedikt Bastin <benedikt@benedikt-bastin.de>
Date: Sun, 17 Jan 2021 00:17:37 +0100
Subject: [PATCH 2/3] fix: Extrapolation of mean vaccination rate had an off by
 one error

---
 plot.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/plot.py b/plot.py
index 9a8591d..9ad3d08 100644
--- a/plot.py
+++ b/plot.py
@@ -61,7 +61,7 @@ days_extrapolated = int(np.ceil(to_be_vaccinated / mean_vaccinations_daily))
 
 extrapolated_dates = np.array([dates[0] + datetime.timedelta(days=i) for i in range(days_extrapolated)])
 
-extrapolated_vaccinations = mean_vaccinations_daily * range(days_extrapolated)
+extrapolated_vaccinations = mean_vaccinations_daily * range(1, days_extrapolated + 1)
 
 mean_vaccinations_daily_up_to_date  = np.round(cumulative / range(1, len(cumulative) + 1))
 

From 49fda4d43f1275799512039397ddad598db48210 Mon Sep 17 00:00:00 2001
From: Benedikt Bastin <benedikt@benedikt-bastin.de>
Date: Sun, 17 Jan 2021 02:34:49 +0100
Subject: [PATCH 3/3] feat: Created dashboard and reworked folder structure

---
 dashboard_template.xhtml           | 122 +++++++++++++++++++++++++++
 plot.py                            | 129 +++++++++++++++++++++++++----
 requirements.txt                   |   2 +
 {Quellen => site}/.gitignore       |   1 +
 {Plots => site/archive}/.gitignore |   0
 site/rki-dashboard.css             |  59 +++++++++++++
 6 files changed, 297 insertions(+), 16 deletions(-)
 create mode 100644 dashboard_template.xhtml
 rename {Quellen => site}/.gitignore (62%)
 rename {Plots => site/archive}/.gitignore (100%)
 create mode 100644 site/rki-dashboard.css

diff --git a/dashboard_template.xhtml b/dashboard_template.xhtml
new file mode 100644
index 0000000..aefde8a
--- /dev/null
+++ b/dashboard_template.xhtml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de">
+	<head>
+		<title></title>
+		<style>
+			<![CDATA[
+
+
+
+			]]>
+		</style>
+		<link rel="stylesheet" type="text/css" href="rki-dashboard.css" />
+		<meta name="viewport" content="width=device-width, initial-scale=1" />
+	</head>
+	<body>
+		<h1>Dashboard</h1>
+		<h2>
+			Quelle: <a href="https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Daten/Impfquotenmonitoring.xlsx?__blob=publicationFile">Robert-Koch-Institut (RKI)</a><br />
+			Stand: {{ stand }}
+		</h2>
+
+		<section>
+			<p class="data-text">
+				Bislang wurden <em>{{ total_vaccinations }}</em> Impfungen<sup><a href="#footnote-001">1</a></sup> innerhalb von <em>{{ days_since_start }}</em> Tagen<sup><a href="#footnote-002">2</a></sup> vorgenommen.
+				Das entspricht einem Anteil von <em>{{ total_vaccinations_percentage }} %</em> der Bevölkerung<sup><a href="#footnote-003">3</a></sup>.
+			</p>
+			<p class="data-text">
+			</p>
+			<p class="data-text">
+				Durchschnittlich fanden seit Start täglich <em>{{ mean_vaccinations_daily }}</em> Impfungen statt.
+				Mit dieser durschnittlichen Rate dauert es bis zum <em>{{ mean_vaccinations_daily_herd_immunity }}</em>, bis {{ herd_immunity }} %<sup><a href="#footnote-004">4</a></sup> der Bevölkerung die erste Impfung erhalten haben,
+				und bis zum <em>{{ mean_vaccinations_daily_done }}</em> für 100 %.
+			</p>
+			<p class="data-text">
+				Am <em>{{ last_date }}</em> wurden <em>{{ last_date_day_rate }}</em> Impfungen vorgenommen.
+				Mit der Rate vom <em>{{ last_date }}</em> dauert es bis zum <em>{{ last_date_day_rate_herd_immunity }}</em> für {{ herd_immunity }} % und bis zum <em>{{ last_date_day_rate_done }}</em> für 100 %<sup><a href="#footnote-005">5</a></sup>.
+			</p>
+			<p class="data-text">
+				In den letzten sieben Tagen wurden <em>{{ mean_vaccinations_last_seven_days }}</em> Impfungen vorgenommen.
+				Mit dieser Rate dauert es bis zum <em>{{ mean_vaccinations_last_seven_days_herd_immunity }}</em> für {{ herd_immunity }} % und bis zum <em>{{ mean_vaccinations_last_seven_days_done }}</em> für 100 %.
+			</p>
+		</section>
+		<section>
+			<h2>Was ist dieses Dashboard?</h2>
+			<p>
+				Dieses Dashboard soll einen Überblick über den aktuellen Stand sowie die Entwicklungen der Impfungen in Deutschland geben.
+				Inspiriert wurde es durch <a href="https://impfdashboard.de/">impfdashboard.de</a>, ein Impfdashboard, das ebenfalls die Daten des Robert-Koch-Instituts auswertet und vom Bundesministerium für Gesundheit betrieben wird.
+			</p>
+			<p>
+				Als zusätzliche Informationen rechnet es aber aus, wie lange es dauert könnte, bis die Impfungen abgeschlossen sind oder wenigstens eine Herdenimmunität erreicht wird (sofern die aktuelle Schätzung der für eine Herdenimmunität benötigten Bevölkerung korrekt ist).
+				Dafür wird einerseits die durchschnittliche Impfrate aller Tage und andererseits die Impfrate der letzten sieben Tage herangezogen, die noch zu impfende Bevölkerung ausgerechnet und diese durch die Impfrate geteilt.
+				Dadurch erhält man die lineare Extrapolation, wenn jeden weiteren Tag genau mit dieser Impfrate weitergeimpft würde.
+			</p>
+		</section>
+		<section>
+			<h2>Wie genau sind die Daten?</h2>
+			<p>
+				Die Datenquelle ist das Robert-Koch-Institut.
+				Für die Meldungen der jeweiligen Tage gibt es immer etwas Meldeverzug, das heißt, dass teilweise Meldungen einige Tage im Nachhinein noch korrigiert werden.
+				Das allerdings für gewöhnlich nur nach oben, und bislang hat das auch nur wenig Unterschied verursacht.
+			</p>
+			<p>
+				Für die Extrapolation sieht das etwas anders aus.
+				Die lineare Extrapolation nimmt natürlich an, dass sich an dem Vorgang des Impfens über einen längeren Zeitraum nichts signifikant ändert.
+				Diese Annahme ist aber falsch;
+				aktuell (Stand: 17. Januar 2021) sind die meisten Impfzentren beispielsweise noch nicht geöffnet, stattdessen sind mobile Impfteams unterwegs, die hauptsächlich die Altenheime besuchen.
+				Dadurch liegt die aktuelle Impfrate deutlich unter dem, was bei den Impfzentren unter voller Last zu erwarten ist.
+				Entsprechend darf man vorsichtig optimistisch hoffen, dass die Impfrate in den nächsten Wochen und Monaten noch stark ansteigen wird.
+				Dennoch wird es vermutlich ungefähr anderthalb Jahre dauern, bis die meisten Menschen in Deutschland geimpft wurden.
+			</p>
+			<p>
+				Ich finde den Ausblick, auch wenn die Schätzung sehr unpräzise sein wird, dennoch interessant, deshalb habe ich das gemacht.
+			</p>
+		</section>
+
+		<figure>
+			<a href="{{ filename_stand }}_extrapolated_to_10_percent.png">
+				<img
+					src="{{ filename_stand }}_extrapolated_to_10_percent.png"
+					alt="" />
+				<figcaption>
+					<a name="figure-001"><span class="ref">Abbildung 1:</span></a>
+					Tägliche Impfquote, kumulierte Impfungen und lineare Extrapolation bis 10 % der Bevölkerung Deutschlands
+				</figcaption>
+			</a>
+		</figure>
+
+		<figure>
+			<a href="{{ filename_stand }}_extrapolated_to_70_percent.png">
+				<img
+					src="{{ filename_stand }}_extrapolated_to_70_percent.png"
+					alt="" />
+				<figcaption>
+					<a name="figure-002"><span class="ref">Abbildung 2:</span></a>
+					Tägliche Impfquote, kumulierte Impfungen und lineare Extrapolation bis 70 % der Bevölkerung Deutschlands
+				</figcaption>
+			</a>
+		</figure>
+		<figure>
+			<a href="{{ filename_stand }}_extrapolated_to_100_percent.png">
+				<img
+					src="{{ filename_stand }}_extrapolated_to_100_percent.png"
+					alt="" />
+				<figcaption>
+					<a name="figure-003"><span class="ref">Abbildung 3:</span></a>
+					Tägliche Impfquote, kumulierte Impfungen und lineare Extrapolation bis 100 % der Bevölkerung Deutschlands
+				</figcaption>
+			</a>
+		</figure>
+
+		<section>
+			<h3>Fußnoten</h3>
+			<ol>
+				<li><a name="footnote-001">Gezählt werden verabreichte Dosen, unabhängig ob erste oder zweite Dosis.</a></li>
+				<li><a name="footnote-002">Starttermin der Impfungen war der 27. Dezember 2020.</a></li>
+				<li><a name="footnote-003">Bevölkerungsstand vom 31. Dezember 2019: {{ einwohner_deutschland }}.</a></li>
+				<li><a name="footnote-004">{{ herd_immunity }} % ist der Wert, bei dem aktuell von einer Herdenimmunität ausgegangen wird.</a></li>
+				<li><a name="footnote-005">Die täglichen Impfraten unterliegen starken Schwankungen und sind daher wenig aussagekräftig.</a></li>
+			</ol>
+		</section>
+	</body>
+</html>
diff --git a/plot.py b/plot.py
index 9ad3d08..559a938 100644
--- a/plot.py
+++ b/plot.py
@@ -10,14 +10,15 @@ import re
 import requests as req
 import locale
 import os.path
+import shutil
 
 locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')
 
-sources_folder = 'Quellen'
-plots_folder = 'Plots'
-
+site_folder = 'site/'
+data_folder = 'data/'
 
 einwohner_deutschland = 83190556
+herd_immunity = 0.7
 
 today = datetime.date.today()
 print_today = today.isoformat()
@@ -30,17 +31,18 @@ plt.rcParams["figure.figsize"] = [11.69, 8.27]
 
 # Download
 
-filename = '{}/{}_Impfquotenmonitoring.xlsx'.format(sources_folder, filename_now)
+data_filename = '{}/{}_Impfquotenmonitoring.xlsx'.format(data_folder, filename_now)
 
 r = req.get('https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Daten/Impfquotenmonitoring.xlsx?__blob=publicationFile')
 
-with open(filename, 'wb') as outfile:
+with open(data_filename, 'wb') as outfile:
 	outfile.write(r.content)
 
 
 
 
-rki_file = pd.read_excel(filename, sheet_name=None, engine='openpyxl')
+
+rki_file = pd.read_excel(data_filename, sheet_name=None, engine='openpyxl')
 
 raw_data = rki_file['Impfungen_proTag']
 
@@ -61,10 +63,41 @@ days_extrapolated = int(np.ceil(to_be_vaccinated / mean_vaccinations_daily))
 
 extrapolated_dates = np.array([dates[0] + datetime.timedelta(days=i) for i in range(days_extrapolated)])
 
+mean_vaccinations_daily_done = extrapolated_dates[-1]
+mean_vaccinations_daily_herd_immunity = extrapolated_dates[int(np.ceil(days_extrapolated * herd_immunity))]
+
+days_extrapolated_with_todays_rate = int(np.ceil(to_be_vaccinated / daily.iloc[-1]))
+
+last_date = dates.iloc[-1]
+
+last_date_day_rate = daily.iloc[-1]
+last_date_day_rate_done = dates[0] + datetime.timedelta(days=days_extrapolated_with_todays_rate)
+last_date_day_rate_herd_immunity = dates[0] + datetime.timedelta(days=int(np.ceil(days_extrapolated_with_todays_rate * herd_immunity)))
+
+
 extrapolated_vaccinations = mean_vaccinations_daily * range(1, days_extrapolated + 1)
 
+days_since_start = (dates.iloc[-1].date() - dates[0].date()).days
+print(days_since_start)
+
+mean_vaccinations_last_seven_days = np.mean(daily[-7:])
+mean_vaccinations_last_seven_days_int = int(np.round(mean_vaccinations_last_seven_days))
+
+days_extrapolated_last_seven_days = int(np.ceil(to_be_vaccinated / mean_vaccinations_last_seven_days))
+
+extrapolated_vaccinations_last_seven_days = total_vaccinations + mean_vaccinations_last_seven_days * range(-days_since_start, days_extrapolated - days_since_start)
+
+mean_vaccinations_last_seven_days_done = dates.iloc[-1] + datetime.timedelta(days=days_extrapolated_last_seven_days)
+mean_vaccinations_last_seven_days_herd_immunity = dates.iloc[-1] + datetime.timedelta(days=int(np.ceil(days_extrapolated_last_seven_days * herd_immunity)))
+
+
+
 mean_vaccinations_daily_up_to_date  = np.round(cumulative / range(1, len(cumulative) + 1))
 
+print(extrapolated_vaccinations[:20])
+print(extrapolated_vaccinations_last_seven_days[:20])
+print(cumulative)
+
 # Stand aus Daten auslesen
 #stand = dates.iloc[-1]
 #print_stand = stand.isoformat()
@@ -79,14 +112,22 @@ print_stand = stand_date.isoformat()
 
 filename_stand = stand_date.strftime("%Y%m%d%H%M%S")
 
+
+archive_folder = site_folder + 'archive/' + filename_stand
+
+if os.path.isdir(archive_folder):
+	print('Archive folder {} already exists'.format(archive_folder))
+else:
+	os.mkdir(archive_folder)
+
 def plot_extrapolation_portion(percentage):
 
 	print_percentage = int(percentage * 100)
-	plot_filename = '{}/{}_extrapolated_to_{}_percent'.format(plots_folder, filename_stand, print_percentage)
-	plot_filename_latest = '{}/latest_extrapolated_to_{}_percent'.format(plots_folder, print_percentage)
+	archive_plot_filename = '{}/extrapolated_to_{}_percent'.format(archive_folder, print_percentage)
+	latest_plot_filename = '{}/extrapolated_to_{}_percent'.format(site_folder, print_percentage)
 
-	if os.path.isfile(plot_filename + 'pdf'):
-		print('Plot {} already exists'.format(plot_filename))
+	if os.path.isfile(archive_plot_filename + '.pdf'):
+		print('Plot {} already exists'.format(archive_plot_filename))
 		return
 
 	fig, ax = plt.subplots(1)
@@ -111,7 +152,8 @@ def plot_extrapolation_portion(percentage):
 	ax2.set_xlim(xmax=dates[0] + datetime.timedelta(days=percentage * days_extrapolated))
 	ax2.grid(True)
 	ax2.plot(dates, cumulative, color='red', label='Kumulierte Impfungen')
-	ax2.plot(extrapolated_dates, extrapolated_vaccinations, color='orange', label='Extrap. kumulierte Impfungen\n({:n} Impfungen/Tag)'.format(mean_vaccinations_daily_int))
+	ax2.plot(extrapolated_dates, extrapolated_vaccinations, color='orange', label='Extrap. kumulierte Impfungen (Ø gesamt)\n{:n} Impfungen/Tag'.format(mean_vaccinations_daily_int))
+	ax2.plot(extrapolated_dates, extrapolated_vaccinations_last_seven_days, color='goldenrod', label='Extrap. kumulierte Impfungen (Ø 7 Tage)\n{:n} Impfungen/Tag'.format(mean_vaccinations_last_seven_days_int))
 	#ax2.plot()
 
 	ax.legend(loc='upper left')
@@ -128,14 +170,69 @@ def plot_extrapolation_portion(percentage):
 
 	ax2.set_ylabel('Kumulierte Impfungen')
 
-	plt.savefig(plot_filename + '.pdf')
-	plt.savefig(plot_filename + '.png')
-	plt.savefig(plot_filename_latest + '.pdf')
-	plt.savefig(plot_filename_latest + '.png')
+	plt.savefig(archive_plot_filename + '.pdf')
+	plt.savefig(archive_plot_filename + '.png')
+	plt.savefig(latest_plot_filename + '.pdf')
+	plt.savefig(latest_plot_filename + '.png')
 	plt.close()
 
-	print('Created plot {} as pdf and png'.format(plot_filename))
+	print('Created plot {} as pdf and png'.format(archive_plot_filename))
 
 
 plot_extrapolation_portion(0.1)
+plot_extrapolation_portion(0.7)
 plot_extrapolation_portion(1.0)
+
+
+def render_dashboard():
+	dashboard_filename = 'site/index.xhtml'
+	dashboard_archive_filename = 'site/archive/{}/index.xhtml'.format(filename_stand)
+	stylesheet_filename = 'site/rki-dashboard.css'
+	stylesheet_archive_filename = 'site/archive/{}/rki-dashboard.css'.format(filename_stand)
+
+	if os.path.isfile(dashboard_archive_filename):
+		print('Dashboard {} already exists'.format(dashboard_archive_filename))
+		return
+
+	from jinja2 import Template, Environment, FileSystemLoader, select_autoescape
+	env = Environment(
+		loader=FileSystemLoader('./'),
+		autoescape=select_autoescape(['html', 'xml', 'xhtml'])
+	)
+
+	german_text_date_format = '%d. %B %Y'
+	df = german_text_date_format
+
+	german_text_datetime_format = '%d. %B %Y, %H:%M:%S Uhr'
+	dtf = german_text_datetime_format
+
+	latest_dashboard_filename = site_folder + 'index.xhtml'
+	archive_dashboard_filename = archive_folder
+
+	template = env.get_template('dashboard_template.xhtml')
+	template.stream(
+		stand = stand_date.strftime(dtf),
+		filename_stand = filename_stand,
+		einwohner_deutschland = '{:n}'.format(einwohner_deutschland).replace('.', ' '),
+		herd_immunity = '{:n}'.format(int(herd_immunity * 100)),
+		total_vaccinations = '{:n}'.format(total_vaccinations).replace('.', ' '),
+		total_vaccinations_percentage = '{:.3n}'.format(total_vaccinations_percentage * 100),
+		days_since_start = days_since_start,
+		last_date = last_date.strftime(df),
+		last_date_day_rate = '{:n}'.format(last_date_day_rate).replace('.', ' '),
+		mean_vaccinations_daily = '{:n}'.format(mean_vaccinations_daily_int).replace('.', ' '),
+		mean_vaccinations_daily_herd_immunity = mean_vaccinations_daily_herd_immunity.strftime(df),
+		mean_vaccinations_daily_done = mean_vaccinations_daily_done.strftime(df),
+		last_date_day_rate_herd_immunity = last_date_day_rate_herd_immunity.strftime(df),
+		last_date_day_rate_done = last_date_day_rate_done.strftime(df),
+		mean_vaccinations_last_seven_days = '{:n}'.format(mean_vaccinations_last_seven_days_int).replace('.', ' '),
+		mean_vaccinations_last_seven_days_herd_immunity = mean_vaccinations_last_seven_days_herd_immunity.strftime(df),
+		mean_vaccinations_last_seven_days_done = mean_vaccinations_last_seven_days_done.strftime(df)
+	).dump('site/index.xhtml')
+
+	shutil.copyfile(dashboard_filename, dashboard_archive_filename)
+	shutil.copyfile(stylesheet_filename, stylesheet_archive_filename)
+
+	print('Created dashboard')
+
+render_dashboard()
diff --git a/requirements.txt b/requirements.txt
index 5e7a8f3..1e70fad 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,5 @@ matplotlib
 pandas
 openpyxl
 requests
+jinja2
+
diff --git a/Quellen/.gitignore b/site/.gitignore
similarity index 62%
rename from Quellen/.gitignore
rename to site/.gitignore
index a5baada..86ddff1 100644
--- a/Quellen/.gitignore
+++ b/site/.gitignore
@@ -1,3 +1,4 @@
 *
 !.gitignore
+!archive
 
diff --git a/Plots/.gitignore b/site/archive/.gitignore
similarity index 100%
rename from Plots/.gitignore
rename to site/archive/.gitignore
diff --git a/site/rki-dashboard.css b/site/rki-dashboard.css
new file mode 100644
index 0000000..759bdcb
--- /dev/null
+++ b/site/rki-dashboard.css
@@ -0,0 +1,59 @@
+body {
+	font-family: Noto Sans;
+	max-width: 1000px;
+	margin: 0px auto;
+	padding: 0.4em;
+	hyphens: auto;
+}
+
+h1, h2, h3 {
+	font-weight: normal;
+}
+
+a {
+	text-decoration: none;
+}
+
+p {
+	text-align: justify;
+}
+
+.data-text {
+	color: #666666;
+	font-size: 2em;
+	font-weight: normal;
+	line-height: 1.75em;
+}
+
+.data-text em {
+	font-weight: inherit;
+	font-style: inherit;
+	/*color: #ffac12;*/
+	color: #333333;
+	/*background-image: linear-gradient(rgba(255, 255, 255, 0) 90%, #f7bb3c 91%, #f7bb3c 98%, rgba(255, 255, 255, 0) 99%);*/
+	text-decoration: underline;
+	text-decoration-color: #ffac12;
+}
+
+sup {
+	line-height: 0;
+}
+
+
+
+figure a {
+	color: inherit;
+}
+
+figure img {
+	width: 90%;
+	height: auto;
+}
+
+figure figcaption {
+
+}
+
+figure figcaption .ref {
+	font-weight: bolder;
+}