20 November 2023

Wie heisst es so schön: «Wer den Schaden hat, braucht für den Spott nicht zu sorgen». Man weiss zwar (noch) nicht genau, wie das Wahl-Statistik-Debakel passiert ist, aber ein wenig frotzeln lässt sich schon. Die meisten Informationen stehen meines Erachtens in einem NZZ-Artikel. Lassen wir mal die Microservices («Für jedes mögliche (kantonale) Datenformat existiert ein solches Programm.»: Ojeeeee…​) beiseite und widmen uns den «Datenlieferungen» in Form einer Exceldatei. Ich weiss wirklich nicht, wie man auf eine solche Idee kommt. Aber Rohdaten (wenn man den Begriff verwenden darf) in einer Exceldatei als Transferformat? Ballsy! Ein weiterer Kritikpunkt ist die Datenstruktur: Nicht alles auf dieser Welt lässt sich als einfache Tabelle abbilden. Manchmal wäre ein normalisiertes Datenmodell definitiv die bessere Wahl. Dann zählt man die Gemeinden auch nicht dreifach.

Also: man nehme den Screenshot aus dem NZZ-Artikel und generiere daraus ein INTERLIS-Modell:

Datenmodell

Das Modell hat keinen Anspruch auf Vollständigkeit und Genialität. Wahrscheinlich fehlen noch ein paar Attribute, insbesondere müsste es sowohl Majorz- und Proporzwahlen abbilden können. Zuerst wollte ich mich an den eCH-Standard anlehnen. Den verstehe ich aber nicht so wirklich. Ich fühle mich damit genauso unwohl wie mit den eCH-Objektwesen-Standards. Ich finde sie massiv schlechter lesbar als INTERLIS-Modelle.

Mein INTERLIS-Modell ist sehr einfach gehalten. Trotzdem ist es natürlich nicht ganz so einfach wie die flache Exceldatei. Es gibt zwei Klassen: Gemeinde und Kandidaten. Die Gemeinde-Klasse beinhaltet Informationen zu einer Gemeinde, die Kandidaten-Klasse Informationen zu einem Kandidaten. Kandidat und Gemeinde stehen in einer Beziehung zueinander. Jeder Kandidat kann in jeder Gemeinde gewählt werden. Aus diesem Grund hat die Beziehung ein Stimmen-Attribut. Es beschreibt die Anzahl Stimmen, die ein Kandidat in einer Gemeinde geholt hat.

Mit ili2gpkg habe ich eine leere GeoPackage-Datei erstellt und die Zahlen aus dem Screenshot mit dbeaver übertragen. Das ging interessanterweise trotz der Beziehungen noch ganz entspannt, da dbeaver bei Fremdschlüsseln die möglichen Beziehungs-Records auflistet. Man kann anschliessend den zu verknüpfenden Record bequem mit einem Mausklick auswählen:

dbeaver

Nicht viel anstrengender als eine Exceldatei auszufüllen.

Was gewinnt das BFS damit? Es kann sowohl seine Microservices wie auch seine Excel-Importskripts wegwerfen. Wenn es die Geowelt hinkriegt, sollte auch das BFS eine standardisierte Lieferung hinkriegen. Trotz oder genau wegen des Föderalismus.

Interessant sind nun die Prüfmöglichkeiten, die dank INTERLIS gratis mitkommen:

Die Einfachsten sind die UNIQUE-Constraints. So darf die Gemeindenummer und die Kandidatenummer nur einmal pro Lieferung vorkommen (Kandidatenummer müsste wohl in Kombination mit Kanton geprüft werden). Die UNIQUE-Constraints helfen auch dabei, dass man in der Hitze des Gefechts eine Gemeinde nicht x-fach importiert.

Spannender sind die Constraints, die prüfen, ob das mitgelieferte Total gleich der Summe einzelner Werte ist. So bei den Stimmberechtigten, die als Total wie auch aufgeteilt in Frauen- und Männerstimmen, geliefert werden. Und bei bei den Wahlzetteln: Hier wird das Total geliefert, wie auch ungültige, leere etc. Die Constraints sind straight forward:

!!@ name=korrekteAnzahlStimmen
!!@ ilivalid.msg="Eingelegte Wahlzettel ungleich Summe leere, ungültige und in Betracht gezogene Wahlzettel."
MANDATORY CONSTRAINT Math.add(Math.add(leere_Wahlzettel, ungueltige_Wahlzettel), in_Betracht_Wahlzettel) == eingelegte_Wahlzettel;

!!@ name=korrekteSummeMaennerFrauen
!!@ ilivalid.msg="Anzahl Stimmberechtigte ungleich Summe Maenner und Frauen."
MANDATORY CONSTRAINT Math.add(Maenner, Frauen) == wahlberechtigt;

Das Rechnen mit Math.add etc. ist ziemlich noisy und es wird schnell unübersichtlich. Mit INTERLIS 2.4 geht dann auch + usw.

Das sind alles Constraints, die für jede kantonale Lieferung in identischer Form gelten würden. Was macht man, wenn man spezifisch pro Kanton etwas prüfen will. Eine einfache Variante sind Validierungsmodelle. Besagter Kanton schickt einen Nationalrat nach Bern. D.h. das Attribut gewaehlt in der Klasse Kandidat darf nur ein einziges Mal den Wert true aufweisen. Der für die Prüfung notwendige Constraint sieht so aus:

!!@ name = korrekteAnzahlGewaehlter
!!@ ilivalid.msg = "Anzahl gewählter Personen ist nicht korrekt."
SET CONSTRAINT WHERE gewaehlt : INTERLIS.objectCount(ALL) == 1;

Eine andere, zukünftige Variante ist die Verwendung von Runtime-Parameter. Man könnte den Constraint im Hauptmodell belassen und beim Prüfaufruf den für den Kanton korrekten Wert als Paramter mitliefern:

java -jar ilivalidator.jar \
--runtimeParams "CH_BFS_Wahlresultate_20231109.anzahlGewaehlteKandidaten=1"

Der Constraint hat Zugriff auf den Runtime-Parameter:

!!@ name = korrekteAnzahlGewaehlter
!!@ ilivalid.msg = "Anzahl gewählter Personen ist nicht korrekt."
SET CONSTRAINT WHERE gewaehlt : INTERLIS.objectCount(ALL) == PARAMETER CH_BFS_Wahlresultate_20231109.anzahlGewaehlteKandidaten;

Wer hätte das gedacht! INTERLIS hilft sogar bei der Publikation korrekter Wahlresultate. Spass beiseite. Ich bin auf den Bericht gespannt, der die ganze Sache untersuchen soll. Jedenfalls dünkt mich viel Optimierungspotenzial vorhanden. Die Vorteile mit INTERLIS liegen auf der Hand. Ein nicht ganz offensichtlicher Vorteil ist die gute Testbarkeit des gesamten Systems. Alles kann mit Testdaten bequem getestet werden und die Businesslogik ist nicht irgendwie verpackt in Skripts und Microservices, sondern in einem bestehenden Kommandozeilen-Werkzeug. Brauche ich wirklich Hipster-Microservices, kann ich die INTERLIS-Programmbibliotheken verwenden.

Die XTF-Datei zum Rumspielen und Provozieren von Fehlern kann heruntergeladen werden.

INTERLIS: ein Angebot auch für das BFS.

Posted by Stefan Ziegler. | INTERLIS , ilivalidator