gwoe-wahlpruefsteine/wahlpruefsteine/schema.sql
Dotty Dotter f2a12f1238 Initial: GWÖ-Wahlprüfsteine Auswertung Bayern 2026
- Scraper: HTML-Extraktion von ECOnGOOD-Webseite
- Analyzer: LLM-Bewertung (Qwen) nach GWÖ-Matrix 2.0
- Aggregator: Partei-Auswertung + Kandidat:innen-Ranking
- CLI: Reproduzierbarer Workflow (scrape → analyze → aggregate)
- Output: 7 Dokumente inkl. Pressemitteilung und Methodik
- 27 Kandidat:innen, 162 Einzelbewertungen
2026-03-30 23:37:11 +02:00

138 lines
5.5 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- GWÖ-Wahlprüfsteine Auswertung — Datenbankschema
-- Version 1.0, 29.03.2026
-- Kandidat:innen
CREATE TABLE IF NOT EXISTS kandidaten (
id INTEGER PRIMARY KEY,
vorname TEXT NOT NULL,
nachname TEXT NOT NULL,
plz TEXT,
kommune TEXT NOT NULL,
landkreis TEXT,
partei_raw TEXT NOT NULL, -- Original aus Umfrage
partei_normalisiert TEXT NOT NULL, -- Normalisierte Hauptpartei
ist_waehlergemeinschaft BOOLEAN DEFAULT FALSE,
pdf_url TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Die 6 Fragen
CREATE TABLE IF NOT EXISTS fragen (
id INTEGER PRIMARY KEY,
nummer INTEGER UNIQUE NOT NULL, -- 1-6
kurztext TEXT NOT NULL, -- z.B. "Leitlinien"
volltext TEXT NOT NULL -- Vollständige Frage
);
-- Rohantworten
CREATE TABLE IF NOT EXISTS antworten_raw (
id INTEGER PRIMARY KEY,
kandidat_id INTEGER REFERENCES kandidaten(id) ON DELETE CASCADE,
frage_id INTEGER REFERENCES fragen(id) ON DELETE CASCADE,
antwort_kurz TEXT, -- Ja/Nein/leer
antwort_erlaeuterung TEXT, -- Optionale Erläuterung
UNIQUE(kandidat_id, frage_id)
);
-- LLM-Bewertungen (pro Antwort)
CREATE TABLE IF NOT EXISTS bewertungen (
id INTEGER PRIMARY KEY,
antwort_id INTEGER REFERENCES antworten_raw(id) ON DELETE CASCADE UNIQUE,
-- Substanz: Hat tatsächlich geantwortet?
substanz_score INTEGER CHECK(substanz_score >= 0 AND substanz_score <= 3),
-- 0 = keine Antwort/nur Ja-Nein
-- 1 = ausweichend/oberflächlich
-- 2 = substanziell
-- 3 = umfassend mit konkreten Maßnahmen
-- Umfang
umfang TEXT CHECK(umfang IN ('keine', 'kurz', 'mittel', 'ausführlich')),
wortanzahl INTEGER,
-- GWÖ-Bewertung
gwoe_score REAL CHECK(gwoe_score >= 0 AND gwoe_score <= 10),
gwoe_begruendung TEXT,
-- Matrix-Felder (JSON-Array, z.B. ["D5", "C2"])
matrix_felder TEXT,
-- Qualitativ
staerken TEXT,
schwaechen TEXT,
-- Meta
bewertet_am DATETIME DEFAULT CURRENT_TIMESTAMP,
model TEXT DEFAULT 'qwen-plus'
);
-- Partei-Aggregation (View)
CREATE VIEW IF NOT EXISTS v_partei_statistik AS
SELECT
k.partei_normalisiert AS partei,
COUNT(DISTINCT k.id) AS anzahl_kandidaten,
COUNT(b.id) AS anzahl_antworten,
-- Durchschnittswerte
ROUND(AVG(b.substanz_score), 2) AS avg_substanz,
ROUND(AVG(b.gwoe_score), 2) AS avg_gwoe,
ROUND(AVG(b.wortanzahl), 0) AS avg_wortanzahl,
-- Verteilung Umfang
SUM(CASE WHEN b.umfang = 'keine' THEN 1 ELSE 0 END) AS umfang_keine,
SUM(CASE WHEN b.umfang = 'kurz' THEN 1 ELSE 0 END) AS umfang_kurz,
SUM(CASE WHEN b.umfang = 'mittel' THEN 1 ELSE 0 END) AS umfang_mittel,
SUM(CASE WHEN b.umfang = 'ausführlich' THEN 1 ELSE 0 END) AS umfang_ausfuehrlich
FROM kandidaten k
LEFT JOIN antworten_raw ar ON k.id = ar.kandidat_id
LEFT JOIN bewertungen b ON ar.id = b.antwort_id
GROUP BY k.partei_normalisiert;
-- Statistik pro Frage
CREATE VIEW IF NOT EXISTS v_fragen_statistik AS
SELECT
f.nummer,
f.kurztext,
COUNT(b.id) AS anzahl_bewertungen,
ROUND(AVG(b.substanz_score), 2) AS avg_substanz,
ROUND(AVG(b.gwoe_score), 2) AS avg_gwoe,
SUM(CASE WHEN ar.antwort_kurz = 'Ja' THEN 1 ELSE 0 END) AS ja_antworten,
SUM(CASE WHEN ar.antwort_kurz = 'Nein' THEN 1 ELSE 0 END) AS nein_antworten
FROM fragen f
LEFT JOIN antworten_raw ar ON f.id = ar.frage_id
LEFT JOIN bewertungen b ON ar.id = b.antwort_id
GROUP BY f.id;
-- Beste/Schlechteste Antworten
CREATE VIEW IF NOT EXISTS v_top_antworten AS
SELECT
k.vorname || ' ' || k.nachname AS name,
k.kommune,
k.partei_normalisiert AS partei,
f.kurztext AS frage,
b.gwoe_score,
b.substanz_score,
ar.antwort_erlaeuterung AS zitat
FROM bewertungen b
JOIN antworten_raw ar ON b.antwort_id = ar.id
JOIN kandidaten k ON ar.kandidat_id = k.id
JOIN fragen f ON ar.frage_id = f.id
WHERE b.gwoe_score IS NOT NULL
ORDER BY b.gwoe_score DESC;
-- Indizes
CREATE INDEX IF NOT EXISTS idx_kandidaten_partei ON kandidaten(partei_normalisiert);
CREATE INDEX IF NOT EXISTS idx_antworten_kandidat ON antworten_raw(kandidat_id);
CREATE INDEX IF NOT EXISTS idx_antworten_frage ON antworten_raw(frage_id);
CREATE INDEX IF NOT EXISTS idx_bewertungen_gwoe ON bewertungen(gwoe_score);
-- Stammdaten: Fragen
INSERT OR IGNORE INTO fragen (nummer, kurztext, volltext) VALUES
(1, 'Leitlinien', 'Werden Sie sich für Maßnahmen einsetzen, welche die Werte und Themen der GWÖ in Leitlinien und Strategien Ihrer Kommune/Verwaltung und Eigenbetriebe integrieren?'),
(2, 'Anreize', 'Werden Sie sich in Ihrer Kommune für die Schaffung von Anreizen einsetzen, um Unternehmen darin zu unterstützen gemeinwohl-orientierter zu wirtschaften?'),
(3, 'Vergabe', 'Werden Sie sich in Ihrer Kommune dafür einsetzen, dass öffentliche Aufträge bevorzugt an Unternehmen vergeben werden, die eine gültige Gemeinwohl-Bilanz vorlegen?'),
(4, 'Information', 'Möchten Sie dafür sorgen, dass die Bürger*innen Ihrer Kommune regelmäßig die wichtigsten Informationen zur Entwicklung Ihrer Kommune erhalten und zwar mit einer Einschätzung inwieweit sie das Gemeinwohl stärken bzw. schwächen?'),
(5, 'Mitentscheidung', 'Möchten Sie dafür sorgen, dass die Bürger*innen Ihrer Kommune in kommunale Entscheidungsprozesse fortan stärker eingebunden werden?'),
(6, 'Bekanntheit', 'Möchten Sie dafür sorgen, dass die Werte-Orientierung, Themen und Inhalte der GWÖ in Ihrer Kommune und auf höheren politischen Ebenen bekannter werden?');