- 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
138 lines
5.5 KiB
SQL
138 lines
5.5 KiB
SQL
-- 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?');
|