diff --git a/R/Analyse/Auswertung.R b/R/Analyse/Auswertung.R
new file mode 100644
index 0000000000000000000000000000000000000000..a93516b051e22f816dbbaba4ea71e5fe9dc37057
--- /dev/null
+++ b/R/Analyse/Auswertung.R
@@ -0,0 +1,153 @@
+# Auswertung nach Stadtteilen vs. Kommunalwahlergebnis 2021
+
+
+# Parteienliste 
+parteien_df <- read.xlsx("index/obwahl_da_2023/parteien-kommunalwahl.xlsx")
+
+# Kommunaldaten: Stadtverordnetenwahl
+kommunal_url <- "https://votemanager-da.ekom21cdn.de/2021-03-14/06411000/praesentation/Open-Data-06411000-Stadtverordnetenwahl-Wahlbezirk.csv?ts=1679256774922"
+k_stimm_df <- lies_stimmbezirke(kommunal_url) %>% 
+  # Die Spalten D1-D14 enthalten die Gesamtergebnisse der Parteien. 
+  # Unzählige weitere Spalten enthalten die Ergebnisse für jeden Kandidaten
+  # auf den sehr, sehr langen Wahllisten. 
+  select(zeitstempel,
+         nr,
+         name,
+         meldungen_anz,
+         meldungen_max,
+         wahlberechtigt,
+         waehler_regulaer,
+         waehler_wahlschein,
+         waehler_nv,
+         stimmen,
+         stimmen_wahlschein,
+         ungueltig,
+         gueltig,
+         matches("D[0-9]+$")) %>% 
+  mutate(nr = as.integer(str_extract(nr,"[0-9]+")))
+
+
+k_stadtteile_df <- k_stimm_df %>% 
+  left_join(stimmbezirke_df %>% select(nr,ortsteilnr,stadtteil),
+            by="nr") %>% 
+  group_by(ortsteilnr)   %>% 
+  # Fasse alle Spalten von meldungen_anz bis Ende der Tabelle zusammen - 
+  # mit der sum()-Funktion (NA wird wie null behandelt)
+  summarize(zeitstempel = last(zeitstempel),
+            nr = first(ortsteilnr), 
+            meldungen_anz = sum(meldungen_anz,na.rm =T),
+            meldungen_max = sum(meldungen_max,na.rm = T),
+            wahlberechtigt = sum(wahlberechtigt, na.rm = T),
+            waehler_regulaer = sum(waehler_regulaer, na.rm = T),
+            waehler_wahlschein = sum(waehler_wahlschein, na.rm = T),
+            waehler_nv = sum(waehler_nv, na.rm = T),
+            stimmen = sum(stimmen, na.rm = T),
+            stimmen_wahlschein = sum(stimmen_wahlschein, na.rm = T),
+            ungueltig = sum(ungueltig, na.rm = T),
+            gueltig = sum(gueltig, na.rm = T),
+            across(starts_with("D"), ~ sum(.,na.rm = T))) %>%
+  mutate(across(where(is.numeric), ~ifelse(is.na(.), 0, .))) %>% 
+  # Stadtteilnamen, Geokoordinaten dazuholen
+  left_join(stadtteile_df, by="nr") %>% 
+  # Wichtige Daten für bessere Lesbarkeit nach vorn
+  relocate(zeitstempel,nr,name,lon,lat)
+
+# Sicherheitscheck: Warnen, wenn nicht alle Ortsteile zugeordnet
+if (nrow(stadtteildaten_df) != nrow(stadtteile_df)) teams_warnung("Nicht alle Stadtteile zugeordnet")
+if (nrow(stimmbezirke_df) != length(unique(stimmbezirke_df$nr))) teams_warnung("Nicht alle Stimmbezirke zugeordnet")
+
+  tmp_long_df <- k_stadtteile_df %>%
+    pivot_longer(cols = starts_with("D"), names_to = "partei_nr", values_to = "partei_stimmen") %>% 
+    mutate(partei_nr = as.integer(str_extract(partei_nr,"[0-9]+"))) %>% 
+    # Ortsteil- bzw. Stimmbezirks-Gruppen, um dort nach Stimmen zu sortieren
+    group_by(nr,name) %>% 
+    arrange(desc(partei_stimmen)) %>% 
+    mutate(Platz = row_number()) %>%
+    left_join(parteien_df %>% select(partei_nr = Nummer, 
+                                       partei = Parteikürzel,
+                                       farbe= Farbwert), by="partei_nr") %>% 
+    mutate(prozent = if_else(gueltig != 0,partei_stimmen / gueltig * 100, 0)) 
+  k_ergänzt_df <- tmp_long_df %>% 
+    # Ist noch nach Stadtteil (name, nr) sortiert
+    arrange(partei_nr) %>% 
+    # Alles weg, was verhindert, was individuell auf den Kand ist - außer
+    #  kand und Prozentwert
+    select(-partei_stimmen, -partei_nr, -Platz, -farbe) %>% 
+    # Kandidatennamen in die Spalten zurückverteilen
+    pivot_wider(names_from = partei, values_from = prozent) %>% 
+    ungroup() %>% 
+    # und die zweite Hälfte dazu: 
+    left_join(
+      tst <- tmp_long_df %>% 
+        # Brauchen nur die Kand-Ergebnisse - und den (Stadtteil-)name
+        select(name, Platz, partei,prozent,farbe) %>% 
+        # Nur die ersten (top) Plätze
+        filter(Platz <= (3)) %>% 
+        #The Big Pivot: Breite die ersten (3) aus. 
+        pivot_wider(names_from = Platz,
+                    values_from = c(partei,prozent,farbe),
+                    names_glue = "{.value}{Platz}") %>% 
+        ungroup() %>% 
+        select(-nr),   
+      by="name") %>% 
+    # Jetzt auswählen und umbenennen
+    select(nr, # ortsteilnr
+           k_wahlberechtigt = wahlberechtigt,
+           k_stimmen = stimmen, 
+           k_stimmen_wahlschein = stimmen_wahlschein,
+           k_gueltig = gueltig, 
+           ungueltig:partei3) %>% 
+    rename(k_ungueltig = ungueltig)
+
+ergänzt3_df <- berechne_ergänzt(stadtteildaten_df,3)  
+
+vergleichstabelle_df <- ergänzt3_df %>% 
+  left_join(k_ergänzt_df,by="nr") %>% 
+  select(-zeitstempel,
+         -ortsteilnr,
+         -meldungen_anz,
+         -meldungen_max,
+         -waehler_regulaer,
+         -waehler_wahlschein,
+         -waehler_nv) 
+  # Gesamt-Ergebnisse ergänzen
+
+write.xlsx(vergleichstabelle_df,"daten/obwahl_da_2023/vergleichstabelle2021.xlsx", overwrite=T)
+
+
+# Briefwahlergebnis
+urnenwahl_df <- stimmbezirksdaten_df %>% 
+  # Achtung: Prüfen, ob die Benennung der Briefwahllokale hierzu passt.
+  filter(nr < 9999) %>% 
+  summarize(gueltig = sum(gueltig),
+            across(starts_with("D"),~ sum(.))) %>% 
+  pivot_longer(cols = starts_with("D"), names_to = "kand_nr", 
+               values_to = "kand_stimmen") %>% 
+  mutate(kand_nr = as.integer(str_extract(kand_nr,"[0-9]+"))) %>% 
+  left_join(kandidaten_df %>% select(kand_nr=Nummer,
+                                     Parteikürzel,
+                                     kand_name=Name),by="kand_nr") %>% 
+  mutate(`Kandidat/in` = paste0(kand_name," (",Parteikürzel,")")) %>% 
+  mutate(Prozent = kand_stimmen / gueltig *100) %>% 
+  select(`Kandidat/in`, Urnenwahl = Prozent)
+
+briefwahl_df <- stimmbezirksdaten_df %>% 
+  filter(nr > 9999) %>% 
+  summarize(gueltig = sum(gueltig),
+                  across(starts_with("D"),~ sum(.))) %>% 
+  pivot_longer(cols = starts_with("D"), names_to = "kand_nr", 
+               values_to = "kand_stimmen") %>% 
+  mutate(kand_nr = as.integer(str_extract(kand_nr,"[0-9]+"))) %>% 
+  left_join(kandidaten_df %>% select(kand_nr=Nummer,
+                                     Parteikürzel,
+                                     kand_name=Name),by="kand_nr") %>% 
+  mutate(`Kandidat/in` = paste0(kand_name," (",Parteikürzel,")")) %>% 
+  mutate(Prozent = kand_stimmen / gueltig *100) %>% 
+  select(`Kandidat/in`, Briefwahl = Prozent) %>% 
+  left_join(urnenwahl_df,by = "Kandidat/in")
+
+
+
+write.xlsx(briefwahl_df,"daten/briefwahl_ergebnis.xlsx", overwrite = T)  
+
+            
\ No newline at end of file
diff --git a/R/aktualisiere_karten.R b/R/aktualisiere_karten.R
index 9fa849149aebf0cc6df4c3d9445f381678a48ed7..c3e641f753ff9292348ec23ee08af70b5b6f6f5c 100644
--- a/R/aktualisiere_karten.R
+++ b/R/aktualisiere_karten.R
@@ -277,6 +277,11 @@ aktualisiere_top <- function(kand_tabelle_df,top=5) {
     head(top)
   # Daten pushen
   dw_data_to_chart(daten_df,chart_id = top_id)
+  # Daten aufs Google Bucket (für CORS-Aktualisierung)
+  if (SERVER) {
+    write.csv(daten_df,"daten/top.csv")
+    system('gsutil -h "Cache-Control:no-cache, max_age=0" cp daten/top.csv gs://d.data.gcp.cloud.hr.de/obwahl_top.csv')
+  }
   # Intro_Text nicht anpassen. 
   # Balken reinrendern
   balken_text <- generiere_auszählungsbalken(gezaehlt,stimmbezirke_n,ts)
@@ -297,6 +302,10 @@ aktualisiere_tabelle_alle <- function(kand_tabelle_df) {
   # Daten und Metadaten hochladen, für die Balkengrafik mit allen 
   # Stimmen für alle Kandidaten
   dw_data_to_chart(kand_tabelle_df, chart_id = tabelle_alle_id)
+  if (SERVER) {
+    write.csv(kand_tabelle_df,"daten/kand_tabelle.csv")
+    system('gsutil -h "Cache-Control:no-cache, max_age=0" cp daten/kand_tabelle.csv gs://d.data.gcp.cloud.hr.de/obwahl_kand_tabelle.csv')
+  }
   balken_text <- generiere_auszählung_nurtext(gezaehlt,stimmbezirke_n,ts)
   # Metadaten anpassen: Farbcodes für Parteien
   metadata_chart <- dw_retrieve_chart_metadata(tabelle_alle_id)
@@ -320,7 +329,12 @@ aktualisiere_karten <- function(ergänzt_df) {
   ergänzt_f_df <- ergänzt_df %>% filter(meldungen_anz > 0)
   balken_text = generiere_auszählungsbalken(gezaehlt,stimmbezirke_n,ts)
   dw_edit_chart(chart_id = karte_sieger_id, annotate = balken_text)
+  # Daten pushen
   dw_data_to_chart(ergänzt_f_df,chart_id = karte_sieger_id)
+  if (SERVER) {
+    write.csv(ergänzt_f_df,"daten/ergaenzt.csv")
+    system('gsutil -h "Cache-Control:no-cache, max_age=0" cp daten/ergaenzt.csv gs://d.data.gcp.cloud.hr.de/obwahl_ergaenzt.csv')
+  }
   dw <- dw_publish_chart(karte_sieger_id)
   # Jetzt die Choropleth-Karten für alle Kandidierenden
   for (i in 1:nrow(switcher_df)) {
@@ -333,7 +347,12 @@ aktualisiere_karten <- function(ergänzt_df) {
 
 aktualisiere_hochburgen <- function(hochburgen_df) {
   # Das ist ziemlich geradeheraus. 
+  # Pushe Daten.
   dw_data_to_chart(hochburgen_df, chart_id = hochburgen_id)
+  if (SERVER) {
+    write.csv(hochburgen_df,"daten/hochburgen.csv")
+    system('gsutil -h "Cache-Control:no-cache, max_age=0" cp daten/hochburgen.csv gs://d.data.gcp.cloud.hr.de/obwahl_hochburgen.csv')
+  }
   balken_text <- generiere_auszählung_nurtext(gezaehlt,stimmbezirke_n,ts)
   # Metadaten anpassen: Farbcodes für Parteien
   metadata_chart <- dw_retrieve_chart_metadata(hochburgen_id)
@@ -430,7 +449,12 @@ aktualisiere_ergebnistabelle <- function(stadtteildaten_df) {
     ungroup() %>% 
     arrange(sort) %>% 
     select(-name,-sort)
+  # Daten pushen
   dw_data_to_chart(ergebnistabelle_df %>% select(-nr), chart_id = tabelle_stadtteile_id)
+  if (SERVER) {
+    write.csv(ergebnistabelle_df,"daten/stadtteile.csv")
+    system('gsutil -h "Cache-Control:no-cache, max_age=0" cp daten/stadtteile.csv gs://d.data.gcp.cloud.hr.de/obwahl_stadtteile.csv')
+  }
     # Trendergebnis? Schreibe "Trend" oder "Endergebnis" in den Titel
   gezählt <- e_tmp_df %>% pull(meldungen_anz) %>% sum(.)
   stimmbezirke_n <- e_tmp_df %>% pull(meldungen_max) %>% sum(.)
diff --git a/R/lies_aktuellen_stand.R b/R/lies_aktuellen_stand.R
index 2ffc5fcd4cadd952b90337bfabecd094bd80fb06..685c7c669ddc578d6ef72b689568a92cbd43bdd1 100644
--- a/R/lies_aktuellen_stand.R
+++ b/R/lies_aktuellen_stand.R
@@ -85,7 +85,7 @@ check_for_timestamp <- function(my_url) {
     # } else {
     #   t <- tmp[stringr::str_detect(tmp,"last-modified")] %>% 
     #     stringr::str_replace("last-modified: ","") %>% 
-    #     parse_date_time("%a, %d %m %Y %H:%M:%S",tz = "CET") + hours(1)
+    #     parse_date_time("%a, %d %m %Y %H:%M:%S",tz = "CET") 
     # }
   } else { # lokale Datei
     t = file.info(my_url)$mtime %>%  as_datetime
@@ -102,14 +102,15 @@ lies_stimmbezirke <- function(stand_url = stimmbezirke_url) {
   #' Schreibt eine Meldung ins Logfile - zugleich ein Lesezeichen
   cat(as.character(now())," - Neue Daten lesen\n") # Touch logfile
   check = tryCatch(
-    { stand_df <- read_delim(stand_url, 
+    { 
+      stand_df <- read_delim(stand_url, 
                          delim = ";", escape_double = FALSE, 
                          locale = locale(date_names = "de", 
                                          decimal_mark = ",", 
                                          grouping_mark = "."), 
                          trim_ws = TRUE) %>% 
       # Spalten umbenennen, Zeitstempel-Spalte einfügen
-                    mutate(zeitstempel=ts) %>%
+                    mutate(zeitstempel=ts)  %>%
       # Sonderregel: wir haben einen Zeitstempel, die "datum"-Spalte macht
       # Probleme, weil: starts_with("D"). 
                     select(-datum) %>% 
@@ -129,7 +130,9 @@ lies_stimmbezirke <- function(stand_url = stimmbezirke_url) {
                            ungueltig = C,
                            gueltig = D,
                            # neu: alle Zeilen mit Stimmen (D1..Dn)
-                           starts_with("D"))
+                           starts_with("D")) %>% 
+        # Zusatz für Frankfurt, das die Stimmbezirksnummern als character überträgt
+        mutate(nr = as.integer(nr))
       
       },
     warning = function(w) {teams_warning(w,title="OB-Wahl: Datenakquise")},
@@ -166,8 +169,8 @@ aggregiere_stadtteildaten <- function(stimmbezirksdaten_df = stimmbezirksdaten_d
     relocate(zeitstempel,nr,name,lon,lat)
     
   # Sicherheitscheck: Warnen, wenn nicht alle Ortsteile zugeordnet
-  if (nrow(stadtteildaten_df) != nrow(stadtteile_df)) teams_warnung("Nicht alle Stadtteile zugeordnet")
-  if (nrow(stimmbezirke_df) != length(unique(stimmbezirke_df$nr))) teams_warnung("Nicht alle Stimmbezirke zugeordnet")
+  if (nrow(stadtteildaten_df) != nrow(stadtteile_df)) teams_warning("Nicht alle Stadtteile zugeordnet")
+  if (nrow(stimmbezirke_df) != length(unique(stimmbezirke_df$nr))) teams_warning("Nicht alle Stimmbezirke zugeordnet")
   cat("Stadtteildaten aggregiert.\n")
   return(stadtteildaten_df)
 }
@@ -237,7 +240,9 @@ berechne_kand_tabelle <- function(stimmbezirksdaten_df = stimmbezirksdaten_df) {
     left_join(kandidaten_df %>%  select(Nummer, Name, Parteikürzel, Farbwert), 
               by="Nummer") %>% 
     mutate(name = paste0(Name," (",Parteikürzel,")")) %>% 
-    mutate(Prozent = Stimmen / gueltig * 100) %>% 
+    mutate(Prozent = ifelse(gueltig > 0, 
+                            Stimmen / gueltig * 100,
+                            0)) %>% 
     select(Nummer, `Kandidat/in` = name, Parteikürzel, Stimmen, Prozent)
   cat("Gesamttabelle alle Kandidaten berechnet.\n")
   return(kand_tabelle_df)
@@ -355,8 +360,10 @@ hole_wahldaten <- function() {
     }
     # Stadtteil neu ausgezählt?
   }
-  meldung_s <- paste0(meldung_s,"<br><br>",
+  if (!exists("NO_SOCIAL")) {
+    meldung_s <- paste0(meldung_s,"<br><br>",
                       generiere_socialmedia())
+  }
   teams_meldung(meldung_s,title=wahl_name)
   
   check = tryCatch(
diff --git a/R/lies_konfiguration.R b/R/lies_konfiguration.R
index b36523afcf71e436a4663c4bce56ab973a57ac7a..4c4d629148e81b2f3cb2fc2bd64c447a48a5b779 100644
--- a/R/lies_konfiguration.R
+++ b/R/lies_konfiguration.R
@@ -25,10 +25,20 @@
 # - wahlberechtigt - Zahl der Wahlberechtigen (kommt Sonntag)
 # - briefwahl - Zahl der Briefwahlstimmen (kommt Sonntag)
 
+# Falls der Parameter wahl_name noch nicht definiert ist, 
+# setze ihn erst mal auf das derzeitige Verzeichnis. 
+if (exists("wahl_name")) {
+  index_pfad = paste0("index/",wahl_name,"/")
+} else {
+  index_pfad = paste0("index/")
+}
+
+# Lies die Indexdatei aus dem Verzeichnis wahl_name. 
+# Falls keines angegeben: aus dem aktuellen Verzeichnis
 if (TEST) {
-  config_df <- read_csv("index/config_test.csv")
+  config_df <- read_csv(paste0(index_pfad,"config_test.csv"))
 } else {
-  config_df <- read_csv("index/config.csv")
+  config_df <- read_csv(paste0(index_pfad,"config.csv"))
 }
 for (i in c(1:nrow(config_df))) {
   # Erzeuge neue Variablen mit den Namen und Werten aus der CSV
diff --git a/R/main.R b/R/main.R
index 695ed73c5ee9bbf9638d5de0d6cad4ec5701f021..a4cb93c50b14b68d58608fa24ea1ee0b6ec8b1f7 100644
--- a/R/main.R
+++ b/R/main.R
@@ -16,7 +16,7 @@ p_load(R.utils)
 rm(list=ls())
 
 TEST = FALSE
-DO_PREPARE_MAPS = TRUE
+DO_PREPARE_MAPS = FALSE
 
 
 
@@ -85,7 +85,7 @@ if (DO_PREPARE_MAPS) {
 while (gezaehlt < stimmbezirke_n) {
   check = tryCatch(
     { # Zeitstempel der Daten holen
-      ts_daten <- check_for_timestamp(stimmbezirke_url)
+      ts_daten <- check_for_timestamp(stimmbezirke_url) + hours(1)
     },
     warning = function(w) {teams_warning(w,title=paste0(wahl_name,": CURL-Polling"))},
     error = function(e) {teams_warning(e,title=paste0(wahl_name,": CURL-Polling"))}
@@ -107,6 +107,7 @@ dw_publish_chart(top_id)
 
 # Logging beenden
 if (!TEST) {
+  cat("OK: FERTIG - alle Stimmbezirke ausgezählt: ",as.character(ts),"\n")
   sink()
   sink(type="message")
   file.rename("obwahl.log","obwahl_success.log")
diff --git a/R/main_oneshot.R b/R/main_oneshot.R
index d4914a895036cd0f446a0b0e3a48dfa802061d8a..b23dd48f9ce2fa37bb0acc0e0107d469fe5496e0 100644
--- a/R/main_oneshot.R
+++ b/R/main_oneshot.R
@@ -11,19 +11,41 @@ p_load(DatawRappr)
 p_load(curl)
 p_load(magick)
 p_load(openxlsx)
+p_load(R.utils)
 
 rm(list=ls())
 
-TEST = TRUE
-DO_PREPARE_MAPS = FALSE
-
-
-
 # Aktuelles Verzeichnis als workdir
 setwd(this.path::this.dir())
 # Aus dem R-Verzeichnis eine Ebene rauf
 setwd("..")
 
+# Lies Kommandozeilen-Parameter: 
+# (Erweiterte Funktion aus dem R.utils-Paket)
+args = R.utils::commandArgs(asValues = TRUE)
+if (length(args)!=0) { 
+  if (any(c("h","help","HELP") %in% names(args))) {
+    cat("Parameter: \n",
+        "--TEST schaltet Testbetrieb ein\n",
+        "--DO_PREPARE_MAPS schaltet Generierung der Switcher ein\n",
+        "wahl_name=<name> holt Index-Dateien aus dem Verzeichnis ./index/<name>\n\n")
+  }
+  TEST <- "TEST" %in% names(args)
+  DO_PREPARE_MAPS <- "DO_PREPARE_MAPS" %in% names(args)
+  if ("wahl_name" %in% names(args)) {
+    wahl_name <- args[["wahl_name"]]
+    if (!dir.exists(paste0("index/",wahl_name))) stop("Kein Index-Verzeichnis für ",wahl_name)
+  }
+} 
+
+# Defaults
+if (!exists("wahl_name")) wahl_name = "obwahl_ffm_stichwahl_2023"
+if (!exists("TEST")) TEST = FALSE
+if (!exists("DO_PREPARE_MAPS")) DO_PREPARE_MAPS = FALSE
+NO_SOCIAL = TRUE
+
+
+
 # Logfile anlegen, wenn kein Test
 # if (!TEST) {
 #   logfile = file("obwahl.log")
@@ -92,4 +114,4 @@ ts <- ts_daten
 
 hole_wahldaten()
 
-# EOF
\ No newline at end of file
+# EOF
diff --git a/R/messaging.R b/R/messaging.R
index 77807cabdd5cdf51c44314c1398d25a8c7c75a46..80d8784d33cc05369036d0306a07b4f1c2978094 100644
--- a/R/messaging.R
+++ b/R/messaging.R
@@ -22,6 +22,7 @@ if (Sys.getenv("WEBHOOK_OBWAHL") == "") {
 
 teams_meldung <- function(...,title="OB-Wahl-Update") {
   cc <- teamr::connector_card$new(hookurl = t_txt)
+  if (TEST) {title <- paste0("TEST: ",title) }
   cc$title(paste0(title," - ",lubridate::with_tz(lubridate::now(),
                                                  "Europe/Berlin")))
   alert_str <- paste0(...)
@@ -32,13 +33,13 @@ teams_meldung <- function(...,title="OB-Wahl-Update") {
 
 teams_error <- function(...) {
   alert_str <- paste0(...)
-  teams_meldung(title="OB-Wahl: FEHLER: ", ...)
+  teams_meldung("***FEHLER: ",...)
   stop(alert_str)
 } 
 
 teams_warning <- function(...) {
   alert_str <- paste0(...)
-  teams_meldung("OB-Wahl: WARNUNG: ",...)
+  teams_meldung("***WARNUNG: ",...)
   warning(alert_str)
 } 
 
diff --git a/README.md b/README.md
index 32998ac136dcdba01a3fc7798046c8ff89016c03..aae3d03fe3f0b71f2236271c26d4454e8039cffd 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,18 @@ Grafiken:
 * Tabelle nach Kandidaten (3 beste, 3 schlechteste Stadtteile)
 * Tabelle alle Ergebnisse nach Stadtteil 
 
+### Aktualisierung via CORS/GBucket
+
+Der normale Weg, eine Datawrapper-Grafik anzuzeigen, ist: pushe die Daten auf den Datawrapper-Server - mit dw_data_to_chart() - und aktualisiere. 
+
+Alternativ kann die Grafik aus live bereitgestellten Daten in einem Google Bucket bestückt werden. Die Adressen der Dateien, die an die Grafiken übergeben werden müssen, sind: 
+
+- https://d.data.gcp.cloud.hr.de/obwahl_top.csv
+- https://d.data.gcp.cloud.hr.de/obwahl_kand_tabelle.csv
+- https://d.data.gcp.cloud.hr.de/obwahl_ergaenzt.csv
+- https://d.data.gcp.cloud.hr.de/obwahl_hochburgen.csv
+- https://d.data.gcp.cloud.hr.de/obwahl_stadtteile.csv
+
 ### Konfiguration
 
 Das Programm holt sich seine Daten aus einer Konfigurationsdatei - entweder für den Live- oder den Testbetrieb, was über die Variable TEST im Progammcode umgestellt wird. Die Indizes für die jeweile Wahl - Kandidatinnen und Kandidaten, Stadtteile und Wahllokal-Zuordnungen - liegen in einem Unterordner mit dem Namen der Wahl, als CSV oder XLSX. 
@@ -86,7 +98,7 @@ Spalte | Wert
 ---- | ----
 nr | ID des Stimmbezirks
 ortsteilnr | ID des Stadtteils
-ortsteil Name des Stadtteils
+ortsteil | Name des Stadtteils
 
 Nicht benötigte Spalten können in der Tabelle bleiben, sollten aber möglichst nicht "name" oder so heißen. 
 
@@ -107,11 +119,28 @@ Aggregation auf Stadtebene
 
 (siehe ["Sitemap"](./sitemap.md) für den Code)
 
+## Vorbereitung einer Wahl
+
+- Shapefile für die Stadt besorgen; Stimmbezirksebene; Stadtteile
+- Stadtteile aggregieren, GEOJSON generieren
+- Ordner für die Wahl im Index-Ordner; Datei Kandidaten, Stadtteile (mit Geokoordinaten für die Zentrumspunkte), Stimmbezirke (mit Zuordnung Stadtteil)
+- Kopien für die vier Grafiken anlegen: Top, alle Stimmen, Hochburgen, Stadtteile. Link zum Wahlamt nicht vergessen.
+- Leerdatei Ergebnisse nach Stadtteil vorbereiten
+- Kopie der Sieger-Karte mit GEOJSON anlegen; GEOJSON hochladen, Leerdatei hochladen. Link zum Wahlamt korrigieren. 
+- Eine erste Kopie der Choropleth-Karten nach Kandidat: Wahlamt-Link ändern und Karte und Leerdatei hochladen, dann kopieren. 
+- Kopien für alle Kandidierenden anlegen. Jeweils die Werte-Spalte des jeweiligen Kandidaten auswählen; benennen, um sie zuordnen zu können. (Farben und Namen werden automatisch nachgetragen.)
+- Indexdatei vorbereiten: Wahlname, Anzahl TOP, Dateinahmen der Index-Dateien, Datawrapper-IDs für die Karten und Diagramme
+
+## TODO
+
+- Analyse: Weshalb hängt das Polling manchmal hinterher?
+- Aufruf mit Parametern ermöglichen ("main.R obwahl_ffm_2023")
+- Oneshot-Variante für Kassel
+
+- Auswertung Briefwahldaten
 
 ## Nice-To-Have 
 
-- Vergleich letzte Kommunalwahl
 - Zusatzfeature: Briefwahlprognostik - wieviele Stimmen fehlen vermutlich noch?
 - Shapefiles KS, DA verbessern
-- Datensparsamere Alternativ-CURL-Poll-Datei (zB mit dem Gesamtergebnis)
-- Mehr Licht in den Choropleth-Karten farbabhängig
+- Vergleich letzte Kommunalwahl regulär
\ No newline at end of file
diff --git a/howto_shapefiles.md b/howto_shapefiles.md
index c75fe0a9c11e08ba9a8f97e1262d823741b41b40..981477235c0847ce22de40831048abf1f51ea235 100644
--- a/howto_shapefiles.md
+++ b/howto_shapefiles.md
@@ -8,6 +8,8 @@ Dazu Rechtsklick auf den Layer; Koordinatensystem WGS84, exportieren
 3. Stadtteile generieren
 Menü "Vektor", "Geometrieverarbeitungswerkzeuge", "Auflösen" - und dann in der Dialogbox auswählen "Felder auflösen [optional]", und dann die Attribute hinzufügen, nach denen zusammengeführt werden soll. 
 
+Nach den Attributen schauen - die Ortsteilnr. ist ein String, kein Integer! Rechtsklick auf den neuen Layer; Eigenschaften..., dann den "Felder..."-Editor, da oben auf das kleine Abakus-Symbol klicken, Namen für das neue Feld in Ausgabefeldname (z.B. "nr"), dann in Feld Ausdruck eintragen"to_int(Ortsbezirk)" - und OK klicken. Neues Feld wird angelegt. Dann das alte Feld löschen (auswählen, oben Klick auf Löschen-Feld).
+
 - Rechtsklick auf den Layer; Exportieren als GEOJSON - nicht vergessen, das Bezugssystem auf WGS84 umzustellen!
 - Rechtsklick auf den Layer; Export als XLSX - ggf. Geo-Attribute abschalten
 
@@ -22,4 +24,12 @@ Dann noch Geokoordinaten der Zentroidpunkte: Rechte Seite die Toolbox, dort "Vek
 5. CSV-/XLSX-Dateien putzen
 
 - Brauchen eine Stadtteil-Datei mit nr,name,lon,lat (erzeugt aus den Zentroiden)
-- Brauchen einen Wahlbezirks-Zuordnung
\ No newline at end of file
+- Brauchen einen Wahlbezirks-Zuordnung
+
+
+6. Reparatur der Darmstadt-Karte
+
+- Laden (falsche Geometrie - das erst zum Schluss fixen!)
+- Vereinfachen: Fläche
+- Auflösen
+- Löcher löschen
\ No newline at end of file
diff --git a/index/obwahl_ffm_2023/ffm_config.csv b/index/obwahl_ffm_2023/ffm_config.csv
new file mode 100644
index 0000000000000000000000000000000000000000..2f97d66ece55a4ae2800db52ad5c9972853d3f90
--- /dev/null
+++ b/index/obwahl_ffm_2023/ffm_config.csv
@@ -0,0 +1,37 @@
+name,value,comment
+wahl_name,obwahl_ffm_2023,Welche Wahl?
+stimmbezirke_url,https://votemanager-ffm.ekom21cdn.de/2023-03-05/06412000/daten/opendata/Open-Data-06412000-OB-Wahl-Wahlbezirk.csv?ts=1677904123448,URL Daten-CSV Stimmbezirke
+wahlberechtigt,508182,Anzahl Wahlberechtigte lt. Wahlamt (kommt Sonntag)
+briefwahl,250000,Anzahl Briefwahlstimmen lt. Wahlamt (kommt Sonntag)
+kandidaten_fname,kandidaten.xlsx,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+datawrapper_fname,datawrapper.xlsx,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+zuordnung_fname,zuordnung_wahllokale.csv,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+stadtteile_fname,stadtteile.csv,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+startdatum,2023-03-05 16:00:00,Beginn der Auszählung
+top5_id,2DYBQ,
+karte_sieger_id,ANKmx,
+karte_kand1_id,RcvQp,Rottmann (Grüne)
+karte_kand2_id,jrm2v,Becker (CDU)
+karte_kand3_id,bKR8r,Josef (SPD)
+karte_kand4_id,etN3J,Mehler-Würzbach (Linke)
+karte_kand5_id,3mydT,Pürsün (FDP)
+karte_kand6_id,K3aCw,Lobenstein (AfD)
+karte_kand7_id,vtG4Y,Pfeiffer (BFF)
+karte_kand8_id,tRHeI,Tanczos (PARTEI)
+karte_kand9_id,v4Y5m,Schwichtenberg (Gartenpartei)
+karte_kand10_id,g3iBN,Wirth (unabh.)
+karte_kand11_id,4LxcN,Camara (FPF)
+karte_kand12_id,RZDF7,Pauli (unabh.)
+karte_kand13_id,F86gf,Junghans (unabh.)
+karte_kand14_id,bLPXL,Xu (unabh.)
+karte_kand15_id,Ktufa,Wolff (unabh.)
+karte_kand16_id,MO41j,Akhtar (Todenhöfer)
+karte_kand17_id,ccrfL,Großenbach (Basis)
+karte_kand18_id,q2S6m,Pawelski (unabh.)
+karte_kand19_id,697CL,Schulte (unabh.)
+karte_kand20_id,3lMmu,Eulig (unabh.)
+tabelle_alle_id,7kRPR,
+hochburgen_id,oB3KH,
+tabelle_stadtteile_id,LiXnz,
+social1_id,2DYBQ,5 stärkste
+social2_id,S9BbQ,Alle Stimmen angepasst
diff --git a/index/obwahl_ffm_2023/ffm_config_test.csv b/index/obwahl_ffm_2023/ffm_config_test.csv
new file mode 100644
index 0000000000000000000000000000000000000000..07d5d1ab78591ee0f2e7376fb6d28c043eb752fa
--- /dev/null
+++ b/index/obwahl_ffm_2023/ffm_config_test.csv
@@ -0,0 +1,37 @@
+name,value,comment
+wahl_name,obwahl_ffm_2023,Welche Wahl?
+stimmbezirke_url,testdaten/dummy.csv,URL Daten-CSV Stimmbezirke
+wahlberechtigt,508182,Anzahl Wahlberechtigte lt. Wahlamt (kommt Sonntag)
+briefwahl,250000,Anzahl Briefwahlstimmen lt. Wahlamt (kommt Sonntag)
+kandidaten_fname,kandidaten.xlsx,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+datawrapper_fname,datawrapper.xlsx,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+zuordnung_fname,zuordnung_wahllokale.csv,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+stadtteile_fname,stadtteile.csv,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+startdatum,2023-01-01 18:00:00 CET,Beginn der Auszählung
+top5_id,028Fp,
+karte_sieger_id,7gscI,
+karte_kand1_id,hM9SE,Rottmann (Grüne)
+karte_kand2_id,hM9SE,Becker (CDU)
+karte_kand3_id,07CR4,Josef (SPD)
+karte_kand4_id,07CR4,Mehler-Würzbach (Linke)
+karte_kand5_id,07CR4,Pürsün (FDP)
+karte_kand6_id,07CR4,Lobenstein (AfD)
+karte_kand7_id,07CR4,Pfeiffer (BFF)
+karte_kand8_id,07CR4,Tanczos (PARTEI)
+karte_kand9_id,07CR4,Schwichtenberg (Gartenpartei)
+karte_kand10_id,07CR4,Wirth (unabh.)
+karte_kand11_id,07CR4,Camara (FPF)
+karte_kand12_id,07CR4,Pauli (unabh.)
+karte_kand13_id,07CR4,Junghans (unabh.)
+karte_kand14_id,07CR4,Xu (unabh.)
+karte_kand15_id,07CR4,Wolff (unabh.)
+karte_kand16_id,07CR4,Akhtar (Todenhöfer)
+karte_kand17_id,07CR4,Großenbach (Basis)
+karte_kand18_id,07CR4,Pawelski (unabh.)
+karte_kand19_id,07CR4,Schulte (unabh.)
+karte_kand20_id,07CR4,Eulig (unabh.)
+tabelle_alle_id,PLwHI,
+hochburgen_id,Im2PX,
+tabelle_stadtteile_id,BM8kD,
+social1_id,028Fp,5 stärkste
+social2_id,S9BbQ,Alle Stimmen angepasst
diff --git a/index/obwahl_ks_2023/config.csv b/index/obwahl_ks_2023/config.csv
new file mode 100644
index 0000000000000000000000000000000000000000..cf31ec139f7f2a771c626b571e5387861466f98e
--- /dev/null
+++ b/index/obwahl_ks_2023/config.csv
@@ -0,0 +1,23 @@
+name,value,comment
+wahl_name,obwahl_ks_2023,Welche Wahl?
+stimmbezirke_url,https://votemanager-ks.ekom21cdn.de/2023-03-12/06611000/daten/opendata/Open-Data-06611000-Direktwahl-zur-Oberbuergermeisterin-zum-Oberbuergermeister-Wahlbezirk.csv?ts=1678486050153,URL Daten-CSV Stimmbezirke
+wahlberechtigt,147463,Anzahl Wahlberechtigte lt. Wahlamt (kommt Sonntag)
+briefwahl,39092,Anzahl Briefwahlstimmen lt. Wahlamt (kommt Sonntag)
+top,6,Anzahl der Top-Kandidaten in den Darstellungen
+kandidaten_fname,kandidaten.xlsx,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+zuordnung_fname,wahlbezirke.xlsx,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+stadtteile_fname,ks-stadtteile.csv,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+startdatum,2023-03-12 16:00:00,Beginn der Auszählung
+top_id,Ts1oS,
+karte_sieger_id,O9wPT,
+karte_kand1_id,hM9SE,Schöller
+karte_kand2_id,07CR4,Carqueville
+karte_kand3_id,whgzp,Kühne-Hörmann
+karte_kand4_id,5CpYu,Bock
+karte_kand5_id,pc6vH,Käufler
+karte_kand6_id,sEJhl,Geselle
+tabelle_alle_id,EQ4dd,
+hochburgen_id,GMTSJ,
+tabelle_stadtteile_id,gJNPD,
+social1_id,Ts1oS,5 stärkste
+social2_id,S9BbQ,Alle Stimmen angepasst
diff --git a/index/obwahl_ks_2023/config_test.csv b/index/obwahl_ks_2023/config_test.csv
new file mode 100644
index 0000000000000000000000000000000000000000..8c2e436731078e59a1d0619b113f72e81e26311d
--- /dev/null
+++ b/index/obwahl_ks_2023/config_test.csv
@@ -0,0 +1,23 @@
+name,value,comment
+wahl_name,obwahl_ks_2023,Welche Wahl?
+stimmbezirke_url,https://www.eggers-elektronik.de/files/test.csv,URL Daten-CSV Stimmbezirke
+wahlberechtigt,147463,Anzahl Wahlberechtigte lt. Wahlamt (kommt Sonntag)
+briefwahl,39092,Anzahl Briefwahlstimmen lt. Wahlamt (kommt Sonntag)
+kandidaten_fname,kandidaten.xlsx,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+zuordnung_fname,wahlbezirke.xlsx,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+stadtteile_fname,ks-stadtteile.csv,"XLSX oder CSV, wird im Ordner <wahl_name> erwartet"
+startdatum,2023-01-01 18:00:00 CET,Beginn der Auszählung
+top,6,
+top_id,028Fp,
+karte_sieger_id,7gscI,
+karte_kand1_id,hM9SE,Schöller
+karte_kand2_id,07CR4,Carqueville
+karte_kand3_id,whgzp,Kühne-Hörmann
+karte_kand4_id,5CpYu,Bock
+karte_kand5_id,pc6vH,Käufler
+karte_kand6_id,sEJhl,Geselle
+tabelle_alle_id,PLwHI,
+hochburgen_id,Im2PX,
+tabelle_stadtteile_id,BM8kD,
+social1_id,028Fp,5 stärkste
+social2_id,S9BbQ,Alle Stimmen angepasst
diff --git a/index/obwahl_ks_2023/kandidaten.xlsx b/index/obwahl_ks_2023/kandidaten.xlsx
index e10b674797487ee43e2033972e604fe8f5e8c83b..f1c1eac6703306255f182e4ea891e49f72ddc201 100644
Binary files a/index/obwahl_ks_2023/kandidaten.xlsx and b/index/obwahl_ks_2023/kandidaten.xlsx differ
diff --git a/index/obwahl_ks_2023/parteien-kommunalwahl.xlsx b/index/obwahl_ks_2023/parteien-kommunalwahl.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..5b382a6653d2514848c5bbbe8651c2b62bdb5d04
Binary files /dev/null and b/index/obwahl_ks_2023/parteien-kommunalwahl.xlsx differ